type FullyConnectedLayer <: Layer
    filters::Vector{Vol}
    biases::Vol
    inputActivation::Vol
    outputActivation::Vol

    function FullyConnectedLayer(numOutputs::Int, bias::Float64, dimensions)
        filters = [Vol(dimensions) for _ in 1:numOutputs]
        biases = Vol((1, 1, numOutputs), bias)
        new(filters, biases, Vol(dimensions, 0.0), Vol((1, 1, numOutputs), 0.0))
    end
end

function forward(self::FullyConnectedLayer, inputActivation::Vol)
    @assert size(self.inputActivation) == size(inputActivation)
    self.inputActivation = inputActivation
    for i in 1:length(self.filters)
        filter = self.filters[i]
        @assert size(self.inputActivation) == size(filter)
        self.outputActivation.w[1, 1, i] = sum(filter.w .* inputActivation.w) + self.biases.w[1, 1, i]
    end
    return self.outputActivation
end

function backward(self::FullyConnectedLayer)
    fill!(self.inputActivation.dw, 0.0)
    for i in 1:length(self.filters)
        filter = self.filters[i]
        chainGrad = self.outputActivation.dw[1, 1, i]

        self.inputActivation.dw += filter.w * chainGrad
        filter.dw += self.inputActivation.w * chainGrad
        self.biases.dw[1, 1, i] += chainGrad
        @printf("Filter %s, chainGrad: %s, inputActivation.w: %s\n", i, chainGrad, flatten(self.inputActivation.w))
    end
end

function gradientStates(self::FullyConnectedLayer)
    grads = [GradientState(filter, 1.0, 1.0) for filter in self.filters]
    append!(grads, [GradientState(self.biases, 0.0, 0.0)])
    return grads
end


### A Pluto.jl notebook ###
# v0.15.1

using Markdown
using InteractiveUtils

# ╔═╡ 8af90e7a-0e5e-4a9d-b39e-f7603fc1e25e
begin
	using Pkg
	Pkg.activate(mktempdir())

	ENV["LC_NUMERIC"]="C" # necessary for ensuring
	                      # parallel proper use of PyPlot and Triangulate
	Pkg.develop("Triangulate")
	Pkg.add("PyPlot")
	Pkg.add("PlutoUI")

	using Triangulate, PyPlot, PlutoUI, Printf
	PyPlot.svg(true)
end;

# ╔═╡ 7b468c8a-ce16-11eb-3009-f9e4c79e6c17
md"""
# Triangulate examples
$(TableOfContents(title=""))
## Triangulations of point sets
"""

# ╔═╡ 83f07f94-7e35-42de-9214-715c92b6dddd
md"""
### Delaunay triangulation (DT)

The Delaunay triangulation of a set of points is a triangulation
where for each triangle, the interior of its circumcircle  does not contain any points
of the triangulation.

The Delaunay triangulation of a set of points in general position
(no 4 of them on a circle) is unique. At the same time, it is a
triangulation of the convex hull of these points.

Given an input list of points, without any further flags, Triangle creates
just this triangulation (the "Q" flag suppresses the text output of Triangle).
For this and the next examples, the input list of points is created randomly,
but on a raster, preventing the appearance of too close points.

"""

# ╔═╡ c56434f3-e0a4-462e-86d5-fcfbb6778c11
md"""
### DT with boundary
Same as the previous example, but in addition specify the "c" flag.

In this case, Triangle outputs an additional list of segments
describing the boundary of the convex hull. In fact this is a constrained
Delaunay triangulation (CDT) where the boundary segments
are the seen constraining edges which must appear in the output.

"""

# ╔═╡ f13ad00e-a3d3-462f-93d4-ebe1fe4a7b4d
md"""
### DT with Voronoi diagram

Same as the previous example, but instead of "c" specify the "v" flag.
In this case, Triangle outputs information about the Voronoi diagram
of the point set which is a structure dual to  the Delaunay triangulation.

The Voronoi cell around a point $p$ a point set $S$ is defined
as the set of points $x$ such that $|x-p|<|x-q|$ for all $q\in S$
such that $p\neq q$.
The Voronoi cells of boundary points
of the convex hull are of infinite size. The corners of the Voronoi
cells are the circumcenters of the triangles. They can be far
outside of the triangulated domain.

"""

# ╔═╡ 852e1ebf-3f29-472a-945e-53920d7653be
md"""
### Boundary conforming DT (BCDT)
Specify "c" flag for convex hull segments, "v" flag for Voronoi
and "D" flag for creating a boundary conforming Delaunay triangulation of
the point set.

In this case additional points ("Steiner points") are created which split
the boundary segments and ensure that all triangle circumcenters
lie within the convex hull.
Due to random input, there may be situations where Triangle fails with this task,
so we check for the corresponding exception.

"""

# ╔═╡ b95859af-ab21-4c41-b095-82b3ea39724a
md"""
### Constrained DT (CDT)
Constrained Delaunay triangulation (CDT) of a point set with
additional constraints given a priori. This is obtained when
specifying the "p" flag and an additional list of segments each described
by two points
which should become edges of the triangulation. Note that
the resulting triangulation is not Delaunay in the sense
given above.

"""

# ╔═╡ c5a87fb5-c264-498d-aa7c-91d9e6d7bfe2
md"""
## Triangulations of domains
"""

# ╔═╡ 7f2b4252-a827-4de4-abb6-42d1bbde44b9
md"""
### CDT of a domain

Specification is similar to that of the CDT of a point set.

The domain is given by a segment list specifying its boundary.

This is obtained by specifying the "p" flag.

"""

# ╔═╡ 6793711b-d476-45c6-8b2b-b1d0693d8e6e
md"""
### CDT with maximum area constraint

This constraint is specfied as a floating
point number given after the -a flag.
Be careful to not give it in the exponential format as Triangle would be unable to analyse it.
Therefore it is dangerous to provide it via string interpolation and it is better to convert it to a string before using `@sprintf`.

Specifying only the maximum area constraint does not prevent very thin
triangles from occuring at the boundary.

"""

# ╔═╡ 3f2f0b19-3c13-482d-abd5-e6325802c6e8
md"""
### BCDT with maximum area constraint

In addition to the area constraint specify the -D flag
in order to keep the triangle circumcenters  within the domain.

"""

# ╔═╡ 14145886-2a5f-4ae8-bf00-a5eaaa7e0568
md"""
### CDT with minimum angle condition

The "q" flag  allows to specify a minimum angle
constraint preventing skinny triangles.

This combination of flags, possibly with an additional "D" flag is recommended
when creating triangulations for finite element or finite volume methods.
It the mimimum angle is larger then 28.6 degrees, Triangle's algorithm may
run into an infinite loop.

"""

# ╔═╡ 61dc30ce-9256-4f20-9163-e8d473ac9e53
md"""
### Triangulation with refinement callback

A maximum area constraint is specified in the `unsuitable` callback
which is activated via the "u" flag if it has been passed before calling triangulate.

In addition, the "q" flag  allows to specify a minimum angle
constraint preventing skinny triangles.

"""

# ╔═╡ 30990760-92e1-4a26-98a5-b43be658dee7
md"""
### Triangulation of a heterogeneous domain

The segment list specifies its boundary and the inner boundary between subdomains.
An additional region list is specified which provides "region points" in `regionlist[1,:]`
and `regionlist[2,:]`.  These kind of mark the subdomains. `regionlist[3,:]` contains an attribute
which labels the subdomains. `regionlist[4,:]` contains a maximum area value. `size(regionlist,2)`
is the number of regions.

With the "A" flag, the subdomain labels are spread to all triangles in the corresponding
subdomains, becoming available in `triangleattributelist[1,:]`.
With the "a" flag, the area constraints are applied in the corresponding subdomains.

"""

# ╔═╡ 783975f5-cbab-4b23-9155-7a6276cd25df
md"""
### Triangulation of a domain with holes
The segment list specifies its boundary and the boundaries of the holes.
An additional hole list is specified which provides "hole points" in `holelist[1,:]`
and `holelist[2,:]`.

"""

# ╔═╡ 9a090bba-093b-4ca4-a186-1c43b52cd4ff
html"""<hr>"""

# ╔═╡ 9447e874-22ce-4b99-9037-e0d202430ee2
# Wrap "Pyplotting" into this function in order to shield calling code
# from all these peculiarities.
function pyplot(f;w=650,h=300)
	PyPlot.close()
	PyPlot.clf()
	fig=PyPlot.figure(1,dpi=100)
	fig.set_size_inches(w/100,h/100,forward=true)
	f()
	PyPlot.gcf()
end;

# ╔═╡ a5f7aca5-9e40-471a-bece-34498a804bd8
function example_convex_hull(;n=10,raster=10)
    triin=Triangulate.TriangulateIO()
    triin.pointlist=hcat(unique([ Cdouble[rand(1:raster)/raster, rand(1:raster)/raster] for i in 1:n])...)
    display(triin)
    (triout, vorout)=triangulate("Q", triin)
	pyplot() do
		plot_in_out(PyPlot,triin,triout,title="Convex hull",circumcircles=true)
	end
end;

# ╔═╡ d0e63ebd-9288-42d5-9735-3c94a2baa8e3
example_convex_hull(;n=10,raster=10)

# ╔═╡ 6f3f0014-42bc-4565-a03e-208fed8b8f48
function example_convex_hull_with_boundary(;n=10,raster=10)
    triin=Triangulate.TriangulateIO()
    triin.pointlist=hcat(unique([ Cdouble[rand(1:raster)/raster, rand(1:raster)/raster] for i in 1:n])...)
    display(triin)
    (triout, vorout)=triangulate("cQ", triin)
	pyplot() do
    	plot_in_out(PyPlot,triin,triout,title="Convex hull with boundary")
	end
end;

# ╔═╡ 6ec811ca-e6d3-43ea-8da9-02bb05060d8d
example_convex_hull_with_boundary(;n=10,raster=10)

# ╔═╡ 6e2d70c4-dc0f-4672-823a-930a50114811
function example_convex_hull_voronoi(;n=10,raster=10)
    triin=Triangulate.TriangulateIO()
    triin.pointlist=hcat(unique([ Cdouble[rand(1:raster)/raster, rand(1:raster)/raster] for i in 1:n])...)
    (triout, vorout)=triangulate("vQ", triin)
	pyplot() do
    plot_in_out(PyPlot,triin,triout,voronoi=vorout,title="Convex hull with Voronoi diagram")
	end
end;

# ╔═╡ 0b54833f-0458-4417-aa03-27c4fe2a873c
example_convex_hull_voronoi(;n=10,raster=10)

# ╔═╡ 1ee0af11-96e4-4929-926e-d7143ed1f791
function example_convex_hull_voronoi_delaunay(;n=10,raster=10)
    triin=Triangulate.TriangulateIO()
    triin.pointlist=hcat(unique([ Cdouble[rand(1:raster)/raster, rand(1:raster)/raster] for i in 1:n])...)
    try
        (triout, vorout)=triangulate("vcDQ", triin)
		pyplot() do
        plot_in_out(PyPlot,triin,triout,voronoi=vorout,title="Convex hull with Voronoi diagram")
		end
    catch err
        if typeof(err)==TriangulateError
            println("Triangle had some problem.")
            return true
        end
    end
end;

# ╔═╡ 4da9a598-bf59-4b3b-aea7-ee1c2721c206
 example_convex_hull_voronoi_delaunay(;n=10,raster=10)

