##

using ACE
using StaticArrays, Random, Printf, Test, LinearAlgebra, ACE.Testing
using ACE: evaluate, evaluate_d, SymmetricBasis, PIBasis, O3
using ACE.Random: rand_rot, rand_refl
using ACEbase.Testing: fdtest, println_slim
using ACE.Testing: __TestSVec

# using Profile, ProfileView

# Extra using Wigner for computing Wigner Matrix
using ACE.Wigner
using ACE.Wigner: get_orbsym

# construct the 1p-basis
maxdeg = 6
ord = 3
Bsel = SimpleSparseBasis(ord, maxdeg)

B1p = ACE.Utils.RnYlm_1pbasis(; maxdeg=maxdeg)

# generate a configuration
nX = 10
Xs = rand(PositionState{Float64}, B1p.bases[1], nX)
cfg = ACEConfig(Xs)

##

@info("SymmetricBasis construction and evaluation: Invariant Scalar")

φ = ACE.Invariant()
basis = SymmetricBasis(φ, B1p, Bsel)
@time SymmetricBasis(φ, B1p, Bsel);

BB = evaluate(basis, cfg)

# a stupid but necessary test
AA = evaluate(basis.pibasis, cfg)
BB1 = basis.A2Bmap * AA
println_slim(@test isapprox(BB, BB1, rtol=1e-10))

@info("evaluate with vector vs config")
println_slim(@test BB ≈ evaluate(basis, Xs))
println_slim(@test evaluate_d(basis, cfg) ≈ evaluate_d(basis, Xs))

# check there are no superfluous columns
Iz = findall(iszero, sum(norm, basis.A2Bmap, dims=1)[:])
if !isempty(Iz)
   @warn("The A2B map for Invariants has $(length(Iz))/$(length(basis.pibasis)) zero-columns!!!!")
end

for ntest = 1:30
      Xs1 = shuffle(rand_refl(rand_rot(Xs)))
      local BB1 = evaluate(basis, ACEConfig(Xs1))
      print_tf(@test isapprox(BB, BB1, rtol=1e-10))
end
println()

##

@info("Test Invariant Basis without Constant")
basis_noc = SymmetricBasis(φ, B1p, Bsel; filterfun = ACE.NoConstant())
println_slim(@test length(basis_noc) == length(basis) - 1)
BB_noc = evaluate(basis_noc, cfg)
println_slim(@test (BB_noc == BB[2:end]))

##

@info("Test what happens with an empty configuration")

Xs_empty = Vector{eltype(Xs)}(undef, 0)
cfg_empty = ACEConfig(Xs_empty)
B_empty = evaluate(basis, cfg_empty)
println(@test( all(iszero, B_empty[2:end]) ))
println(@test( B_empty[1] == ACE.Invariant(1.0) ) )

##
import ACEbase
@info("Test FIO")
let basis1 = basis
   println_slim(@test(all(ACEbase.Testing.test_fio(basis1; warntype=true))))
end

##

@info("Test linear independence of the basis")
# generate some random configurations; ord^2 + 1 sounds good :)
cfgs = [ ACEConfig(rand(PositionState{Float64}, B1p.bases[1], nX))
         for _ = 1:(3*length(basis)) ]
A = zeros(length(cfgs), length(basis))
for (i, cfg) in enumerate(cfgs)
   A[i, :] = getproperty.(evaluate(basis, cfg), :val)
end
println_slim(@test rank(A) == length(basis))

# ## Keep for futher profiling
# φ = ACE.Invariant()
# pibasis = PIBasis(B1p, ord, maxdeg; property = φ)
# basis = SymmetricBasis(pibasis, φ)
# @time SymmetricBasis(pibasis, φ);
#
# Profile.clear()
# @profile SymmetricBasis(pibasis, φ);
# ProfileView.view()
#

## Testing derivatives

for ntest = 1:30
   _rrval(x::ACE.XState) = x.rr
   Us = randn(SVector{3, Float64}, length(Xs))
   c = randn(length(basis))
   F = t -> sum(c .* ACE.evaluate(basis, ACEConfig(Xs + t[1] * Us))).val
   dF = t -> [ Us' * _rrval.(sum(c .* ACE.evaluate_d(basis, ACEConfig(Xs + t[1] * Us)), dims=1)[:]) ]
   print_tf(@test fdtest(F, dF, [0.0], verbose=false))
end
println()

##
@info("SymmetricBasis construction and evaluation: Spherical Vector")

for L = 0:3
   @info "Tests for L = $L ⇿ $(get_orbsym(0))-$(get_orbsym(L)) block"
   local φ, pibasis, basis, BB, Iz
   φ = ACE.SphericalVector(L; T = ComplexF64)
   basis = SymmetricBasis(φ, B1p, Bsel)
   @time SymmetricBasis(φ, B1p, Bsel)
   BB = evaluate(basis, cfg)

   @info("check that no-const gives the same (sub-)basis")
   basis0 = SymmetricBasis(φ, B1p, Bsel; filterfun = ACE.NoConstant())
   BB0 = evaluate(basis0, cfg)
   if L == 0
      println_slim(@test BB[2:end] ≈ BB0)
   else
      println_slim(@test BB ≈ BB0)
   end

   Iz = findall(iszero, sum(norm, basis.A2Bmap, dims = 1))
   if !isempty(Iz)
      @warn("The A2B map for SphericalVector has $(length(Iz))/$(length(basis.pibasis)) zero-columns!!!!")
   end

   @info("check symmetry ")
   for ntest = 1:30
      local Q, D, BB1
      Q, D = ACE.Wigner.rand_QD(L)
      cfg1 = ACEConfig( shuffle(Ref(Q) .* Xs) )
      BB1 = evaluate(basis, cfg1)
      DtxBB1 = Ref(D') .* BB1
      print_tf(@test isapprox(DtxBB1, BB, rtol=1e-10))
   end
   println()

   @info(" .... derivatives")
   for ntest = 1:30
      _rrval(x::ACE.XState) = x.rr
      Us = __TestSVec.(randn(SVector{3, Float64}, length(Xs)))
      C = randn(typeof(φ.val), length(basis))
      F = t -> sum( sum(c .* b.val)
                    for (c, b) in zip(C, ACE.evaluate(basis, ACEConfig(Xs + t[1] * Us))) )
      dF = t -> [ sum( sum(c .* db)
                  for (c, db) in zip(C, _rrval.(ACE.evaluate_d(basis, ACEConfig(Xs + t[1] * Us))) * Us) ) ]
      print_tf(@test fdtest(F, dF, [0.0], verbose=false))
   end
   println()
end

# ## Keep for futher profiling
# L = 1
# φ = ACE.SphericalVector(L; T = ComplexF64)
# pibasis = PIBasis(B1p, 4, 8; property = φ, isreal = false)
# basis = SymmetricBasis(pibasis, φ)
# @time SymmetricBasis(pibasis, φ);
#
# Profile.clear(); # Profile.init(; delay = 0.0001)
# @profile SymmetricBasis(pibasis, φ);
# ProfileView.view()

##

@info("SymmetricBasis construction and evaluation: Spherical Matrix")

for L1 = 0:2, L2 = 0:2
   @info "Tests for L₁ = $L1, L₂ = $L2 ⇿ $(get_orbsym(L1))-$(get_orbsym(L2)) block"
   local φ, pibasis, basis, BB, Iz
   φ = ACE.SphericalMatrix(L1, L2; T = ComplexF64)
   basis = SymmetricBasis(φ, B1p, Bsel)
   @time basis = SymmetricBasis(φ, B1p, Bsel)
   BB = evaluate(basis, cfg)

   @info("check that no-const gives the same (sub-)basis")
   basis0 = SymmetricBasis(φ, B1p, Bsel; filterfun = ACE.NoConstant())
   BB0 = evaluate(basis0, cfg)
   if L1 == L2
      println_slim(@test BB[2:end] ≈ BB0)
   else
      println_slim(@test BB ≈ BB0)
   end

   @info("check symmetry ")
   for ntest = 1:30
      local Q, D1, D2, BB1
      Q, D1, D2 = ACE.Wigner.rand_QD(L1, L2)
      cfg1 = ACEConfig( shuffle(Ref(Q) .* Xs) )
      BB1 = evaluate(basis, cfg1)
      D1txBB1xD2 = Ref(D1') .* BB1 .* Ref(D2)
      print_tf(@test isapprox(D1txBB1xD2, BB, rtol=1e-10))
   end
   println()

   @info(" .... derivatives")
   for ntest = 1:30
      _rrval(x::ACE.XState) = x.rr
      Us = __TestSVec.(randn(SVector{3, Float64}, length(Xs)))
      C = randn(typeof(φ.val), length(basis))
      F = t -> sum( sum(c .* b.val)
                    for (c, b) in zip(C, ACE.evaluate(basis, ACEConfig(Xs + t[1] * Us))) )
      dF = t -> [ sum( sum(c .* db)
                       for (c, db) in zip(C, _rrval.(ACE.evaluate_d(basis, ACEConfig(Xs + t[1] * Us))) * Us) ) ]
      print_tf(@test fdtest(F, dF, [0.0], verbose=false))
   end
   println()
end

##
@info("Consistency between SphericalVector & SphericalMatrix")

for L = 0:3
   @info "L = $L"
   local Xs, cfg
   φ1 = ACE.SphericalVector(L; T = ComplexF64)
   basis1 = SymmetricBasis(φ1, B1p, Bsel)

   φ2 = ACE.SphericalMatrix(L, 0; T = ComplexF64)
   basis2 = SymmetricBasis(φ2, B1p, Bsel)

   for ntest = 1:30
      Xs = rand(PositionState{Float64}, B1p.bases[1], nX)
      cfg = ACEConfig(Xs)
      BBvec = evaluate(basis1, cfg)
      value1 = [reshape(BBvec[i].val, 2L+1, 1) for i in 1:length(BBvec)]
      BBmat = evaluate(basis2, cfg)
      value2 = [BBmat[i].val for i in 1:length(BBvec)]
      print_tf(@test isapprox(value1, value2, rtol=1e-10))
   end
   println()
end

##
@info("Consistency between Invariant Scalar & SphericalMatrix")

φ = ACE.Invariant()
basis = SymmetricBasis(φ, B1p, Bsel)
φ2 = ACE.SphericalMatrix(0, 0; T = ComplexF64)
basis2 = SymmetricBasis(φ2, B1p, Bsel)

for ntest = 1:30
   local Xs, cfg, BB
   Xs = rand(PositionState{Float64}, B1p.bases[1], nX)
   cfg = ACEConfig(Xs)
   BB = evaluate(basis, cfg)
   BBsca = [BB[i].val for i in 1:length(BB)]
   BB2 =  evaluate(basis2, cfg)
   BBCFlo = [ComplexF64(BB2[i].val...) for i in 1:length(BB2)]
   print_tf(@test isapprox(BBsca, BBCFlo, rtol=1e-10))
end
println()

# ## Keep for futher profiling
#
# L1 = 1; L2 = 1
# φ = ACE.SphericalMatrix(L1, L2; T = ComplexF64)
# pibasis = PIBasis(B1p, ord, maxdeg; property = φ, isreal = false)
# basis = SymmetricBasis(pibasis, φ)
# @time SymmetricBasis(pibasis, φ);
#
# Profile.clear()
# @profile SymmetricBasis(pibasis, φ);
# ProfileView.view()

##


# Run tests on Newton methods & rfi methods

using NonlinearEigenproblemsTest
using NonlinearEigenproblems
using Test
using LinearAlgebra

@testset "Newton iterations" begin
    nep=nep_gallery("dep0")

    @bench @testset "Newton and AugNewton" begin
        @info "Newton and AugNewton test"

        # newton and augnewton are equivalent, therefore I expect them
        # to generate identical results
        λ1,x1 = newton(nep,logger=displaylevel,v=ones(size(nep,1)),λ=0,tol=eps()*10)
        λ2,x2 = augnewton(nep,logger=displaylevel,v=ones(size(nep,1)),λ=0,tol=eps()*10)
        @test λ1 ≈ λ2
        @test x1 ≈ x2
        @test compute_resnorm(nep,λ1,x1) < eps()*100
        @test compute_resnorm(nep,λ2,x2) < eps()*100

    end

    @bench @testset "QuasiNewton" begin
    @info "QuasiNewton  test"

        λ,x = quasinewton(nep,logger=displaylevel,v=ones(size(nep,1)),λ=0,tol=1e-12)
        @test compute_resnorm(nep,λ,x) < 1e-11*100

    end

    @bench @testset "Newton QR" begin

        @info "Newton QR test"
        #Run derivative test for left and right eigenvectors returned by newtonqr
        λ3,x3,y3 =  newtonqr(nep, λ=0, v=ones(size(nep,1)), logger=displaylevel,tol=eps()*10)
        @test compute_resnorm(nep,λ3,x3) < eps()*100

        @info "  Testing formula for derivative"
        tau = 1
        Mλ = -I - tau * nep.A[2] * exp(-tau*λ3)
        Mtau = -λ3*nep.A[2]*exp(-tau*λ3)

        # Formula for derivative
        λp=-(y3'*(Mtau)*x3) / (y3'* Mλ*x3)

        δ=0.0001
        nepp = deepcopy(nep)
        nepp.tauv[2] = nep.tauv[2]+δ
        λ3δ,x3,y3 = newtonqr(nepp, λ=0, v=ones(size(nep,1)), logger=displaylevel,tol=eps()*10)
        λp_approx = (λ3δ-λ3)/δ
        @test abs(λp-λp_approx)< (δ*10)
    end

    @bench @testset "Resinv" begin
        @info "resinv + periodicdde"
        nep1=nep_gallery("periodicdde", name="mathieu")
        # basic functionality of resinv. Start close to solution to speed up unit test
        λ4,x4 =  resinv(nep1, λ=-0.2447, v=[0.970208+0.0im, -0.242272+0.0im],
                        logger=displaylevel,tol=eps()*10)
        r4=compute_resnorm(nep1,λ4,x4)

        @test r4 < eps()*100
    end

    @bench @testset "Rayleigh Function Iteration" begin
        @info "rfi"
        nept = DEP([copy(nep.A[1]'), copy(nep.A[2]')],copy(nep.tauv))

        n=size(nep,1)
        u0=ones(n)
        v0=ones(n)

        λ,x,y =rfi(nep, nept, logger=displaylevel, v=ones(n), u=ones(n), tol=1e-15)
        @test compute_resnorm(nep,λ,x) < eps()*100
        @test compute_resnorm(nept,λ,y) < eps()*100

        # Test RFIb

        @info "rfi_b"
        λb,xb,yb =rfi_b(nep, nept, logger=displaylevel, v=v0, u=u0, λ=λ+0.01, tol=1e-15)
        @test λ ≈ λb

        @info "  Testing formula for derivative (with left and right eigvecs)"
        tau = 1
        Mλ = -I - tau*nep.A[2] * exp(-tau*λ)
        Mtau = -λ*nep.A[2]*exp(-tau*λ)

        # Formula for derivative
        λp=-(y'*(Mtau)*x) / (y'* Mλ*x)

        δ=0.0001
        nepp = deepcopy(nep)
        nepp.tauv[2] = nep.tauv[2]+δ
        neptp = deepcopy(nept)
        neptp.tauv[2] = nept.tauv[2]+δ
        λδ,x,y = rfi(nepp,neptp,logger=displaylevel, v=ones(n), u=ones(n))
        λp_approx=(λδ-λ)/δ
        @test abs(λp-λp_approx)< (δ*10)
    end

    @bench @testset "implicitdet" begin
        @info "Implicitdet test"
        nepd=nep_gallery("periodicdde", name="mathieu")
        λ,v=implicitdet(nepd, v=ones(size(nepd,1)), logger=displaylevel)
        @test norm(compute_Mder(nepd,λ)*v) < eps()*100
    end

    @bench @testset "type stability" begin
        dep = nep_gallery("dep0")
        @inferred augnewton(Float64, dep, v = ones(size(dep,1)))
        @inferred augnewton(ComplexF64, dep, v = ones(size(dep,1)))
    end
end


include("utils.jl")

struct TestVectorSpaceType <: VectorSpaceType end

@testset "Tangent bundle" begin
    M = Sphere(2)

    @testset "FVector" begin
        @test sprint(show, TangentSpace) == "TangentSpace"
        @test sprint(show, CotangentSpace) == "CotangentSpace"
        tvs = ([1.0, 0.0, 0.0], [0.0, 1.0, 0.0])
        fv_tvs = map(v -> FVector(TangentSpace, v), tvs)
        fv1 = fv_tvs[1]
        tv1s = allocate(fv_tvs[1])
        @test isa(tv1s, FVector)
        @test size(tv1s) == (3,)
        @test tv1s.type == TangentSpace
        @test size(tv1s.data) == size(tvs[1])
        @test number_eltype(tv1s) == number_eltype(tvs[1])
        @test number_eltype(tv1s) == number_eltype(typeof(tv1s))
        @test isa(fv1 + fv1, FVector)
        @test (fv1 + fv1).type == TangentSpace
        @test isa(fv1 - fv1, FVector)
        @test (fv1 - fv1).type == TangentSpace
        @test isa(-fv1, FVector)
        @test (-fv1).type == TangentSpace
        @test isa(2 * fv1, FVector)
        @test (2 * fv1).type == TangentSpace

        PM = ProductManifold(Sphere(2), Euclidean(2))
        @test_throws ErrorException flat(
            PM,
            ProductRepr([0.0], [0.0]),
            FVector(CotangentSpace, ProductRepr([0.0], [0.0])),
        )
        @test_throws ErrorException sharp(
            PM,
            ProductRepr([0.0], [0.0]),
            FVector(TangentSpace, ProductRepr([0.0], [0.0])),
        )

        fv2 = FVector(TangentSpace, ProductRepr([1, 0, 0], [1 2]))
        @test submanifold_component(fv2, 1) == [1, 0, 0]
        @test submanifold_component(fv2, 2) == [1 2]
        @test (@inferred submanifold_component(fv2, Val(1))) == [1, 0, 0]
        @test (@inferred submanifold_component(fv2, Val(2))) == [1 2]
        @test submanifold_component(PM, fv2, 1) == [1, 0, 0]
        @test submanifold_component(PM, fv2, 2) == [1 2]
        @test (@inferred submanifold_component(PM, fv2, Val(1))) == [1, 0, 0]
        @test (@inferred submanifold_component(PM, fv2, Val(2))) == [1 2]
        @test submanifold_components(PM, fv2) == ([1.0, 0.0, 0.0], [1.0 2.0])
    end

    types = [Vector{Float64}]
    TEST_FLOAT32 && push!(types, Vector{Float32})
    TEST_STATIC_SIZED && push!(types, MVector{3,Float64})

    for T in types
        x = convert(T, [1.0, 0.0, 0.0])
        TB = TangentBundle(M)
        @test sprint(show, TB) == "TangentBundle(Sphere(2, ℝ))"
        @test base_manifold(TB) == M
        @test manifold_dimension(TB) == 2 * manifold_dimension(M)
        @test representation_size(TB) == (6,)
        CTB = CotangentBundle(M)
        @test sprint(show, CTB) == "CotangentBundle(Sphere(2, ℝ))"
        @test sprint(show, VectorBundle(TestVectorSpaceType(), M)) ==
              "VectorBundle(TestVectorSpaceType(), Sphere(2, ℝ))"
        @testset "Type $T" begin
            pts_tb = [
                ProductRepr(convert(T, [1.0, 0.0, 0.0]), convert(T, [0.0, -1.0, -1.0])),
                ProductRepr(convert(T, [0.0, 1.0, 0.0]), convert(T, [2.0, 0.0, 1.0])),
                ProductRepr(convert(T, [1.0, 0.0, 0.0]), convert(T, [0.0, 2.0, -1.0])),
            ]
            @inferred ProductRepr(
                convert(T, [1.0, 0.0, 0.0]),
                convert(T, [0.0, -1.0, -1.0]),
            )
            for pt in pts_tb
                @test bundle_projection(TB, pt) ≈ pt.parts[1]
            end
            diag_basis = DiagonalizingOrthonormalBasis(log(TB, pts_tb[1], pts_tb[2]))
            basis_types = (
                DefaultOrthonormalBasis(),
                get_basis(TB, pts_tb[1], DefaultOrthonormalBasis()),
                diag_basis,
                get_basis(TB, pts_tb[1], diag_basis),
            )
            test_manifold(
                TB,
                pts_tb,
                test_injectivity_radius = false,
                test_reverse_diff = isa(T, Vector),
                test_forward_diff = isa(T, Vector),
                test_tangent_vector_broadcasting = false,
                test_vee_hat = true,
                test_project_tangent = true,
                test_project_point = true,
                test_default_vector_transport = true,
                basis_types_vecs = basis_types,
                projection_atol_multiplier = 4,
            )
        end
    end

    @test TangentBundle{ℝ,Sphere{2,ℝ}} ==
          VectorBundle{ℝ,Manifolds.TangentSpaceType,Sphere{2,ℝ}}
    @test CotangentBundle{ℝ,Sphere{2,ℝ}} ==
          VectorBundle{ℝ,Manifolds.CotangentSpaceType,Sphere{2,ℝ}}

    @test base_manifold(TangentBundle(M)) == M
    @testset "spaces at point" begin
        x = [1.0, 0.0, 0.0]
        t_x = TangentSpaceAtPoint(M, x)
        t_x2 = TangentSpace(M, x)
        @test t_x == t_x2
        ct_x = CotangentSpaceAtPoint(M, x)
        t_xs = sprint(show, "text/plain", t_x)
        sp = sprint(show, "text/plain", x)
        sp = replace(sp, '\n' => "\n ")
        t_xs_test = "Tangent space to the manifold $(M) at point:\n $(sp)"
        @test t_xs == t_xs_test
        @test base_manifold(t_x) == M
        @test base_manifold(ct_x) == M
        @test t_x.fiber.manifold == M
        @test ct_x.fiber.manifold == M
        @test t_x.fiber.fiber == TangentSpace
        @test ct_x.fiber.fiber == CotangentSpace
        @test t_x.point == x
        @test ct_x.point == x
        # generic vector space at
        fiber = VectorBundleFibers(TestVectorSpaceType(), M)
        v_x = VectorSpaceAtPoint(fiber, x)
        v_xs = sprint(show, "text/plain", v_x)
        fiber_s = sprint(show, "text/plain", fiber)
        v_xs_test = "$(typeof(v_x))\nFiber:\n $(fiber_s)\nBase point:\n $(sp)"
        @test v_xs == v_xs_test
    end

    @testset "tensor product" begin
        TT = Manifolds.TensorProductType(TangentSpace, TangentSpace)
        @test sprint(show, TT) == "TensorProductType(TangentSpace, TangentSpace)"
        @test vector_space_dimension(VectorBundleFibers(TT, Sphere(2))) == 4
        @test vector_space_dimension(VectorBundleFibers(TT, Sphere(3))) == 9
        @test base_manifold(VectorBundleFibers(TT, Sphere(2))) == M
        @test sprint(show, VectorBundleFibers(TT, Sphere(2))) ==
              "VectorBundleFibers(TensorProductType(TangentSpace, TangentSpace), Sphere(2, ℝ))"
    end

    @testset "Error messages" begin
        vbf = VectorBundleFibers(TestVectorSpaceType(), Euclidean(3))
        @test_throws ErrorException inner(vbf, [1, 2, 3], [1, 2, 3], [1, 2, 3])
        @test_throws ErrorException Manifolds.project!(vbf, [1, 2, 3], [1, 2, 3], [1, 2, 3])
        @test_throws ErrorException zero_vector!(vbf, [1, 2, 3], [1, 2, 3])
        @test_throws ErrorException vector_space_dimension(vbf)
        a = fill(0.0, 6)
        @test_throws ErrorException get_coordinates!(
            TangentBundle(M),
            a,
            ProductRepr([1.0, 0.0, 0.0], [0.0, 0.0, 0.0]),
            ProductRepr([1.0, 0.0, 0.0], [0.0, 0.0, 1.0]),
            CachedBasis(DefaultOrthonormalBasis(), []),
        )
    end

    @testset "log and exp on tangent bundle for power and product manifolds" begin
        M = PowerManifold(Circle(ℝ), 2)
        N = TangentBundle(M)
        p1 = ProductRepr([0.0, 0.0], [0.0, 0.0])
        p2 = ProductRepr([-1.047, -1.047], [0.0, 0.0])
        X1 = log(N, p1, p2)
        @test isapprox(N, p2, exp(N, p1, X1))
        @test is_tangent_vector(N, p2, vector_transport_to(N, p1, X1, p2))

        M2 = ProductManifold(Circle(ℝ), Euclidean(2))
        N2 = TangentBundle(M2)
        p1_2 = ProductRepr(ProductRepr([0.0], [0.0, 0.0]), ProductRepr([0.0], [0.0, 0.0]))
        p2_2 = ProductRepr(
            ProductRepr([-1.047], [1.0, 0.0]),
            ProductRepr([-1.047], [0.0, 1.0]),
        )
        @test isapprox(N2, p2_2, exp(N2, p1_2, log(N2, p1_2, p2_2)))

        ppt = PowerVectorTransport(ParallelTransport())
        tbvt = Manifolds.VectorBundleVectorTransport(ppt, ppt)
        @test TangentBundle(M, tbvt).vector_transport === tbvt
        @test CotangentBundle(M, tbvt).vector_transport === tbvt
        @test VectorBundle(TangentSpace, M, tbvt).vector_transport === tbvt
    end
end


"""
    euclidean(img)
    euclidean(img::BitArray)

Wrapper function for `ImageMorphology.feature_transform` and
`ImageMorphology.distance_transform`. Applies a true Euclidean
distance transform to the array elements and returns an array
with spatial information embedded in the elements.

# Arguments
- img: N-dimensional array to be transformed based on location
    to the nearest background (0) pixel

# Citation
'A Linear Time Algorithm for Computing Exact Euclidean Distance
Transforms of Binary Images in Arbitrary Dimensions' [Maurer et al.,
2003] (DOI: 10.1109/TPAMI.2003.1177156)
"""
euclidean(img) = distance_transform(feature_transform(.!(Bool.(img))))
euclidean(img::BitArray) = distance_transform(feature_transform(.!(img)))


const NanosPerNanosecond  = 1
const NanosPerMicrosecond = 1000
const NanosPerMillisecond = 1000*1000
const NanosPerSecond      = 1000*1000*1000
const NanosPerMinute      = 1000*1000*1000*60
const NanosPerHour        = 1000*1000*1000*60*60

const NanosPer = (NanosPerHour, NanosPerMinute, NanosPerSecond, NanosPerMillisecond, NanosPerMicrosecond, NanosPerNanosecond)
(3600000000000, 60000000000, 1000000000, 1000000, 1000, 1)

const TimeInParts = NamedTuple{(:hours, :minutes, :seconds, :millis, :micros, :nanos)}

struct TimeParts <: Function
end

TimeParts(x::Vector) = TimeInParts(x...)

function timeparts(nanos::Int128, parts::Vector{Int128})
    ns = nanos

    for (idx, unit_in_nanos) in enumerate(NanosPer)
        units = fld(ns, unit_in_nanos)
        ns -= units * unit_in_nanos
        parts[idx] = units
    end

    parts
end

function timeparts(nanos::Int128)
    ns = nanos
    result = zeros(Int128, length(NanosPer))

    for (idx, unit_in_nanos) in enumerate(NanosPer)
        units = fld(ns, unit_in_nanos)
        ns -= units * unit_in_nanos
        result[idx] = units
    end

    result
end

#=

julia> tm = Time(now()); tmv = tm.instant.value;

julia> string(tm), tm, tmv
("21:05:38.181", Time(21, 5, 38, 181), 75938181000000)

julia> timeparts(tmv)
6-element Vector{Int128}:
  21
   5
  38
 181
   0
   0

julia> forparts = zeros(Int128,6);

julia> timeparts(tmv, forparts)
6-element Vector{Int128}:
  21
   5
  38
 181
   0
   0

julia> TimeInParts(timeparts(tmv, forparts))
(hours = 21, minutes = 5, seconds = 38, millis = 181, micros = 0, nanos = 0)
=#

function timeparts(nanos::S) where {S<:Signed}
    ns = nanos%Int128
    hr = fld(ns, NanosPerHour)
    ns -= hr * NanosPerHour
    mn = fld(ns, NanosPerMinute)
    ns -= mn * NanosPerMinute
    sc = fld(ns, NanosPerSecond)
    ns -= sc * NanosPerSecond
    ms = fld(ns, NanosPerMillisecond)
    ns -= ms * NanosPerMillisecond
    us = fld(ns, NanosPerMicrosecond)
    ns -= us * NanosPerMicrosecond
    (hr, mn, sc, ms, us, ns)
end


path = joinpath(dirname(@__FILE__), "..", "..")
push!(Base.LOAD_PATH, path)
using ProxSDP, JuMP

# Create a JuMP model using ProxSDP as the solver
model = Model(with_optimizer(ProxSDP.Optimizer, log_verbose=true, tol_primal = 1e-5, tol_dual = 1e-5))

@variable(model, X[1:2,1:2], PSD)

x = X[1,1]
y = X[2,2]

@constraint(model, ub_x, x <= 2)
@constraint(model, ub_y, y <= 30)
@constraint(model, con, 1x + 5y <= 3)

# ProxSDP supports maximization or minimization
# of linear functions
@objective(model, Max, 5x + 3 * y)

# Then we can solve the model
JuMP.optimize!(model)

# And ask for results!
JuMP.objective_value(model)

JuMP.value(x)

JuMP.value(y)


function test_ln2_all()
    print(rpad("Math.Series.ln(2)...", 50, ' '))

    ln2_N2a = Brainstorm._Math._Series._Ln2._Machin.ln2_N2a
    ln2_N2b = Brainstorm._Math._Series._Ln2._Machin.ln2_N2b
    ln2_N2c = Brainstorm._Math._Series._Ln2._Machin.ln2_N2c

    ln2_N3a = Brainstorm._Math._Series._Ln2._Machin.ln2_N3a
    ln2_N3b = Brainstorm._Math._Series._Ln2._Machin.ln2_N3b
    ln2_N3c = Brainstorm._Math._Series._Ln2._Machin.ln2_N3c
    ln2_N3d = Brainstorm._Math._Series._Ln2._Machin.ln2_N3d
    ln2_N3e = Brainstorm._Math._Series._Ln2._Machin.ln2_N3e

    ln2_N4a = Brainstorm._Math._Series._Ln2._Machin.ln2_N4a
    ln2_N4b = Brainstorm._Math._Series._Ln2._Machin.ln2_N4b
    ln2_N4c = Brainstorm._Math._Series._Ln2._Machin.ln2_N4c

    ln2_N5a = Brainstorm._Math._Series._Ln2._Machin.ln2_N5a

    setprecision(400_000) do
        expected = [trunc(BigInt, log(big(2))*(big(10)^d)) for d in (1, 10, 100, 1_000, 10_000, 100_000)]

        @test all([ln2_N2a(d) for d in (1, 10, 100, 1_000, 10_000, 100_000)] - expected .== 0)
        @test all([ln2_N2b(d) for d in (1, 10, 100, 1_000, 10_000, 100_000)] - expected .== 0)
        @test all([ln2_N2c(d) for d in (1, 10, 100, 1_000, 10_000, 100_000)] - expected .== 0)

        @test all([ln2_N3a(d) for d in (1, 10, 100, 1_000, 10_000, 100_000)] - expected .== 0)
        @test all([ln2_N3b(d) for d in (1, 10, 100, 1_000, 10_000, 100_000)] - expected .== 0)
        @test all([ln2_N3c(d) for d in (1, 10, 100, 1_000, 10_000, 100_000)] - expected .== 0)
        @test all([ln2_N3d(d) for d in (1, 10, 100, 1_000, 10_000, 100_000)] - expected .== 0)
        @test all([ln2_N3e(d) for d in (1, 10, 100, 1_000, 10_000, 100_000)] - expected .== 0)

        @test all([ln2_N4a(d) for d in (1, 10, 100, 1_000, 10_000, 100_000)] - expected .== 0)
        @test all([ln2_N4b(d) for d in (1, 10, 100, 1_000, 10_000, 100_000)] - expected .== 0)
        @test all([ln2_N4c(d) for d in (1, 10, 100, 1_000, 10_000, 100_000)] - expected .== 0)

        @test all([ln2_N5a(d) for d in (1, 10, 100, 1_000, 10_000, 100_000)] - expected .== 0)
    end

    println("PASS")
end


"""
    struct SignSymmetry <: Sparsity end

Sign symmetry as developed in [Section III.C, L09].

[L09] Lofberg, Johan.
*Pre-and post-processing sum-of-squares programs in practice*.
IEEE transactions on automatic control 54, no. 5 (2009): 1007-1011.
"""
struct SignSymmetry <: Sparsity end

function binary_exponent(exponents, ::Type{T}) where T
    cur = zero(T)
    for exponent in exponents
        cur <<= 1
        if isodd(exponent)
            cur |= one(T)
        end
    end
    return cur
end
bin_dot(x, y) = isodd(count_ones(x & y))
function buckets_sign_symmetry(monos, r, ::Type{T}, ::Type{U}) where {T, U}
    # We store vector of indices instead of vector of monos in the bucket
    # as `monos` is already sorted and the buckets content will be sorted
    # so it will remain sorted and `DynamicPolynomials` can keep the
    # `MonomialVector` struct in `monos[I]`.
    buckets = Dict{U, Vector{Int}}()
    for (idx, mono) in enumerate(monos)
        exp = binary_exponent(MP.exponents(mono), T)
        i = 1 + binary_exponent([bin_dot(ri, exp) for ri in r], U)
        if !haskey(buckets, i)
            buckets[i] = Int[]
        end
        push!(buckets[i], idx)
    end
    return [monos[I] for I in values(buckets)]
end
function sign_symmetry(monos::AbstractVector{<:MP.AbstractMonomial}, n, ::Type{T}, gram_monos) where T
    r = xor_complement((binary_exponent(MP.exponents(mono), T) for mono in monos), n, T)
    return buckets_sign_symmetry(gram_monos,
                                 r, T, appropriate_type(length(r)))
end
function sparsity(monos::AbstractVector{<:MP.AbstractMonomial}, sp::SignSymmetry,
                  gram_monos::AbstractVector = SumOfSquares.Certificate.monomials_half_newton_polytope(monos, tuple()))
    n = MP.nvariables(monos)
    return sign_symmetry(monos, n, appropriate_type(n), gram_monos)
end


using PowerModels
using Memento

# Suppress warnings during testing.
setlevel!(getlogger(PowerModels), "error")

using Ipopt
using Pajarito
using GLPKMathProgInterface
using SCS

# needed for Non-convex OTS tests
if (Pkg.installed("AmplNLWriter") != nothing && Pkg.installed("CoinOptServices") != nothing)
    using AmplNLWriter
    using CoinOptServices
end

using Base.Test

# default setup for solvers
ipopt_solver = IpoptSolver(tol=1e-6, print_level=0)
pajarito_solver = PajaritoSolver(mip_solver=GLPKSolverMIP(), cont_solver=ipopt_solver, log_level=0)
scs_solver = SCSSolver(max_iters=1000000, verbose=0)

include("common.jl")

include("pti.jl")

include("matpower.jl")

include("output.jl")

include("modify.jl")

include("data.jl")

include("pf.jl")

include("opf.jl")

include("ots.jl")

include("tnep.jl")

include("multinetwork.jl")

include("docs.jl")


#!/usr/bin/env julia

using Plots
using VectorizedRoutines

plotly()  # Select a plotting backend.

# Read the antenna data file (CSV format).
raw_data = readdlm("dipole_pattern.csv", ',', Float32, skipstart=1)

# Separate and sort the data.
freq = unique(raw_data[:,1])
theta = unique(raw_data[:,2])
phi = unique(raw_data[:,3])
pattern_db = reshape(raw_data[:,4], (length(phi), length(theta), length(freq)))

# If complex gain values are needed, uncomment the following line:
#complex_pattern = 10^(raw_data[:,4]/10)*exp(1j*raw_data[:,5]/180*π)

## Plot in 2D ##

# Pick a frequency
fidx = 1

# Control plot dynamic range
pltmax = 5.
pltmin = -35.

# Create the 2D colormap (heatmap)
xs = [string(i) for i = round(Int32, theta)]
ys = [string(i) for i = round(Int32, phi)]
heatmap(ys, xs, pattern_db[:,:,fidx]', clims = (pltmin,pltmax),
        title = "Dipole Realized Gain", xlabel = "phi (deg)",
        ylabel = "theta (deg)", fc=:haline, colorbar_title="dBi")

## Plot in 3D (project onto surface of sphere) ##
theta2d, phi2d = Matlab.meshgrid(theta, phi)
x2d = sind(theta2d).*cosd(phi2d)
y2d = sind(theta2d).*sind(phi2d)
z2d = cosd(theta2d)
color_mat = pattern_db[:,:,fidx]
color_mat[pattern_db[:,:,fidx] .< pltmin] = pltmin

surface(x2d, y2d, z2d, fill_z = color_mat, fc = :haline, grid=false,
        title = "Dipole Realized Gain", colorbar_title="dBi")

# Remove the axes
# Currently ticks=false doesn't entirely remove the axes.
plot!(foreground_color = :white, foreground_color_title = :black)

## Plot in 3D ##
r = pattern_db[:,:,fidx] - pltmin
r[r .< 0.0] = 0.0

x2d = r.*sind(theta2d).*cosd(phi2d)
y2d = r.*sind(theta2d).*sind(phi2d)
z2d = r.*cosd(theta2d)

surface(x2d, y2d, z2d, fill_z = color_mat, fc = :haline,
        title = "Dipole Realized Gain", colorbar_title="dBi",
        grid = false)

# Remove the axes
# Currently ticks=false doesn't entirely remove the axes.
plot!(foreground_color = :white, foreground_color_title = :black)


export Quaternion, rotationmatrix_normalized, qrotation_x, qrotation_y, qrotation_z

import Base: *, ≈

@doc raw"""
A structure representing a quaternion.

Stripeline uses quaternions to encode rotations when generating pointings. The
fields of `Quaternion` are:

- `s`
- `v1`
- `v2`
- `v3`

It is possible to multiply quaternions using the `*` operator. Constructors for
rotation quaternions are provided by `qrotation_x`, `qrotation_y`, and
`qrotation_z`.

`Quaternion` implements the `≈` binary relationship, in order to ease the
implementation of tests.
"""
struct Quaternion
    s::Float64
    v1::Float64
    v2::Float64
    v3::Float64
end

(*)(q::Quaternion, w::Quaternion) = Quaternion(q.s * w.s - q.v1 * w.v1 - q.v2 * w.v2 - q.v3 * w.v3,
                                               q.s * w.v1 + q.v1 * w.s + q.v2 * w.v3 - q.v3 * w.v2,
                                               q.s * w.v2 - q.v1 * w.v3 + q.v2 * w.s + q.v3 * w.v1,
                                               q.s * w.v3 + q.v1 * w.v2 - q.v2 * w.v1 + q.v3 * w.s)

(≈)(q::Quaternion, w::Quaternion) = (q.s ≈ w.s) && (q.v1 ≈ w.v1) && (q.v2 ≈ w.v2) && (q.v3 ≈ w.v3)

@doc raw"""

    rotationmatrix_normalized(q::Quaternion)

Return a 3×3 matrix representing the rotation encoded by quaternion `q`.
The function assumes that the quaternion is normalized. This is the
case if `q` is a composition of rotations built using the functions
`qrotation_x`, `qrotation_y`, and `qrotation_z`.
"""
function rotationmatrix_normalized(q::Quaternion)
    sx, sy, sz = 2q.s * q.v1, 2q.s * q.v2, 2q.s * q.v3
    xx, xy, xz = 2q.v1^2, 2q.v1 * q.v2, 2q.v1 * q.v3
    yy, yz, zz = 2q.v2^2, 2q.v2 * q.v3, 2q.v3^2
    Float64[(1 - (yy + zz))  (xy - sz)        (xz + sy);
            (xy + sz)        (1 - (xx + zz))  (yz - sx);
            (xz - sy)        (yz + sx)        (1 - (xx + yy))]
end

qrotation_x(theta) = Quaternion(cos(theta / 2), sin(theta / 2), 0.0, 0.0)
qrotation_y(theta) = Quaternion(cos(theta / 2), 0.0, sin(theta / 2), 0.0)
qrotation_z(theta) = Quaternion(cos(theta / 2), 0.0, 0.0, sin(theta / 2))

"""
    qrotation_x(theta)
    qrotation_y(theta)
    qrotation_z(theta)

Return a `Quaternion` object representing a rotation around the ``e_x``,
``e_y``, or ``e_z`` axis by an angle `theta` (in radians). The quaternions
returned by these functions are already normalized and can be used with the
function `rotationmatrix_normalized`.
"""
qrotation_x, qrotation_y, qrotation_z


"""
    abstract type AbstractLegendreNorm end

Abstract trait supertype for normalization conditions of the associated Legendre functions.
"""
abstract type AbstractLegendreNorm end

# Define element types as a way to conveniently promote, even for the trait types.
# Union{} will always lose out in type promotion.
Base.eltype(::Type{<:AbstractLegendreNorm}) = Union{}
# Normalizations are scalars in a broadcasting context
Base.broadcastable(x::AbstractLegendreNorm) = Ref(x)

## Below are overloadable non-exported functions to implement the AbstractLegendreNorm
## interface.

"""
    initcond(::N, ::Type{T}) where {N<:AbstractLegendreNorm, T}

Returns the initial condition ``P_0^0(x)`` for the associated Legendre recursions based
on the normalization choice `N` for numeric type `T`.
"""
function initcond end

"""
    coeff_μ(norm::N, ::Type{T}, l::Integer) where {N<:AbstractLegendreNorm, T}

Returns the coefficient ``\\mu_\\ell`` for the single-term recursion relation
```math
    P_\\ell^\\ell(x) = -\\mu_\\ell \\sqrt{1-x^2} P_{\\ell-1}^{\\ell-1}(x)
```
where ``\\mu_\\ell`` is appropriate for the choice of normalization `N`.
"""
function coeff_μ end

"""
    coeff_ν(norm::N, ::Type{T}, l::Integer) where {N<:AbstractLegendreNorm, T}

Returns the coefficient ``\\nu_\\ell`` for the single-term recursion relation
```math
    P_\\ell^{\\ell-1}(x) = \\nu_\\ell x P_{\\ell-1}^{\\ell-1}(x)
```
where ``\\nu_\\ell`` is appropriate for the choice of normalization `N`.
"""
function coeff_ν end

"""
    coeff_α(norm::N, ::Type{T}, l::Integer, m::Integer) where {N<:AbstractLegendreNorm, T}

Returns the coefficient ``\\alpha_\\ell^m`` for the two-term recursion relation
```math
    P_\\ell^m(x) = \\alpha_\\ell^m x P_{\\ell-1}^m(x) - \\beta_\\ell^m P_{\\ell-2}^m(x)
```
where ``\\alpha_\\ell^m`` is appropriate for the choice of normalization `N`.
"""
function coeff_α end

"""
    coeff_β(norm::N, ::Type{T}, l::Integer, m::Integer) where {N<:AbstractLegendreNorm, T}

Returns the coefficient ``\\beta_\\ell^m`` for the two-term recursion relation
```math
    P_\\ell^m(x) = \\alpha_\\ell^m x P_{\\ell-1}^m(x) - \\beta_\\ell^m P_{\\ell-2}^m(x)
```
where ``\\beta_\\ell^m`` is appropriate for the choice of normalization `N`.
"""
function coeff_β end

"""
    boundscheck_hook(norm::AbstractLegendreNorm, lmax, mmax)

A bounds-checking hook executed at the beginning of each [`legendre!`](@ref) call to
permit a normalization `norm` to validate that the given maximum ``(\\ell,m)`` will be
within the ability to satisfy. The default case always returns `nothing`. A custom
normalization should throw an error if `lmax` or `mmax` is out of bounds or return `nothing`
otherwise.

For example, the precomputed coefficients of [`LegendreNormCoeff`](@ref) are limited to
a given domain at time of construction and cannot be used to calculate terms to arbitrary
orders/degrees.
"""
function boundscheck_hook end

boundscheck_hook(norm::AbstractLegendreNorm, lmax, mmax) = nothing


using PyPlot
#lines
x = LinRange(0, 10, 100)
y1 = sin.(x)
y2 = cos.(x)

subplot(221)
plot(x, y1, "r")
subplot(222)
plot(x, y2, "b")

subplot(2,2, (3,4))
plot(x, y1, "r", label = "sin")
plot(x, y2, "b", label = "cos")

if !isinteractive()
    PyPlot.show()
end

#fig = Figure()
#lines(fig[1, 1], x, y1, color = :red)
#lines(fig[1, 2], x, y2, color = :blue)

#lines(fig[2, 1:2], x, y1, color = :red, label = "sin")
#lines!(fig[2, 1:2], x, y2, color = :blue, label = "cos")
#axislegend()
#current_figure()
#fig


__precompile__(true)
module RubberDucks

export rubber_ducks_in_earth
"""
Computes the number of rubber ducks that would fill the volume of the Earth.

Assumes a spherical Earth, and liquid rubber ducks (to avoid the packing problem).

Arguments:
- `num_ruber::Real` is the first function argument. [_m^3_]
- `r_earth::Real` is the first function argument. Default: 6378000 [_m_]

Returns:
- `num_ducks::Float64` Number of rubber ducks that could
"""
function rubber_ducks_in_earth(duck_volume::Real, r_earth::Real=6378000)
    # Volume of spherrical Earth
    v_earth =  4.0/3.0*pi*r_earth^3

    # Return number of ducks in Earth
    return v_earth/duck_volume
end

export RUBBER_DUCK_PER_SEC
"""
Number of rubber ducks produced per second in the entire world on average.

Units: _ducks/sec_
"""
const RUBBER_DUCK_PER_SEC = 200.0

export add_one
"""
Adds one to a value

Argments:
- `val:Real` A value

Returns:
- `x::Float64` Value plus one
"""
function add_one(val::Real)
    return val + 1
end

end # End of module RubberDucks (This comment is nice to distinguish from an end)


module ICR

using SmoothedParticles
using Printf
using SparseArrays

function add_element!(I::Vector{Int64}, J::Vector{Int64}, V::Vector{Float64}, i::Int64, j::Int64, v::Float64)
    if v == 0.0
        return
    end
    push!(I, i)
    push!(J, j)
    push!(V, v)
end

function lhs(sys::ParticleSystem, rDker::Function)::SparseMatrixCSC{Float64}
	N = length(sys.particles)
    I = Int64[]
    J = Int64[]
    V = Float64[]
    for i in 1:N
        p = sys.particles[i]
        S1 = SPHLib.sum(sys, (p,q,r) -> -2*rDker(sys.h,r)*(p.x[1]-q.x[1]), p)
        S2 = SPHLib.sum(sys, (p,q,r) -> -2*rDker(sys.h,r)*(p.x[2]-q.x[2]), p)
        S = RealVector(S1, S2, 0.)
        for j in 1:N
            q = sys.particles[j]
            r = dist(p,q)
            if r > sys.h
                continue
            end
            tmp0 = Float64(p==q)
            tmp1 = rDker(sys.h,r)*(p.x - q.x)
            tmp2 = 0.5*Float64(p==q)*S
            #UL block = I
            add_element!(I,J,V,i+0N,j+0N,tmp0)
            add_element!(I,J,V,i+1N,j+1N,tmp0)
            #UR block = Grad
            add_element!(I,J,V,i+0N,j+2N, tmp1[1]-tmp2[1])
            add_element!(I,J,V,i+1N,j+2N, tmp1[2]-tmp2[2])
            #DL block = Div
            add_element!(I,J,V,i+2N,j+0N, tmp1[1]+tmp2[1])
            add_element!(I,J,V,i+2N,j+1N, tmp1[2]+tmp2[2])
        end
    end
    return sparse(I, J, V, 3N, 3N)
end

function rhs(sys::ParticleSystem, ker::Function, rho0::Float64)
    N = length(sys.particles)
    b = zeros(3N)
    for i in 1:N
        p = sys.particles[i]
        b[i+2N] = SPHLib.sum(sys, (p,q,r) -> ker(sys.h,r), p) - rho0
	end
    return b
end

function renormalize!(sys::ParticleSystem, dr::Float64;
    tol = 1e-6, max_steps = 10,
    ker::Function = wendland2, rDker::Function = rDwendland2)
    @info("renormalizing, please wait...")
    rho0 = 1/dr^2
    for p in sys.particles
        p.x += 0.3*dr*RealVector(rand() - 1.0, rand() - 1.0, 0.)
    end
    for _ in 1:max_steps
        create_cell_list!(sys)
        b = rhs(sys, ker, rho0)
        err = maximum(x -> abs(x), b)
        @show err
        if err < tol
            break
        end
        A = lhs(sys, rDker)
        y = A\b
        N = length(sys.particles)
        for i in 1:N
            sys.particles[i].x += RealVector(y[i], y[i+N], 0.)
        end
	end
end

end


"""
Subroutine dtrpcg

Given a sparse symmetric matrix A in compressed column storage,
this subroutine uses a preconditioned conjugate gradient method
to find an approximate minimizer of the trust region subproblem

  min { q(s) : || L'*s || <= delta }.

where q is the quadratic

  q(s) = 0.5*s'*A*s + g'*s,

A is a symmetric matrix in compressed column storage, L is a
lower triangular matrix in compressed column storage, and g
is a vector.

This subroutine generates the conjugate gradient iterates for
the equivalent problem

  min { Q(w) : || w || <= delta },

where Q is the quadratic defined by

  Q(w) = q(s),        w = L'*s.

Termination occurs if the conjugate gradient iterates leave
the trust regoin, a negative curvature direction is generated,
or one of the following two convergence tests is satisfied.

Convergence in the original variables:

  || grad q(s) || <= tol

Convergence in the scaled variables:

  || grad Q(w) || <= stol

Note that if w = L'*s, then L*grad Q(w) = grad q(s).

MINPACK-2 Project. March 1999.
Argonne National Laboratory.
Chih-Jen Lin and Jorge J. More'.

August 1999

Corrected documentation for l, ldiag, lcol_ptr, and lrow_ind.

February 2001

We now set iters = 0 in the special case g = 0.
"""
function dtrpcg(n,A,g,delta,L,
                tol,stol,itermax,w,
                p,q,r,t,z)
    zero = 0.0
    one = 1.0

    # Initialize the iterate w and the residual r.
    fill!(w, 0)

    # Initialize the residual t of grad q to -g.
    # Initialize the residual r of grad Q by solving L*r = -g.
    # Note that t = L*r.
    dcopy(n,g,1,t,1)
    dscal(n,-one,t,1)
    dcopy(n,t,1,r,1)
    dnsol(n, L, r)

    # Initialize the direction p.
    dcopy(n,r,1,p,1)

    # Initialize rho and the norms of r and t.
    rho = ddot(n,r,1,r,1)
    rnorm0 = sqrt(rho)

    # Exit if g = 0.
    iters = 0
    if rnorm0 == zero
        iters = 0
        info = 1
        return info, iters
    end

    for iters=1:itermax

        # Note:
        # Q(w) = 0.5*w'Bw + h'w, where B=L^{-1}AL^{-T}, h=L^{-1}g.
        # Then p'Bp = p'L^{-1}AL^{-T}p = p'L^{-1}Az = p'q.
        # alpha = r'r / p'Bp.

        dcopy(n,p,1,z,1)
        dtsol(n, L, z)

        # Compute q by solving L*q = A*z and save L*q for
        # use in updating the residual t.
        dssyax(n, A, z, q)
        dcopy(n,q,1,z,1)
        dnsol(n, L, q)

        # Compute alpha and determine sigma such that the trust region
        # constraint || w + sigma*p || = delta is satisfied.
        ptq = ddot(n,p,1,q,1)
        if ptq > zero
            alpha = rho/ptq
        else
            alpha = zero
        end
        sigma = dtrqsol(n,w,p,delta)

        # Exit if there is negative curvature or if the
        # iterates exit the trust region.

        if (ptq <= zero) || (alpha >= sigma)
            daxpy(n,sigma,p,1,w,1)
            if ptq <= zero
                info = 3
            else
                info = 4
            end

            return info, iters
        end

        # Update w and the residuals r and t.
        # Note that t = L*r.

        daxpy(n,alpha,p,1,w,1)
        daxpy(n,-alpha,q,1,r,1)
        daxpy(n,-alpha,z,1,t,1)

        # Exit if the residual convergence test is satisfied.

        rtr = ddot(n,r,1,r,1)
        rnorm = sqrt(rtr)
        tnorm = sqrt(ddot(n,t,1,t,1))

        if tnorm <= tol
            info = 1
            return info, iters
        end

        if rnorm <= stol
            info = 2
            return info, iters
        end

        # Compute p = r + beta*p and update rho.
        beta = rtr/rho
        dscal(n,beta,p,1)
        daxpy(n,one,r,1,p,1)
        rho = rtr
    end

    iters = itermax
    info = 5
    return info, iters
end

@inline function dtrpcg(n::Int, A::CuDeviceArray{Float64,2},
                        g::CuDeviceArray{Float64,1}, delta::Float64,
                        L::CuDeviceArray{Float64,2},
                        tol::Float64, stol::Float64, itermax::Int,
                        w::CuDeviceArray{Float64,1},
                        p::CuDeviceArray{Float64,1},
                        q::CuDeviceArray{Float64,1},
                        r::CuDeviceArray{Float64,1},
                        t::CuDeviceArray{Float64,1},
                        z::CuDeviceArray{Float64,1})
  zero = 0.0
  one = 1.0

  # Initialize the iterate w and the residual r.
  fill!(w, 0)

  # Initialize the residual t of grad q to -g.
  # Initialize the residual r of grad Q by solving L*r = -g.
  # Note that t = L*r.
  dcopy(n,g,1,t,1)
  dscal(n,-one,t,1)
  dcopy(n,t,1,r,1)
  dnsol(n, L, r)

  # Initialize the direction p.
  dcopy(n,r,1,p,1)

  # Initialize rho and the norms of r and t.
  rho = ddot(n,r,1,r,1)
  rnorm0 = sqrt(rho)

  # Exit if g = 0.
  iters = 0
  if rnorm0 == zero
    iters = 0
    info = 1
    return info, iters
  end

  for iters=1:itermax

    # Note:
    # Q(w) = 0.5*w'Bw + h'w, where B=L^{-1}AL^{-T}, h=L^{-1}g.
    # Then p'Bp = p'L^{-1}AL^{-T}p = p'L^{-1}Az = p'q.
    # alpha = r'r / p'Bp.

    dcopy(n,p,1,z,1)
    dtsol(n, L, z)

    # Compute q by solving L*q = A*z and save L*q for
    # use in updating the residual t.
    dssyax(n, A, z, q)
    dcopy(n,q,1,z,1)
    dnsol(n, L, q)

    # Compute alpha and determine sigma such that the trust region
    # constraint || w + sigma*p || = delta is satisfied.
    ptq = ddot(n,p,1,q,1)
    if ptq > zero
      alpha = rho/ptq
    else
      alpha = zero
    end
    sigma = dtrqsol(n,w,p,delta)

    # Exit if there is negative curvature or if the
    # iterates exit the trust region.

    if (ptq <= zero) || (alpha >= sigma)
      daxpy(n,sigma,p,1,w,1)
      if ptq <= zero
        info = 3
      else
        info = 4
      end

      return info, iters
    end

    # Update w and the residuals r and t.
    # Note that t = L*r.

    daxpy(n,alpha,p,1,w,1)
    daxpy(n,-alpha,q,1,r,1)
    daxpy(n,-alpha,z,1,t,1)

    # Exit if the residual convergence test is satisfied.

    rtr = ddot(n,r,1,r,1)
    rnorm = sqrt(rtr)
    tnorm = sqrt(ddot(n,t,1,t,1))

    if tnorm <= tol
      info = 1
      return info, iters
    end

    if rnorm <= stol
      info = 2
      return info, iters
    end

    # Compute p = r + beta*p and update rho.
    beta = rtr/rho
    dscal(n,beta,p,1)
    daxpy(n,one,r,1,p,1)
    rho = rtr
  end

  iters = itermax
  info = 5
  return info, iters
end


function compute_int_K(params, α₁, n₁, α₂, n₂)
    @unpack r, D, dim = params

    m = length(D)
    R = r[m]

    dr = r[m] * 1e-12
    r₁ = [0; r[1:m-1]] .+ dr
    r₂ = r[1:m] .- dr

    K = 0
    if abs(n₁ - n₂) == 1
        λ₁ = α₁^2
        λ₂ = α₂^2
        ν₁ = compute_ν(n₁, dim)
        ν₂ = compute_ν(n₂, dim)

        # Computation at point r₁
        V₁, dV₁ = compute_v(params, α₁, n₁, r₁)
        V₂, dV₂ = compute_v(params, α₂, n₂, r₁)
        I₁a =
            (
                (λ₁ + λ₂) * D .* r₁ .^ (dim - 1) -
                (ν₁ + ν₂ - (dim - 1)) * D .^ 2 .* r₁ .^ (dim - 3)
            ) .* V₁ .* V₂
        I₁b =
            (
                (λ₂ - λ₁) * D .* r₁ .^ dim -
                (ν₁ - ν₂ - (dim - 1)) * D .^ 2 .* r₁ .^ (dim - 2)
            ) .* dV₁ .* V₂
        I₁c =
            (
                (λ₁ - λ₂) * D .* r₁ .^ dim -
                (ν₂ - ν₁ - (dim - 1)) * D .^ 2 .* r₁ .^ (dim - 2)
            ) .* V₁ .* dV₂
        I₁d = 2 * D .^ 2 .* r₁ .^ (dim - 1) .* dV₁ .* dV₂
        I₁ = I₁a + I₁b + I₁c + I₁d

        # Computation at point r₂
        V₁, dV₁ = compute_v(params, α₁, n₁, r₂)
        V₂, dV₂ = compute_v(params, α₂, n₂, r₂)
        I₂a =
            (
                (λ₁ + λ₂) * D .* r₂ .^ (dim - 1) -
                (ν₁ + ν₂ - (dim - 1)) * D .^ 2 .* r₂ .^ (dim - 3)
            ) .* V₁ .* V₂
        I₂b =
            (
                (λ₂ - λ₁) * D .* r₂ .^ dim -
                (ν₁ - ν₂ - (dim - 1)) * D .^ 2 .* r₂ .^ (dim - 2)
            ) .* dV₁ .* V₂
        I₂c =
            (
                (λ₁ - λ₂) * D .* r₂ .^ dim -
                (ν₂ - ν₁ - (dim - 1)) * D .^ 2 .* r₂ .^ (dim - 2)
            ) .* V₁ .* dV₂
        I₂d = 2 * D .^ 2 .* r₂ .^ (dim - 1) .* dV₁ .* dV₂
        I₂ = I₂a + I₂b + I₂c + I₂d
    end

    K = sum(I₂ - I₁) / (λ₁ - λ₂)^2 / R^(dim + 1)
end


struct KalmanFilter <: Transducer
    lssm::LinearStateSpaceModel
    lobs::LinearObservation
end

function predict(l::LinearStateSpaceModel,m,P)
    l.A*m, l.A*P*l.A'+l.Q
end

function update(l::LinearObservation,y,m,P)
    v = y-l.H*m
    S = l.H*P*l.H' + l.R
    K = P*l.H'*inv(S)
    mu = m + K*v
    Pu = P - K*S*K'
    return mu, Pu, v, S
end

function kalman(lssm::LinearStateSpaceModel, lo::LinearObservation,y,m,P)
    m_i,P_i = predict(lssm,m,P)
    update(lo,y,m_i,P_i)
end

function Transducers.start(rf::R_{KalmanFilter}, result)
    private_state = (zeros(size(xform(rf).lssm.A,1)), one(xform(rf).lssm.A))
    return wrap(rf, private_state, start(inner(rf), result))
end

function Transducers.next(rf::R_{KalmanFilter}, result, input)
    wrapping(rf, result) do st, iresult
        (m, P) = st
        mu, Pu, v, S = kalman(xform(rf).lssm, xform(rf).lobs, input, m, P)
        iresult = next(inner(rf), iresult, (mu, Pu, v, S))
        return (mu, Pu), iresult
    end
end


# function Vnk_NGG_old(n, k, β, σ)
#     β_arb = RR(β)
#     σ_arb = RR(σ)
#     return exp(β_arb) * σ_arb^(k-1) // RR(gamma(n)) * sum([binom(n-1, i, RR) * (-1)^i * β_arb ^(i//σ_arb) * real(gamma(k-i//CC(σ_arb), CC(β_arb))) for i in 0:(n-1)])
# end

"""
    Vnk_NGG(n, k, β, σ)

Calculation of the Vnk with arbitrary precision. This function is less accurate for small values of k.

# Examples
```julia-repl
julia> GibbsTypePriors.Vnk_NGG(950,50, 0.5, 0.2)
[3.8352033538906132801013353906868979398003615464023850790896863425714903553677226925055088746374316383109460954810284197859760984821731337318749595774947223616262209073739394021758498274625163338449050351539476265420058089446771803706284551125841735045167180428032503440230421477960661771838407576902892779249509437591900661480877143065301185622812541265406061355208499807014555543564206395963786037047501112201458115571178196894484765277010637093746870221360851774075869753015052347546075947342163011788718201438701670521824067688930574380546890246338996488357288469652851508886233959379599363414674816239620509492570297419371380404876526047019213064592743056547790007323431419320067128517155931031446743616067264862092384235340693861382516829416661260795864740780157957535281924044152165190002256884051072596878645089791000193415582741991189356825795269080464284696216369289690437152742813909679764874302349498025317865584922699306108146943457154750027744867148116339573187411564950371482234809283153942745404958070388914430591794906329509545071131596480723325249062012958207542739071651236894164675019841047663212800147578131631060869316509586195876115342586677261181109184501899398551285655781119232061487943017880727759501463530477776e-2387 +/- 8.27e-3625]
```
"""
function Vnk_NGG(n, k, β, σ)
    β_arb = RR(β)
    σ_arb = RR(σ)
    n_m_1_Flint = FlintZZ(n-1)
    return exp(β_arb) * σ_arb^(k-1) // RR(gamma(n)) * sum(binomial(n_m_1_Flint, FlintZZ(i)) * (-1)^i * β_arb ^(i//σ_arb) * real(gamma(k-i//CC(σ_arb), CC(β_arb))) for i in 0:(n-1))
end
#
# function Vnk_NGG2(n, k, β, σ)
#     gamma_n::arb = gamma(n)
#     return exp(β) * σ^(k-1) // gamma_n * sum([binom(n-1, i, RR) * (-1)^i * β^(i//σ) * real(gamma(k-i//CC(σ), CC(β))) for i in 0:(n-1)])
# end
# function Vnk_NGG3(n, k, β, σ)
#     gamma_n::arb = gamma(n)
#     return exp(β) * σ^(k-1) // gamma_n * sum(binom(n-1, i, RR) * (-1)^i * β^(i//σ) * real(gamma(k-i//CC(σ), CC(β))) for i in 0:(n-1))
# end
# function Vnk_NGG4(n, k, β, σ)
#     gamma_n::arb = gamma(n)
#     n_m_1_Flint = FlintZZ(n-1)
#     return exp(β) * σ^(k-1) // gamma_n * sum(binomial(n_m_1_Flint, FlintZZ(i)) * (-1)^i * β^(i//σ) * real(gamma(k-i//CC(σ), CC(β))) for i in 0:(n-1))
# end

# @memoize function Vnk_NGG_rec(n, k, β, σ)
#     # println("n = $n, k = $k")
#     if n < 100 || k ≥ n/2
#         return Vnk_NGG(n, k, β, σ)
#     else
#         return (Vnk_NGG_rec(n-1, k, β, σ)-Vnk_NGG_rec(n, k+1, β, σ))/RR(n-1-σ*k)
#     end
# end

@memoize function Vnk_NGG_rec(n, k, β, σ)
    res = Vnk_NGG(n, k, β, σ)
    if has_reasonable_precision(res)
        return res
    else
        println("n = $n, k = $k, Accuracy = $(accuracy_bits(res))")
        return (Vnk_NGG_rec(n-1, k, β, σ)-Vnk_NGG_rec(n, k+1, β, σ))/RR(n-1-σ*k)
    end
end

"""
    Vnk_2PD(n, k, θ, σ)

Calculation of the Vnk for a 2-parameter Poisson-Dirichlet (also known as Pitman-Yor) process with arbitrary precision.

# Examples
```julia-repl
julia> GibbsTypePriors.Vnk_2PD(7, 6, 0.5, 0.01)
[1.9770514285714285829380091500954323693644674824124026984992171309957729383892396509600074280486078176779783843427919567657142584744842411272863181245428808578583616597575557259694224232057348431696299003094318048035523344218002322698832143887862395401395274358633297792959324407688665501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501165501e-5 +/- 2.28e-1510]
```
"""
function Vnk_2PD(n, k, θ, σ)
    θ_arb::arb = RR(θ)
    σ_arb::arb = RR(σ)
    if k==1
        num::arb = arb_1
    else
        num = prod([θ_arb + i * σ_arb for i in 1:(k-1)])
    end
    return num // risingfac(1+θ_arb,n-1)
end


# Implement kernels for polar formulation

"""
    power_balance(V, Ybus, Sbus, pv, pq)

Assembly residual function for N-R power flow.
In complex form, the balance equations writes

``
g(V) = V * (Y_{bus} * V)^* - S_{bus}
``

# Note
Code adapted from MATPOWER.
"""
function power_balance(V, Ybus, Sbus, pv, pq)
    # form mismatch vector
    mis = V .* conj(Ybus * V) - Sbus
    # form residual vector
    F = [real(mis[pv])
         real(mis[pq])
         imag(mis[pq]) ]
    return F
end

"""
    residual_jacobian(V, Ybus, pv, pq)

Compute the Jacobian w.r.t. the state `x` of the power
balance function [`power_balance`](@ref).

# Note
Code adapted from MATPOWER.
"""
function residual_jacobian(V, Ybus, pv, pq)
    n = size(V, 1)
    Ibus = Ybus*V
    diagV       = sparse(1:n, 1:n, V, n, n)
    diagIbus    = sparse(1:n, 1:n, Ibus, n, n)
    diagVnorm   = sparse(1:n, 1:n, V./abs.(V), n, n)

    dSbus_dVm = diagV * conj(Ybus * diagVnorm) + conj(diagIbus) * diagVnorm
    dSbus_dVa = 1im * diagV * conj(diagIbus - Ybus * diagV)

    j11 = real(dSbus_dVa[[pv; pq], [pv; pq]])
    j12 = real(dSbus_dVm[[pv; pq], pq])
    j21 = imag(dSbus_dVa[pq, [pv; pq]])
    j22 = imag(dSbus_dVm[pq, pq])

    J = [j11 j12; j21 j22]
end

function _state_jacobian(polar::PolarForm)
    pf = polar.network
    ref = polar.network.ref
    pv = polar.network.pv
    pq = polar.network.pq
    n = PS.get(pf, PS.NumberOfBuses())

    Y = pf.Ybus
    # Randomized inputs
    Vre = rand(n)
    Vim = rand(n)
    V = Vre .+ 1im .* Vim
    return residual_jacobian(V, Y, pv, pq)
end
_sparsity_pattern(polar::PolarForm) = findnz(_state_jacobian(polar))

"""
    get_power_injection(fr, v_m, v_a, ybus_re, ybus_im)

Computes the power injection at node `fr`.
In polar form, the power injection at node `i` satisfies
```math
p_{i} = \\sum_{j} v_{i} v_{j} (g_{ij} \\cos(\\theta_i - \\theta_j) + b_{ij} \\sin(\\theta_i - \\theta_j))
```
"""
function get_power_injection(fr, v_m, v_a, ybus_re, ybus_im)
    P = 0.0
    for c in ybus_re.colptr[fr]:ybus_re.colptr[fr+1]-1
        to = ybus_re.rowval[c]
        aij = v_a[fr] - v_a[to]
        P += v_m[fr]*v_m[to]*(ybus_re.nzval[c]*cos(aij) + ybus_im.nzval[c]*sin(aij))
    end
    return P
end

"""
    get_react_injection(fr, v_m, v_a, ybus_re, ybus_im)

Computes the reactive power injection at node `fr`.
In polar form, the power injection at node `i` satisfies
```math
q_{i} = \\sum_{j} v_{i} v_{j} (g_{ij} \\sin(\\theta_i - \\theta_j) - b_{ij} \\cos(\\theta_i - \\theta_j))
```
"""
function get_react_injection(fr::Int, v_m, v_a, ybus_re::Spmat{VI,VT}, ybus_im::Spmat{VI,VT}) where {VT <: AbstractVector, VI<:AbstractVector}
    Q = zero(eltype(v_m))
    for c in ybus_re.colptr[fr]:ybus_re.colptr[fr+1]-1
        to = ybus_re.rowval[c]
        aij = v_a[fr] - v_a[to]
        Q += v_m[fr]*v_m[to]*(ybus_re.nzval[c]*sin(aij) - ybus_im.nzval[c]*cos(aij))
    end
    return Q
end

@kernel function residual_kernel!(F, v_m, v_a,
                                  ybus_re_nzval, ybus_re_colptr, ybus_re_rowval,
                                  ybus_im_nzval, ybus_im_colptr, ybus_im_rowval,
                                  pinj, qinj, pv, pq, nbus)

    npv = size(pv, 1)
    npq = size(pq, 1)

    i = @index(Global, Linear)
    # REAL PV: 1:npv
    # REAL PQ: (npv+1:npv+npq)
    # IMAG PQ: (npv+npq+1:npv+2npq)
    fr = (i <= npv) ? pv[i] : pq[i - npv]
    F[i] -= pinj[fr]
    if i > npv
        F[i + npq] -= qinj[fr]
    end
    @inbounds for c in ybus_re_colptr[fr]:ybus_re_colptr[fr+1]-1
        to = ybus_re_rowval[c]
        aij = v_a[fr] - v_a[to]
        # f_re = a * cos + b * sin
        # f_im = a * sin - b * cos
        coef_cos = v_m[fr]*v_m[to]*ybus_re_nzval[c]
        coef_sin = v_m[fr]*v_m[to]*ybus_im_nzval[c]
        cos_val = cos(aij)
        sin_val = sin(aij)
        F[i] += coef_cos * cos_val + coef_sin * sin_val
        if i > npv
            F[npq + i] += coef_cos * sin_val - coef_sin * cos_val
        end
    end
end

function residual_polar!(F, v_m, v_a,
                         ybus_re, ybus_im,
                         pinj, qinj, pv, pq, nbus)
    npv = length(pv)
    npq = length(pq)
    if isa(F, Array)
        kernel! = residual_kernel!(CPU(), 4)
    else
        kernel! = residual_kernel!(CUDADevice(), 256)
    end
    ev = kernel!(F, v_m, v_a,
                 ybus_re.nzval, ybus_re.colptr, ybus_re.rowval,
                 ybus_im.nzval, ybus_im.colptr, ybus_im.rowval,
                 pinj, qinj, pv, pq, nbus,
                 ndrange=npv+npq)
    wait(ev)
end

@kernel function transfer_kernel!(vmag, vang, pinj, qinj,
                        x, u, p, pv, pq, ref,
                        ybus_re_nzval, ybus_re_colptr, ybus_re_rowval,
                        ybus_im_nzval, pload)
    i = @index(Global, Linear)
    npv = length(pv)
    npq = length(pq)
    nref = length(ref)

    # PV bus
    if i <= npv
        bus = pv[i]
        qacc = 0
        @inbounds for c in ybus_re_colptr[bus]:ybus_re_colptr[bus+1]-1
            to = ybus_re_rowval[c]
            aij = vang[bus] - vang[to]
            # f_re = a * cos + b * sin
            # f_im = a * sin - b * cos
            coef_cos = vmag[bus]*vmag[to]*ybus_re_nzval[c]
            coef_sin = vmag[bus]*vmag[to]*ybus_im_nzval[c]
            cos_val = cos(aij)
            sin_val = sin(aij)
            qacc += coef_cos * sin_val - coef_sin * cos_val
        end
        qinj[bus] = qacc
        vang[bus] = x[i]
        pinj[bus] = u[nref + i] - pload[bus]
        vmag[bus] = u[nref + npv + i]
    # PQ bus
    elseif i <= npv + npq
        i_pq = i - npv
        bus = pq[i_pq]
        vang[bus] = x[npv+i_pq]
        vmag[bus] = x[npv+npq+i_pq]
        pinj[bus] = p[nref + i_pq]
        qinj[bus] = p[nref + npq + i_pq]
    # REF bus
    else i <= npv + npq + nref
        i_ref = i - npv - npq
        bus = ref[i_ref]
        pacc = 0
        qacc = 0
        @inbounds for c in ybus_re_colptr[bus]:ybus_re_colptr[bus+1]-1
            to = ybus_re_rowval[c]
            aij = vang[bus] - vang[to]
            # f_re = a * cos + b * sin
            # f_im = a * sin - b * cos
            coef_cos = vmag[bus]*vmag[to]*ybus_re_nzval[c]
            coef_sin = vmag[bus]*vmag[to]*ybus_im_nzval[c]
            cos_val = cos(aij)
            sin_val = sin(aij)
            pacc += coef_cos * cos_val + coef_sin * sin_val
            qacc += coef_cos * sin_val - coef_sin * cos_val
        end
        pinj[bus] = pacc
        qinj[bus] = qacc
        vmag[bus] = u[i_ref]
        vang[bus] = p[i_ref]
    end
end

# Transfer values in (x, u, p) to buffer
function transfer!(polar::PolarForm, buffer::PolarNetworkState, x, u, p)
    if isa(x, Array)
        kernel! = transfer_kernel!(CPU(), 1)
    else
        kernel! = transfer_kernel!(CUDADevice(), 256)
    end
    nbus = length(buffer.vmag)
    pv = polar.indexing.index_pv
    pq = polar.indexing.index_pq
    ref = polar.indexing.index_ref
    ev = kernel!(
        buffer.vmag, buffer.vang, buffer.pinj, buffer.qinj,
        x, u, p,
        pv, pq, ref,
        polar.ybus_re.nzval, polar.ybus_re.colptr, polar.ybus_re.rowval,
        polar.ybus_im.nzval, polar.active_load,
        ndrange=nbus
    )
    wait(ev)
end

@kernel function active_power_kernel!(
    pg, vmag, vang, pinj, qinj,
    pv, ref, pv_to_gen, ref_to_gen,
    ybus_re_nzval, ybus_re_colptr, ybus_re_rowval,
    ybus_im_nzval, pload
)
    i = @index(Global, Linear)
    npv = length(pv)
    nref = length(ref)
    # Evaluate active power at PV nodes
    if i <= npv
        bus = pv[i]
        i_gen = pv_to_gen[i]
        pg[i_gen] = pinj[bus] + pload[bus]
    # Evaluate active power at slack nodes
    elseif i <= npv + nref
        i_ = i - npv
        bus = ref[i_]
        i_gen = ref_to_gen[i_]
        inj = 0
        @inbounds for c in ybus_re_colptr[bus]:ybus_re_colptr[bus+1]-1
            to = ybus_re_rowval[c]
            aij = vang[bus] - vang[to]
            # f_re = a * cos + b * sin
            # f_im = a * sin - b * cos
            coef_cos = vmag[bus]*vmag[to]*ybus_re_nzval[c]
            coef_sin = vmag[bus]*vmag[to]*ybus_im_nzval[c]
            cos_val = cos(aij)
            sin_val = sin(aij)
            inj += coef_cos * cos_val + coef_sin * sin_val
        end
        pg[i_gen] = inj + pload[bus]
    end
end

# Refresh active power (needed to evaluate objective)
function refresh!(polar::PolarForm, ::PS.Generator, ::PS.ActivePower, buffer::PolarNetworkState)
    if isa(buffer.vmag, Array)
        kernel! = active_power_kernel!(CPU(), 1)
    else
        kernel! = active_power_kernel!(CUDADevice(), 256)
    end
    pv = polar.indexing.index_pv
    pq = polar.indexing.index_pq
    ref = polar.indexing.index_ref
    pv_to_gen = polar.indexing.index_pv_to_gen
    ref_to_gen = polar.indexing.index_ref_to_gen

    range_ = length(pv) + length(ref)

    ev = kernel!(
        buffer.pg,
        buffer.vmag, buffer.vang, buffer.pinj, buffer.qinj,
        pv, ref, pv_to_gen, ref_to_gen,
        polar.ybus_re.nzval, polar.ybus_re.colptr, polar.ybus_re.rowval,
        polar.ybus_im.nzval, polar.active_load,
        ndrange=range_
    )
    wait(ev)
end

@kernel function reactive_power_kernel!(
    qg, vmag, vang, pinj, qinj,
    pv, ref, pv_to_gen, ref_to_gen,
    ybus_re_nzval, ybus_re_colptr, ybus_re_rowval,
    ybus_im_nzval, qload
)
    i = @index(Global, Linear)
    npv = length(pv)
    nref = length(ref)
    # Evaluate reactive power at PV nodes
    if i <= npv
        bus = pv[i]
        i_gen = pv_to_gen[i]
    # Evaluate reactive power at slack nodes
    elseif i <= npv + nref
        i_ = i - npv
        bus = ref[i_]
        i_gen = ref_to_gen[i_]
    end
    inj = 0
    @inbounds for c in ybus_re_colptr[bus]:ybus_re_colptr[bus+1]-1
        to = ybus_re_rowval[c]
        aij = vang[bus] - vang[to]
        # f_re = a * cos + b * sin
        # f_im = a * sin - b * cos
        coef_cos = vmag[bus]*vmag[to]*ybus_re_nzval[c]
        coef_sin = vmag[bus]*vmag[to]*ybus_im_nzval[c]
        cos_val = cos(aij)
        sin_val = sin(aij)
        inj += coef_cos * sin_val - coef_sin * cos_val
    end
    qg[i_gen] = inj + qload[bus]
end

function refresh!(polar::PolarForm, ::PS.Generator, ::PS.ReactivePower, buffer::PolarNetworkState)
    if isa(buffer.vmag, Array)
        kernel! = reactive_power_kernel!(CPU(), 1)
    else
        kernel! = reactive_power_kernel!(CUDADevice(), 256)
    end
    pv = polar.indexing.index_pv
    pq = polar.indexing.index_pq
    ref = polar.indexing.index_ref
    pv_to_gen = polar.indexing.index_pv_to_gen
    ref_to_gen = polar.indexing.index_ref_to_gen

    range_ = length(pv) + length(ref)
    ev = kernel!(
        buffer.qg,
        buffer.vmag, buffer.vang, buffer.pinj, buffer.qinj,
        pv, ref, pv_to_gen, ref_to_gen,
        polar.ybus_re.nzval, polar.ybus_re.colptr, polar.ybus_re.rowval,
        polar.ybus_im.nzval, polar.reactive_load,
        ndrange=range_
    )
    wait(ev)
end

@kernel function load_power_constraint_kernel!(
    g, qg, ref_to_gen, pv_to_gen, nref, npv, shift
)
    i = @index(Global, Linear)
    # Evaluate reactive power at PV nodes
    if i <= npv
        ig = pv_to_gen[i]
        g[i + nref + shift] = qg[ig]
    else i <= npv + nref
        i_ = i - npv
        ig = ref_to_gen[i_]
        g[i_ + shift] = qg[ig]
    end
end


using IterativeSolvers, KrylovKit, LinearAlgebra

# In this file, we provide linear solvers for the Package

abstract type AbstractLinearSolver end

# The function linsolve(J, x; kwargs...) must return whether the solve was successfull and how many steps were required for the solve.

# the following function can be used to cache some factorization, see DefaultLS() case for example
function (ls::AbstractLinearSolver)(J, rhs1, rhs2; kwargs...)
	sol1, flag1, it1 = ls(J, rhs1; kwargs...)
	sol2, flag2, it2 = ls(J, rhs2; kwargs...)
	return sol1, sol2, flag1 & flag2, (it1, it2)
end

####################################################################################################
# The following functions are used for the Continuation of Hopf points and the computation of Floquet multipliers

# this function returns a₀ * I + a₁ .* J and ensures that we don't do unnecessary computations like 0*I + 1*J
function _axpy(J, a₀, a₁)
	if a₀ == 0
		if a₁ == 1
			return J
		else
			return a₁ .* J
		end
	elseif a₀ == 1
		if a₁ == 1
			return I + J
		else
			return I + a₁ .* J
		end
	else
		return a₀ * I + a₁ .* J
	end
end

function _axpy_op(J, v::AbstractArray, a₀, a₁)
	if a₀ == 0
		if a₁ == 1
			return apply(J, v)
		else
			return a₁ .* apply(J, v)
		end
	elseif a₀ == 1
		if a₁ == 1
			return v .+ apply(J, v)
		else
			return v .+ a₁ .* apply(J, v)
		end
	else
		return a₀ .* v .+ a₁ .* apply(J, v)
	end
end

####################################################################################################
# Solvers for default \ operator (backslash)
####################################################################################################
"""
$(TYPEDEF)

This struct is used to provide the backslash operator. Can be used to solve `(a₀ * I + a₁ * J) * x = rhs`.

$(TYPEDFIELDS)
"""
@with_kw struct DefaultLS <: AbstractLinearSolver
	"Whether to catch a factorization for multiple solves. Some operators may not support LU (like ApproxFun.jl) or QR factorization so it is best to let the user decides. Some matrices do not have `factorize` like `StaticArrays.MMatrix`."
	useFactorization::Bool = true
end

# this function is used to solve (a₀ * I + a₁ * J) * x = rhs
# the options a₀, a₁ are only used for the Hopf Newton / Continuation
function (l::DefaultLS)(J, rhs; a₀ = 0, a₁ = 1, kwargs...)
	return _axpy(J, a₀, a₁) \ rhs, true, 1
end

# this function is used to solve (a₀ * I + a₁ * J) * x = rhs
# with multiple RHS. We can cache the factorization in this case
# the options a₀, a₁ are only used for the Hopf Newton / Continuation
function (l::DefaultLS)(J, rhs1, rhs2; a₀ = 0, a₁ = 1, kwargs...)
	if l.useFactorization
		Jfact = factorize(_axpy(J, a₀, a₁))
		return Jfact \ rhs1, Jfact \ rhs2, true, (1, 1)
	else
		_J = _axpy(J, a₀, a₁)
		return _J \ rhs1, _J \ rhs2, true, (1, 1)
	end
end
####################################################################################################
# Solvers for IterativeSolvers
####################################################################################################
"""
$(TYPEDEF)
Linear solver based on gmres from `IterativeSolvers.jl`. Can be used to solve `(a₀ * I + a₁ * J) * x = rhs`.
$(TYPEDFIELDS)
"""
@with_kw mutable struct GMRESIterativeSolvers{T, Tl, Tr} <: AbstractLinearSolver
	"Absolute tolerance for solver"
	abstol::T = 0.0

	"Relative tolerance for solver"
	reltol::T = 1e-8

	"Number of restarts"
	restart::Int64 = 200

	"Maximum number of iterations"
	maxiter::Int64 = 100

	"Dimension of the problem"
	N::Int64 = 0

	"Display information during iterations"
	verbose::Bool = false

	"Record information"
	log::Bool = true

	"Start with zero guess"
	initially_zero::Bool = true

	"Left preconditioner"
	Pl::Tl = IterativeSolvers.Identity()

	"Right preconditioner"
	Pr::Tr = IterativeSolvers.Identity()

	"Whether the linear operator is written inplace"
	ismutating::Bool = false
end

# this function is used to solve (a₀ * I + a₁ * J) * x = rhs
# the optional shift is only used for the Hopf Newton / Continuation
function (l::GMRESIterativeSolvers{T, Tl, Tr})(J, rhs; a₀ = 0, a₁ = 1, kwargs...) where {T, Ts, Tl, Tr}
	# no need to use fancy axpy! here because IterativeSolvers "only" handles AbstractArray
	if l.ismutating == true
		Jmap = LinearMap{T}((o, v) -> J(o, v), l.N, l.N ; ismutating = true)
		@assert ((a₀ == 0) && (a₁ == 1)) "Perturbed inplace linear problem not done yet!"
	else
		J_map = v -> _axpy_op(J, v, a₀, a₁)
		Jmap = LinearMap{T}(J_map, l.N, l.N ; ismutating = false)
	end
	res = IterativeSolvers.gmres(Jmap, rhs; abstol = l.abstol, reltol = l.reltol, log = l.log, verbose = l.verbose, restart = l.restart, maxiter = l.maxiter, initially_zero = l.initially_zero, Pl = l.Pl, Pr = l.Pr, kwargs...)
	(res[2].iters >= l.maxiter) && (@warn "IterativeSolvers.gmres iterated maxIter = $(res[2].iters) times without achieving the desired tolerance.\n")
	return res[1], length(res) > 1, res[2].iters
end
####################################################################################################
# Solvers for KrylovKit
####################################################################################################
"""
$(TYPEDEF)

Create a linear solver based on GMRES from `KrylovKit.jl`. Can be used to solve `(a₀ * I + a₁ * J) * x = rhs`.
$(TYPEDFIELDS)
!!! tip "Different linear solvers"
    By tuning the options, you can select CG, GMRES... see [here](https://jutho.github.io/KrylovKit.jl/stable/man/linear/#KrylovKit.linsolve)
"""
@with_kw mutable struct GMRESKrylovKit{T, Tl} <: AbstractLinearSolver
	"Krylov Dimension"
	dim::Int64 = KrylovDefaults.krylovdim

	"Absolute tolerance for solver"
	atol::T  = KrylovDefaults.tol

	"Relative tolerance for solver"
	rtol::T  = KrylovDefaults.tol

	"Maximum number of iterations"
	maxiter::Int64 = KrylovDefaults.maxiter

	"Verbosity ∈ {0,1,2}"
	verbose::Int64 = 0

	"If the linear map is symmetric, only meaningful if T<:Real"
	issymmetric::Bool = false

	"If the linear map is hermitian"
	ishermitian::Bool = false

	"If the linear map is positive definite"
	isposdef::Bool = false

	"Left preconditioner"
	Pl::Tl = nothing
end

# this function is used to solve (a₀ * I + a₁ * J) * x = rhs
# the optional shift is only used for the Hopf Newton / Continuation
function (l::GMRESKrylovKit{T, Tl})(J, rhs; a₀ = 0, a₁ = 1, kwargs...) where {T, Tl}
	if Tl == Nothing
		res, info = KrylovKit.linsolve(J, rhs, a₀, a₁; rtol = l.rtol, verbosity = l.verbose, krylovdim = l.dim, maxiter = l.maxiter, atol = l.atol, issymmetric = l.issymmetric, ishermitian = l.ishermitian, isposdef = l.isposdef, kwargs...)
	else # use preconditioner
		res, info = KrylovKit.linsolve(x -> (out = apply(J, x); ldiv!(l.Pl, out)), ldiv!(l.Pl, copy(rhs)), a₀, a₁; rtol = l.rtol, verbosity = l.verbose, krylovdim = l.dim, maxiter = l.maxiter, atol = l.atol, issymmetric = l.issymmetric, ishermitian = l.ishermitian, isposdef = l.isposdef, kwargs...)
	end
	info.converged == 0 && (@warn "KrylovKit.linsolve solver did not converge")
	return res, true, info.numops
end


test1a=JuMP.Model()
ux(i)=[100;100][i]
lx(i)=[-100;-100][i]
JuMP.@variable(test1a,x[i=1:2], upper_bound=ux(i), lower_bound=lx(i),start=1.0)
JuMP.@NLobjective(test1a,Min,x[1]-x[2])
JuMP.@constraint(test1a,1-x[2]>=0)

testa = MathOptNLPModel(test1a) #MathProgNLPModel(test1a)
mpcca = MPCCAmpl(testa, [0], 0)

@test mpcca.meta.ncc == 0
@test mpcca.meta.nvar == 2
@test mpcca.cvar == [0]
@test mpcca.meta.lvar == [-100.;-100.]
@test mpcca.meta.uvar == [ 100.; 100.]
@test mpcca.meta.ncon == 1

@test obj(mpcca.mp, mpcca.meta.x0)  == 0.0
@test grad(mpcca.mp, mpcca.meta.x0) == [1.0, -1.0]
@test cons(mpcca.mp, mpcca.meta.x0) == [-1.0]
@test jac(mpcca.mp, mpcca.meta.x0)  == [0.0 -1.0]

@test hessG(mpcca, mpcca.meta.x0, zeros(0)) == zeros(2,2)
@test hGprod(mpcca, mpcca.meta.x0, zeros(0), ones(2)) == zeros(2)
@test issparse(jacG(mpcca, mpcca.meta.x0))
@test consG(mpcca, mpcca.meta.x0) == zeros(0)

@test hessH(mpcca, mpcca.meta.x0, zeros(0)) == zeros(2,2)
@test hHprod(mpcca, mpcca.meta.x0, zeros(0), ones(2)) == zeros(2)
@test sum(jacH(mpcca, mpcca.meta.x0)) == 0
@test consH(mpcca, mpcca.meta.x0) == zeros(0)

@test jprod(mpcca, mpcca.meta.x0, zeros(2)) == zeros(1)
@test jtprod(mpcca, mpcca.meta.x0, zeros(1)) == zeros(2)

@test size(hess(mpcca, mpcca.meta.x0)) == (2,2)
@test hess(mpcca, mpcca.meta.x0, zeros(1), obj_weight = 0.0) == zeros(2,2)
@test hprod(mpcca, mpcca.meta.x0, zeros(2)) == zeros(2)
@test hprod(mpcca, mpcca.meta.x0, zeros(1), ones(2), obj_weight = 0.0) == zeros(2)

@test hessH_structure(mpcca) == hessG_structure(mpcca)
@test length(hessH_coord(mpcca, mpcca.meta.x0, zeros(0))) == mpcca.meta.nvar * (mpcca.meta.nvar + 1) / 2
@test length(hessG_coord(mpcca, mpcca.meta.x0, zeros(0))) == mpcca.meta.nvar * (mpcca.meta.nvar + 1) / 2


@block PietroVertechi [statsmakie] begin
    @cell "StatsMakie" [Group, Style, scatter, plot, wireframe, statistics] begin
        using StatsMakie
        using DataFrames, RDatasets
        using StatsMakie: linear, smooth
        N = 1000
        a = rand(1:2, N) # a discrete variable
        b = rand(1:2, N) # a discrete variable
        x = randn(N) # a continuous variable
        y = @. x * a + 0.8*randn() # a continuous variable
        z = x .+ y # a continuous variable
        @substep

        scatter(x, y, markersize = 0.2)
        @substep

        scatter(Group(a), x, y, markersize = 0.2)
        @substep

        scatter(Group(a), x, y, color = [:black, :red], markersize = 0.2)
        @substep

        scatter(Group(marker = a), x, y, markersize = 0.2)
        @substep

        scatter(Group(marker = a, color = b), x, y, markersize = 0.2)
        @substep

        scatter(Group(marker = a), Style(color = z), x, y)
        @substep

        scatter(Group(color = a), x, y, Style(markersize = z ./ 10))
        @substep

        plot(linear, x, y)
        @substep

        plot(linear, Group(a), x, y)
        @substep

        scatter(Group(a), x, y, markersize = 0.2)
        plot!(linear, Group(a), x, y)
        @substep

        plot(linear, Group(linestyle = a), x, y)
        @substep

        N = 200
        x = 10 .* rand(N)
        a = rand(1:2, N)
        y = sin.(x) .+ 0.5 .* rand(N) .+ cos.(x) .* a
        @substep

        scatter(Group(a), x, y)
        plot!(smooth, Group(a), x, y)
        @substep

        plot(histogram, y)
        @substep

        plot(histogram, x, y)

        @substep

        plot(histogram(nbins = 30), x, y)
        @substep

        wireframe(histogram(nbins = 30), x, y)
        @substep

        iris = RDatasets.dataset("datasets", "iris")
        scatter(Data(iris), Group(:Species), :SepalLength, :SepalWidth)
        @substep
        # use Position.stack to signal that you want bars stacked vertically rather than superimposed
        plot(Position.stack, histogram, Data(iris), Group(:Species), :SepalLength)
        @substep

        wireframe(
            density(trim=true),
            Data(iris), Group(:Species), :SepalLength, :SepalWidth,
            transparency = true, linewidth = 0.1
        )
        @substep

        scatter(
            Data(iris),
            Group(marker = :Species, color = bycolumn),
            :SepalLength, (:PetalLength, :PetalWidth)
        )
        @substep

        barplot(["hi", "ima", "string"], rand(3))
        @substep

    end
end


"Nearest interpolation type"
abstract type Nearest <: InterpMode end

function interp(xi::Vector{T}, yi::VecOrMat{T}, ::Type{Nearest};
                copy=true, sorted=false) where T
    _xi, _yi = _prepare_input(xi, yi, copy, sorted)

    function f(x)
        idx = _searchsortednearest(_xi, x)
        return ndims(_yi) == 1 ? _yi[idx] : _yi[:, idx]
    end

    return f
end

function _searchsortednearest(a,x)
   idx = searchsortedfirst(a,x)
   idx==1 && return idx
   idx>length(a) && return length(a)
   a[idx]==x && return idx
   if abs(a[idx]-x) < abs(a[idx-1]-x)
      return idx
   else
      return idx-1
   end
end


using LinearAlgebra
using Images
using FFTW

# load data
xs = load("./examples/StarGalaxy/star.png")
xd = load("./examples/StarGalaxy/galaxy.png")

# size
m, n = size(xs)

# preprocessing on xs
xs = Gray.(xs); xs = convert(Array{Float64}, xs)
γ = xs[3,1]; xs = xs .- γ # normalize
idx = findall(x->abs(x)<=7e-2, xs); xs[idx] .= 0
ks = count(x->x!=0, xs)
xs = vec(xs)

# preprocessing on xd
xd = Gray.(xd); xd = convert(Array{Float64}, xd);
zd = dct(xd); idx = findall(x->abs(x)<=0.02, zd); zd[idx] .= 0
kd = count(x->x!=0, zd)
xd = idct(zd)
xd = vec(xd)

# observation
b = xs + xd

# write to file
f = open("/home/zhenan/Github/AtomicOpt.jl/examples/StarGalaxy/StarGalaxyData.txt", "w")
l = length(b)
@printf(f, "xs = ")
for i in 1:l
    @printf(f, "%.3f ", xs[i])
end
println(f, "\n")
@printf(f, "xd = ")
for i in 1:l
    @printf(f, "%.3f ", xd[i])
end
println(f, "\n")
@printf(f, "b = ")
for i in 1:l
    @printf(f, "%.3f ", b[i])
end
println(f, "\n")
@printf(f, "ks = %d\n", ks)
@printf(f, "kd = %d\n", kd)
@printf(f, "m = %d\n", m)
@printf(f, "n = %d\n", n)
close(f)


# graphstructure.jl
# Authors : Heitor Guimaraes and Luiz Ciafrino
# @brief: Module to handle basic graph manipulation

type simpleGraph
"""
@brief: This composite type is an abstraction of a simple undirected
	graph. It is constructed through a text file contain details about it's
	edges and vertices. To a specific use in search algorithms as BFS and DFS
	you can use a method to transform this abstraction in a more concrete
	data structure as Adjacency List or Adjacency Matrix.
"""
	# Properties of the Graph
	num_vertex::Int64
	num_edge::Int64
	Graph::Union{Array{Array{Int64},1}, Array{UInt8,2}, Array{Array{Array{Float64}}}}
	make_report::Function
	format_graph::ASCIIString
	is_weighted::Bool

	function simpleGraph(filename::ASCIIString, format::ASCIIString = "adjlist")
	"""
	@brief: Constructor of the composite type simpleGraph. This method
		read the text file and construct the Graph as a List with integer
		numbers. Also extract basic information about the graph to make
		a report if the user ask for it.
	@param filename::ASCIIString Name or path of the file contain the graph
		information. This function expect an ASCIIString.
	@param format::ASCIIString Option for specify the type of representation
		for the graph. Options are: 'adjlist' or 'adjmatrix'.
	"""
		# Instantiate the Class
		this = new()

		# Read the text file
		infile = open(filename)
		this.num_vertex = parse(Int64, readline(infile))

		lines = readlines(infile)
		this.num_edge = size(lines)[1]

		this.is_weighted = false
		if length(split(lines[2])) == 3
			this.is_weighted = true
		end

		this.format_graph = format

		# Return the selected structure
		if format == "adjlist"
		"""
		@brief: With this flag you can take the graph abstraction and transform
			into an Adjacency List.
		"""
			if !this.is_weighted
				# Pre-allocate the structure
				this.Graph = Array{Array{Int64}}(this.num_vertex)

				# Initialize with Zeros
				@inbounds @simd for i in 1:this.num_vertex
	   	 			this.Graph[i] = Array{Int64}(0)
	    		end

	    		# Fill the Adjacency List
				@inbounds for i in lines
					vertex = parse(Int64,split(i)[1])
					edge = parse(Int64,split(i)[2])
					push!(this.Graph[vertex], edge)
					push!(this.Graph[edge], vertex)
				end

			else
				# Pre-allocate the structure
				this.Graph = Array{Array{Array{Float64}}}(this.num_vertex)

				# Initialize with Zeros
				@inbounds @simd for i in 1:this.num_vertex
	   	 		this.Graph[i] = Array[Int64[], Float64[]]
	    	end

	    	# Fill the Adjacency List
				@inbounds for i in lines
					vertex = parse(Int64, split(i)[1])
					edge = parse(Int64, split(i)[2])
					weight = parse(Float64, split(i)[3])

					push!(this.Graph[vertex][1], edge)
					push!(this.Graph[edge][1], vertex)

					push!(this.Graph[vertex][2], weight)
					push!(this.Graph[edge][2], weight)
				end
			end

		elseif format == "adjmatrix"
		"""
		@brief: With this flag you can take the graph abstraction and transform
			into an Adjacency Matrix.
		"""
			if !this.is_weighted
				# Pre-allocate the structure and initialize with zeros
				this.Graph = zeros(UInt8, this.num_vertex, this.num_vertex)

				@inbounds for i in lines
					rnum = parse(Int64,split(i)[1])
					cnum = parse(Int64,split(i)[2])
					this.Graph[rnum, cnum] = 1
					this.Graph[cnum, rnum] = 1
				end
			else
				# Pre-allocate the structure and initialize with zeros
				this.Graph = zeros(Float64, this.num_vertex, this.num_vertex)

				@inbounds for i in lines
					rnum = parse(Int64,split(i)[1])
					cnum = parse(Int64,split(i)[2])
					weight = parse(Float64,split(i)[3])
					this.Graph[rnum, cnum] = weight
					this.Graph[cnum, rnum] = weight
				end
			end
		else
			error(""" Invalid format selected, options are:\n
				* adjlist\n
				* adjmatrix\n
				Example: graph = simpleGraph(\"mygraph.txt\", \"adjmatrix\")""")
		end

		this.make_report = function(outfilename::ASCIIString)
		"""
		@brief: Method to make a small report with usefull informations
			about the graph.
		@param outfilename String with the name of the report filename
			in the ASCIIString format.
		"""
			# Get the mean degree and the empirical distance.
			mean_degree, empirical_dist = graph_properties(this)

			# Open the file and write the information on it.
			outfile = open(outfilename, "w")
			write(outfile, string("# n = ", this.num_vertex,"\n"))
			write(outfile, string("# m = ", this.num_edge,"\n"))
			write(outfile, string("# d_medio = ", mean_degree,"\n"))

			for i in 1:length(empirical_dist)
				if empirical_dist[i] != 0
					write(outfile, string(i, " ", empirical_dist[i], "\n"))
				end
			end

			# Close and save the file.
			close(outfile)
		end

		close(infile)
		return this
	end
end

function graph_properties(G::simpleGraph)
	#= Calculate the mean degree of the graph and
	store the distance for empirical distance =#
	empirical_dist = zeros(Float64, G.num_edge - 1)
	mean_degree = 0.0
	sum = 0

	for i in 1:G.num_vertex
		degree = (G.format_graph == "adjmatrix") ? +(G.Graph[:,i]...) : length(G.Graph[i])
		if degree != 0
			sum += degree
			empirical_dist[degree] += 1
		end
	end

	mean_degree = (sum/G.num_edge)
	empirical_dist /= G.num_vertex
	return mean_degree, empirical_dist
end