# ╔═╡ 0d648d22-aa03-437b-b019-59bd693bc55e
function example_cdt(;n=10,raster=10)
    triin=Triangulate.TriangulateIO()
    triin.pointlist=hcat(unique([ Cdouble[rand(1:raster)/raster, rand(1:raster)/raster] for i in 1:n])...)
    npt=size(triin.pointlist,2)
    triin.segmentlist=Matrix{Cint}([1 2; npt-1 npt-2;  1 npt;]')
    triin.segmentmarkerlist=Vector{Cint}([2,3,4])
    (triout, vorout)=triangulate("pcQ", triin)
	pyplot() do
    plot_in_out(PyPlot,triin,triout,title="CDT")
	end
end;

# ╔═╡ 910ce428-4989-4b08-8037-887dae5c847e
example_cdt(;n=10,raster=10)

# ╔═╡ ccd8ed8d-7991-4a19-8a05-65761adc2fee
function example_domain_cdt()
    triin=Triangulate.TriangulateIO()
    triin.pointlist=Matrix{Cdouble}([0.0 0.0 ; 1.0 0.0 ; 1.0  1.0 ; 0.6 0.6; 0.0 1.0]')
    triin.segmentlist=Matrix{Cint}([1 2 ; 2 3 ; 3 4 ; 4 5 ; 5 1 ]')
    triin.segmentmarkerlist=Vector{Int32}([1, 2, 3, 4, 5])
    (triout, vorout)=triangulate("pQ", triin)
	pyplot() do
    plot_in_out(PyPlot,triin,triout,title="Domain CDT")
	end
end;

# ╔═╡ fc5264cf-b1f7-43c5-bcff-09e6274ca215
example_domain_cdt()

# ╔═╡ b71b1faf-ff3f-4404-8a2c-9de84fa498f7
function example_domain_cdt_area(;maxarea=0.05)
    triin=Triangulate.TriangulateIO()
    triin.pointlist=Matrix{Cdouble}([0.0 0.0 ; 1.0 0.0 ; 1.0  1.0 ; 0.6 0.6; 0.0 1.0]')
    triin.segmentlist=Matrix{Cint}([1 2 ; 2 3 ; 3 4 ; 4 5 ; 5 1 ]')
    triin.segmentmarkerlist=Vector{Int32}([1, 2, 3, 4, 5])
    area=@sprintf("%.15f",maxarea) # Don't use exponential format!
    (triout, vorout)=triangulate("pa$(area)Q", triin)
	pyplot() do
    plot_in_out(PyPlot,triin,triout,voronoi=vorout, title="Domain CDT with area constraint",circumcircles=true)
	end
end;

# ╔═╡ cd007961-7f0e-4c56-9c86-1a9827a71e3e
example_domain_cdt_area(;maxarea=0.05)

# ╔═╡ 8ac6cc0b-e4e7-4aa0-bc37-b22b15bf2a83
function example_domain_bcdt_area(;maxarea=0.05)
    triin=Triangulate.TriangulateIO()
    triin.pointlist=Matrix{Cdouble}([0.0 0.0 ; 1.0 0.0 ; 1.0  1.0 ; 0.6 0.6; 0.0 1.0]')
    triin.segmentlist=Matrix{Cint}([1 2 ; 2 3 ; 3 4 ; 4 5 ; 5 1 ]')
    triin.segmentmarkerlist=Vector{Int32}([1, 2, 3, 4, 5])
    area=@sprintf("%.15f",maxarea)
    (triout, vorout)=triangulate("pa$(area)DQ", triin)
	pyplot() do
    plot_in_out(PyPlot,triin,triout,voronoi=vorout, title="Boundary conforming Delaunay triangulation", circumcircles=true)

	end
end;

# ╔═╡ a0cb6060-8278-444a-a4e5-46055d98616c
example_domain_bcdt_area(;maxarea=0.05)

# ╔═╡ a7e97f1c-e091-4555-abc0-a71abd22dd8a
function example_domain_qcdt_area(;minangle=20, maxarea=0.05)
    triin=Triangulate.TriangulateIO()
    triin.pointlist=Matrix{Cdouble}([0.0 0.0 ; 1.0 0.0 ; 1.0  1.0 ; 0.6 0.6; 0.0 1.0]')
    triin.segmentlist=Matrix{Cint}([1 2 ; 2 3 ; 3 4 ; 4 5 ; 5 1 ]')
    triin.segmentmarkerlist=Vector{Int32}([1, 2, 3, 4, 5])
    area=@sprintf("%.15f",maxarea)
    angle=@sprintf("%.15f",minangle)
    (triout, vorout)=triangulate("pa$(area)q$(angle)", triin)
	pyplot() do
    plot_in_out(PyPlot,triin,triout,voronoi=vorout, title="Quality triangulation")
	end
end;

# ╔═╡ 4e7dad4d-7d3c-4201-a56f-28e3df51e885
example_domain_qcdt_area(;maxarea=0.05,minangle=20)

# ╔═╡ 083b8aef-67f1-489a-8eff-b70fb6dc9da4
function example_domain_localref(;minangle=20)
    center_x=0.6
    center_y=0.6
    localdist=0.1
    function unsuitable(x1,y1,x2,y2,x3,y3,area)
        bary_x=(x1+x2+x3)/3.0
        bary_y=(y1+y2+y3)/3.0
        dx=bary_x-center_x
        dy=bary_y-center_y
        qdist=dx^2+dy^2
        qdist>1.0e-5 && area>0.1*qdist
    end

    triunsuitable(unsuitable)
    triin=Triangulate.TriangulateIO()
    triin.pointlist=Matrix{Cdouble}([0.0 0.0 ; 1.0 0.0 ; 1.0  1.0 ; 0.6 0.6; 0.0 1.0]')
    triin.segmentlist=Matrix{Cint}([1 2 ; 2 3 ; 3 4 ; 4 5 ; 5 1 ]')
    triin.segmentmarkerlist=Vector{Int32}([1, 2, 3, 4, 5])
    angle=@sprintf("%.15f",minangle)
    (triout, vorout)=triangulate("pauq$(angle)Q", triin)
	pyplot() do
    plot_in_out(PyPlot,triin,triout,voronoi=vorout, title="Quality triangulation with local refinement")
	end
end;

# ╔═╡ 3c88e5cb-5e85-4146-a6d7-3682f4b9a892
example_domain_localref(;minangle=20)

# ╔═╡ f0f9922b-70ce-4866-bc41-349a26095ade
function example_domain_regions(;minangle=20)
    triin=Triangulate.TriangulateIO()
    triin.pointlist=Matrix{Cdouble}([0.0 0.0 ;0.5 0.0; 1.0 0.0 ; 1.0  1.0 ; 0.6 0.6; 0.0 1.0]')
    triin.segmentlist=Matrix{Cint}([1 2 ; 2 3 ;3 4 ; 4 5 ; 5 6 ; 6 1 ; 2 5]')
    triin.segmentmarkerlist=Vector{Int32}([1, 2, 3, 4, 5, 6, 7])
    triin.regionlist=Matrix{Cdouble}([0.2 0.8; 0.2 0.2; 1 2 ; 0.01 0.05])
    angle=@sprintf("%.15f",minangle)
    (triout, vorout)=triangulate("paAq$(angle)Q", triin)
	pyplot() do
    plot_in_out(PyPlot,triin,triout,voronoi=vorout, title="Hetero domain triangulation")
	end
end;

# ╔═╡ 58dbd0e3-b34f-44ec-905a-488c7bbd07ca
example_domain_regions(minangle=20)

# ╔═╡ 3dfe5acd-72f7-4510-b3a0-40364f679073
function example_domain_holes(;minangle=20,maxarea=0.001)
    triin=Triangulate.TriangulateIO()
    triin.pointlist=Matrix{Cdouble}([0.0 0.0;
                                     1.0 0.0;
                                     1.0 1.0;
                                     0.0 1.0;
                                     0.2 0.2;
                                     0.3 0.2;
                                     0.3 0.3;
                                     0.2 0.3;
                                     0.6 0.6;
                                     0.7 0.6;
                                     0.7 0.7;
                                     0.6 0.7;
                                     ]')
    triin.segmentlist=Matrix{Cint}([1 2; 2 3; 3 4; 4 1; 5 6; 6 7; 7 8; 8 5;  9 10; 10 11; 11 12; 12 9;]')
    triin.segmentmarkerlist=Vector{Int32}([1, 1,1,1, 2,2,2,2, 3,3,3,3])
    triin.holelist=[0.25 0.25; 0.65 0.65;]'
    area=@sprintf("%.15f",maxarea) # Don't use exponential format!
    angle=@sprintf("%.15f",minangle)
    (triout, vorout)=triangulate("pa$(area)q$(angle)Q", triin)

	pyplot() do
    plot_in_out(PyPlot,triin,triout,voronoi=vorout, title="Domain with holes")
	end
end;

# ╔═╡ 6545d149-d4dd-4f26-8f51-5db90f6b444d
example_domain_holes(minangle=20,maxarea=0.05)

# ╔═╡ Cell order:
# ╟─7b468c8a-ce16-11eb-3009-f9e4c79e6c17
# ╟─83f07f94-7e35-42de-9214-715c92b6dddd
# ╠═a5f7aca5-9e40-471a-bece-34498a804bd8
# ╠═d0e63ebd-9288-42d5-9735-3c94a2baa8e3
# ╟─c56434f3-e0a4-462e-86d5-fcfbb6778c11
# ╠═6f3f0014-42bc-4565-a03e-208fed8b8f48
# ╠═6ec811ca-e6d3-43ea-8da9-02bb05060d8d
# ╟─f13ad00e-a3d3-462f-93d4-ebe1fe4a7b4d
# ╠═6e2d70c4-dc0f-4672-823a-930a50114811
# ╠═0b54833f-0458-4417-aa03-27c4fe2a873c
# ╟─852e1ebf-3f29-472a-945e-53920d7653be
# ╠═1ee0af11-96e4-4929-926e-d7143ed1f791
# ╠═4da9a598-bf59-4b3b-aea7-ee1c2721c206
# ╟─b95859af-ab21-4c41-b095-82b3ea39724a
# ╠═0d648d22-aa03-437b-b019-59bd693bc55e
# ╠═910ce428-4989-4b08-8037-887dae5c847e
# ╟─c5a87fb5-c264-498d-aa7c-91d9e6d7bfe2
# ╟─7f2b4252-a827-4de4-abb6-42d1bbde44b9
# ╠═ccd8ed8d-7991-4a19-8a05-65761adc2fee
# ╠═fc5264cf-b1f7-43c5-bcff-09e6274ca215
# ╟─6793711b-d476-45c6-8b2b-b1d0693d8e6e
# ╠═b71b1faf-ff3f-4404-8a2c-9de84fa498f7
# ╠═cd007961-7f0e-4c56-9c86-1a9827a71e3e
# ╟─3f2f0b19-3c13-482d-abd5-e6325802c6e8
# ╠═8ac6cc0b-e4e7-4aa0-bc37-b22b15bf2a83
# ╠═a0cb6060-8278-444a-a4e5-46055d98616c
# ╟─14145886-2a5f-4ae8-bf00-a5eaaa7e0568
# ╠═a7e97f1c-e091-4555-abc0-a71abd22dd8a
# ╠═4e7dad4d-7d3c-4201-a56f-28e3df51e885
# ╟─61dc30ce-9256-4f20-9163-e8d473ac9e53
# ╠═083b8aef-67f1-489a-8eff-b70fb6dc9da4
# ╠═3c88e5cb-5e85-4146-a6d7-3682f4b9a892
# ╟─30990760-92e1-4a26-98a5-b43be658dee7
# ╠═f0f9922b-70ce-4866-bc41-349a26095ade
# ╠═58dbd0e3-b34f-44ec-905a-488c7bbd07ca
# ╟─783975f5-cbab-4b23-9155-7a6276cd25df
# ╠═3dfe5acd-72f7-4510-b3a0-40364f679073
# ╠═6545d149-d4dd-4f26-8f51-5db90f6b444d
# ╟─9a090bba-093b-4ca4-a186-1c43b52cd4ff
# ╠═8af90e7a-0e5e-4a9d-b39e-f7603fc1e25e
# ╠═9447e874-22ce-4b99-9037-e0d202430ee2


using SuccessiveConvexProgrammings
using LinearAlgebra

function my_func_array(x, u, t, k)
    A = [1 2;
         3 4;
         5 6]
    B = Matrix(I, 3, 3)
    return A*x + B*u
end

x = [1, 2]
u = [3, 4, 5]
t = [6, 7]
k = [8, 9]
jacob = get_jacobian(my_func_array, x, u, t, k)


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

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

module GaussFactorials

using Nemo, IterTools
using RecordSearch, NumberTheory, Products

export ModuleGaussFactorials, GaussFactorial
export V216919, V001783, V055634, V232980, V232981, V232982, V124441, V124442, V066570
export I193338, F193338, L193338, V193338
export I193339, F193339, L193339, V193339

"""
The Gauß factorial is ``∏_{1 ≤ j ≤ N, j ⊥ n} j``, the product of the positive integers which are ``≤ N`` and are prime to ``n``.

* GaussFactorial, I193338, F193338, L193338, V193338, I193339, F193339, L193339, V193339, V216919, V001783, V055634, V232980, V232981, V232982, V124441, V124442, V066570
"""
const ModuleGaussFactorials = ""

"""
Return ``∏_{1 ≤ j ≤ N, j ⊥ n} j``, the product of the positive integers which are ``≤ N`` and are prime to ``n``.
"""
GaussFactorial(N, n) = ∏([j for j in 1:Int(N) if ⊥(j, n)])

"""
Return ``∏_{1 ≤ j ≤ n, j ⊥ n} j``, the product of the positive integers which are ``≤ n`` and are prime to ``n``.
"""
GaussFactorial(n) = GaussFactorial(n, n)

# V000142(n) = n! = GaussFactorial(n, 1).
# V001147(n) = GaussFactorial(2*n, 2), double factorial of odd numbers.

"""
Return GaussFactorial(N, n).
"""
V216919(N, n) = GaussFactorial(N, n)

"""
Return ``∏_{1 ≤ j ≤ n, j ⊥ n} j``, the product of the positive integers which are ``≤ N`` and are prime to ``n``, a.k.a. the phi-torial of n.
"""
V001783(n) = GaussFactorial(n, n)

"""
Return the 2-adic factorial of n.
"""
V055634(n) = GaussFactorial(n, 2)*(-1)^n

"""
Return GaussFactorial(n, 3).
"""
V232980(n) = GaussFactorial(n, 3)

"""
Return GaussFactorial(n, 5).
"""
V232981(n) = GaussFactorial(n, 5)

"""
Return GaussFactorial(n, 6).
"""
V232982(n) = GaussFactorial(n, 6)

"""
Return GaussFactorial(div(n, 2), n).
"""
V124441(n) = GaussFactorial(div(n, 2), n)

"""
Return GaussFactorial(n, n)/GaussFactorial(div(n, 2), n).
"""
V124442(n) = div(GaussFactorial(n, n), GaussFactorial(div(n, 2), n))

"""
Return GaussFactorial(n, 1)/GaussFactorial(n, n).
"""
V066570(n) = div(GaussFactorial(n, 1), GaussFactorial(n, n))

# -----------------------------------------------------------------
# Indices!

"""
Iterate over the indices of the first ``n`` record values of the Gauß factorial.
"""
I193339(n) = Records(GaussFactorial, n, true, true)

"""
Iterate over indices of the record values of the Gauß factorial which do not exceed ``n`` (``1 ≤ i ≤ n``).
"""
F193339(n) = Records(GaussFactorial, n, false, true)

"""
Return the indices of the first ``n`` record values of the Gauß factorial as an array.
"""
L193339(n) = collect(I193339(n))

"""
Return the index of the ``n``-th record value of the Gauß factorial.
"""
V193339(n) = nth(I193339(n), n)

# -----------------------------------------------------------------
# Values!

"""
Iterate over the first ``n`` record values of the Gauß factorial (``1 ≤ r``).
"""
I193338(n) = Records(GaussFactorial, n, true, false)

"""
Iterate over the record values of the Gauß factorial which do not exceed ``n`` (``1 ≤ i ≤ n``).
"""
F193338(n) = Records(GaussFactorial, n, false, false)

"""
Return the first ``n`` record values of the Gauß factorial as an array.
"""
L193338(n) = collect(I193338(n))

"""
Return the (value of the) ``n``-th record of the Gauß factorial.
"""
V193338(n) = nth(I193338(n), n)

#START-TEST-########################################################

using Test, SeqTests

function test()

    @testset "GaussFactorial" begin

        #@test
        if is_oeis_installed()

            V = [V001783, V124441, V124442, V066570]
            for v in V SeqTest(v, 'V', 1) end

            W = [V055634, V232980, V232981, V232982]
            for w in W SeqTest(w, 'V', 0) end
        end
    end
end

function demo()
    println("\n Iterate over the indices of the first 10 record values of the Gauß factorial.")
    for r in I193339(10) print(r, ", ") end; println("...")

    println("\n Iterate over indices of the record values of the Gauß factorial which do not exceed 10.")
    for r in F193339(10) print(r, ", ") end; println("...")

    println("\n Return the indices of the first 10 record values of the Gauß factorial as an array.")
    println(L193339(10))

    println("\n Return the index of the 9-th record value of the Gauß factorial.")
    println("9 -> ", V193339(9))

    println("\n Iterate over the first 10 record values of the Gauß factorial.")
    for r in I193338(10) print(r, ", ") end; println("...")

    println("\n Iterate over the record values of Gauß factorial which do not exceed 10.")
    for r in F193338(10) print(r, ", ") end; println("...")

    println("\n Return the first 10 record values of the Gauß factorial as an array.")
    println(L193338(10))

    println("\n Return the (value of the) 9-th record of the Gauß factorial.")
    println("9 -> ", V193338(9))
end

function perf()
end

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

main()

end # module

#=
Iterate over the indices of the first 10 record values ofthe Gauß factorial.
1, 3, 4, 5, 7, 9, 11, 13, 17, ...

Iterate over indices of the record values of the Gauß factorial which do not exceed 10.
1, 3, 4, 5, 7, 9, ...

Return the indices of the first 10 record values of the Gauß factorial as an array.
Nemo.fmpz[1, 3, 4, 5, 7, 9, 11, 13, 17]

Return the index of the 9-th record value of the Gauß factorial.
9 -> 13

Iterate over the first 10 record values of the Gauß factorial.
1, 2, 3, 24, 720, 2240, 3628800, 479001600, 20922789888000, ...

Iterate over the record values of Gauß factorial which do
not exceed 10.
1, 2, 3, 24, 720, 2240, ...

Return the first 10 record values of the Gauß factorial as an array.
Nemo.fmpz[1, 2, 3, 24, 720, 2240, 3628800, 479001600, 20922789888000]

Return the (value of the) 9-th record of the Gauß factorial.
9 -> 479001600
=#


function KronGraph500NoPerm(SCALE,EdgePerVertex)

    # Set the number of vertices and edges to be generated
    N = 2^SCALE
    M = round(EdgePerVertex * N)

    # Set R_MAT (2x2 Kronecker) coefficients
    A = 0.57
    B = 0.19
    C = 0.19
    D = 1-(A+B+C)

    # Initialize index arrays
    ij = ones(Int,2,M)

    # Normalize coefficients
    ab = A+B
    c_norm = C/(1-(A+B))
    a_norm = A/(A+B)

    # Loop over each scale
    for ib = 1:SCALE
        ii_bit = rand(1,M) .> ab
        jj_bit = rand(1,M) .> (c_norm * ii_bit + a_norm * .!(ii_bit))
        ij = ij + 2^(ib-1) * [ii_bit; jj_bit]
    end

    StartVertex = ij[1,:]
    EndVertex = ij[2,:]

    return StartVertex, EndVertex
end


using MathGL
mgl = MathGL

gr = mgl.Graph(800, 500)
@mglplot gr [
    xlabel "x"
    ylabel "y"
    box
    axis
    grid
    plot 0.8*sin(linspace(-4pi, 4pi, 200))
    write "../graphs/sin_mgl.png"
]


#___  ____       ___ _____   _   _       _ _
#|  \/  | |     |_  /  __ \ | | | |     (_| |
#| .  . | |       | | /  \/ | | | |_ __  _| |_ ___
#| |\/| | |       | | |     | | | | '_ \| | __/ _ \
#| |  | | |___/\__/ | \__/\ | |_| | | | | | || (_) |
#_______\_____\____/ \____/  _____|_|___|_____\_____ _____ _____
#| ___ \        (_)         | | \ \ / / / __  |  _  / __  |  _  |
#| |_/ _ __ ___  _  ___  ___| |_ \ V /  `' / /| |/' `' / /| |/' |
#|  __| '__/ _ \| |/ _ \/ __| __|/   \    / / |  /| | / / |  /| |
#| |  | | | (_) | |  __| (__| |_/ /^\ \ ./ /__\ |_/ ./ /__\ |_/ /
#\_|  |_|  \___/| |\___|\___|\__\/   \/ \_____/\___/\_____/\___/
#              _/ |
#             |__/
#
# This code is part of the proposal of the team "MLJC UniTo" - University of Turin
# for "ProjectX 2020" Climate Change for AI.
# The code is licensed under MIT 3.0
# Please read readme or comments for credits and further information.

#Compiler: Julia 1.5

# Short description of this file: SIMPLE PRELIMINARY EXAMPLE OF NEURAL ODE using
# Julia's library DiffEqFlux.

Pkg.add("DiffEqSensitivity")
using DiffEqFlux, OrdinaryDiffEq, Flux, Optim, Plots, DiffEqSensitivity
using Plots

u0 = Float32[2.0; 0.0]
datasize = 30
tspan = (0.0f0, 1.5f0)
tsteps = range(tspan[1], tspan[2], length = datasize)

function trueODEfunc(du, u, p, t)
    true_A = [-0.1 2.0; -2.0 -0.1]
    du .= ((u.^3)'true_A)'
end

prob_trueode = ODEProblem(trueODEfunc, u0, tspan)
ode_data = Array(solve(prob_trueode, Tsit5(), saveat = tsteps))

dudt2 = FastChain((x, p) -> x.^3,
                  FastDense(2, 50, tanh),
                  FastDense(50, 2))
neural_ode_f(u,p,t) = dudt2(u,p)
pinit = initial_params(dudt2)
prob = ODEProblem(neural_ode_f, u0, tspan, pinit)

function predict_neuralode(p)
  tmp_prob = remake(prob,p=p)
  Array(solve(tmp_prob,Tsit5(),saveat=tsteps,sensealg=BacksolveAdjoint(autojacvec=ReverseDiffVJP(true))))
end

function loss_neuralode(p)
    pred = predict_neuralode(p)
    loss = sum(abs2, ode_data .- pred)
    return loss, pred
end

callback = function (p, l, pred; doplot = true) #callback function to observe
  #training
  display(l)
  # plot current prediction against data
  plt =Plots.scatter(tsteps, ode_data[1,:], label = "data")
  scatter!(plt, tsteps, pred[1,:], label = "prediction")
  if doplot
    display(Plots.plot(plt))
  end
  return false
end

## Solve ODE and train

@time result_neuralode = DiffEqFlux.sciml_train(loss_neuralode, pinit,
                                          ADAM(0.05), cb = callback,
                                          maxiters = 500)

##Predict solution
result_neuralode

@show result_neuralode.minimizer
#=
2.687161 seconds (17.79 M allocations: 1002.418 MiB, 7.41% gc time)

* Status: success

* Candidate solution
  Final objective value:     2.761669e-02

* Found with
  Algorithm:     ADAM

* Convergence measures
  |x - x'|               = NaN ≰ 0.0e+00
  |x - x'|/|x'|          = NaN ≰ 0.0e+00
  |f(x) - f(x')|         = NaN ≰ 0.0e+00
  |f(x) - f(x')|/|f(x')| = NaN ≰ 0.0e+00
  |g(x)|                 = NaN ≰ 0.0e+00

* Work counters
  Seconds run:   3  (vs limit Inf)
  Iterations:    500
  f(x) calls:    500
  ∇f(x) calls:   500
=#


function find_roots(f, a::Real, b::Real, args...;
                    no_pts::Int=101,
                    abstol::Real=10*eps(), reltol::Real=10*eps(), ## should be abstol, reltol as used.
                    kwargs...)

    @assert a != b
    @assert no_pts > 1

    ( a < b ) || ( (a, b) = (b, a) )

    work_f(x) = real(float(f(complex(x))))

    root_list = _find_recursive_roots(
      work_f, a, b, args...;
      no_pts=no_pts, abstol=abstol, reltol=reltol, kwargs...
    )

    if length(root_list) > Int(ceil( (1/4) * no_pts ))
      custom_log("Redoing root hunt on larger grid")
      return find_roots(
        f, a, b, args...;
        no_pts = 10*no_pts, abstol=abstol, reltol=reltol, kwargs...
      )
    end

    sort!(root_list)

    root_list
end

function _find_recursive_roots(f, a::Real, b::Real, args...;
                      cur_depth::Int=1,
                      no_pts::Int=101,
                      abstol::Real=10*eps(), reltol::Real=10*eps(), ## should be abstol, reltol as used.
                      kwargs...)

    cur_range = collect(linspace(a, b, no_pts))

    root_list = find_root_list(f, cur_range, abstol, reltol; kwargs...)

    isempty(root_list) && return root_list

    sub_no_pts = no_pts
    sub_no_pts /= 1 + length(root_list) / 3
    sub_no_pts /= 2 ^ ( cur_depth - 1 )
    sub_no_pts = Int(round(sub_no_pts))

    ( sub_no_pts > 1 ) || return root_list

    cur_intervals = zip(
        vcat(first(cur_range),root_list),
        vcat(root_list,last(cur_range))
    )

    for (cur_index, (cur_a, cur_b)) in enumerate(cur_intervals)
        loose_isapprox(cur_a, cur_b, abstol, reltol) && continue

        tmp_a = find_next_non_root(cur_a, f, cur_b, reltol, abstol)
        tmp_b = find_next_non_root(cur_b, f, cur_a, reltol, abstol)

        isnan(tmp_a) || isnan(tmp_b) && continue
        loose_isapprox(tmp_a, tmp_b, abstol, reltol) && continue
        ( tmp_a < tmp_b ) || continue

        if cur_depth < 5 && 1 < cur_index < length(cur_intervals)
          tmp_no_pts = no_pts
        else
          tmp_no_pts = sub_no_pts
        end

        cur_roots = _find_recursive_roots(
            f, tmp_a, tmp_b, args...;
            no_pts=tmp_no_pts, abstol=abstol, reltol=reltol,
            cur_depth=(cur_depth+1), kwargs...
        )

        append!(root_list, cur_roots)
    end

    root_list
end

function find_next_non_root(cur_root, cur_func, barrier_point, reltol, abstol)
  cur_direction = sign(barrier_point - cur_root)

  cur_adder = abstol * cur_direction
  cur_value = cur_root + cur_adder

  while sign(barrier_point - cur_value) == cur_direction
    if !loose_isapprox(cur_value, cur_root, abstol, reltol)
      cur_f = cur_func(cur_value)
      isapprox(cur_f, 0.0, atol=abstol) || break
    end

    cur_adder *= 2
    cur_value += cur_adder
  end

  is_in_range = ( sign(barrier_point - cur_value) == cur_direction )
  is_in_range || return NaN

  for tmp_value in linspace(cur_value - cur_adder, cur_value)
    ( sign(tmp_value - cur_root) == cur_direction ) || continue

    loose_isapprox(tmp_value, cur_root, abstol, reltol) && continue

    cur_f = cur_func(tmp_value)

    isapprox(cur_f, 0.0, atol=abstol) && continue

    cur_value = tmp_value
    break
  end

  ( cur_root == cur_value ) && return NaN

  cur_value
end

function find_root_list(f, cur_range::AbstractVector{T}, abstol::Real, reltol::Real; kwargs...) where T <: Real
  isempty(cur_range) && return Real[]

  cur_roots = find_bisection_roots(f, cur_range, abstol, reltol; kwargs...)
  isempty(cur_roots) || return cur_roots

  cur_root = find_order_root(f, cur_range, abstol, reltol; kwargs...)
  isnan(cur_root) && return Real[]

  cur_roots = [cur_root]
  cur_roots
end

function find_bisection_roots(f, cur_range::AbstractVector{T}, abstol::Real, reltol::Real; kwargs...) where T <: Real
  no_pts = length(cur_range)

  root_list = Real[]

  cur_values = f.(cur_range)

  filtered_cur_values = filter(isfinite, cur_values)

  isempty(filtered_cur_values) && return []

  cur_signs = map(sign, cur_values[1:end-1] .* cur_values[2:end])

  for (cur_index, cur_sign) in enumerate(cur_signs)
    isinf(cur_sign) && continue
    isnan(cur_sign) && continue

    cur_a, cur_b = cur_range[cur_index:cur_index+1]

    if iszero(cur_sign)
      iszero(cur_values[cur_index]) && push!(root_list, cur_a)

      ( cur_index == no_pts - 1 ) &&
        iszero(cur_values[cur_index+1]) && push!(root_list, cur_b)

      continue
    end

    ( cur_sign > 0 ) && continue

    cur_root = NaN
    try
      cur_root = find_zero(f, [cur_a, cur_b], FalsePosition(), maxevals=16)
      @assert isapprox(f(cur_root), 0.0, atol=abstol)
    catch
      cur_root = custom_bisection(f, cur_a, cur_b, f(cur_a), f(cur_b); abstol=abstol, kwargs...)
    end
    isnan(cur_root) || push!(root_list, cur_root)
  end

  isempty(root_list) || return root_list

  super_diff = diff(filtered_cur_values)
  cur_attempt_index = []
  if length(super_diff) == 0 || ( any(is_positive, super_diff) && any(!is_positive, super_diff) )
    cur_attempt_index = [
      findfirst(isfinite, cur_values),
      findlast(isfinite, cur_values)
    ]
  else
    if all(is_positive, filtered_cur_values) || all(!is_positive, filtered_cur_values)
      cur_is_increasing = is_positive(first(super_diff))

      first_finite_index = findfirst(isfinite, cur_values)
      last_finite_index = findlast(isfinite, cur_values)

      if cur_is_increasing
        if cur_values[first_finite_index] < 0
          cur_attempt_index = [last_finite_index]
        else
          cur_attempt_index = [first_finite_index]
        end
      else
        if cur_values[first_finite_index] < 0
          cur_attempt_index = [first_finite_index]
        else
          cur_attempt_index = [last_finite_index]
        end
      end
    else
      tmp_signs = map(sign, filtered_cur_values[1:end-1] .* filtered_cur_values[2:end])
      cur_attempt_index = findin(cur_values, filtered_cur_values[find(!is_positive, tmp_signs)])
    end
  end

  cur_min_index = indmin(map(cur_value -> isfinite(cur_value) ? abs(cur_value) : Inf, cur_values))

  push!(cur_attempt_index, cur_min_index)

  cur_attempt_index = unique(cur_attempt_index)

  for cur_index in cur_attempt_index
    cur_point = cur_range[cur_index]

    cur_diff_list = diff(cur_range)
    filter_approx!(cur_diff_list; atol=10*eps())

    @assert length(cur_diff_list) == 1
    cur_diff = cur_diff_list[1]

    if 1 < cur_index < length(cur_values)
      cur_c = cur_point
      cur_f_c = f(cur_c)

      cur_a = cur_point - cur_diff
      cur_b = cur_point + cur_diff

      cur_root = custom_bisection(f, cur_a, cur_c, f(cur_a), cur_f_c; abstol=abstol, kwargs...)
      isnan(cur_root) || return [cur_root]

      cur_root = custom_bisection(f, cur_c, cur_b, cur_f_c, f(cur_b); abstol=abstol, kwargs...)
      isnan(cur_root) || return [cur_root]
    end
  end

  []
end

function find_order_root(f, cur_range::AbstractVector{T}, abstol::Real, reltol::Real; kwargs...) where T <: Real
  min_value = first(cur_range)
  max_value = last(cur_range)

  cur_guesses = view( cur_range , (3:4:length(cur_range)-2) )

  cur_root = NaN

  for cur_guess in cur_guesses
    tmp_root = NaN

    try
      tmp_root = find_zero(
        f, cur_guess, Order8();
        maxevals=10, abstol=abstol, reltol=reltol
      )
    catch cur_error
        continue
    end

    ( min_value < tmp_root < max_value ) || continue

    isapprox(f(tmp_root), 0.0, atol=abstol) || continue

    cur_root = tmp_root

    break
  end

  cur_root
end

function loose_isapprox(first_value, second_value, atol, rtol)
  isapprox(first_value, second_value, atol=atol) && return true
  isapprox(first_value, second_value, rtol=rtol) && return true

  false
end

function custom_bisection(f, cur_a::Number, cur_b::Number, cur_f_a::Number, cur_f_b::Number, cur_flag::Bool=true; abstol::Number=10*eps(), cur_c::Number=NaN, cur_f_c::Number=NaN, cur_d::Number=NaN, cur_f_d::Number=NaN, bad_streak::Number=0, kwargs...)
  ( bad_streak > 6 ) && return NaN

  init_a = cur_a
  init_b = cur_b

  init_f_a = cur_f_a
  init_f_b = cur_f_b

  isnan(cur_c) && ( cur_c = middle(cur_a, cur_b) )
  isnan(cur_f_c) && ( cur_f_c = f(cur_c) )

  if abs(cur_f_a) < abs(cur_f_b)
    cur_a, cur_b = cur_b, cur_a
    cur_f_a, cur_f_b = cur_f_b, cur_f_a
  end

  isapprox(cur_f_a, 0.0, atol=abstol) && return cur_a
  isapprox(cur_f_b, 0.0, atol=abstol) && return cur_b
  isapprox(cur_f_c, 0.0, atol=abstol) && return cur_c
  isapprox(cur_f_d, 0.0, atol=abstol) && return cur_d

  isapprox(cur_f_a, cur_f_b, atol=abstol) && return NaN
  isapprox(cur_a, cur_b, atol=2*eps()) && return NaN

  is_bad_a = isinf(cur_f_a) || isnan(cur_f_a)
  is_bad_b = isinf(cur_f_b) || isnan(cur_f_b)
  is_bad_c = isinf(cur_f_c) || isnan(cur_f_c)
  is_bad_d = isinf(cur_f_d) || isnan(cur_f_d)

  ( !is_bad_a && !is_bad_b && cur_f_a * cur_f_b > 0 ) && return NaN

  ( is_bad_a && is_bad_b ) && return NaN

  if is_bad_a || is_bad_b || is_bad_c
    if !is_bad_d && ( isapprox(cur_c, cur_a, atol=abstol) || isapprox(cur_c, cur_b, atol=abstol) )
      cur_g = cur_d
      cur_f_g = cur_f_d
    else
      cur_g = cur_c
      cur_f_g = cur_f_c
    end

    cur_f_list = [cur_f_a, cur_f_b, cur_f_g]
    filter!(isfinite, cur_f_list)

    is_on_good_streak = isfinite(cur_f_g)
    is_on_good_streak &= abs(cur_f_g) == minimum(map(abs, cur_f_list))

    is_on_good_streak &= !isapprox(cur_f_g, cur_f_a, atol=abstol)
    is_on_good_streak &= !isapprox(cur_f_g, cur_f_b, atol=abstol)

    is_on_good_streak &= !isapprox(cur_g, cur_a, atol=abstol)
    is_on_good_streak &= !isapprox(cur_g, cur_b, atol=abstol)

    if is_on_good_streak
      bad_streak -= 1

      filter!(work_f -> work_f == cur_f_g, cur_f_list)

      second_min_f = minimum(map(abs, cur_f_list))

      ( second_min_f / abs(cur_f_g) > 8 ) &&
        ( bad_streak -= 1 )

      bad_streak = max(0, bad_streak)
    else
      if length(cur_f_list) == 1
        bad_streak += 1
      else
        bad_streak += 2
      end
    end

    cur_root = NaN

    !isapprox(cur_f_a, cur_f_g, atol=abstol) &&
      ( cur_root = custom_bisection(f, cur_a, cur_g, cur_f_a, cur_f_g; abstol=abstol, bad_streak=bad_streak, kwargs...) )

    ( isnan(cur_root) && !isapprox(cur_f_g, cur_f_b, atol=abstol) ) &&
      ( cur_root = custom_bisection(f, cur_g, cur_b, cur_f_g, cur_f_b; abstol=abstol, bad_streak=bad_streak, kwargs...) )

    return cur_root
  end

  cur_d = NaN
  cur_s = 0.0

  if !isapprox(cur_f_a, cur_f_c, atol=abstol) && !isapprox(cur_f_b, cur_f_c, atol=abstol)
    cur_term_1 = cur_f_b / ( cur_f_a - cur_f_c )
    cur_term_2 = cur_f_c / ( cur_f_a - cur_f_b )
    cur_term_3 = cur_f_a / ( cur_f_b - cur_f_c )

    cur_s += cur_a * cur_term_1 * cur_term_2
    cur_s -= cur_b * cur_term_2 * cur_term_3
    cur_s += cur_c * cur_term_3 * cur_term_1
  else
    cur_term = ( cur_b - cur_a )
    cur_term /= ( cur_f_b - cur_f_a )

    cur_s = cur_b
    cur_s -= cur_f_b * cur_term
  end

  cur_bool = ( cur_s < cur_b && cur_s < ( ( 3 * cur_a + cur_b ) / 4 ) )
  cur_bool |= ( cur_s > cur_b && cur_s > ( ( 3 * cur_a + cur_b ) / 4 ) )

  if cur_flag
    cur_diff = abs( cur_b - cur_c )
  else
    cur_diff = abs( cur_d - cur_c )
  end

  cur_bool |= abs( cur_s - cur_b ) >= ( cur_diff / 2 )
  cur_bool |= isapprox(cur_diff, 0.0, atol=abstol)

  cur_flag = cur_bool
  cur_bool && ( cur_s = middle(cur_a, cur_b) )

  cur_d = cur_c
  cur_c = cur_b

  cur_f_d = cur_f_c
  cur_f_c = cur_f_b

  cur_f_s = f(cur_s)

  if cur_f_a * cur_f_s < 0
    cur_b = cur_s
    cur_f_b = cur_f_s
  else
    cur_a = cur_s
    cur_f_a = cur_f_s
  end

  is_bad_f_a = isapprox(cur_f_a, init_f_a, atol=abstol)
  is_bad_f_b = isapprox(cur_f_b, init_f_b, atol=abstol)

  is_bad_f_a |= abs(cur_f_a) > abs(init_f_a)
  is_bad_f_b |= abs(cur_f_b) > abs(init_f_b)

  if ( is_bad_f_a && is_bad_f_b )
    bad_streak += 1
  else
    bad_streak -= 2
    bad_streak = max(0, bad_streak)
  end

  is_unchanged = isapprox(cur_a, init_a, atol=2*eps())
  is_unchanged &= isapprox(cur_b, init_b, atol=2*eps())

  if is_unchanged
    cur_c = middle(cur_a, cur_b)
    cur_f_c = f(cur_c)

    cur_root = custom_bisection(f, cur_a, cur_c, cur_f_a, cur_f_c; abstol=abstol, bad_streak=bad_streak, kwargs...)
    isnan(cur_root) || return cur_root

    cur_root = custom_bisection(f, cur_c, cur_b, cur_f_c, cur_f_b; abstol=abstol, bad_streak=bad_streak, kwargs...)
    return cur_root
  end

  custom_bisection(
    f, cur_a, cur_b, cur_f_a, cur_f_b, cur_flag;
    abstol=abstol, bad_streak=bad_streak,
    cur_c=cur_c, cur_f_c=cur_f_c,
    cur_d=cur_d, cur_f_d=cur_f_d,
    kwargs...
  )
end


using Test
using BasisFunctions, DomainIntegrals

using BasisFunctions: support

@testset "Normalization" begin
    F = Fourier(10)
    f = exp
    for g in (PeriodicEquispacedGrid(10,support(F)), interpolation_grid(F), MidpointEquispacedGrid(10,support(F)))
        s = sampling_normalization(GridBasis(g), FourierWeight())
        @test abs(sum(s*s*f)-applymeasure(FourierWeight(),f)) < .1
        s = sampling_normalization(GridBasis(g), lebesguemeasure(support(F)))
        @test abs(sum(s*s*f)-applymeasure(lebesguemeasure(support(F)),f)) < .1
    end

    C = ChebyshevT(9)
    f = exp
    g = ChebyshevNodes(9)
    s = sampling_normalization(GridBasis(g), lebesguemeasure(support(C)))
    @test abs(sum(s*s*f)-applymeasure(lebesguemeasure(support(C)),f)) < .1
    s = sampling_normalization(GridBasis(g), LegendreWeight{Float64}())
    @test abs(sum(s*s*f)-applymeasure(lebesguemeasure(support(C)),f)) < .1

    g = ChebyshevExtremae(9)
    s = sampling_normalization(GridBasis(g), lebesguemeasure(support(C)))
    @test abs(sum(s*s*f)-applymeasure(lebesguemeasure(support(C)),f)) < .1
    s = sampling_normalization(GridBasis(g), LegendreWeight{Float64}())
    @test abs(sum(s*s*f)-applymeasure(lebesguemeasure(support(C)),f)) < .1

    C = ChebyshevT(1000)
    f = exp
    g = MidpointEquispacedGrid(1000,-1,1)
    s = sampling_normalization(GridBasis(g), ChebyshevTWeight{Float64}())
    @test abs(sum(s*s*f)-applymeasure(ChebyshevTWeight{Float64}(),f)) < .1

    x, w = rectangular_rule(10)
    μ = gauss_rule(Fourier(10))
    @test points(μ) ≈ x
    @test weights(μ) ≈ w
end


using BlockBandedMatrices
using LinearAlgebra

function der_blocks(::Type{T}, basis::Basis, a, b) where T
    (a ∉ [0,1] || b ∉ [0,1]) &&
        error("Can only calculate derivative operators of orders 0–2!")

    g = basis.grid
    elrange = elems(g)
    n = order(g)

    f0 = zeros(elcount(g), n, n)
    for i = elrange
        f0[i,:,:] = sparse(I, n, n)
    end
    fa = a == 1 ? -basis.L′ : f0 # ∂ᴴ = -∂
    fb = b == 1 ? basis.L′ : f0

    d̃ = [zeros(T,n,n) for i = elrange]
    indices = Tuple{Integer,Integer}[]
    for i = elrange
        ii = (i-1)*(n-1) .+ 1
        push!(indices, (ii,ii))
        sel = (1:n) .+ (i-1)*(n-1)
        fael = view(fa, i, :, :)
        fbel = view(fb, i, :, :)
        d̃el = d̃[i]
        for m = 1:n
            fm = fael[m,:]
            for m′ = 1:n
                fm′ = fbel[m′,:]
                d̃el[m,m′] = dot(fm,fm′.*basis.grid.W[i,:])
            end
        end
    end

    for i = 2:elcount(g)
        d̃⁻ = d̃[i-1][end,end]
        d̃⁺ = d̃[i][1,1]
        d̃[i-1][end,end] += d̃⁺
        d̃[i][1,1] += d̃⁻
    end
    indices,d̃
end

der_blocks(::Type{T},basis::Basis,o) where T =
    der_blocks(T,basis::Basis,fld(o,2),cld(o,2))

function triderop(indices, D::AbstractVector{AbstractMatrix{T}}) where T
    tmp = Vector{T}(undef, maximum(maximum.(indices)) + size(last(D),1)-1)
    Dm = Tridiagonal(tmp[2:end], tmp, tmp[2:end])
    for (i,d) in zip(indices,D)
        s = i[1]:i[1]+size(d,1)-1
        @view(Dm[s,s]) .= d
    end
    Dm
end

function block_banded_derop(basis::Basis, D::Vector{M}) where {T,M<:AbstractMatrix{T}}
    rows = block_structure(basis)
    lu = length(D) > 1 ? vcat(1,repeat([2,1],length(D)-1)) : [0]
    m = sum(rows)
    Dm = BlockSkylineMatrix(Zeros{T}(m,m), (rows,rows), (lu,lu))
    for i in 1:length(D)
        s = 1+(i>1)
        e = size(D[i],1)-(i<length(D))
        @view(Dm[Block(2i-1,2i-1)]) .= @view((D[i])[s:e,s:e])
        if i < length(D)
            @view(Dm[Block(2i,2i)]) .= @view((D[i])[end,end])
            @view(Dm[Block(2i-1,2i)]) .= @view((D[i])[s:e,end])
            @view(Dm[Block(2i,2i-1)]) .= reshape(@view((D[i])[end,s:e]), 1, length(s:e))
        end
        if i > 1
            @view(Dm[Block(2i-1,2i-2)]) .= @view((D[i])[s:e,1])
            @view(Dm[Block(2i-2,2i-1)]) .= reshape(@view((D[i])[1,s:e]), 1, length(s:e))
        end
        if i > 1 && i < length(D)
            @view(Dm[Block(2i-2,2i)]) .= @view((D[i])[1,end])
            @view(Dm[Block(2i,2i-2)]) .= @view((D[i])[end,1])
        end
    end
    Dm
end

function derop(::Type{T}, basis::Basis, o) where T
    g = basis.grid
    n = order(g)
    elrange = elems(g)
    indices,d̃ = der_blocks(T, basis, o)
    D = [zeros(T,n,n) for i = elrange]
    for i = elrange
        Del = D[i]
        d̃el = d̃[i]
        for m = 1:n
            for m′ = 1:n
                Del[m,m′] = g.N[i,m]*g.N[i,m′]*d̃el[m,m′]
            end
        end
    end

    if g.bl == :dirichlet0
        D[1] = D[1][2:end,2:end]
        for i in 2:length(indices)
            indices[i] = (indices[i][1]-1,indices[i][2]-1)
        end
    end
    if g.br == :dirichlet0
        D[end] = D[end][1:end-1,1:end-1]
    end

    n == 2 && return triderop(indices, D)

    block_banded_derop(basis, D)
end

derop(basis::Basis, o) =
    derop(eltype(basis), basis, o)

function kinop(::Type{T}, basis::Basis) where T
    D2 = derop(T, basis, 2)
    D2 ./= -2
    D2
end
kinop(basis::Basis) = kinop(eltype(basis), basis)

export derop, kinop


function avg2(x)
if (typeof(x[1])==Float64) | (typeof(x[1])==Float32)
        return (x[1:2:end] + x[2:2:end])/2;
else
        return x[1:2:end]
end

end

function avg2_2d(x)

return (x[1:2:end,:] + x[2:2:end,:])/2;

end

function avg2_df(x)

ncol = Integer(size(x)[2]);
nrow = Integer(size(x)[1]/2);
ans = deepcopy(x[1:nrow,:]);

for j in 1:ncol
        ans[:,j] = avg2(x[:,j]);
end

return ans;

end

function convert_sim(x)
a = avg2_df(x[1]);
b = avg2_2d(x[2]);
c = avg2_2d(x[3]);
d = avg2_2d(x[4]);
d2 = avg2(x[5]);

return a,b,c,d,d2
end

function get_diurnal(x,navg)
	if (typeof(x[1])==Float64) | (typeof(x[1])==Float32)
			return mean(reshape(x,(navg,:)),dims=2)[:,1];
	else
			return x[1:navg]
	end
end

function get_diurnal_summer(x,navg)
	if (typeof(x[1])==Float64) | (typeof(x[1])==Float32)
		day_reshape = reshape(x,(navg,:));
		day_list = collect(1:(size(day_reshape)[2])) .% 365;
		summer_mask = (day_list .>= 150) .* (day_list .< 250);
		summer_only = day_reshape[:,summer_mask];
		return mean(summer_only,dims=2)[:,1];
	else
			return x[1:navg]
	end
end

function get_daily(x,navg)
	if (typeof(x[1])==Float64) | (typeof(x[1])==Float32)
			return mean(reshape(x,(navg,:)),dims=1)[1,:];
	else
			return x[1:navg:end]
	end
end

function get_daily2d(x,navg)
	return mean(reshape(x,(navg,:,size(x)[2])),dims=1)[1,:,:];
end


using Statistics: mean!, sum!

using Oceananigans.Utils: tupleit
using Oceananigans.Grids: regular_dimensions
using Oceananigans.Fields: condition_operand
import Oceananigans.Fields: Reduction

#####
##### Metric inference
#####

reduction_grid_metric(dims::Number) = reduction_grid_metric(tuple(dims))

reduction_grid_metric(dims) = dims === tuple(1)  ? Δx :
                              dims === tuple(2)  ? Δy :
                              dims === tuple(3)  ? Δz :
                              dims === (1, 2)    ? Az :
                              dims === (1, 3)    ? Ay :
                              dims === (2, 3)    ? Ax :
                              dims === (1, 2, 3) ? volume :
                              throw(ArgumentError("Cannot determine grid metric for reducing over dims = $dims"))

#####
##### Metric reductions
#####

struct Average end

function Reduction(avg::Average, field::AbstractField; condition = nothing, mask = 0, dims)
    dims = dims isa Colon ? (1, 2, 3) : tupleit(dims)
    dx = reduction_grid_metric(dims)

    if all(d in regular_dimensions(field.grid) for d in dims)
        # Dimensions being reduced are regular; just use mean!
        return Reduction(mean!, condition_operand(field, condition, mask); dims)
    else
        # Compute "size" (length, area, or volume) of averaging region
        metric = GridMetricOperation(location(field), dx, field.grid)
        L = sum(metric; condition, mask, dims)

        # Construct summand of the Average
        L⁻¹_field_dx = field * dx / L

        return Reduction(sum!, condition_operand(L⁻¹_field_dx, condition, mask), dims)
    end
end

"""
    Average(field::AbstractField; condition = nothing, mask = 0, dims=:)

Return `Reduction` representing a spatial average of `field` over `dims`.

Over regularly-spaced dimensions this is equivalent to a numerical `mean!`.

Over dimensions of variable spacing, `field` is multiplied by the
appropriate grid length, area or volume, and divided by the total
spatial extent of the interval.
"""
Average(field::AbstractField; condition = nothing, mask = 0, dims=:) = Reduction(Average(), field; condition, mask, dims)

struct Integral end

function Reduction(int::Integral, field::AbstractField; condition = nothing, mask = 0, dims)
    dims = dims isa Colon ? (1, 2, 3) : tupleit(dims)
    dx = reduction_grid_metric(dims)
    return Reduction(sum!, condition_operand(field * dx, condition, mask), dims)
end

"""
    Integral(field; dims=:)

Return a `Reduction` representing a spatial integral of `field` over `dims`.
"""
Integral(field::AbstractField; condition = nothing, mask = 0, dims=:) = Reduction(Integral(), condition_operand(field, condition, mask), dims)

#####
##### show
#####

Base.summary(r::Reduction{<:Average}) = string("Average of ", summary(r.operand), " over dims ", r.dims)
Base.summary(r::Reduction{<:Integral}) = string("Integral of ", summary(r.operand), " over dims ", r.dims)


using StructuresKit

shape_name = "W14X90"

shape_info = StructuresKit.CrossSection.AISC(shape_name)

#A Ix Iy J Cw xc yc xs ys
section_properties = [(shape_info.A, shape_info.Ix, shape_info.Iy, shape_info.J, shape_info.Cw, 0.0, shape_info.d/2, 0.0, shape_info.d/2)]

#E  ν
material_properties = [(29000,0.30)]

#kx ky kϕ hx hy
springs = [0.0, 0.0, 0.0, 0.0, 0.0]

#member information
#L dL SectionProperties MaterialProperties Springs
member_definitions = [(13.0*12, 1.0,1,1,1)]

num_nodes = Int(member_definitions[1][1]/member_definitions[1][2]) + 1

#P
loads = 10000.0 * ones(num_nodes)

#end boundary conditions
#type=1 u''=v''=ϕ''=0 (simply supported), type=2 u'=v'=ϕ'=0  (fixed), type=3 u''=v''=ϕ''=u'''=v'''=ϕ'''=0 (free end, e.g., a cantilever)
end_boundary_conditions = [1 1]

#supports
#location where u=v=ϕ=0
supports = [0.0, 13.0*12]

#imperfection
L = member_definitions[1][1]
dL = member_definitions[1][2]
z = 0: dL :L
Δo = L / 1000 * sin.(π*z/L)

uo = Δo  #in the weak axis
vo = zeros(Float64, num_nodes)
ϕo = zeros(Float64, num_nodes)

imperfections = [uo, vo, ϕo]

#Calculate weak axis flexural buckling load
E = material_properties[1][1]
Iy = shape_info.Iy
Pcry = π^2*E*Iy/L^2   #weak axis buckling load

#calculate yield load
Py = 50*shape_info.A

#define load range for a VR test
P = 0:0.99*Pcry/10:0.99*Pcry

#inelasticity
inelasticity_flag = 0

# mutable struct Properties

#     A::Array{Float64,1}
#     Ix::Array{Float64,1}
#     Iy::Array{Float64,1}
#     J::Array{Float64,1}
#     Cw::Array{Float64,1}
#     xc::Array{Float64,1}
#     yc::Array{Float64,1}
#     xs::Array{Float64,1}
#     ys::Array{Float64,1}
#     xo::Array{Float64,1}
#     yo::Array{Float64,1}
#     Io::Array{Float64,1}
#     E::Array{Float64,1}
#     ν::Array{Float64,1}
#     G::Array{Float64,1}
#     kx::Float64
#     ky::Float64
#     kϕ::Float64
#     hx::Float64
#     hy::Float64
#     uo::Array{Float64,1}
#     vo::Array{Float64,1}
#     ϕo::Array{Float64,1}
#     P::Array{Float64,1}
#     dz::Array{Float64,1}
#     z::Array{Float64,1}
#     dm::Array{Float64,1}
#     Azz::Array{Float64,2}
#     Azzzz::Array{Float64,2}
#     supports::Array{Float64,1}

# end

# dz, z, dm = Mesh.define_line_element(member_definitions)

#    NumberOfNodes=length(dz)+1

#    #define property vectors
#    A =  Mesh.create_line_element_property_array(member_definitions, dm, dz, section_properties, 3, 1)
#    Ix = Mesh.create_line_element_property_array(member_definitions, dm, dz, section_properties, 3, 2)
#    Iy = Mesh.create_line_element_property_array(member_definitions, dm, dz, section_properties, 3, 3)
#    J = Mesh.create_line_element_property_array(member_definitions, dm, dz, section_properties, 3, 4)
#    Cw = Mesh.create_line_element_property_array(member_definitions, dm, dz, section_properties, 3, 5)
#    xc = Mesh.create_line_element_property_array(member_definitions, dm, dz, section_properties, 3, 6)
#    yc = Mesh.create_line_element_property_array(member_definitions, dm, dz, section_properties, 3, 7)
#    xs = Mesh.create_line_element_property_array(member_definitions, dm, dz, section_properties, 3, 8)
#    ys = Mesh.create_line_element_property_array(member_definitions, dm, dz, section_properties, 3, 9)

#    xo = -(xc .- xs)
#    yo = yc .- ys

#    Io = Ix .+ Iy .+ A .* (xo.^2 + yo.^2)

#    E = Mesh.create_line_element_property_array(member_definitions, dm, dz, material_properties, 4, 1)
#    ν = Mesh.create_line_element_property_array(member_definitions, dm, dz, material_properties, 4, 2)
#    G = E./(2 .*(1 .+ ν))

#    kx = springs[1]
#    ky = springs[2]
#    kϕ = springs[3]

#    hx = springs[4]
#    hy = springs[5]

#    #define imperfections
#    uo = imperfections[1]
#    vo = imperfections[2]
#    ϕo = imperfections[3]

#    #define load
#    P =  loads

#    #calculate derivative operators
#    Azzzz,Azz = Column.calculate_derivative_operators(dz)

#    #apply left and right end boundary condition stencils to derivative operators
#    NthDerivative = 4
#    Azzzz = Column.apply_end_boundary_conditions(Azzzz, end_boundary_conditions, NthDerivative, dz)

#    NthDerivative = 2
#    Azz = Column.apply_end_boundary_conditions(Azz, end_boundary_conditions, NthDerivative, dz)

#    column_properties = Properties(A, Ix, Iy, J, Cw, xc, yc, xs, ys, xo, yo, Io, E, ν, G, kx, ky, kϕ, hx, hy, uo, vo, ϕo, P, dz, z, dm, Azz, Azzzz, supports)

#********************************************************************************************

# dz, z, dm = StructuresKit.Mesh.define_line_element(member_definitions)
# A =  StructuresKit.Mesh.create_line_element_property_array(member_definitions, dm, dz, section_properties, 3, 1)

def_coordinates = []
connectivity = []

for i = 1:length(P)

    loads = P[i] * ones(num_nodes)

    #define column information
    column_properties = Column.initialize(member_definitions, section_properties, material_properties, loads, springs, end_boundary_conditions, supports, imperfections)

    #solve for column deformation
    u, v, ϕ = Column.solve(column_properties, inelasticity_flag)

    #add initial imperfection to solution
    u_total = uo .+ u
    v_total = vo .+ v
    ϕ_total = ϕo .+ ϕ

    #create surface mesh of undeformed column

    #define discretization along member
    zcoords = 0:dL:L
    n=(4, 4, 4, 4, 4)

    xcoords, ycoords = StructuresKit.CrossSection.wshape_nodes(shape_info, n)

    #undeformed shape
    coordinates, connectivity = StructuresKit.Mesh.surface(xcoords, ycoords, zcoords)

    #deformed shape

    σ_total_normal = Column.normal_stresses(xcoords, ycoords, zcoords, u, v, column_properties)
    ϵ_total_normal = Column.normal_stains(column_properties, σ_total_normal)
    w = Column.warping_displacements(zcoords, ϵ_total_normal)

    scale_u = 1.0
    scale_v = 1.0
    scale_ϕ = 1.0
    scale_w = 1.0

    x_deformed, y_deformed, z_deformed = Column.deformed_shape(xcoords, ycoords, zcoords, u, v, ϕ, w, scale_u, scale_v, scale_ϕ, scale_w)

    filename = string(@__DIR__, "/", "W14x90", "_SS_", string(i-1), ".ply")

    def_coordinates = [x_deformed y_deformed z_deformed]

    # Visualize.create_ply_file(def_coordinates, connectivity, filename)

end

using Makie
scene = poly(def_coordinates, connectivity, color=:lightgray, shading=:true, show_axis=:true, overdraw=:false, strokecolor = (:black, 0.6), strokewidth = 1)


using WiltonInts84

using Test
using StaticArrays

include("num_quad.jl")

if !@isdefined record
    record = false
end

p1 = SVector(0.4,0.7000000000000001,0.0)
p2 = SVector(0.5,0.7000000000000001,0.0)
p3 = SVector(0.5,0.6000000000000001,0.0)
z  = SVector(0.0,0.0,1.0)

N = 2
T = eltype(Float64)
P = SVector{3,T}
J = Vector{SVector{N+3,T}}()
L = Vector{SVector{N+3,P}}()
I = similar(J)
K = similar(L)

h = 1.2

# circle through triangle vertex
c = SVector(0.020000000000000004,0.06,0.0)
r, R = 0.0, 0.8
q = contour(p1,p2,p3,c,r,R)
@test length(q.segments) == 0
@test length(q.arcs) == 0
@test length(q.circles) == 0
A,B = wiltonints(q,c, Val{N})
push!(I,SVector(A)); push!(K,SVector(B))
if record
    P,Q = dblquadints1(p1,p2,p3,c,Val{N},r,R)
    push!(J,P); push!(L,Q)
end


include("HelperFunctions.jl")

"""
    This function applies a Kalman filter and fixed-interval smoother. The script uses the following model:
        y_t = C_t * Z_t + e_t, for e_t ~ N(0, R)
        Z_t = A * Z_{t-1} + mu_t, for mu_t ~ N(0, Q)
    It then applies a fixed-interval smoother using the results from the KF.
    Throughout this file,
        'm' denotes the number of elements in the state vector Z_t.
        'k' denotes the number of elements (observed variables) in y_t.
        'nobs' denotes the number of time periods for which data are observed.

     parameters:
        y_est : Array
            k x nobs matrix of input data
        A : Array
            m x m transition matrix
        C: Array
            k x m measurement matrix
        Q: Array
            m x m covariance matrix for transition equation residuals (mu_t)
        R: Array
            k x k covariance for measurement matrix residuals (e_t)
        Z0: Array
            1 x m vector, initial value of state
        V0 : Array
            m x m matrix, initial value of factor covariance matrix
    returns: Dict
       Zsmooth => k-by-(nobs+1) matrix, smoothed factor estimates (i.e. Zsmooth[, t + 1] = Z_t|T)
       Vsmooth => k-by-k-by-(nobs+1) array, smoothed factor covariance matrices (i.e. Vsmooth[, , t + 1) = Cov(Z_t|T))
       VmU => Filtered factor posterior covariance
       VVsmooth => k-by-k-by-nobs array, lag 1 factor covariance matrices (i.e. Cov(Z_t, Z_t-1|T))
       loglik => scalar, log-likelihood
"""
function kalman_filter(y_est, A, C, Q, R, Z0, V0)
    # initialize
    m = size(C)[2]
    n_obs = size(y_est)[2]
    Zm = Array{Union{Missing, Float64},2}(undef, m, n_obs) # Z_t | t-1 (prior)
    Vm = Array{Union{Missing, Float64},3}(undef, m, m, n_obs) # V_t | t-1 (prior)
    ZmU = Array{Union{Missing, Float64},2}(undef, m, n_obs+1) # Z_t | t (posterior/updated)
    VmU = Array{Union{Missing, Float64},3}(undef, m, m, n_obs+1) # V_t | t (posterior/updated)
    ZmT = [0.0 for i in 1:(m * (n_obs + 1))] |> x-> reshape(x, m, n_obs + 1) # Z_t | T (smoothed states)
    VmT = [0.0 for i in 1:(m * m * (n_obs + 1))] |> x-> reshape(x, m, m, n_obs + 1) # V_t | T = Cov(Z_t|T) (smoothed factor covariance)
    VmT_lag = Array{Union{Missing, Float64},3}(undef, m, m, n_obs) # Cov(Z_t, Z_t-1|T) (smoothed lag 1 factor covariance)
    loglik = 0

    # initial values
    Zu = Z0 # Z_0|0 (In loop, Zu gives Z_t | t)
    Vu = V0 # V_0|0 (In loop, Vu gives V_t | t)
    ZmU[:, 1] = Zu
    VmU[:,:, 1] = Vu

    # kalman filter
    for t in 1:n_obs
        ### Calculate prior distribution
        Z = A * Zu # Use transition equation to create prior estimate for factor, i.e. Z = Z_t|t-1
        V = A * Vu * transpose(A) + Q # Prior covariance matrix of Z (i.e. V = V_t|t-1)
        V = 0.5 * (V + transpose(V))  # Trick to make symmetric

        ### Calculate posterior distribution
        non_missing = findall(!ismissing, y_est[:,t]) # Remove missing series: These are removed from Y, C, and R
        global yt = y_est[:,t][non_missing]
        global Ct = C[non_missing,:]
        Rt = R[non_missing,non_missing]

        # Check if yt contains no data; if this is the case, replace Zu and Vu with prior
        if length(yt) == 0
            Zu = Z
            Vu = V
        else
            # Steps for variance and population regression coefficients:
            VC = V * transpose(Ct)
            iF = inv(Ct * VC + Rt)
            global VCF = VC * iF # Matrix of population regression coefficients (QuantEcon eqn #4)
            innov = yt - Ct * Z # Gives difference between actual and predicted measurement matrix values
            Zu  = Z  + VCF * innov # Update estimate of factor values (posterior)
            Vu = V - VCF * transpose(VC) # Update covariance matrix (posterior) for time t
            Vu = 0.5 * (Vu + transpose(Vu)) # Trick to make symmetric
            loglik = loglik + 0.5 * (log(det(iF)) - transpose(innov) * iF * innov) # Update log likelihood
        end

        ### Store output
        # Store covariance and observation values for t-1 (priors)
        Zm[:,t] = Z
        Vm[:,:,t] = V
        # Store covariance and state values for t (posteriors), i.e. Zu = Z_t|t & Vu = V_t|t
        ZmU[:,t + 1] = Zu
        VmU[:,:,t + 1] = Vu
    end

    # Store Kalman gain k_t
    if length(yt) == 0
        k_t = zeros(m, m)
    else
        k_t = VCF * Ct
    end

    ### Apply fixed interval smoother
    # Fill the final period of ZmT & VmT with posterior values from KF
    ZmT[:,n_obs + 1] = ZmU[:,n_obs + 1]
    VmT[:,:,n_obs + 1] = VmU[:,:,n_obs + 1]
    VmT_lag[:,:,n_obs] = (I(m) - k_t) * A * VmU[:,:,n_obs] # Initialize VmT_1 lag 1 covariance matrix for final period
    J_2 = VmU[:,:, n_obs] * transpose(A) * pinv(Vm[:,:,n_obs]) # Used for recursion process, see companion file for details

    ### Run smoothing algorithm
    # Loop through time reverse-chronologically (starting at final period nobs)
    for t in n_obs:-1:1
        # Store posterior and prior factor covariance values
        VmUt = VmU[:,:,t]
        Vmt = Vm[:,:,t]
        # Store previous period smoothed factor covariance and lag-1 covariance
        Vt = VmT[:,:,t+1]
        Vt_lag = VmT_lag[:,:,t]
        J_1 = copy(J_2)
        ZmT[:,t] = ZmU[:,t] + J_1 * (ZmT[:,t+1] - A * ZmU[:,t])  # Update smoothed factor estimate
        VmT[:,:,t] = VmUt + J_1 * (Vt - Vmt) * transpose(J_1) # Update smoothed factor covariance matrix
        if t > 1
            J_2 = VmU[:,:,t-1] * transpose(A) * pinv(Vm[:,:,t-1]) # Update weight
            VmT_lag[:,:,t-1] = VmUt * transpose(J_2) + J_1 * (Vt_lag - A * VmUt) * transpose(J_2) # Update lag 1 factor covariance matrix
        end
    end

    ### output
    return Dict(
        :Zsmooth => ZmT,
        :Vsmooth => VmT,
        :VmU => VmU,
        :VVsmooth => VmT_lag,
        :loglik => loglik
    )
end

"""
    This function applies a Kalman filter for news calculation step, when model parameters are already estimated. This procedure only smoothes and fills missing data for a given data matrix
     parameters:
        data : Array
            input data matrix
        output_dfm : Dict
            output of estimate_dfm function
        lag: Int
            number of lags
    returns: Dict
        Plag => Smoothed factor covariance for transition matrix
        Vsmooth => Smoothed factor covariance matrix
        X_smooth => Smoothed data matrix
        F => Smoothed factors
"""
function kalman_filter_constparams(data; output_dfm, lag)
    # initialization
    Z0 = output_dfm[:Z0]; V0 = output_dfm[:V0]; A = output_dfm[:A]; C = output_dfm[:C]; Q = output_dfm[:Q]; R = output_dfm[:R]; means = output_dfm[:means]; stds = output_dfm[:stds];
    stds = output_dfm[:stds]
    means = output_dfm[:means]
    y = transpose(Array(standardize(data)))
    # y = standardize(data) |> j-> j[[sum(.!ismissing.(Array(x))) > 0 for x in eachrow(j)], :] |> Array |> transpose # temporary, should I removing missing before here?
    kalman_output = kalman_filter(y, A, C, Q, R, Z0, V0)

    Vs = kalman_output[:Vsmooth][:,:,1:end-1] # Smoothed factor covariance for transition matrix
    Vf = kalman_output[:VmU][:,:,1:end-1] # Filtered factor posterior covariance
    Zsmooth = kalman_output[:Zsmooth] # Smoothed factors
    Vsmooth = kalman_output[:Vsmooth] # Smoothed covariance value
    Plag = []
    push!(Plag, Vs)

    if lag > 0
        for jk in 1:lag
            tmp_plag = Array{Union{Missing, Float64},3}(undef, size(C)[2], size(C)[2], size(y)[2])
            for jt in size(Plag[1])[3]:-1:(lag + 1)
                As = Vf[:,:, jt - jk] * transpose(A) * pinv(A * Vf[:,:,jt-jk] * transpose(A) + Q)
                tmp_plag[:,:,jt] = As * Plag[jk][:,:,jt]
            end
            push!(Plag, tmp_plag)
        end
    end
    Zsmooth = transpose(Zsmooth)
    x_sm = Zsmooth[1:end-1,:] * transpose(C) # Factors to series representation
    X_smooth = repeat(stds, size(y)[2]) .* x_sm .+ repeat(means, size(y)[2]) # standardized to unstandardized

    return Dict(
        :Plag => Plag,
        :Vsmooth => Vsmooth,
        :X_smooth => X_smooth,
        :F => Zsmooth[1:end-1,:]
    )
end


using Revise
using AdFem
using PyPlot
using LinearAlgebra
using Statistics
using MAT

mmesh = Mesh(joinpath(PDATA, "twoholes_large.stl"))
mmesh = Mesh(mmesh.nodes * 10, mmesh.elems, -1, 2)

ν = sigmoid(Variable(zeros(get_ngauss(mmesh))) ) * 0.002
K = constant(compute_fem_laplace_matrix(ν, mmesh))
B = constant(compute_interaction_matrix(mmesh))
Z = [K -B'
    -B spzero(size(B,1))]

bd1 = bcnode((x,y)->y>0.23-1e-5, mmesh)
bd2 = bcnode((x,y)->y<=0.23-1e-5, mmesh)
bd = [bd1;bd2]
bd = [bd; bd .+ mmesh.ndof; 2mmesh.ndof + 1]

rhs = zeros(2mmesh.ndof + mmesh.nelem)
bdval = zeros(length(bd))
bdval[1:length(bd1)] .= 1.0
Z, rhs = impose_Dirichlet_boundary_conditions(Z, rhs, bd, bdval)
sol = Z\rhs

U = matread("fenics/fwd2.mat")["u"]
loss = sum((sol[2mmesh.ndof+1:end] - U[2mmesh.ndof+1:end])^2)

sess = Session(); init(sess)

_loss = Float64[]
cb = (vs, iter, loss)->begin
    global _loss
    push!(_loss, loss)
    printstyled("[#iter $iter] loss=$loss\n", color=:green)
    if mod(iter, 10)==1
        make_directory("pixel")
        close("all")
        visualize_scalar_on_gauss_points(vs[1], mmesh)
        matwrite("pixel/bwd3-$iter.mat", Dict("iter"=>iter,"loss"=>_loss, "nu"=>vs[1]))
        savefig("pixel/bwd3_pixel$iter.png")
    end
end

BFGS!(sess, loss, vars = [ν], callback = cb)


I = π * besseli(0.0, 1.0)
for endpt in [neither, left, right, both]
    x, w = chebyshev(10, 1, endpt)
    Q = sum(w .* exp.(x))
    @test I ≈ Q
end

I = π * besseli(1.0, 1.0)
for endpt in [neither, left, right, both]
    x, w = chebyshev(10, 2, endpt)
    Q = sum(w .* exp.(x))
    @test I ≈ Q
end


### A Pluto.jl notebook ###
# v0.14.5

using Markdown
using InteractiveUtils

# ╔═╡ a5ec2a7e-2dab-46c9-9013-9e7adbd44d78
using Random, Statistics, ForwardDiff

# ╔═╡ 61709a7e-18c5-4017-8e84-6eaf16ba210d
md"# Tipos e Funções"

# ╔═╡ bd149090-b5e2-11eb-34e9-ef542421d474
begin
	Random.seed!(0)

	struct LayerDense{T<:Real}
		W::Matrix{T}
		b::Vector{T}
		σ::Function

		function LayerDense(W::Matrix{T}, b::Vector{T}, σ::Function = identity) where {T<:Real}
			return new{T}(W, b, σ)
		end
	end

	function LayerDense(W::Matrix{T}, b::Vector{S}, σ::Function = identity) where {T<:Real, S<:Real}
		R = promote_type(T, S)
		return LayerDense(Matrix{R}(W), Vector{R}(b), σ)
	end

	function LayerDense(W::Matrix{<:Real}, σ::Function = identity)
		b = zeros(size(W)[1])
		return LayerDense(W, b, σ)
	end

	function LayerDense(n_in::Int, n_out::Int, σ::Function = identity)
		W = 0.01 .* randn(n_out, n_in)
		b = zeros(n_out)
		return LayerDense(W, b, σ)
	end

	function (L::LayerDense)(input::AbstractVector{<:Real})
		W, b, σ = L.W, L.b, L.σ
		return σ(W * input + b)
	end

	function (L::LayerDense)(input::AbstractMatrix{<:Real})
		W, b, σ = L.W, L.b, L.σ
		return σ(W * input .+ b)
	end
end

# ╔═╡ 92a9ff09-50f1-42d1-8b12-c37771610d84
begin
	function σ(x::Real)
		t = exp(-abs(x))
		return ifelse(x ≥ 0, inv(1 + t), t / (1 + t))
	end

	σ(x::AbstractVecOrMat{T}) where {T<:Real} = σ.(x)

	const sigmoid = σ
end

# ╔═╡ 468eb0e4-5c6f-49c6-9589-533071bb1271
begin
	relu(x::Real) = max(zero(x), x) # zero function keep the type of x
	relu(x::AbstractVecOrMat{<:Real}) = relu.(x)

	#log1p(x) is the seme as log(1 + x)
	softplus(x::Real) = ifelse(x > 0, x + log(1 + exp(-x)), log(1 + exp(x)))
	softplus(x::AbstractVecOrMat{<:Real}) = softplus.(x)

	function softmax(x::Vector{T}) where {T<:Real}
		m = maximum(x)
		exp_val = exp.(x .- m)
		s = sum(exp_val)
		return exp_val ./ s
	end
	function softmax(x::Matrix{T}) where {T<:Real}
		m = maximum(x, dims = 1)
		exp_val = exp.(x .- m)
		s = sum(exp_val, dims = 1)
		return exp_val ./ s
	end
end

# ╔═╡ 65358f5c-04a5-4618-a78e-1fd02d72060d
begin
	function xlogy(x::Real, y::Real)
		result = x * log(y)
		ifelse(iszero(x) && !isnan(y), zero(result), result)
	end

	function crossentropy(ŷ::AbstractVecOrMat{<:Real},
						  y::AbstractVecOrMat{<:Real};
						  dims::Int = 1,
						  agg::Function = Statistics.mean)
    	agg(.-sum(xlogy.(y, ŷ), dims = dims))
	end
end

# ╔═╡ c66c7488-8667-4694-915e-b022488ccd8b
begin
	struct Chain{T<:Tuple}
		layers::T
		Chain(layers...) = new{typeof(layers)}(layers)
	end

	function (L::Chain)(input::AbstractVecOrMat{<:Real})
		return ∘(reverse(L.layers)...)(input)
	end
end

# ╔═╡ bd4c6ca8-93a2-4344-b598-fa533ad7e169
function params(C::Chain)
	p = []
	for layer in C.layers
		#push!(p, layer.W[:], layer.b )
		push!(p, layer.W, layer.b )
	end
	p #return vcat(p...)
end

# ╔═╡ b10cf718-528a-4433-9ae4-25ca78256ec8
md"# Exemplos"

# ╔═╡ 5fefc40a-dbc9-42e5-b9ea-bb2eed97a805
begin
	Random.seed!(1998)

	L1 = LayerDense(10, 5, σ)
	L2 = LayerDense(5, 2, softmax)

	r = rand(10)

	m = Chain(L1, L2)				# C1 = L2(L1(r))

	label = [1.0, 0.0]

	#function loss(p::Vector)
		# atualiza o modelo com os parametros p
		#update(m, p)
		# aplica na função de erro
		#crossentropy(m(r), label)
	#end

	#∇G = x -> ForwardDiff.gradient(loss, x); # g = ∇f

	p = params(m)
	# for iter in iteracoes
		# calcula o loss (crossentropy)
		#loss(r, label)
		# acha o gradiente negativo dos params (p) com o ForwardDiff
		#grad = ∇G(m(r))
		# atualiza o parametro (p) substraindo o gradiente negativo
		#p .-= grad
	# end
end

# ╔═╡ Cell order:
# ╟─61709a7e-18c5-4017-8e84-6eaf16ba210d
# ╠═a5ec2a7e-2dab-46c9-9013-9e7adbd44d78
# ╠═bd149090-b5e2-11eb-34e9-ef542421d474
# ╠═92a9ff09-50f1-42d1-8b12-c37771610d84
# ╠═468eb0e4-5c6f-49c6-9589-533071bb1271
# ╠═65358f5c-04a5-4618-a78e-1fd02d72060d
# ╠═bd4c6ca8-93a2-4344-b598-fa533ad7e169
# ╠═c66c7488-8667-4694-915e-b022488ccd8b
# ╟─b10cf718-528a-4433-9ae4-25ca78256ec8
# ╠═5fefc40a-dbc9-42e5-b9ea-bb2eed97a805


using Random
using StatsBase: sample!
using OffsetArrays
#implements https://arxiv.org/pdf/2006.07060.pdf
#thousands of times faster than https://github.com/manhtuando97/KDD-20-Hypergraph/blob/master/Code/Generator/hyper_preferential_attachment.py

function hyper_pa(degree_distribution, edgesize_distribution, max_edgesize::Integer, nodes::I;
    rng::AbstractRNG=Random.GLOBAL_RNG) where I <: Integer

    edges = [[a,a+I(1)] for a in I(1):I(2):I(max_edgesize-1)]
    edges_by_size = [Vector{I}[] for _ in 1:max_edgesize]
    edges_by_size[2] = copy(edges)

    cum_sum_source = Vector{Int}(undef, max_edgesize)

    binomial_table = [binomial(a,b) for a in 1:max_edgesize, b in 1:max_edgesize-1]

    for n in last(last(edges))+1:nodes
        for _ in 1:rand(degree_distribution)
            new_edgesize = rand(edgesize_distribution)
            new_edge = Vector{I}(undef, new_edgesize)
            new_edge[1] = n

            if new_edgesize > 1
                #binom = 1
                acc = 0
                cum_sum = OffsetVector(view(cum_sum_source, 1:max_edgesize-new_edgesize+2), (new_edgesize-1):max_edgesize) #13%
                for source_edgesize in eachindex(cum_sum)
                    binom = binomial_table[source_edgesize,new_edgesize-1]
                    acc += length(edges_by_size[source_edgesize])*binom
                    cum_sum[source_edgesize] = acc
                    #binom = binom*(source_edgesize+1)÷(source_edgesize-new_edgesize+2)
                end

                total = last(cum_sum)
                if total == 0
                    sample!(rng, 1:(n-1), (@view new_edge[2:end]), replace=false) # sample! mutates its 3rd argument only.
                else
                    key = rand(rng, 1:total)
                    source_edgesize = firstindex(cum_sum)
                    while #=@inbounds=# cum_sum[source_edgesize] < key
                        source_edgesize += 1
                    end

                    #Huzzah! we have a source edgesize!

                    source_edge = rand(rng, edges_by_size[source_edgesize])

                    #Huzzah! and a specific source edge

                    # This sampling takes ~38% of runtime. ~20% of that (~7.5% total) can be
                    # alleviated by passing a workspace vector through. StatsBase doesn't
                    # support that, though.
                    sample!(rng, source_edge, (@view new_edge[2:end]), replace=false) # sample! mutates its 3rd argument only.

                    #Huzzah! and an actual edge to use
                end
            end

            push!(edges, new_edge)
            @assert length(new_edge) == new_edgesize
            push!(edges_by_size[new_edgesize], new_edge)
        end
    end

    edges
end


# This is part of Calendars.jl. See the copyright note there.
# ========================= Julian dates ====================

# Day number of the start of the Julian calendar.
# JL-0001-01-01 = CE-0000-12-30  ~ DN#-1 = EpochJulian.
# JL-0001-01-03 = CE-0001-01-01  ~ DN#1.
# JD-1111-11-11 = CE-1111-11-18  ~ DN#0405740
# JD-9999-12-31 = CE-10000-03-13 ~ DN#3652132
const EpochJulian = -1
const ValidYearsJulian = (1, 9999) # by convention

# True if year is a Julian leap year.
function isLeapYearJulian(year::DPart)
    rem(year, 4) == 0
end

# Return the last day of the month for the Julian calendar.
function LastDayOfMonthJulian(year::DPart, month::DPart)
    leap = isLeapYearJulian(year)
    (month == 2 && leap) && return 29
    return MonthLength[month]
end

# Is the date a valid Julian date?
function isValidDateJulian(cd::CDate, warn=true)
    cal, year, month, day = cd
    val = ( cal == JD
        && (ValidYearsJulian[1] <= year && year <= ValidYearsJulian[2])
        && (1 <= month && month <= 12)
        && (1 <= day && day <= LastDayOfMonthJulian(year, month)))
    ! val && warn && @warn(Warning(cd))
    return val
end

# Represents the day number a valid Julian date?
function isValidDateJulian(dn::DPart, warn=true)
    val = EpochJulian <= dn
    ! val && warn && @warn(Warning(dn))
    return val
end

# Return the days this year so far.
function DayOfYearJulian(year::DPart, month::DPart, day::DPart)
    for m in (month - 1):-1:1  # days in prior months this year
        day += LastDayOfMonthJulian(year, m)
    end
    return day - 1
end

# Returns the day number from a valid Julian date.
function DayNumberJulian(year::DPart, month::DPart, day::DPart)
    day = DayOfYearJulian(year, month, day)

    return (EpochJulian      # days elapsed before absolute date 1
        + 365 * (year - 1)   # days in previous years ignoring leap days
        + div(year - 1, 4)   # leap days before this year...
        + day                # days this year
    )
end
DayNumberJulian(d::CDate) = DayNumberJulian(d[2], d[3], d[4])

# Computes the Julian date from the day number.
function DateJulian(dn::DPart)
    ! isValidDateJulian(dn) && return InvalidDate

    # Search forward year by year from approximate year
    year = div(dn + EpochJulian, 366)

    while dn >= DayNumberJulian(year + 1, 1, 1)
        year += 1
    end

    # Search forward month by month from January
    month = 1
    while dn > DayNumberJulian(year, month,
                    LastDayOfMonthJulian(year, month))
        month += 1
    end

    day = dn - DayNumberJulian(year, month, 1) + 1
    return (JD, year, month, day)::CDate
end

# Return the day number of the first day in the given Julian year.
function YearStartJulian(year::DPart)
    return DayNumberJulian(year, 1, 1)
end

# Return the day number of the last day in the given Julian year.
function YearEndJulian(year::DPart)
    return DayNumberJulian(year + 1, 1, 1) - 1
end

# Return the number of months in the given Julian year.
function MonthsInYearJulian(year::DPart)
    return 12
end

# Return the number of days in the given Julian year.
function DaysInYearJulian(year::DPart)
    return YearStartJulian(year + 1) - YearStartJulian(year)
end


# dirichlet distribution sample

module DirichletDistribution
  using Distributions

  function main()
    tau = [1, 2, 3, 4, 5]

    for i in 1:3
      pi_ = rand(Dirichlet(tau))
      println("π = $(pi_), Sum: $(sum(pi_))")
    end
  end
end


__precompile__()

module ImplicitSkinning

using SymPy

function grad(f, x)
    [diff(f, x[i]) for i in 1:length(x)]
end

function hrbf_field(x, phi, v, lambda, beta)
    phi_expr = phi(norm(x - v))
    return lambda * phi_expr + dot(beta, grad(phi_expr, x))[1]
end

type HRBFGenerator
	dimension::Integer
	partials::Dict{Tuple{Symbol, Symbol}, Function}
	field_func::Function
end

function HRBFGenerator(dimension::Integer, phi::Function)
	lambda = symbols("lambda", real=true)
	x = Sym[symbols("x$(i)", real=true) for i in 1:dimension]
	v = Sym[symbols("v$(i)", real=true) for i in 1:dimension]
	beta = Sym[symbols("beta$(i)", real=true) for i in 1:dimension]
	f = hrbf_field(x, phi, v, lambda, beta)
	g = grad(f, x)

	df_dlambda_func = lambdify(diff(f, lambda), [x...,v...])
	df_dbeta_func = [lambdify(diff(f, beta[i]), [x...,v...]) for i in 1:dimension]
	dg_dlambda_func = [lambdify(diff(g[i], lambda), [x...,v...]) for i in 1:dimension]
	dg_dbeta_func = [lambdify(diff(g[i], beta[j]), [x...,v...]) for i in 1:dimension, j in 1:dimension]
	f_func = lambdify(f, [lambda, beta..., x..., v...])

	partials = Dict{Tuple{Symbol, Symbol}, Function}()
	partials[(:f, :lambda)] = (point_index, lambda_index) -> (points -> df_dlambda_func(points[point_index,:]..., points[lambda_index,:]...))
	partials[(:f, :beta)] = (point_index, beta_index, coord_index) -> (points -> df_dbeta_func[coord_index](points[point_index,:]..., points[beta_index,:]...))
	partials[(:g, :lambda)] = (point_index, lambda_index, grad_coord_index) -> (points -> dg_dlambda_func[grad_coord_index](points[point_index,:]..., points[lambda_index,:]...))
	partials[(:g, :beta)] = (point_index, beta_index, grad_coord_index, beta_coord_index) -> (points -> dg_dbeta_func[grad_coord_index, beta_coord_index](points[point_index,:]..., points[beta_index,:]...))
	HRBFGenerator(dimension, partials, f_func)
end

function get_field{T}(gen::HRBFGenerator, points::Array{T}, normals::Array{T})
	dimension = gen.dimension
	@assert dimension == size(points, 2)
	@assert dimension == size(normals, 2)
	num_points = size(points, 1)

	A_type = promote_type(T, Float64)
	A_11 = A_type[gen.partials[(:f, :lambda)](i,j)(points) for i in 1:num_points, j in 1:num_points]
	A_12 = hcat([A_type[gen.partials[(:f, :beta)](i,j,k)(points) for i in 1:num_points, k in 1:dimension] for j in 1:num_points]...)
	A_21 = vcat([A_type[gen.partials[(:g, :lambda)](i, j, k)(points) for k in 1:dimension, j in 1:num_points] for i in 1:num_points]...)
	A_22 = vcat([hcat([A_type[gen.partials[(:g, :beta)](i, j, k, l)(points) for k in 1:dimension, l in 1:dimension] for j in 1:num_points]...) for i in 1:num_points]...)

	A = [A_11 A_12; A_21 A_22]
	A[map(isnan, A)] = 0.0
	b = vcat(zeros(num_points), normals'[:])
	y = A \ b
	lambda_values = y[1:num_points]
	beta_values = reshape(y[num_points+1:end], dimension, num_points)'
	return (x_values...) -> sum([gen.field_func(lambda_values[i], beta_values[i,:]..., x_values..., points[i,:]...) for i = 1:num_points])
end

end # module


# Baseline version:
module MatlabPort

# matlab compat functions
_land(a, b) = a != 0 && b != 0
function land(a, b)
    map(_land, a, b)
end

ndgrid(v::AbstractVector) = copy(v)
function ndgrid(v1::AbstractVector, v2::AbstractVector)
    m, n = length(v1), length(v2)
    v1 = reshape(v1, m, 1)
    v2 = reshape(v2, 1, n)
    (repmat(v1, 1, n), repmat(v2, m, 1))
end

function ndgrid_fill(a, v, s, snext)
    for j = 1:length(a)
        a[j] = v[div(rem(j-1, snext), s)+1]
    end
end
function ndgrid{T}(vs::AbstractVector{T}...)
    n = length(vs)
    sz = map(length, vs)
    out = ntuple(i->Array{T}(sz), n)
    s = 1
    for i=1:n
        a = out[i]::Array
        v = vs[i]
        snext = s*size(a,i)
        ndgrid_fill(a, v, s, snext)
        s = snext
    end
    out
end

type ISF
    px::Array{Float64,3}; py::Array{Float64,3}; pz::Array{Float64,3}          # coordinates of grid points
    ix::UnitRange{Int}; iy::UnitRange{Int}; iz::UnitRange{Int}          # 1D index array
    iix::Array{Int, 3}; iiy::Array{Int, 3}; iiz::Array{Int, 3}         # 3D index array
    dx::Float64; dy::Float64; dz::Float64          # edge length
    sizex::Int; sizey::Int; sizez::Int # size of grid
    resx::Int; resy::Int; resz::Int    # number of grid points in each dimension

    hbar::Float64             # reduced Planck constant
    dt::Float64               # time step
    mask::Array{Complex{Float64},3} # Fourier coefficient for solving Schroedinger eq

    function ISF(vol_size::NTuple{3}, vol_res::NTuple{3}, hbar, dt)
        obj = new()
        obj.sizex, obj.sizey, obj.sizez = vol_size
        obj.resx = round(Int, vol_res[1])
        obj.resy = round(Int, vol_res[2])
        obj.resz = round(Int, vol_res[3])
        obj.dx = obj.sizex / obj.resx
        obj.dy = obj.sizey / obj.resy
        obj.dz = obj.sizez / obj.resz
        obj.ix = 1:obj.resx
        obj.iy = 1:obj.resy
        obj.iz = 1:obj.resz
        obj.iix, obj.iiy, obj.iiz = ndgrid(obj.ix,obj.iy,obj.iz)
        obj.px = (obj.iix-1) * obj.dx
        obj.py = (obj.iiy-1) * obj.dy
        obj.pz = (obj.iiz-1) * obj.dz
        obj.hbar = hbar
        obj.dt = dt
        BuildSchroedinger(obj)
        obj
    end

end

function BuildSchroedinger(obj::ISF)
    nx=obj.resx; ny=obj.resy; nz=obj.resz;
    fac = -4*pi^2*obj.hbar;
    kx = (obj.iix .- 1 .- nx ./ 2) ./ (obj.sizex);
    ky = (obj.iiy .- 1 .- ny ./ 2) ./ (obj.sizey);
    kz = (obj.iiz .- 1 .- nz ./ 2) ./ (obj.sizez);
    lambda = fac .* (kx .^ 2 .+ ky .^ 2 .+ kz .^ 2);
    obj.mask = exp.(1.0im*lambda*obj.dt/2.);
end
"""
solves Schroedinger equation for dt time.
"""
function SchroedingerFlow(obj, psi1, psi2)
    psi1 = fftshift(fft(psi1)); psi2 = fftshift(fft(psi2));
    psi1 = psi1.*obj.mask;
    psi2 = psi2.*obj.mask;
    psi1 = ifft(fftshift(psi1)); psi2 = ifft(fftshift(psi2));
    psi1, psi2
end

"""
 For a 1-form v compute the corresponding vector field `v^sharp` as
 a staggered vector field living on edges
"""
function StaggeredSharp(obj,vx,vy,vz)
    ux = vx/obj.dx
    uy = vy/obj.dy
    uz = vz/obj.dz
    ux,uy,uz
end

function VelocityOneForm(obj, psi1, psi2, hbar=1.0)
    ixp = mod.(obj.ix, obj.resx) + 1;
    iyp = mod.(obj.iy, obj.resy) + 1;
    izp = mod.(obj.iz, obj.resz) + 1;
    vx = angle.(
        conj.(psi1).*view(psi1, ixp,:,:) .+
        conj.(psi2).*view(psi2, ixp,:,:)
    );
    vy = angle.(
        conj.(psi1).*view(psi1,:,iyp,:) .+
        conj.(psi2).*view(psi2,:,iyp,:)
    )
    vz = angle.(
        conj.(psi1).*view(psi1,:,:,izp) .+
        conj.(psi2).*view(psi2,:,:,izp)
    )
    vx = vx*hbar;
    vy = vy*hbar;
    vz = vz*hbar;
    vx,vy,vz
end

function PoissonSolve(obj, f)
    f = fft(f)
    sx = sin.(pi .* (obj.iix .- 1) ./ obj.resx) ./ obj.dx
    sy = sin.(pi .* (obj.iiy .- 1) ./ obj.resy) ./ obj.dy
    sz = sin.(pi .* (obj.iiz .- 1) ./ obj.resz) ./ obj.dz
    denom = sx.^2 + sy.^2 + sz.^2
    fac = -0.25./denom
    fac[1,1,1] = 0.0
    f = f .* fac
    ifft(f)
end

function GaugeTransform(psi1, psi2, q)
    eiq = exp.(1.0im .* q);
    psi1 = psi1 .* eiq
    psi2 = psi2 .* eiq
    psi1, psi2
end

function PressureProject(obj, psi1, psi2)
    vx,vy,vz = VelocityOneForm(obj, psi1, psi2)
    div = Div(obj, vx,vy,vz)
    q = -PoissonSolve(obj, div)
    x = GaugeTransform(psi1, psi2, q)
    x
end

function Normalize(psi1, psi2)
    psi_norm = sqrt.(abs.(psi1) .^ 2 .+ abs.(psi2) .^ 2)
    psi1 = psi1 ./ psi_norm
    psi2 = psi2 ./ psi_norm
    psi1, psi2
end

function Div(obj, vx, vy, vz)
    ixm = mod.(obj.ix-2, obj.resx) .+ 1
    iym = mod.(obj.iy-2, obj.resy) .+ 1
    izm = mod.(obj.iz-2, obj.resz) .+ 1
    f =     (vx .- view(vx, ixm,:,:)) ./ (obj.dx .^ 2)
    f = f .+ (vy .- view(vy, :,iym,:)) ./ (obj.dy .^ 2)
    f = f .+ (vz .- view(vz, :,:,izm)) ./ (obj.dz .^ 2)
    f
end

"""
 Particles - class of particle which can be advected by staggered velocity
 field on a TorusDEC grid, using RK4 method.
 Velocities are trilinearly interpolated.
"""
type Particles
    x::Vector{Float32}
    y::Vector{Float32}
    z::Vector{Float32} # array of positions
    length::Int
    active::Vector{Int}
end
Base.length(p::Particles) = p.length

function Base.append!(p::Particles, x, y, z)
    @assert length(x) + p.length < length(p.x)
    range = (p.length+1):(p.length+length(x))
    p.x[range] = x
    p.y[range] = y
    p.z[range] = z
    p.length   = (p.length+length(x))
    append!(p.active, range)
    nothing
end
"""
 advect particle positions using RK4 in a grid torus with
 staggered velocity vx,vy,vz, for dt period of time
"""
function StaggeredAdvect(p, torus, vx, vy, vz, dt)
    px, py, pz = p.x[p.active], p.y[p.active], p.z[p.active]
    k1x,k1y,k1z = StaggeredVelocity(
        px,py,pz,
        torus,vx,vy,vz
    )
    k2x,k2y,k2z = StaggeredVelocity(
        px .+ k1x .* dt ./ 2,py .+ k1y .* dt/2,pz .+ k1z .* dt ./ 2,
        torus,vx,vy,vz
    )
    k3x,k3y,k3z = StaggeredVelocity(
        px .+ k2x .* dt ./ 2, py .+ k2y .* dt ./ 2, pz .+ k2z .* dt ./2,
        torus, vx, vy, vz
    )
    k4x,k4y,k4z = StaggeredVelocity(
        px .+ k3x .* dt,py .+ k3y .* dt,pz .+ k3z .* dt,
        torus,vx,vy,vz
    )
    p.x[p.active] .= px .+ dt ./ 6 .* (k1x .+ 2 .* k2x .+ 2 .* k3x .+ k4x)
    p.y[p.active] .= py .+ dt ./ 6 .* (k1y .+ 2 .* k2y .+ 2 .* k3y .+ k4y)
    p.z[p.active] .= pz .+ dt ./ 6 .* (k1z .+ 2 .* k2z .+ 2 .* k3z .+ k4z)
end
"""
for removing particles
"""
function Keep(particle, ind)
    particle.x = particle.x[ind]
    particle.y = particle.y[ind]
    particle.z = particle.z[ind]
end

"""
 evaluates velocity at (px,py,pz) in the grid torus with staggered
 velocity vector field vx,vy,vz
"""
function StaggeredVelocity(pxf,pyf,pzf,torus,vx,vy,vz)
    px = mod.(pxf, torus.sizex)
    py = mod.(pyf, torus.sizey)
    pz = mod.(pzf, torus.sizez)

    ix = floor.(Int, px/torus.dx) .+ 1
    iy = floor.(Int, py/torus.dy) .+ 1
    iz = floor.(Int, pz/torus.dz) .+ 1
    ixp = mod.(ix,torus.resx) .+ 1
    iyp = mod.(iy,torus.resy) .+ 1
    izp = mod.(iz,torus.resz) .+ 1

    res = Base.RefValue((torus.resx,torus.resy,torus.resz))

    ind0 = sub2ind.(res, ix, iy, iz)
    indxp = sub2ind.(res, ixp, iy, iz)
    indyp = sub2ind.(res, ix, iyp, iz)
    indzp = sub2ind.(res, ix, iy, izp)
    indxpyp = sub2ind.(res, ixp, iyp, iz)
    indypzp = sub2ind.(res, ix, iyp, izp)
    indxpzp = sub2ind.(res, ixp, iy, izp)

    wx = px .- (ix .- 1) .* torus.dx
    wy = py .- (iy .- 1) .* torus.dy
    wz = pz .- (iz .- 1) .* torus.dz

    ux = ((1-wz).*((1-wy).*vx[ind0 ]+wy.*vx[indyp  ]) .+
            wz .*((1-wy).*vx[indzp]+wy.*vx[indypzp]))

    uy = ((1-wz).*((1-wx).*vy[ind0 ]+wx.*vy[indxp  ]) .+
            wz .*((1-wx).*vy[indzp]+wx.*vy[indxpzp]))

    uz = ((1-wy).*((1-wx).*vz[ind0 ]+wx.*vz[indxp  ]) .+
            wy .*((1-wx).*vz[indyp]+wx.*vz[indxpyp]))

    ux,uy,uz
end

end


function a2c_loss(π, 𝒫, 𝒟; info = Dict())
    new_probs = logpdf(π, 𝒟[:s], 𝒟[:a])
    p_loss = -mean(new_probs .* 𝒟[:advantage])
    e_loss = -mean(entropy(π, 𝒟[:s]))

    # Log useful information
    ignore() do
        info[:entropy] = -e_loss
        info[:kl] = mean(𝒟[:logprob] .- new_probs)
    end

    𝒫[:λp]*p_loss + 𝒫[:λe]*e_loss
end

function A2C(;π::ActorCritic,
              a_opt::NamedTuple=(;),
              c_opt::NamedTuple=(;),
              log::NamedTuple=(;),
              λp::Float32=1f0,
              λe::Float32=0.1f0,
              required_columns=[],
              kwargs...)

    OnPolicySolver(;agent=PolicyParams(π),
                    𝒫=(λp=λp, λe=λe),
                    log=LoggerParams(;dir = "log/a2c", log...),
                    a_opt=TrainingParams(;loss=a2c_loss, early_stopping = (infos) -> (infos[end][:kl] > 0.015), name = "actor_", a_opt...),
                    c_opt=TrainingParams(;loss=(π, 𝒫, D; kwargs...) -> Flux.mse(value(π, D[:s]), D[:return]), name = "critic_", c_opt...),
                    post_sample_callback=(𝒟; kwargs...) -> (𝒟[:advantage] .= whiten(𝒟[:advantage])),
                    required_columns = unique([required_columns..., :return, :logprob, :advantage]),
                    kwargs...)
end


## Load transfer scheme
#==========================================================================================#

# Sum adjacent values
adjacent_adder(x1, x2) = @views [ [ x1[1] ]; x1[2:end] .+ x2[1:end-1]; [ x2[end] ] ]

# Compute moments for each section with local beam nodes as origins
section_moment(vlm_ac, fem_pts, half_vlm_force) = @. (vlm_ac - fem_pts) × half_vlm_force
section_moments(vlm_acs, fem_pts, half_vlm_forces) = sum(x -> section_moment(x[1], fem_pts, x[2]), zip(eachrow(vlm_acs), eachrow(half_vlm_forces)))

function compute_loads(vlm_acs, vlm_forces, fem_mesh)
    # Forces
    sec_forces   = vec(sum(vlm_forces, dims = 1)) / 2
    beam_forces  = adjacent_adder(sec_forces / 2, sec_forces / 2)

    # Moments
    M_ins        = @views section_moments(vlm_acs, fem_mesh[1:end-1], vlm_forces / 2)
    M_outs       = @views section_moments(vlm_acs, fem_mesh[2:end],   vlm_forces / 2)
    beam_moments = adjacent_adder(M_ins, M_outs)

    # Concatenate forces and moments into loads array
    [ combinedimsview(beam_forces); combinedimsview(beam_moments) ]
end

# Generate load vector for FEM system
fem_load_vector(vlm_acs, vlm_forces, fem_mesh) = [ zeros(6); vec(compute_loads(vlm_acs, vlm_forces, fem_mesh)) ]

## Displacement transfer scheme
#==========================================================================================#

# Build cross product as an antisymmetric bilinear form.
# (This is just a fancy way of saying antisymmetric matrix in 3 dimensions. The cross product, more generally, is actually the exterior product in higher dimensions.)
rotation_matrix(Ωx, Ωy, Ωz) = @SMatrix [  0  -Ωz  Ωy ;
                                          Ωz  0  -Ωx ;
                                         -Ωy  Ωx  0  ]

rotation_matrix(θs) = rotation_matrix.(θs[1,:], θs[2,:], θs[3,:])

# Transfer states by summing the displacements including rotations.
transfer_displacement(xyz, dx, rot, r) = xyz + dx + rot * (xyz - r)
transfer_displacements(dxs, Ts, chord_mesh, fem_mesh) = permutedims(combinedimsview(map(xyz -> transfer_displacement.(xyz, dxs, Ts, fem_mesh), eachrow(chord_mesh))))

mesh_translation(δs) = @views SVector.(δs[1,:], δs[2,:], δs[3,:])
mesh_rotation(δs)    = @views rotation_matrix(δs[4:6,:])

# Make new horseshoes
function new_horseshoes(dxs, Ts, chord_mesh, camber_mesh, fem_mesh)
    new_chord_mesh = transfer_displacements(dxs, Ts, chord_mesh, fem_mesh)
    new_camber_mesh = transfer_displacements(dxs, Ts, camber_mesh, fem_mesh)
    Horseshoe.(make_panels(new_chord_mesh), normal_vector.(make_panels(new_camber_mesh)))
end


using SequentialLOB
using StylizedFacts
using Plots

# Configuration Arguments
num_paths = 1
M = 400
T = 2299
p₀ = 238.75
L = 200
Δx = L / M

# Free-Parameters
λ = 1.0
D = 1.0
Δt = (Δx^2) / (2.0 * D)
σ = 1.5
μ = 1.0
nu = 0.5
α_slob = 100.0
α_lob = 0.0
println(Δx)
println(Δt)
# LOB Model

lob_model = SLOB(num_paths,
T, p₀, M, L, D, σ, nu, α_lob, SourceTerm(λ, μ))

sample_price_paths = lob_model(7136)
plt = plot(
    1:T + 1, sample_price_paths[:,1], legend=false,
    xlab="Time", ylab="Mid-Price",
)
for i in 2:num_paths
    plot!(plt, 1:T + 1, sample_price_paths[:,i], legend=false)
end
display(plt)
savefig(plt, "$(dirname(@__FILE__))/lob_model_init_pp.pdf")

# SLOB Model

slob_model = SLOB(num_paths,
T, p₀, M, L, D, σ, nu, α_slob, SourceTerm(λ, μ))

sample_price_paths = slob_model(19822)
plt = plot(
    1:T + 1, sample_price_paths[:,1], legend=false,
    xlab="Time", ylab="Mid-Price",
)
for i in 2:num_paths
    plot!(plt, 1:T + 1, sample_price_paths[:,i], legend=false)
end
display(plt)
savefig(plt, "$(dirname(@__FILE__))/slob_model_init_pp.pdf")

# Compare LOB to SLOB

lob_mid_prices = lob_model(7136)
lob_stylized_facts = StylizedFactsPlot(lob_mid_prices[:,1])
plt = StylizedFacts.plot_log_returns(lob_stylized_facts, "")
display(plt)
savefig(plt, "$(dirname(@__FILE__))/lob_model_init_log_rets.pdf")

slob_mid_prices = slob_model(19822)
slob_stylized_facts = StylizedFactsPlot(slob_mid_prices[:,1])
plt = StylizedFacts.plot_log_returns(slob_stylized_facts, "")
display(plt)
savefig(plt, "$(dirname(@__FILE__))/slob_model_init_log_rets.pdf")