# basic Pose2 convolution test

using RoME
using Test

##

@testset "test basic Pose2 convolution based on MvNormal" begin
##

fg = initfg()

addVariable!(fg, :x0, Pose2)
u0 = ProductRepr([0;0.0], [1 0; 0 1.])
M = getManifold(Pose2) # TODO add better dispatch to simplify
x0 = [AMP.makePointFromCoords(M,0.01*randn(3),u0) for _ in 1:100];
X0 = manikde!(M, x0)
initManual!(fg, :x0, X0)

addVariable!(fg, :x1, Pose2)
addFactor!(fg, [:x0;:x1], Pose2Pose2(MvNormal([10;0;pi], 0.1*diagm([1;1;1]))), graphinit=false)

## now do a convolution

X1_ = approxConvBelief(fg, :x0x1f1, :x1)
x1_ = getPoints(X1_) .|> x->AMP.makeCoordsFromPoint(M,x)

μ_x1_ = mean(X1_)
Σ_x1_ =  cov(M, getPoints(X1_))

@test isapprox( μ_x1_.parts[1], [10; 0], atol=0.2)
@test isapprox( μ_x1_.parts[2], [-1 0; 0 -1], atol=0.2)

@test isapprox( Σ_x1_, [0.15 0 0; 0 0.15 0; 0 0 0.15], atol=0.4)

# check X1_ rotation's coordinates is spread between +pi and -pi

ψ_ = (x->x[3]).(x1_)

# bi-modality should occur since ±pi is the same value on-manifold
@test 20 < sum(2 .< ψ_)
@test 20 < sum(ψ_ .< -2)
# numerical check
@test sum(-2 .< ψ_ .< 2) < 1
# on-manifold check
@test sum(3.15 .< abs.(ψ_)) < 1

## now test the deconvolution

pts, meas = approxDeconv(fg, :x0x1f1)
X12_ = manikde!(M, pts)

# check that deconv is good

@test mmd(M, pts, meas) < 0.001

##
end


"""
    continuous model
"""
abstract type Continuous <: Integration end

"""
    explicit midpoint integrator
"""
struct Midpoint <: Integration end

function fd(model::Model{Midpoint, FixedTime}, x, u, w, h, t)
    x + h * f(model, x + 0.5 * h * f(model, x, u, w), u, w)
end

function fd(model::Model{Midpoint, FreeTime}, x, u, w, h, t)
    h = u[end]
    x + h * f(model, x + 0.5 * h * f(model, x, u, w), u, w)
end

"""
    implicit midpoint integrator
"""
function fd(model::Model{Midpoint, FixedTime}, x⁺, x, u, w, h, t)
    x⁺ - (x + h * f(model, 0.5 * (x + x⁺), u, w))
end

function fd(model::Model{Midpoint, FreeTime}, x⁺, x, u, w, h, t)
    h = u[end]
    x⁺ - (x + h * f(model, 0.5 * (x + x⁺), u, w))
end

"""
    explicit RK3 integrator
"""
abstract type RK3 <: Integration end

function fd(model::Model{RK3, FixedTime}, z, u, w, h, t)
    # k1 = k2 = k3 = zero(z)
    k1 = h * f(model, z, u, w)
    k2 = h * f(model, z + 0.5 * k1, u, w)
    k3 = h * f(model, z - k1 + 2.0 * k2, u, w)
    z + (k1 + 4.0 * k2 + k3) / 6.0
end

abstract type RK4 <: Integration end

function fd(model::Model{RK4, FixedTime}, z, u, w, h, t)
	k1 = f(model, z, u, w) * h
	k2 = f(model, z + 0.5 * k1, u, w) * h
	k3 = f(model, z + 0.5 * k2, u, w) * h
	k4 = f(model, z + k3, u, w) * h
	z + (k1 + 2.0 * k2 + 2.0 * k3 + k4) / 6.0
end

"""
    discrete explicit dynamics
"""
abstract type Discrete <: Integration end


using Plots

function main()
    x = 0:0.1:6
    y1 = sin.(x)
    y2 = cos.(x)

    plot(
        x, y1,
        title = "sin & cos",
        label = "sin",
        xlabel = "x",
        ylabel = "y"
    )
    plot!(
        x, y2,
        line = :dash,
        label = "cos"
    )
end

main()


using OrdinaryDiffEq
using Trixi

###############################################################################
# semidiscretization of the shallow water equations

equations = ShallowWaterEquations2D(gravity_constant=9.81)

initial_condition = initial_condition_convergence_test

volume_flux = (flux_wintermeyer_etal, flux_nonconservative_wintermeyer_etal)
surface_flux = (flux_lax_friedrichs, flux_nonconservative_fjordholm_etal)
dg = DGMulti(polydeg=3, element_type = Quad(), approximation_type = SBP(),
             surface_integral = SurfaceIntegralWeakForm(surface_flux),
             volume_integral = VolumeIntegralFluxDifferencing(volume_flux))

cells_per_dimension = 8
vertex_coordinates, EToV = StartUpDG.uniform_mesh(dg.basis.elementType, cells_per_dimension)
vertex_coordinates = map(x -> sqrt(2) .* 0.5 .* (1 .+ x), vertex_coordinates) # map domain to [0, sqrt(2)]^2
mesh = VertexMappedMesh(vertex_coordinates, EToV, dg, is_periodic=(true, true))

semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, dg;
                                    source_terms=source_terms_convergence_test)

###############################################################################
# ODE solvers, callbacks etc.

tspan = (0.0, 0.4)
ode = semidiscretize(semi, tspan)

summary_callback = SummaryCallback()

analysis_interval = 100
analysis_callback = AnalysisCallback(semi, interval=analysis_interval, uEltype=real(dg))
alive_callback = AliveCallback(analysis_interval=analysis_interval)

callbacks = CallbackSet(summary_callback,
                        analysis_callback,
                        alive_callback)

###############################################################################
# run the simulation

sol = solve(ode, RDPK3SpFSAL49(), abstol=1.0e-7, reltol=1.0e-7,
            save_everystep=false, callback=callbacks);

summary_callback() # print the timer summary


############################## Lennard Jones ###################################
struct Morse <: EmpiricalPotential
    D
    α
    σ
    rcutoff
    species :: AbstractVector
end

get_parameters(m::Morse) = Parameter{ (:D,:α, :σ) }((m.D, m.α, m.σ))
set_parameters(p::Parameter{(:D,:α, :σ)}, m::Morse) = Morse(p.D, p.α, p.σ, m.rcutoff, m.species)

deserialize_parameters(p::Parameter{(:D,:α, :σ)}, m::Morse) = [p.ϵ, p.σ]
serialize_parameters(p::Vector, m::Morse) = Parameter{(:ϵ, :σ)}( (p[1], p[2]) )

get_hyperparameters(m::Morse) = Parameter{(:rcutoff,)}( (m.rcutoff,) )
set_hyperparameters(p::Parameter{(:rcutoff,)}, m::Morse) = Morse(m.D, m.α, m.σ, p.rcutoff, m.species)

deserialize_hyperparameters(p::Parameter{(:rcutoff,)}, m::Morse) = [p.rcutoff]
serialize_hyperparameters(p::Vector, m::Morse) = Parameter{(:rcutoff, )}( (p[1],) )

############################# Energies ##########################################

function potential_energy(R::AbstractFloat, p::Morse)
    p.D * (1.0 - exp(-p.α*(R - p.σ)) )^2
end

############################### Forces ##########################################

function force(R::AbstractFloat, r::SVector{3,<:AbstractFloat}, p::Morse)
    SVector( 2 * p.D * p.α * (1.0 - exp(-p.α*(R - p.σ)) ) * exp(-p.α*(R - p.σ)) .* r ./ R)
end


using Stheno: project, pairwise, lhsfinite, unbox, Xt_A_X, Xt_A_Y

@testset "project" begin

let
    rng, N, N′, NZ, NZ′ = MersenneTwister(123456), 10, 11, 5, 3
    X, X′, Z, Z′ = randn(rng, N), randn(rng, N′), randn(rng, NZ), randn(rng, NZ′)

    # Construct projection with non-block matrices.
    f = GP(ConstantMean(3.0), EQ(), GPC())
    ϕ = lhsfinite(EQ(), Z)
    f_pr = project(ϕ, f(Z), sin)

    @test mean_vec(f_pr(X)) == pairwise(EQ(), X, Z) * mean_vec(f(Z)) + sin.(X)
    @test cov(f_pr(X)) ≈ pairwise(EQ(), X, Z) * cov(f(Z)) * pairwise(EQ(), X, Z)'
    @test maximum(abs.(xcov(f_pr(X), f(X′)) - pairwise(EQ(), X, Z) * xcov(f(Z), f(X′)))) < eps()
    @test maximum(abs.(xcov(f(X), f_pr(X′)) - xcov(f(X), f(Z)) * pairwise(EQ(), Z, X′))) < eps()

    # Construct projection with BlockGP as the thing over which we're projecting.
    Zb = BlockData([Z, Z′])
    ϕb = lhsfinite(BlockCrossKernel(reshape([EQ(), EQ()], :, 1)), Zb)
    f_pr_b = project(ϕb, f(Zb), cos)

    @test mean_vec(f_pr_b(X)) == pairwise(EQ(), Zb, X)' * mean_vec(f(Zb)) .+ cos.(X)
    ϕZX, ϕZX′, ϕZ = pairwise(EQ(), Zb, X), pairwise(EQ(), Zb, X′), cov(f(Zb))
    @test cov(f_pr_b(X)) == Xt_A_X(ϕZ, ϕZX)
    @test xcov(f_pr_b(X), f_pr_b(X′)) == Xt_A_Y(ϕZX, ϕZ, ϕZX′)
    @test xcov(f_pr_b(X), f(X′)) == ϕZX' * xcov(f(Zb), f(X′))
    @test xcov(f(X), f_pr_b(X′)) == xcov(f(X), f(Zb)) * ϕZX′

    # Check that Blocked version is project is consistent with dense.
    Zb2 = BlockData([Z[1:3], Z[4:5]])
    ϕb2 = lhsfinite(BlockCrossKernel(reshape([EQ(), EQ()], :, 1)), Zb2)
    f_pr_b2 = project(ϕb2, f(Zb2), sin)

    @test mean_vec(f_pr_b2(X)) ≈ mean_vec(f_pr(X))
    @test cov(f_pr_b2(X)) ≈ cov(f_pr(X))
    @test xcov(f_pr_b2(X), f_pr_b2(X′)) ≈ xcov(f_pr(X), f_pr(X′))
    @test xcov(f_pr_b2(X), f(X′)) ≈ xcov(f_pr(X), f(X′))
    @test xcov(f(X), f_pr_b2(X′)) ≈ xcov(f(X), f_pr(X′))
end

end


using Distances

function get_3dtorus_distance_from_cols(X)

	dismat 		=	pairwise(Euclidean(), X)

	for i = [-1, 0, 1]
		for j = [-1, 0, 1]
			for k = [-1, 0, 1]
				Y 				=	X .+ [i,j,k]
				dismat_new		=	pairwise(Euclidean(), Y)
				dismat 			=	min.(dismat, dismat_new)
			end
		end
	end

	return dismat

end


using Distributions
import Statistics, Plots #Warning: unrecommened - to the personal standard- Reason: has a huge set of other packages- once initial deguggin finished -Remove
import AbstractPlotting.MakieLayout
#--- Bernoulli

"""
Returns a Bernoulli Probability Density Function (PDF) that is;

1. Discrete process
2. I.I.D

"""
function pdf(ntimes)

  return Bernoulli(ntimes)
end

function cdf(pdf, x)
  return sum(pdf(x)) #discrete sum
end
#function #later

function sign(value = 0.5, comparisonFunction = rand(), trueValue = -1, falseValue = 1)

  multiplier += ifelse(comparisonFunction < value, trueValue, falseValue)
  return multiplier
end

"""
A function useful for benchmarking julia's statistical functions

  tested
"""
function _globalRandomWalker()
  @time begin
    pos = 0
    count = 0
    _mean = 0
    Mean = 0
    data = []
    numsteps = 10^4
    numwalkers = 10^4
    final_square_positions = Int[]

    for i in 1:numwalkers
      for j in 1:numsteps
        pos += ifelse(rand() < 0.5, -1, +1)
        count += 1
      end
      push!(final_square_positions, pos^2)
      push!(data, pos)
    end
    println("Total Displacement " * string(pos))
    _mean = pos / count #adding  a naive arithmetic mean
    Mean = Statistics.mean(final_square_positions)
    println("Mean Displacement " * string(_mean))
    println("Mean Squared Displacement " * string(Mean))

    return pos, _mean, Mean, data
  end
end

pos, _mean, Mean, data = _globalRandomWalker()
#root-squared mean
trueMean = Statistics.mean(data)
n = size(data)[1]
rsMean = sqrt(Mean)
#=Q. is mean ≈ rsMean? if not , is it dispered by how much
ended up with 3 means:
1.trueMean (based on Statistics.jl)
2.rsMean (square root of squared values) [hugely large , Disqualified] - say, what if this is the only one that truely expresses the mean?
3._mean (non-weighted, naive mean )
=#
error2 = max(_mean, rsMean) - min(_mean, rsMean) # rsMean did not work  here very smoothly
error = max(_mean, trueMean) - min(_mean, trueMean)
#=by only comparing the 2 results above, only a correct Mean would be close to the true one i.e. calculated naive  _mean =#
#then we can visualize error  with y

x = 1:n; #preferable to use linspace
y = data; # These are the plotting data
Plots.plot(x, y)


using HypothesisTests, UncertainData, Distributions

ts_vals = [UncertainValue(Normal, rand(Uniform(-10, 10)), rand()) for i = 1:10]
ts = UncertainDataset(xs)
#
# @show size(LjungBoxTest(ts))
# @show size(LjungBoxTest(ts, 10))
# @show size(BoxPierceTest(ts))
# @show size(BoxPiercexTest(ts, 10))
#
# @test pvalue(LjungBoxTest(ts)) isa Float64
# @test pvalue.(LjungBoxTest(ts, 10)) isa Vector{Float64}
#
# @test pvalue(BoxPierceTest(ts)) isa Float64
# @test pvalue.(BoxPierceTest(ts, 10)) isa Vector{Float64}


using Sigma
using Base.Test

# Constrain an array of integers to be symmetric
function issymmetric(x)
  n = length(x)
  middle = div(n,2)+1
  x[1:middle-1] == x[length(x):-1:middle]
end

Xs = mvuniform(0,25,100)
sample = rand(Xs, issymmetric(Xs))


# simple example of a harmonic power flow execution
using HarmonicPowerFlow
using StatsPlots
using LinearAlgebra

harmonics = [h for h in 1:2:51]
# settings
coupled = false
settings1 = init_settings(coupled, harmonics)

# import nodes and lines from csv files
net3 = init_power_grid(
    import_nodes_from_csv("examples\\multiple_pv\\net3_buses.csv"), import_lines_from_csv("examples\\multiple_pv\\net3_lines.csv"),
    settings1)

# run harmonic power flow
u, err_h_final, n_iter_h = hpf(net3, settings1)

THD = HarmonicPowerFlow.THD(u)

# show barplot of harmonics at bus 4
HarmonicPowerFlow.barplot_THD(u, 4, h_max= 41)

# Analyze NE parameters
NE = HarmonicPowerFlow.import_Norton_Equivalents(net3.nodes, settings1)
l = @layout [a{0.66w}  b{1h}]
if coupled
    p1 = heatmap(harmonics, harmonics, abs.(NE["SMPS"][2]), ticks = harmonics, title = "Y_Norton Matrix (coupled)", yflip=true)
    p2 = heatmap(transpose(abs.(NE["SMPS"][1])), aspect_ratio=1, yflip=true, title = "I_Norton", yticks = nothing, border = :none)
    plot(p1, p2, layout = l)
else
    Y_N = heatmap(harmonics, harmonics, diagm(vec(abs.(NE["SMPS"][2]))), ticks = harmonics, title = "Y_Norton Vector (uncoupled)", yflip=true)
end


# # Modelling the establishment potential of _Drosophila suzukii_
#
# In this example we will calculate the expected population growth rates of
# Spotted Wing Drosophila (SWD) _D. suzukii_, for each month of the year on a 9km grid
# accross North America.
#
# We'll experiment with running the model with a number of different datasets.
# GrowthMaps.jl, via GeoData.jl, faciliates using a wide range of input sources.

# First we will use aggregated data in tiff files, then aggregate them to in-memory
# files to run the inferface. Finally if you can download it, we'll use the
# (huge) SMAP dataset, which is in a custom HDF5 format.
#
# Load some required packages
using GeoData, Plots, Dates
using Unitful, UnitfulRecipes
using GrowthMaps
# Set the path for docs materials from GrowthMaps.jl
basedir = realpath(joinpath(dirname(pathof(GrowthMaps)), "../docs"))

# ## Define model components

# First we'll define the growth model using `SchoolfieldIntrinsicGrowth`,
# based on Schoolfield (1981).

# ### Growth Model
#
# These are parameters with units.
# To use them in `fit!` and in the interface, we
# use ModelParameters.jl `Param` wrappers
p = Param(3e-1)
ΔH_A = Param(3e4; units=u"cal/mol")
ΔH_L = Param(-1e5; units=u"cal/mol")
ΔH_H = Param(3e5; units=u"cal/mol")
Thalf_L = Param(2e2; units=u"K")
Thalf_H = Param(3e2; units=u"K")
T_ref = u"K"(25.0u"°C")

# Use the parameters in the SchoolfieldIntrinsicGrowth model
growthmodel = SchoolfieldIntrinsicGrowth(p, ΔH_A, ΔH_L, Thalf_L, ΔH_H, Thalf_H, T_ref)

# Now connect the model with a layer from the dataset, and give the layer units
growth = Layer(:surface_temp, u"K", growthmodel)

# Load lab observations
#
# If these are only estimated parameters, we can also fit the model to a a dataset
# of growth rate and temperature.
#
# First extract our independent and dependent variables from the example CSV:
using DataFrames, CSV
obscsv = CSV.File(joinpath(basedir, "swd_ecophys_data.csv"); select=[:x_value, :y_value, :y_key])
obsdata = DataFrame(obscsv) |> df -> filter(d -> d.y_key == "r_m", df) |> dropmissing

# Then extract the required data colummns, and convert temperature values from
# unitless Celcius to explit Kelvins, using Unitful.jl:
obsrate = obsdata.y_value
obstemp = obsdata.x_value .* u"°C" .|> u"K"
obs = collect(zip(obstemp, obsrate))

# Now we can fit the model. The `fitrate` function provides an easy way to fit the
# models in GrowthMaps or your own custom `RateModel`s, using the LsqFit.jl
# package:
model = Model(growth)
GrowthMaps.fit!(model, obs)

# Plot the fit against the data
#
# Define a custom temperature range
temprange = (270.0:0.1:310.0)u"K"
# Plot the rate
temp_plot = plot(temprange; label="fitted") do x
    GrowthMaps.rate(stripparams(model), x)
end
scatter!(temp_plot, obs; label="observed")

# Manual tweaking
#
# We can also try tweaking the fitting the model manually in a user interface.
# Model components are immutable (for performance reasons), so we wrap the model
# in a mutable wraper so we can use the results.
# We parametrise the model over the same temperature range that we are plotting,
# using the :surface_temp key that the model requires:
tempdata = (surface_temp=temprange,)
fit_interface = manualfit!(model, tempdata; observations=obs)

# To use the it in a desktop app, use Blink.jl:
# using Blink
# w = Blink.Window()
# body!(w, fit_interface)

# Note that `manualfit!` will also work with multiple model components
# that use the same source data, like `Model(growth, heatstress, coldstress)`.

# ## Load spatial data
#
# Later we can use real SMAP datasets using GeoData.jl SMAPseries loader.
# But downloading the dataset takes too long for an example.
# Instead we will download and unzip some lower resolution monthly
# data to use in the model:
using Downloads
dataurl = "https://media.githubusercontent.com/media/cesaraustralia/GrowthMaps.jl/data/SMAP_aggregated27km.zip"
zipfilepath = joinpath(basedir, "SMAP_aggregated27km.zip")
unzippedfolder = joinpath(basedir, "SMAP_aggregated27km")
isfile(zipfilepath) || Downloads.download(dataurl, zipfilepath)
run(`unzip -o $zipfilepath -d $basedir`);

# Get the paths for to all the wilting and surface-temp files
filenames = readdir(unzippedfolder)
wilting_filenames = filter(fn -> occursin(r"land_fraction_wilting", fn), filenames)
surface_temp_filenames = filter(fn -> occursin(r"surface_temp", fn), filenames)
wilting_paths = joinpath.(Ref(unzippedfolder), wilting_filenames)
surface_temp_paths = map(p -> joinpath(unzippedfolder, p), surface_temp_filenames)
# Get the dates the `surface_temp` files using regex.
# Then use them to create the time dimension
date_format = DateFormat("yyyymmddTHHMMSS");
dates = DateTime.(replace.(surface_temp_paths, Ref(r".*_(\d+T\d+).tif" => s"\1")), Ref(date_format))

# Make a new time dimension
timedim = Ti(dates; mode=Sampled(span=Regular(Hour(3))))

# We know the `land_fraction_wilting` files are for the same dates.
#
# Now we have the files and date seies, we can put together a series of
# GeoData.jl stacks to load lazily from disk while running `mapgrowth`.
#
# Make a NamedTuple of Vectors of String paths to the files
layers = (land_fraction_wilting=wilting_paths, surface_temp=surface_temp_paths)

# Create the series
tiffseries = GeoSeries(layers, (timedim,); mappedcrs=EPSG(4326))
tiffseries = map(x -> view(x, Band(1)), tiffseries)

# We can plot a layer from a file at some date in the series:
tiffseries[Ti(Near(DateTime(2016, Feb)))] |> plot

# Set the time period to a month, and set the length of the subsample period to
# the available times over one day:
#
# ## Run a model over the spatial data
#
# Define a timespan range to run the model over:
tspan = DateTime(2016, Jan):Month(1):DateTime(2016, Dec)

# Then to start, we'll run a simple model that only calculates growth rate.
growth_output = mapgrowth(model;
    series=tiffseries, tspan=tspan,
)

# Then plot the results:
growth_output[Ti(1)] |> plot

# It doesn't really capture realistic population growth: there are no growth rates
# below zero. We need to add some stress models. Stress models model processes
# that produce negative deathrates in the population, as oposed to rate models,
# where the minimum growth rate is zero.
#
# Stress models have a threshold in K and mortality
# rate per degree K. `LowerStress` models stress below the given threshold, while
# `UpperStress` models stress induced above a threshold.
#
# We will define stress models for cold, heat and wilt stress.
# As SWD live on and around plants, we use the proportion of plants
# wilting as an indicater of stress induced by lack of moisture.
coldthresh = Param(ustrip(u"K", -10.0f0u"°C"); units=u"K", bounds=(240, 290))  # Stephens2015
coldmort = Param(-log(1.23f0); units=u"K"^-1, bounds=(0, 0.4)) # Stephens2015
coldstress = Layer(:surface_temp, u"K", LowerStress(coldthresh, coldmort))

heatthresh = Param(ustrip(u"K"(30.0u"°C")); units=u"K", bounds=(280, 330)) # Kimura2004
heatmort = Param(-log(1.15); units= u"K"^-1, bounds=(0, 0.4))
heatstress = Layer(:surface_temp, u"K", UpperStress(heatthresh, heatmort))

wiltthresh = Param(0.5; bounds=(0, 1)) # default?
wiltmort = Param(-log(1.1); bounds=(0, 0.4));
wiltstress = Layer(:land_fraction_wilting, UpperStress(wiltthresh, wiltmort));

# Missing the aggregated data for this!
# wetthresh = 0.8f0
# wetmort = -10.0f0
# wetstress = Layer(:sm_surface_wetness, WetStress(wetthresh, wetmort));

# ## Analyse the stressors
#
# ## Heat stress
heat_output = mapgrowth(heatstress;
    series=tiffseries, tspan=tspan,
)
plot(heat_output[Ti(1)])

# ## Cold stress
cold_output = mapgrowth(coldstress;
    series=tiffseries, tspan=tspan,
)
plot(cold_output[Ti(1)])

# ## Wilting stress
wilt_output = mapgrowth(wiltstress;
    series=tiffseries, tspan=tspan,
)
plot(wilt_output[Ti(1)])

# ## Combine the stressors
stress_output = mapgrowth(coldstress, heatstress, wiltstress;
    series=tiffseries, tspan=tspan,
)
plot(stress_output[Ti(1)])

plot(stress_output[Ti(1)]; clims=(-1, 0))

# To build a more complex model, we can chain components together
# in a tuple, and again, run and plot them:
model_output = mapgrowth(growth, coldstress, heatstress, wiltstress;
    series=tiffseries, tspan=tspan,
)

# ## Show just rates above zero
#
# For January:
plot(model_output[Ti(Jan)]; color=:oleron, clims=(-0.3, 0.3))

# For July:
plot(model_output[Ti(July)]; color=:oleron, clims=(-0.3, 0.3))

# ## Take the mean growth rate
using Statistics
mean_growth = mean(model_output; dims=Ti)
plot(mean_growth; color=:oleron, clims=(-0.3, 0.3))

# ## Compare with observation data
#
# To compare out simulation with observations data,
# we'll first load them from a CSV file:
csvurl = "https://raw.githubusercontent.com/cesaraustralia/GrowthMaps.jl/data/Oersted_occurrence.csv"
csvfilename = joinpath(basedir, "Oersted_occurrence.csv")
isfile(csvfilename) || download(csvurl, csvfilename)

obs = CSV.File(csvfilename)
occurrence = collect(zip(obs.Longitude, obs.Latitude))

# This means positive and negative growth-rates are clear
growth_plot = plot(mean_growth; color=:oleron, clims=(-0.3, 0.3))
# Then scatter them on a map:
scatter!(growth_plot, occurrence;
     label="obs", legend=:none, fillalpha=0,
     markeralpha=0.4,
     markersize=2.0, markercolor=:red, markershape=:cross,
)

# ## Save the results
#
# Write the output as a NetCDF file
write("growthrates.nc", model_output)
# And check it
GeoArray("growthrates.nc")

# ## Parametrising models using interactive maps

# If you need to adjust the model based on the distribution, this
# can be done live in the interface, as with the manual fit.
#
# But parametrising maps on large datasets is processor intensive, which inhibits
# interactive fedback. To reduce processing, we can aggregate the spatial data to
# a more manageable size.
#
# You can experiment with the `agg` size to compromise between quality and render time.
# Large values will look pixelated but will run fast.
#
# `Center()` simply takes the central cell. `Statistics.mean` would take the mean value.
agg = 8
aggseries = GeoData.aggregate(Center(), read(tiffseries), (X(agg), Y(agg)))

# Then fit the other components. `throttle` will dictate how fast the interface
# updates. Make it larger on a slow machine, smaller on a faster one.
# The `window` argument lets us select a window of the output to view, using
# DimensionalData.jl/GeoData.jl `Dimension`s and `Selector`s. Here we only plot
# the first time: but you can select any/multiple, and select them with
# `Near(DateTime(2016,2))` or similar.
whole_year = DateTime(2016, 1):Year(1):DateTime(2016, 12)
stress_model = Model((coldstress, heatstress, wiltstress))
mapfit_interface = mapfit!(model, (series=aggseries, tspan=whole_year);
    occurrence=occurrence,
    throttle=0.1,
    colorbar=true,
    clims=(-2, 0),
    markershape=:cross,
    markercolor=:red,
    markeropacity=0.4,
    window=(Ti(1),),
)

# In atom, this will show the interface in the plot pane:
display(mapfit_interface)

# To use the it in a desktop app, use Blink.jl:
## using Blink
## w = Blink.Window()
## body!(w, mapfit_interface)

# And get the updated model components from the model:
# wiltstress, coldstress, heatstress = parent(stress_model)

# Now we will put together decent population growth maps
# using the higher resolutions data, and a monthly tiestep:
final_output = mapgrowth(growth, wiltstress, coldstress, heatstress;
    series=tiffseries,
    tspan=tspan,
);
plot(final_output[Ti(1:3:12)]; axis=false)

# ## Save the results
#
# Write the output as a NetCDF file
write("growthrates.nc", final_output)

# We can load it again with:
A = GeoArray("growthrates.nc")
plot(A[Ti(1)])

# Use the real SMAP dataset

# Using aggregated data loses some of the impact of the stress
# models, which respond to extremes, not averages. If you need to use
# the model for a serious application, run it on the real SMAP dataset.
#
# Unfortunately we can't just download the data for you with a script,
# as you need to log in to an account to have access.
#
# But GeoData.jl has a `SMAPseries()` constructor that will automate the
# whole process of loading SMAP HDF5 files from a folder once you have
# the data.
#
# We can also reduce the numer of days processed to 8 each month (or whatever)
# using the `Where` selector from DimensionalData.jl.

## using HDF5

# Set this to your own SMAP data folder
## smappath = "/home/raf/Storage/Data/SMAP/SMAP_L4_SM_gph_v4"

# Choose the dayes of month to use
## days = (1, 4, 8, 12, 16, 20, 23, 27) # 8 days per month

#days = 1:31 # all days
# Use the `Where` selector to choose dates in the time index by day of month
## smapseries = SMAPseries(smappath)[Ti(Where(d -> dayofmonth(d) in days))]

# Run the model:
## @time output = mapgrowth(model, wiltstress, coldstress, heatstress;
##    series=smapseries, tspan=tspan,
## )
## plot(output[Ti(1:3:12)]; axis=false)

# And save a netcdf:
## write(joinpath(basedir, "growthrates.nc"), output)


cue() = CressieRead(1)
hd()  = CressieRead(-1/2)

################################################################################
## Cressie Read Divergence
################################################################################

#=---------------
Evaluate
---------------=#
function evaluate(div::CressieRead, a::T, b::T) where T <: AbstractFloat
    α = div.α
    u = a/b
    if u > 0
        u = ( (u^(1+α)-1)/(α*(α+1)) - u/α + 1/α )*b
    elseif u == 0
        u = b/(1+α)
    else
        u = oftype(a, Inf)
    end
    u
end

function evaluate(div::CressieRead, a::AbstractVector{T}) where T <: AbstractFloat
    α = div.α
    r = zero(T)
    n = length(a)::Int64
    @inbounds for i = 1:n
        u = a[i]
        r += evaluate(div, u, one(T))
    end
    return r
end

function evaluate(div::CressieRead, a::AbstractVector{T}, b::AbstractVector{T}) where T <: AbstractFloat
    if length(a) != length(b)
        throw(DimensionMismatch("first array has length $(length(a)) which does not match the length of the second, $(length(b))."))
    end
    r = zero(T)
    @inbounds for i = eachindex(a, b)
        ai = a[i]
        bi = b[i]
        r += evaluate(div, ai, bi)
    end
    return r
end

#=---------------
gradient
---------------=#
function gradient(div::CressieRead, a::T, b::T) where T <: AbstractFloat
    α = div.α
    if a >= 0 && b > 0
        u = ((a/b)^α-1.0)/α
    elseif a == 0 && b == 0
        u = zero(T)
    else
        u = oftype(a, Inf)
    end
    return u
end

function gradient(div::CressieRead, a::T) where T <: AbstractFloat
    return gradient(div, a, one(T))
end

#=---------------
hessian
---------------=#
function hessian(div::CressieRead, a::T, b::T) where T <: AbstractFloat
    α    = div.α
    if a > 0 && b > 0
        u = (a/b)^α/a
    elseif a == 0 && b > 0
        if α >= 0
            u = zero(T)
        else
            u = oftype(a, Inf)
        end
    elseif a == 0 && b == 0
        u = zero(T)
    elseif a > 0 && b == 0
        u = oftype(a, Inf)
    end
    return u
end

function hessian(div::CressieRead, a::T) where T <: AbstractFloat
    return hessian(div, a, one(T))
end


struct ReidIdealParam <: EoSParam
    a::SingleParam{Float64}
    b::SingleParam{Float64}
    c::SingleParam{Float64}
    d::SingleParam{Float64}
end

abstract type ReidIdealModel <: IdealModel end
@newmodelsimple ReidIdeal ReidIdealModel ReidIdealParam

export ReidIdeal
function ReidIdeal(components::Array{String,1}; userlocations::Array{String,1}=String[], verbose=false)
    params = getparams(components, ["ideal/ReidIdeal.csv"]; userlocations=userlocations, verbose=verbose)
    a = params["a"]
    b = params["b"]
    c = params["c"]
    d = params["d"]
    packagedparams = ReidIdealParam(a, b, c, d)
    references = String[] #  Fill this up.
    return ReidIdeal(packagedparams; references=references)
end

function a_ideal(model::ReidIdealModel, V, T, z)
    x = z/sum(z)
    a = model.params.a.values
    b = model.params.b.values
    c = model.params.c.values
    d = model.params.d.values
    polycoeff = [a, b, c, d]
    return 1/sum(x[i]*(log(z[i]/V) + 1/(R̄*T)*(sum(polycoeff[k][i]/k*(T^k-298^k) for k in 1:4)) -
        1/R̄*((a[i]-R̄)*log(T/298)+sum(polycoeff[k][i]/(k-1)*(T^(k-1)-298^(k-1)) for k in 2:4))) for i in @comps)
end


module FastModulo
export Mod,fastmod,fastmod!,divtest

struct Mod
    modulo::UInt32
    modulo_inv::UInt64
    function Mod(d::UInt32)
        cmod::UInt64 = trunc(UInt64,typemax(UInt64) / d) + 1
        return new(d,cmod)
    end
    function Mod(d::Int)
        ud = convert(UInt32, d)
        return Mod(ud)
    end
end

@inline function fastmod(n::UInt32,T::Mod)::UInt32
    lowbits::UInt64 = T.modulo_inv * n
    return unsafe_trunc(UInt32,(unsafe_trunc(UInt128,lowbits) * T.modulo) >> 64)
end

@inline function fastmod(n::Int,T::Mod)
    return fastmod(convert(UInt32,n),T)
end

#not much better than a loop, need avx to support UInt128
function fastmod!(n::Array{UInt32,N},T::Mod) where N
    n .= (convert.(UInt128,(T.modulo_inv .* n)) .* T.modulo) .>> 64
end

@inline @fastmath function divtest(n::UInt32,T::Mod)::Bool
    return (n*T.modulo_inv) <= (T.modulo_inv - 1)
end
@inline function divtest(n::Int,T::Mod)
    return divtest(convert(UInt32,n),T)
end

# struct SignedMod
#     modulo::UInt32
#     modulo_inv::UInt64
#     function SignedMod(d::Int32)
#         pd = d < 0 ? -d : d
#         cmod::UInt64 = div(typemax(UInt64), d) + 1 + ((pd & (pd -1))==0 ? 1 : 0)
#         return new(pd,cmod)
#     end
#     function SignedMod(d::Int)
#         ud = convert(Int32, d)
#         return SignedMod(ud)
#     end
# end

# @inline function fastmod(n::Int,T::SignedMod)
#     return fastmod(convert(Int32,n),T)
# end
# @inline function fastmod(n::Int32,T::SignedMod)::Int32
#     lowbits::UInt64 = T.modulo_inv * n
#     highbits::Int32 = (convert(UInt128,lowbits) * T.modulo) >> 64
#     return convert(Int32,highbits  - ((T.modulo - 1) & (n >> 31)))
# end

end


@testset "Motzkin with $(factory.constructor)" for factory in sdp_factories
    @polyvar x y

    m = SOSModel(factory)

    p = x^4*y^2 + x^2*y^4 + 1 - 3*x^2*y^2

    @constraint m p >= 0

    JuMP.optimize!(m)

    @test JuMP.dual_status(m) == MOI.InfeasibilityCertificate

    M = SOSModel(factory)

    q = (x^2 + y^2) * p

    @constraint M q >= 0

    JuMP.optimize!(M)

    @test JuMP.primal_status(M) == MOI.FeasiblePoint
end


module kalman;
import StateSpace;
import Distributions;

export newModel,initialGuess,predict,update,extractMeanFromState,extractVarianceFromState;

sampleTimeDeltaTable = Dict{UTF8String,Float64}();

function initialGuess(observations::Array{Float64,1},varianceEstimate::Array{Float64,1})
   state = [observations; [0.0,0.0,0.0]]
   variance  = [varianceEstimate; [0.0001,0.0001,0.0001]]
   return Distributions.MvNormal(state,variance)
end

function fThunk(id)
   function f(t)
      dt = sampleTimeDeltaTable[id];
      F = eye(6)
      F[1,4] = dt
      F[2,5] = dt
      F[3,6] = dt
      return F;
   end
   return f
end

function qThunk(id,accVar)
   function q(t)
      dt = sampleTimeDeltaTable[id];
      G = [ dt^2/2; dt^2/2; dt^2/2; dt; dt; dt ]
      Q = eye(6).*(G*G'*accVar);
      return Q;
   end
   return q
end

function recordSampleTimeDelta(id,sampleTimeDelta)
   sampleTimeDeltaTable[id] = sampleTimeDelta;
end

function newModel(id,accVar::Float64,obVar::Float64,dt0)
   H = [ eye(3) zeros(3,3) ];
   R = eye(3)*obVar;
   recordSampleTimeDelta(id,dt0);
   return StateSpace.LinearGaussianSSM(fThunk(id),(t)->zeros(Float64,6,1),qThunk(id,accVar),(t)->H,(t)->R);
end

function update(id,model,predictedState,observations,dt)
   recordSampleTimeDelta(id,dt);
   return StateSpace.update(model,predictedState,copy(observations));
end

function predict(id,model,state)
   return StateSpace.predict(model,state)
end

function extractMeanFromState(state)
   return Distributions.mean(state)
end

function extractVarianceFromState(state)
   return Distributions.var(state)
end

end


using LinearAlgebra
using Transducers
n = 3
q = 4*rand(Float64)
r = 2*rand(Float64)
k = 10000
actions = 1:k

function empirical_var(diffs,n,k)
    sum(sum([d .^ 2 for d in diffs]))/(n*k)
end

lssm = LinearStateSpaceModel(Matrix{Float64}(I, n, n), q .* Matrix{Float64}(I, n, n))
states = collect(Simulator(lssm), actions)
lobs = LinearObservation(Matrix{Float64}(I, n, n), r .* Matrix{Float64}(I, n, n))
measures = states |> Map(s -> observe(s,lobs)) |> collect


module HurdleDMR

using Lasso, StatsBase, StatsModels, Missings, LambertW,
    SparseArrays, Distributed, SharedArrays, LinearAlgebra, Logging
using GLM: FPVector, FP, Link
using LoggingExtras: MinLevelLogger
using Tables: columntable
using StatsModels: TupleTerm, ColumnTable, missing_omit, schema, apply_schema, termvars
import StatsModels: modelcols
import Tables

export dmr, dmrpaths, hdmr, hdmrpaths, fit, coef, srproj, srprojX, @~, mcdmr, posindic,
  DCR, DMR, HDMR, DMRCoefs, DMRPaths, HDMRCoefs, HDMRPaths, @model, Model,
  CIR, predict, coeffwd, coefbwd, MinCVKfold,
  hasintercept, ncategories, nobs, ncoefs, ncovars, ncovarszero, ncovarspos, ncoefszero, ncoefspos,
  Hurdle, PositivePoisson, LogProductLogLink, logpdf_exact, logpdf_approx,
  InclusionRepetition, TableCountsRegressionModel

##############################################
# hurdle glm model involves
#  1. a choice model for the 0 or positive choice (e.g. binomial with logit link)
#  2. a positive (truncated) count model for positive counts (e.g. poisson with logit link)
#############################################

include("positive_poisson.jl")
include("hurdle.jl")
include("inrep.jl")
include("model.jl")
include("dmr.jl")
include("hdmr.jl")
include("srproj.jl")
include("invreg.jl")
include("multicounts.jl")

end


"""
    CoulombTransformation{T, NT, NE, NG} <: AbstractCoulombTransformationSingle{NT, NE, NG}

Coulomb transformation tensor with even spacing for t-points.

If `k` is zero this tensor solves Poisson equation. If nonzero it works as
Helmholz equation Greens function.

# Fields
- `elementgrid::SMatrix{NG,NE}` : Gauss points in elements
- `t::SVector{NT}{Float64}`     : t-coordinates
- `wt::Vector{T}`               : Weights for t-integration
- `w::SVector{NG}{Float64}`     : Weights for position coordinate integration
- `tmin::Float64`               : t-integration lower limit  (default=0.)
- `tmax::Float64`               : t-integration upper limit  (default=25.)
- `k::T`                        : Helmholz equation constant (default=0.)

# Contruction
    CoulombTransformation(ceg::CubicElementGrid, nt::Int; tmin=0., tmax=25., k=0.)

# Example
```jldoctest
julia> ceg = CubicElementGrid(5, 4, 16)
Cubic elements grid with 4^3 elements with 16^3 Gauss points

julia> CoulombTransformation(ceg, 48)
Coulomb transformation tensor for 48 t-points, tmin=0.0 tmax=25.0 and k=0.0

julia> ct = CoulombTransformation(ceg, 48; tmin=4., tmax=30., k=0.1)
Coulomb transformation tensor for 48 t-points, tmin=4.0 tmax=30.0 and k=0.1

julia> typeof(ct) <: AbstractArray{Float64,5}
true

julia> ct[3,3,1,1,5] ≈ 0.0158700408
true
```
"""
struct CoulombTransformation{T, NT, NE, NG} <: AbstractCoulombTransformationSingle{NT, NE, NG}
    elementgrid::SMatrix{NG,NE}
    t::SVector{NT}{Float64}
    wt::Vector{T}
    w::SVector{NG}{Float64}
    tmin::Float64
    tmax::Float64
    k::T
    function CoulombTransformation(ceg::CubicElementGrid, nt::Int; tmin=0., tmax=25., k=0.)
        t, wt = gausspoints(nt; elementsize=(tmin, tmax))
        grid = grid1d(ceg)
        s = size(grid)
        wt = wt .* exp.(-(k^2)./(4t.^2))
        new{eltype(wt), nt, s[2], s[1]}(grid, t, wt, ceg.w, tmin, tmax, k)
    end
end

"""
    CoulombTransformationLog{T, NT, NE, NG} <: AbstractCoulombTransformationSingle{NT, NE, NG}

Coulomb transformation tensor with logarithmic spacing for t-points.

If `k` is zero this tensor solves Poisson equation. If nonzero it works as
Helmholz equation Greens function.

# Fields
- `elementgrid::SMatrix{NG,NE}` : Gauss points in elements
- `t::SVector{NT}{Float64}`     : t-coordinates
- `wt::Vector{T}`               : Weights for t-integration
- `w::SVector{NG}{Float64}`     : Weights for position coordinate integration
- `tmin::Float64`               : t-integration lower limit
- `tmax::Float64`               : t-integration upper limit
- `k::T`                        : Helmholz equation constant

# Construction
    CoulombTransformationLog(ceg::CubicElementGrid, nt::Int; kwargs...)

## Keywords
- `tmin=0.`
- `tmax=25.`
- `k=0.`

# Example
```jldoctest
julia> ceg = CubicElementGrid(5, 4, 16)
Cubic elements grid with 4^3 elements with 16^3 Gauss points

julia> CoulombTransformationLog(ceg, 48)
Logarithmic Coulomb transformation tensor for 48 t-points, tmin=0.0, tmax=25.0 and k=0.0

julia> ct = CoulombTransformationLog(ceg, 48; tmin=4., tmax=30., k=0.1)
Logarithmic Coulomb transformation tensor for 48 t-points, tmin=4.0, tmax=30.0 and k=0.1

julia> typeof(ct) <: AbstractArray{Float64,5}
true

julia> ct[3,3,1,1,5] ≈ 0.0594740698
true
```
"""
struct CoulombTransformationLog{T, NT, NE, NG} <: AbstractCoulombTransformationSingle{NT, NE, NG}
    elementgrid::SMatrix{NG,NE}
    t::SVector{NT}{Float64}
    wt::Vector{T}
    w::SVector{NG}{Float64}
    tmin::Float64
    tmax::Float64
    k::T
    function CoulombTransformationLog(ceg::CubicElementGrid, nt::Int;
                                      tmin=0., tmax=25., k=0.)
        s, ws = gausspoints(nt; elementsize=(log(tmin+1e-12), log(tmax)))
        t = exp.(s)
        wt = ws .* t
        grid = grid1d(ceg)
        ss = size(grid)
        wt = wt .* exp.(-(k^2)./(4t.^2))
        new{eltype(wt), nt, ss[2], ss[1]}(grid, t, wt, ceg.w, tmin, tmax, k)
    end
end

"""
    CoulombTransformationLocal{T, NT, NE, NG} <: AbstractCoulombTransformationSingle{NT, NE, NG}

Coulomb transformation tensor with even spacing for t-points and t-integration
local average correction.

If `k` is zero this tensor solves Poisson equation. If nonzero it works as
Helmholz equation Greens function.

# Fields
- `elementgrid::SMatrix{NG,NE}` : Gauss points in elements
- `t::SVector{NT}{Float64}`     : t-coordinates
- `wt::Vector{T}`               : Weights for t-integration
- `w::SVector{NG}{Float64}`     : Weights for position coordinate integration
- `tmin::Float64`               : t-integration lower limit
- `tmax::Float64`               : t-integration upper limit
- `k::T`                        : Helmholz equation constant
- `δ::Float64`                  : Correction constant for t-integration ∈ ]0,1]
- `δp::SMatrix{NG,NE}`          : Upper average points for t-integration correction
- `δm::SMatrix{NG,NE}`          : Lower average points for t-integration correction

# Construction
    CoulombTransformationLocal(ceg::CubicElementGrid, nt::Int; kwargs...)

## Keywords
- `tmin=0.`
- `tmax=25.`
- `δ=0.25`
- `k=0.`

# Example
```jldoctest
julia> ceg = CubicElementGrid(5, 4, 16)
Cubic elements grid with 4^3 elements with 16^3 Gauss points

julia> CoulombTransformationLocal(ceg, 48)
Coulomb transformation tensor with local correction for 48 t-points, tmin=0.0, tmax=25.0 and k=0.0

julia> ct = CoulombTransformationLocal(ceg, 48; tmin=4., tmax=30., k=0.1)
Coulomb transformation tensor with local correction for 48 t-points, tmin=4.0, tmax=30.0 and k=0.1

julia> typeof(ct) <: AbstractArray{Float64,5}
true

julia> ct[3,3,1,1,5] ≈ 0.0591078365
true
```
"""
struct CoulombTransformationLocal{T, NT, NE, NG} <: AbstractCoulombTransformationLocal{NT, NE, NG}
    elementgrid::SMatrix{NG,NE}
    t::SVector{NT}{Float64}
    wt::Vector{T}
    w::SVector{NG}{Float64}
    tmin::Float64
    tmax::Float64
    k::T
    δ::Float64
    δp::SMatrix{NG,NE}
    δm::SMatrix{NG,NE}
    function CoulombTransformationLocal(ceg::CubicElementGrid, nt::Int;
                                   tmin=0., tmax=25., δ=0.25, k=0.)
        @assert 0 < δ <= 1
        @assert 0 <= tmin < tmax
        t, wt = gausspoints(nt; elementsize=(tmin, tmax))
        grid = grid1d(ceg)
        s = size(grid)
        δp = zeros(s...)
        δm = zeros(s...)
        δm[2:end,:] = grid[1:end-1,:] .- grid[2:end,:]
        δp[1:end-1,:] = grid[2:end,:] .- grid[1:end-1,:]
        for j in axes(grid, 2)
            δm[1,j] =  austrip(get_1d_element(ceg.elements, j).low) - grid[1,j]
            δp[end,j] = austrip(get_1d_element(ceg.elements, j).high) - grid[end,j]
        end
        wt = wt .* exp.(-(k^2)./(4t.^2))
        new{eltype(wt), nt, s[2], s[1]}(grid, t, wt, ceg.w, tmin, tmax, k, δ, δ.*δp, δ.*δm)
    end
end

"""
    CoulombTransformationLogLocal{T, NT, NE, NG} <: AbstractCoulombTransformationSingle{NT, NE, NG}

Coulomb transformation tensor with logarithmic spacing for t-points and
t-integration local average correction.

If `k` is zero this tensor solves Poisson equation. If nonzero it works as
Helmholz equation Greens function.

# Fields
- `elementgrid::SMatrix{NG,NE}` : Gauss points in elements
- `t::SVector{NT}{Float64}`     : t-coordinates
- `wt::Vector{T}`               : Weights for t-integration
- `w::SVector{NG}{Float64}`     : Weights for position coordinate integration
- `tmin::Float64`               : t-integration lower limit
- `tmax::Float64`               : t-integration upper limit
- `k::T`                        : Helmholz equation constant
- `δ::Float64`                  : Correction constant for t-integration ∈ ]0,1]
- `δp::SMatrix{NG,NE}`          : Upper average points for t-integration correction
- `δm::SMatrix{NG,NE}`          : Lower average points for t-integration correction

# Construction
    CoulombTransformationLogLocal(ceg::CubicElementGrid, nt::Int; kwargs...)

## Keywords
- `tmin=0.`
- `tmax=25.`
- `δ=0.25`
- `k=0.`

# Example
```jldoctest
julia> ceg = CubicElementGrid(5, 4, 16)
Cubic elements grid with 4^3 elements with 16^3 Gauss points

julia> CoulombTransformationLogLocal(ceg, 48)
Logarithmic Coulomb transformation tensor with local correction for 48 t-points, tmin=0.0, tmax=25.0 and k=0.0

julia> ct = CoulombTransformationLogLocal(ceg, 48; tmin=4., tmax=30., k=0.1)
Logarithmic Coulomb transformation tensor with local correction for 48 t-points, tmin=4.0, tmax=30.0 and k=0.1

julia> typeof(ct) <: AbstractArray{Float64,5}
true

julia> ct[3,3,1,1,5] ≈ 0.0591078365
true
```
"""
struct CoulombTransformationLogLocal{T, NT, NE, NG} <: AbstractCoulombTransformationLocal{NT, NE, NG}
    elementgrid::SMatrix{NG,NE}
    t::SVector{NT}{Float64}
    wt::Vector{T}
    w::SVector{NG}{Float64}
    tmin::Float64
    tmax::Float64
    k::T
    δ::Float64
    δp::SMatrix{NG,NE}
    δm::SMatrix{NG,NE}
    function CoulombTransformationLogLocal(ceg::CubicElementGrid, nt::Int;
                                   tmin=0., tmax=25., δ=0.25, k=0.)
        @assert 0 < δ <= 1
        @assert 0 <= tmin < tmax
        s, ws = gausspoints(nt; elementsize=(log(tmin+1e-12), log(tmax)))
        t = exp.(s)
        wt = ws .* t
        t, wt = gausspoints(nt; elementsize=(tmin, tmax))
        grid = grid1d(ceg)
        ss = size(grid)
        δp = zeros(ss...)
        δm = zeros(ss...)
        δm[2:end,:] = grid[1:end-1,:] .- grid[2:end,:]
        δp[1:end-1,:] = grid[2:end,:] .- grid[1:end-1,:]
        for j in 1:ss[2]
            δm[1,j] =  austrip(get_1d_element(ceg.elements, j).low) - grid[1,j]
            δp[end,j] = austrip(get_1d_element(ceg.elements, j).high) - grid[end,j]
        end
        wt = wt .* exp.(-(k^2)./(4t.^2))
        new{eltype(wt), nt, ss[2], ss[1]}(grid, t, wt, ceg.w, tmin, tmax, k, δ, δ.*δp, δ.*δm)
    end
end

"""
    CoulombTransformationCombination{T, NE, NG} <: AbstractCoulombTransformationCombination

Coulomb transformation tensor that is composed of different types of tensors
with different t-integration ranges.

This tensor is intended to to be used to control how t-integration is performed.

# Fields
- `tensors::Vector{AbstractCoulombTransformation}` : Coulomb transformation subtensors
- `ref::Vector{Int}`  : index of tensor wich is referenced at given t-point index
- `t::Vector{Float64}`  : t-integration points
- `wt::Vector{T}`  : weights for t-integration
- `tmin::Float64`               : t-integration lower limit
- `tmax::Float64`               : t-integration upper limit
- `tindex::Vector{Int}`         : Index of t in subtensor
- `k::T`                        : Helmholz equation constant

# Construction
    CoulombTransformationCombination(t::AbstractCoulombTransformation...)

# Example
```jldoctest
julia> ceg = CubicElementGrid(5, 4, 16)
Cubic elements grid with 4^3 elements with 16^3 Gauss points

julia> ct1 = CoulombTransformationLog(ceg, 48)
Logarithmic Coulomb transformation tensor for 48 t-points, tmin=0.0, tmax=25.0 and k=0.0

julia> ct2 = CoulombTransformationLogLocal(ceg, 48; tmin=25, tmax=50)
Logarithmic Coulomb transformation tensor with local correction for 48 t-points, tmin=4.0, tmax=30.0 and k=0.1

julia> cc = CoulombTransformationCombination(ct1)
Coulomb transformation combination tensor for 48 t-points, tmin=0.0, tmax=25.0 and k=0.0

julia> push!(cc, ct2)
Coulomb transformation combination tensor for 96 t-points, tmin=0.0, tmax=50.0 and k=0.0

julia> CoulombTransformationCombination(ct1, ct2)
Coulomb transformation combination tensor for 96 t-points, tmin=0.0, tmax=50.0 and k=0.0
```
"""
mutable struct CoulombTransformationCombination{T, NE, NG} <: AbstractCoulombTransformationCombination
    tensors::Vector{AbstractCoulombTransformation}
    ref::Vector{Int}
    t::Vector{Float64}
    wt::Vector{T}
    tmin::Float64
    tmax::Float64
    tindex::Vector{Int}
    k::T
    function CoulombTransformationCombination(ct::AbstractCoulombTransformationSingle)
        s = size(ct)
        new{eltype(ct.wt), s[3], s[2]}([ct], ones(s[end]), ct.t, ct.wt, ct.tmin, ct.tmax, 1:length(ct.t), ct.k)
    end
end

function CoulombTransformationCombination(t::AbstractCoulombTransformationSingle...)
    ct = CoulombTransformationCombination(t[1])
    for x in t[2:end]
        push!(ct,x)
    end
    return ct
end

function loglocalct(ceg::CubicElementGrid, nt::Int; δ=0.25, tmax=300, tboundary=20, k=0.)
    @warn "loglocalct is depricated use optimal_coulomb_tranformation"
    @assert 0 < tboundary < tmax
    s, ws = gausspoints(nt; elementsize=(log(1e-12), log(tmax)))
    t = exp.(s)
    l = length(t[t .< tboundary])
    ct1 = CoulombTransformationLog(ceg, l; tmax=tboundary, k=k)
    ct2 = CoulombTransformationLogLocal(ceg, nt-l; tmin=tboundary, tmax=tmax, δ=δ, k=k)
    return CoulombTransformationCombination(ct1,ct2)
end

"""
    optimal_coulomb_tranformation(args...; kwargs...)

Returns Coulomb transformation tensor with optimal pre tested parameters.

# Args
- `ceg::CubicElementGrid`  :  grid where transformation is done
- `nt::Int`                :  number of t-points

# Keywords
- `δ=0.25`          : parameter for local average correction
- `tmax=700`        : maximum t value
- `tboundary=20`    : point after which local average correction is applied
- `k=0.`            : constant for Helmholz equation
"""
function optimal_coulomb_tranformation(ceg::CubicElementGrid, nt::Int; δ=0.25, tmax=700, tboundary=20, k=0.)
    @assert 0 < tboundary < tmax
    s, ws = gausspoints(nt; elementsize=(log(1e-12), log(tmax)))
    t = exp.(s)
    l = length(t[t .< tboundary])
    ct1 = CoulombTransformationLog(ceg, l; tmax=tboundary, k=k)
    ct2 = CoulombTransformationLogLocal(ceg, nt-l; tmin=tboundary, tmax=tmax, δ=δ, k=k)
    return CoulombTransformationCombination(ct1,ct2)
end

(ct::AbstractCoulombTransformation)(r,t) = exp(-(t*r)^2)
(ct::AbstractCoulombTransformationSingle)(r,p::Int) = exp(-ct.t[p]^2*r^2)

function (ct::AbstractCoulombTransformationLocal)(r,t)
    rmax = (r+ct.δ)*t
    rmin = (r-ct.δ)*t
    return 0.5*√π*erf(rmin, rmax)/(rmax-rmin)
end

function (ct::AbstractCoulombTransformationLocal)(r,p::Int)
    rmax = (r+ct.δ)*ct.t[p]
    rmin = (r-ct.δ)*ct.t[p]
    return 0.5*√π*erf(rmin, rmax)/(rmax-rmin)
end

function (ct::CoulombTransformationCombination)(r,p::Int)
    i = ct.ref[p]
    return ct.tensors[i](r, ct.t[p])
end

function Base.size(ct::AbstractCoulombTransformationSingle)
    ng, ne = size(ct.elementgrid)
    nt = length(ct.t)
    return (ng,ng,ne,ne,nt)
end

function Base.size(ct::CoulombTransformationCombination{T,NE,NG}) where {T,NE,NG}
    nt = length(ct.ref)
    return (NG,NG,NE,NE,nt)
end

function Base.getindex(ct::AbstractCoulombTransformationSingle, α::Int, β::Int, I::Int, J::Int, p::Int)
    r = ct.elementgrid[α,I] - ct.elementgrid[β,J]
    return ct.w[β]*ct(r, p)
end

function Base.getindex(ct::AbstractCoulombTransformationLocal, α::Int, β::Int, I::Int, J::Int, p::Int)
    r = abs(ct.elementgrid[α,I] - ct.elementgrid[β,J])
    δ = min(abs(ct.δp[α,I]-ct.δm[β,J]), abs(ct.δm[α,I]-ct.δp[β,J]))
    rmax = (r+δ)*ct.t[p]
    rmin = (r-δ)*ct.t[p]
    return ct.w[β]*0.5*√π*erf(rmin, rmax)/(rmax-rmin)
end

function Base.getindex(ct::CoulombTransformationCombination, α::Int, β::Int, I::Int, J::Int, p::Int)
    i = ct.ref[p]
    return ct.tensors[i][α, β, I, J, ct.tindex[p]]
end

function  Base.push!(ctc::CoulombTransformationCombination{T,NE,NG},
            tt::AbstractCoulombTransformationSingle{NT,NE,NG}) where {T,NT,NE,NG}
    @assert ctc.tensors[end].tmax <= tt.tmin
    @assert ctc.k == tt.k
    push!(ctc.tensors,tt)
    ctc.ref = vcat(ctc.ref, ones(NT).*length(ctc.tensors))
    ctc.t = vcat( ctc.t, tt.t)
    ctc.wt = vcat( ctc.wt, tt.wt)
    ctc.tmax = tt.tmax
    ctc.tindex = vcat( ctc.tindex, 1:length(tt.t))
    return ctc
end

function Base.show(io::IO, ::MIME"text/plain", cct::CoulombTransformation)
    s = size(cct)
    print(io, "Coulomb transformation tensor for $(s[end]) t-points,"*
              " tmin=$(cct.tmin), tmax=$(cct.tmax) and k=$(cct.k)"
    )
end

function Base.show(io::IO, ::MIME"text/plain", cct::CoulombTransformationLog)
    s = size(cct)
    print(io, "Logarithmic Coulomb transformation tensor for $(s[end]) t-points,"*
              " tmin=$(cct.tmin), tmax=$(cct.tmax) and k=$(cct.k)"
    )
end

function Base.show(io::IO, ::MIME"text/plain", cct::CoulombTransformationLocal)
    s = size(cct)
    print(io, "Coulomb transformation tensor with local correction for $(s[end])"*
              " t-points, tmin=$(cct.tmin), tmax=$(cct.tmax) and k=$(cct.k)"
    )
end

function Base.show(io::IO, ::MIME"text/plain", cct::CoulombTransformationCombination)
    s = size(cct)
    print(io, "Coulomb transformation combination tensor for $(s[end])"*
              " t-points, tmin=$(cct.tmin), tmax=$(cct.tmax) and k=$(cct.k)"
    )
end

function Base.show(io::IO, ::MIME"text/plain", cct::CoulombTransformationLogLocal)
    s = size(cct)
    print(io, "Logarithmic Coulomb transformation tensor with local correction for $(s[end])"*
              " t-points, tmin=$(cct.tmin), tmax=$(cct.tmax) and k=$(cct.k)"
    )
end

## Integration weight tensor

"""
    ω_tensor(ceg::CubicElementGrid) -> Matrix{Float64}

Return a tensor that contains integration constants to perform integral over
element grid.
"""
function ω_tensor(ceg::CubicElementGrid)
    n = size(ceg)[end]
    l = length(ceg.w)
    ω = reshape( repeat(ceg.w, n), l, n )
end

## Help functions to create charge density

function density_tensor(grid::AbstractArray, r::AbstractVector, a)
    return [ exp(-a*sum( (x-r).^2 )) for x in grid ]
end

density_tensor(grid; r=SVector(0.,0.,0.), a=1.) = density_tensor(grid, r, a)

## Derivative tensor

abstract type AbstractDerivativeTensor <: AbtractTransformationTensor{2} end

"""
    DerivativeTensor{NG} <: AbstractDerivativeTensor

Tensor that performs derivate over [`CubicElementGrid`](@ref).

# Fields
- `gauss_points::SVector{NG}{Float64}`  : point for integration
- `values::Matrix{Float64}`             : tensor values

# Construction
    DerivativeTensor(ceg::CubicElementGrid)
"""
struct DerivativeTensor{NG} <: AbstractDerivativeTensor
    gauss_points::SVector{NG}{Float64}
    values::Matrix{Float64}
    function DerivativeTensor(ceg::CubicElementGrid)
        function _lpoly(x)
            # Legendre polynomials (without prefactor)
            l = length(x)
            out = []
            for i in 1:l
                z = vcat(x[1:i-1],x[i+1:end])
                push!(out, fromroots(z))
            end
            return out
        end
        function _pre_a(x)
            # Legendre plynomial prefactors
            l = length(x)
            a = ones(l)
            for i in 1:l
                for j in 1:l
                    if j != i
                        a[i] *= x[i] - x[j]
                    end
                end
            end
            return a.^-1
        end
        # Derivative is done by analytically deriving Gauss-Legendre basis
        # functions and then calculating values over the derivatives.
        # f(x) = ∑aᵢpᵢ(x) → f'(x) = ∑aᵢpᵢ'(x)
        x = BigFloat.(ceg.gpoints)
        xmin=-0.5*elementsize(ceg.elements)
        xmax=-xmin
        grid = grid1d(ceg)
        ng, ne = size(grid)
        a=_pre_a(x)
        p=_lpoly(x)
        pd = derivative.(p)
        ϕ = zeros(length(x), length(pd))
        for i in eachindex(pd)
            ϕ[:,i] = a[i]*pd[i].(x)
        end
        new{ng}(x, ϕ)
    end
end

function Base.getindex(dt::DerivativeTensor, i::Int, j::Int)
    return dt.values[i,j]
end

Base.size(dt::DerivativeTensor) = size(dt.values)

function Base.show(io::IO, ::MIME"text/plain", dt::DerivativeTensor)
    print(io, "Derivative tensor size=$(size(dt))")
end

function kinetic_energy(ceg::CubicElementGrid, dt, ψ)
    @warn "kinetic_energy is deprecated use operators instead"
    tmp = similar(ψ)

    @tensor tmp[i,j,k,I,J,K] = dt[i,l] * ψ[l,j,k,I,J,K]
    ex = 0.5*integrate(tmp, ceg, tmp)

    @tensor tmp[i,j,k,I,J,K] = dt[j,l] * ψ[i,l,k,I,J,K]
    ey = 0.5*integrate(tmp, ceg, tmp)

    @tensor tmp[i,j,k,I,J,K] = dt[k,l] * ψ[i,j,l,I,J,K]
    ez = 0.5*integrate(tmp, ceg, tmp)

    return ex+ey+ez
end

## Momentum tensor

struct MomentumTensor{NG} <: AbstractArray{ComplexF64, 3}
    dt::DerivativeTensor{NG}
    function MomentumTensor(dt::DerivativeTensor{T}) where T
        @warn "MomentumTensor is deprecated use operators instead"
        new{T}(dt)
    end
end

function MomentumTensor(ceg::CubicElementGrid)
    return MomentumTensor(DerivativeTensor(ceg))
end

Base.size(mt::MomentumTensor) = (size(mt.dt)...,3)

Base.getindex(mt::MomentumTensor,i::Int,j::Int,xyz::Int) = -ComplexF64(0,mt.dt[i,j])

function Base.show(io::IO, ::MIME"text/plain", mt::MomentumTensor)
    print(io, "Momentum tensor size=$(size(mt))")
end

## Nuclear potential tensors

abstract type AbstractNuclearPotential <: AbtractTransformationTensor{3} end
abstract type AbstractNuclearPotentialSingle{T} <: AbstractNuclearPotential end
abstract type AbstractNuclearPotentialCombination{T} <: AbstractNuclearPotential end

"""
    NuclearPotentialTensor{T}

Tensor for nuclear potential in 1D. To get 3D you need 3 of these tensors and
integrate t-coordinate.

Easy way is to use [`PotentialTensor`](@ref) to get 3D tensor.

# Fields
- `elementgrid::Matrix{T}`   : Grid definition, collums are elements, rows Gauss points
- `t::Vector{Float64}`       : t-coordinate
- `wt::Vector{Float64}`      : Integration weights for t
- `tmin::Float64`            : Minimum t-value
- `tmax::Float64`            : Maximum t-value
- `r::T`                     : Nuclear coordinate

# Creation
    NuclearPotentialTensor(r, ceg::CubicElementGrid, nt::Int; tmin=0, tmax=30)

- `r`                       : Nuclear coordinate (1D)
- `ceg::CubicElementGrid`   : Grid for the potential
- `nt::Int`                 : Number of t-points
"""
struct NuclearPotentialTensor{T} <: AbstractNuclearPotentialSingle{T}
    elementgrid::Matrix{T}
    t::Vector{Float64}
    wt::Vector{Float64}
    tmin::Float64
    tmax::Float64
    r::T
    function NuclearPotentialTensor(r, ceg::CubicElementGrid, nt::Int; tmin=0, tmax=30)
        @assert dimension(r) == NoDims || dimension(r) == dimension(u"m")
        rt = austrip(r)
        t, wt = gausspoints(nt; elementsize=(tmin, tmax))
        grid = Array(grid1d(ceg)) .- rt
        new{eltype(grid)}(grid, t, wt, tmin, tmax, rt)
    end
end

"""
    NuclearPotentialTensorLog{T}

Tensor for nuclear potential in 1D. To get 3D you need 3 of these tensors and
integrate t-coordinate. This version has logarithmic spacing for t-points.

Easy way is to use [`PotentialTensor`](@ref) to get 3D tensor.

# Fields
- `elementgrid::Matrix{T}`   : Grid definition, collums are elements, rows Gauss points
- `t::Vector{Float64}`       : t-coordinate
- `wt::Vector{Float64}`      : Integration weights for t
- `tmin::Float64`            : Minimum t-value
- `tmax::Float64`            : Maximum t-value
- `r::T`                     : Nuclear coordinate

# Creation
    NuclearPotentialTensorLog(r, ceg::CubicElementGrid, nt::Int; tmin=0, tmax=30)

- `r`                       : Nuclear coordinate (1D)
- `ceg::CubicElementGrid`   : Grid for the potential
- `nt::Int`                 : Number of t-points
"""
struct NuclearPotentialTensorLog{T} <: AbstractNuclearPotentialSingle{T}
    elementgrid::Matrix{T}
    t::Vector{Float64}
    wt::Vector{Float64}
    tmin::Float64
    tmax::Float64
    r::T
    function NuclearPotentialTensorLog(r, ceg::CubicElementGrid, nt::Int; tmin=0, tmax=30)
        @assert dimension(r) == NoDims || dimension(r) == dimension(u"m")
        rt = austrip(r)
        s, ws = gausspoints(nt; elementsize=(log(tmin+1e-12), log(tmax)))
        t = exp.(s)
        wt = ws .* t
        grid = Array(grid1d(ceg)) .- rt
        new{eltype(grid)}(grid, t, wt, tmin, tmax, rt)
    end
end

"""
    NuclearPotentialTensorLogLocal{T}

Tensor for nuclear potential in 1D. To get 3D you need 3 of these tensors and
integrate t-coordinate. This version has logarithmic spacing for t-points and
local average correction.

Easy way is to use [`PotentialTensor`](@ref) to get 3D tensor.

# Fields
- `elementgrid::Matrix{T}`   : Grid definition, collums are elements, rows Gauss points
- `t::Vector{Float64}`       : t-coordinate
- `wt::Vector{Float64}`      : Integration weights for t
- `tmin::Float64`            : Minimum t-value
- `tmax::Float64`            : Maximum t-value
- `r::T`                     : Nuclear coordinate
- `δ::Float64`               : Correction parameter ∈[0,1]
- `δp::Matrix{T}`            : Upper limits for correction
- `δm::Matrix{T}`            : Lower limits for correction

# Creation
    NuclearPotentialTensorLogLocal(r, ceg::CubicElementGrid, nt::Int; tmin=0, tmax=30, δ=0.25)

- `r`                       : Nuclear coordinate (1D)
- `ceg::CubicElementGrid`   : Grid for the potential
- `nt::Int`                 : Number of t-points
"""
struct NuclearPotentialTensorLogLocal{T} <: AbstractNuclearPotentialSingle{T}
    elementgrid::Matrix{T}
    t::Vector{Float64}
    wt::Vector{Float64}
    tmin::Float64
    tmax::Float64
    r::T
    δ::Float64
    δp::Matrix{T}
    δm::Matrix{T}
    function NuclearPotentialTensorLogLocal(r, ceg::CubicElementGrid, nt::Int;
                                           tmin=0, tmax=30, δ=0.25)
        @assert 0 < δ <= 1
        @assert 0 <= tmin < tmax
        @assert dimension(r) == NoDims || dimension(r) == dimension(u"m")
        rt = austrip(r)
        s, ws = gausspoints(nt; elementsize=(log(tmin+1e-12), log(tmax)))
        t = exp.(s)
        wt = ws .* t
        t, wt = gausspoints(nt; elementsize=(tmin, tmax))
        grid = Array(grid1d(ceg)) .- rt
        ss = size(grid)

        # get range around points [x+δm, x+δp]
        # and calculate average on that range (later on)
        δp = zeros(ss...)
        δm = zeros(ss...)
        δm[2:end,:] = grid[1:end-1,:] .- grid[2:end,:]
        δp[1:end-1,:] = grid[2:end,:] .- grid[1:end-1,:]
        for j in 1:ss[2]
            δm[1,j] = ceg.elements[j].low - grid[1,j]
            δp[end,j] = ceg.elements[j].high - grid[end,j]
        end
        new{eltype(δm)}(grid, t, wt, tmin, tmax, rt, δ, δ.*δp, δ.*δm)
    end
end

"""
    NuclearPotentialTensorGaussian{T}

Tensor for nuclear potential in 1D. To get 3D you need 3 of these tensors and
integrate t-coordinate. This version has logarithmic spacing for t-points
with gaussian nuclear density.

Easy way is to use [`PotentialTensor`](@ref) to get 3D tensor.

# Fields
- `elementgrid::Matrix{T}`   : Grid definition, collums are elements, rows Gauss points
- `t::Vector{Float64}`       : t-coordinate
- `wt::Vector{Float64}`      : Integration weights for t
- `tmin::Float64`            : Minimum t-value
- `tmax::Float64`            : Maximum t-value
- `r::T`                     : Nuclear coordinate
- `β::Float64`               : Width of Gaussian exp(-βr²)

# Creation
    NuclearPotentialTensorGaussian(r, ceg::CubicElementGrid, nt; β=100, tmin=0, tmax=20)

- `r`                       : Nuclear coordinate (1D)
- `ceg::CubicElementGrid`   : Grid for the potential
- `nt::Int`                 : Number of t-points
"""
struct NuclearPotentialTensorGaussian{T} <: AbstractNuclearPotentialSingle{T}
    elementgrid::Matrix{T}
    t::Vector{Float64}
    wt::Vector{Float64}
    tmin::Float64
    tmax::Float64
    r::T
    β::Float64
    function NuclearPotentialTensorGaussian(r, ceg::CubicElementGrid, nt, β; tmin=0, tmax=20)
        @assert dimension(r) == NoDims || dimension(r) == dimension(u"m")
        rt = austrip(r)
        s, ws = gausspoints(nt; elementsize=(log(tmin+1e-12), log(tmax)))
        t = exp.(s)
        wt = ws .* t
        grid = Array(grid1d(ceg)) .- rt
        new{eltype(grid)}(grid, t, wt, tmin, tmax, rt, β)
    end
end

function NuclearPotentialTensorGaussian(r, ceg::CubicElementGrid, nt; σ=0.1, tmin=0, tmax=20)
    min_d = elementsize(ceg.elements) / size(ceg)[1] |> austrip
    β = 0.5*(σ*min_d)^-2
    return NuclearPotentialTensorGaussian(r, ceg, nt, β; tmin=tmin, tmax=tmax)
end

"""
    NuclearPotentialTensorCombination{T}

Combine different tensors to bigger one. Allows combining normal, logarithmic
and local correction ones to one tensor.

# Fields
- `subtensors::Vector{AbstractNuclearPotentialSingle{T}}`  :  combination of these
- `ref::Vector{Int}`    :  transforms t-index to subtensor index
- `index::Vector{Int}`  :  transforms t-index to subtensors t-index
- `wt::Vector{Float64}` :  t-integration weight
- `tmin::Float64`       :  minimum t-value
- `tmax::Float64`       :  maximum t-value
- `r::T`                :  nuclear coordinate

# Creation
    NuclearPotentialTensorCombination(npt::AbstractNuclearPotentialSingle{T}...)
"""
mutable struct NuclearPotentialTensorCombination{T} <: AbstractNuclearPotentialCombination{T}
    subtensors::Vector{AbstractNuclearPotentialSingle{T}}
    ref::Vector{Int}
    index::Vector{Int}
    t::Vector{Float64}
    wt::Vector{Float64}
    tmin::Float64
    tmax::Float64
    r::T
    function NuclearPotentialTensorCombination(npt::AbstractNuclearPotentialSingle)
        nt = size(npt)[end]
        new{eltype(npt.elementgrid)}([npt],
                   ones(nt),
                   1:nt,
                   deepcopy(npt.t),
                   deepcopy(npt.wt),
                   npt.tmin,
                   npt.tmax,
                   npt.r
                   )
    end
end

function NuclearPotentialTensorCombination(npt::AbstractNuclearPotentialSingle{T}...) where {T}
    tmp = NuclearPotentialTensorCombination(npt[1])
    for x in npt[2:end]
        push!(tmp, x)
    end
    return tmp
end

function Base.show(io::IO, ::MIME"text/plain", npt::AbstractNuclearPotential)
    print(io, "Nuclear potential tensor for $(length(npt.t)) t-points,"*
              " tmin=$(npt.tmin), tmax=$(npt.tmax)"
    )
end

Base.size(npt::AbstractNuclearPotential) = (size(npt.elementgrid)..., length(npt.t))

function Base.size(npct::NuclearPotentialTensorCombination)
    return (size(npct.subtensors[1])[1:2]..., length(npct.t))
end

function Base.getindex(npt::AbstractNuclearPotential, i::Int, j::Int, p::Int)
    r = npt.elementgrid[i,j]
    return exp(-npt.t[p]^2 * r^2)
end

function Base.getindex(npt::NuclearPotentialTensorLogLocal, i::Int, j::Int, p::Int)
    r = npt.elementgrid[i,j]
    δ = abs(npt.δp[i,j] - npt.δm[i,j])
    rmax = (r+δ) * npt.t[p]
    rmin = (r-δ) * npt.t[p]
    return 0.5 * √π * erf(rmin, rmax) / (rmax-rmin)
end

function Base.getindex(npt::NuclearPotentialTensorGaussian, i::Int, j::Int, p::Int)
    r = npt.elementgrid[i,j]
    t = npt.t[p]
    a = npt.β
    q = a * t^2 / (t^2 + a)
    return sqrt(a/(t^2+a)) * exp(-q * r^2)
end

function Base.getindex(npct::NuclearPotentialTensorCombination, i::Int, j::Int, p::Int)
    s = npct.ref[p]
    t = npct.index[p]
    return npct.subtensors[s][i,j,t]
end

function Base.push!(nptc::NuclearPotentialTensorCombination{T},
                    npt::AbstractNuclearPotentialSingle{T}) where {T}
    @assert nptc.r == npt.r
    @assert nptc.tmax <= npt.tmin
    push!(nptc.subtensors, npt)
    n = size(npt)[end]
    append!(nptc.ref, (length(nptc.subtensors))*ones(n))
    append!(nptc.t, npt.t)
    append!(nptc.wt, npt.wt)
    append!(nptc.index, 1:n)
    nptc.tmax = npt.tmax
    return nptc
end

"""
    PotentialTensor{Tx,Ty,Tz}

Used to create nuclear potential.

# Creation
    PotentialTensor(npx, npy, npz)

`npx`, `npy` and `npz` are nuclear potential tensors.
To get the best result usually they should be [`NuclearPotentialTensorCombination`](@ref).

# Usage
Create tensor and then call `Array` on it to get nuclear potential.

"""
struct PotentialTensor{Tx<:AbstractNuclearPotential,
                       Ty<:AbstractNuclearPotential,
                       Tz<:AbstractNuclearPotential}
    x::Tx
    y::Ty
    z::Tz
    function PotentialTensor(npx, npy, npz)
        @assert size(npx)[end] == size(npy)[end] == size(npz)[end]
        @assert npx.tmin == npy.tmin == npx.tmin
        @assert npx.tmax == npy.tmax == npx.tmax
        new{typeof(npx), typeof(npy), typeof(npz)}(npx, npy, npz)
    end
end

Base.show(io::IO, ::MIME"text/plain", pt::PotentialTensor) = print(io, "PotentialTensor")

function Base.size(pt::PotentialTensor)
    s1 = size(pt.x)
    s2 = size(pt.y)
    s3 = size(pt.z)
    return s1[1], s2[1], s3[1], s1[2], s2[2], s3[2]
end

function Base.getindex(pt::PotentialTensor, i::Int, j::Int, k::Int, I::Int, J::Int, K::Int)
    out = pt.x[i,I,:]
    out .*= pt.y[j,J,:]
    out .* pt.z[k,K,:]
    out .* pt.x.wt
    return out
end

function Base.Array(pt::PotentialTensor)
    x = Array(pt.x)
    y = Array(pt.y)
    z = Array(pt.z)
    @tullio out[i,j,k,I,J,K] := x[i,I,t] * y[j,J,t] * z[k,K,t] * pt.x.wt[t]
    return 2/sqrt(π) .* out
end

"""
    nuclear_potential(ceg::CubicElementGrid, q, r)
    nuclear_potential(ceg::CubicElementGrid, aname::String, r)

Gives nuclear potential for given nuclear charge or atom name.
"""
function nuclear_potential(ceg::CubicElementGrid, q, r)

    ncx = optimal_nuclear_tensor(ceg, r[1])
    ncy = optimal_nuclear_tensor(ceg, r[2])
    ncz = optimal_nuclear_tensor(ceg, r[3])

    return nuclear_potential(ceg, q, ncx, ncy, ncz)
end

function optimal_nuclear_tensor(ceg, x)
    np = NuclearPotentialTensor(x, ceg, 64; tmin=0, tmax=70)
    npl = NuclearPotentialTensorLog(x, ceg, 16; tmin=70, tmax=120)
    npll = NuclearPotentialTensorGaussian(x, ceg, 16;  σ=0.1, tmin=120, tmax=300)
    nplll = NuclearPotentialTensorGaussian(x, ceg, 32; σ=0.1, tmin=300, tmax=20000)
    return NuclearPotentialTensorCombination(np, npl, npll, nplll)
end

function nuclear_potential(ceg,
                           q,
                           npx::AbstractNuclearPotential,
                           npy::AbstractNuclearPotential,
                           npz::AbstractNuclearPotential)
    @assert dimension(q) == dimension(u"C") || dimension(q) == NoDims
    pt = PotentialTensor(npx, npy, npz)
    qau = austrip(q)
    return (qau*u"hartree/e_au") * ScalarOperator(ceg, Array(pt))
end

function nuclear_potential(ceg::CubicElementGrid, aname::String, r)
    if length(aname) <= 2    # is atomic symbol
        e = elements[Symbol(aname)]
    else
        e = elements[aname]
    end
    @debug "element number $(e.number)"
    return nuclear_potential(ceg, e.number, r)
end


# Compare with the sample dataset from E2LSH

using LSH
using Distances
using Base.Test

points = readdlm("mnist1k.dts")
querypoints = readdlm("mnist1k.q")

# d = 784
# w = 4
# Parameters computed by E2LSH was
# k = 14
# L = 153

T = LSHTable(0.6,LSH.createUHashesAM04(784,4.0,14,153,0.6,18),
    Vector{Float64}[ vec(points[i,:]) for i=1:size(points,1) ])

qs = Vector{Float64}[ vec(querypoints[i,:]) for i=1:size(querypoints,1) ]
for (i,q) in enumerate(qs)
    println("=== Querying point $i ===")
    @time found_points = T[q]
    println("Found $(length(found_points)) points. Distances are:")
    for p in found_points
        println("  $(euclidean(p,q))")
    end
end


"""
This module uses classical laminate theory to calculate stresses and strains in
a thin plate laminate.  Analysis is based on chapter 3 of "Design and Analysis
of Composite Structures" by Kassapoglou.
"""
module Composites

import LinearAlgebra

# Some modules structs
include("structs.jl")
# ABD matrix calculations
include("abd.jl")
# Stress and strain calculations
include("stressstrain.jl")
# Material Failure Theories
include("failuretheories.jl")
# Buckling related functions
include("buckling.jl")

include("deprecated.jl")

end #module


# GMM-based frame-by-frame voice conversion
#
# Reference:
# [Toda 2007] T. Toda, A. W. Black, and K. Tokuda, “Voice conversion based on
# maximum likelihood estimation of spectral parameter trajectory,” IEEE
# Trans. Audio, Speech, Lang. Process, vol. 15, no. 8, pp. 2222–2235, Nov. 2007.
# http://isw3.naist.jp/~tomoki/Tomoki/Journals/IEEE-Nov-2007_MLVC.pdf

# GMMMapParm represents a set of immutable parameters of GMM-based conversion
immutable GMMMapParam
    weights::Vector{Float64}
    μˣ::Matrix{Float64}
    μʸ::Matrix{Float64}
    Σˣˣ::Array{Float64, 3}
    Σˣʸ::Array{Float64, 3}
    Σʸˣ::Array{Float64, 3}
    Σʸʸ::Array{Float64, 3}

    # pre-computed in constructor to avoid dupulicate computation
    # in conversion process
    ΣʸˣΣˣˣ⁻¹::Array{Float64, 3}

    function GMMMapParam(weights::Vector{Float64},
                         μˣ::Matrix{Float64},
                         μʸ::Matrix{Float64},
                         Σˣˣ::Array{Float64, 3},
                         Σˣʸ::Array{Float64, 3},
                         Σʸˣ::Array{Float64, 3},
                         Σʸʸ::Array{Float64, 3})
        M = length(weights)
        D = size(μˣ, 1)
        # pre-allocation and pre-computations
        ΣʸˣΣˣˣ⁻¹ = Array(Float64, D, D, M)
        for m=1:M
            ΣʸˣΣˣˣ⁻¹[:,:,m] = Σʸˣ[:,:,m] * Σˣˣ[:,:,m]^-1
        end
        new(weights, μˣ, μʸ, Σˣˣ, Σˣʸ, Σʸˣ, Σʸʸ, ΣʸˣΣˣˣ⁻¹)
    end
end

function split_joint_gmm(μ::Matrix{Float64}, Σ::Array{Float64,3})
    D = size(μ, 1)>>1

    μˣ = μ[1:D, :]
    μʸ = μ[D+1:end, :]
    Σˣˣ = Σ[1:D,1:D, :]
    Σˣʸ = Σ[1:D,D+1:end, :]
    Σʸˣ = Σ[D+1:end,1:D, :]
    Σʸʸ = Σ[D+1:end,D+1:end, :]

    μˣ, μʸ, Σˣˣ, Σˣʸ, Σʸˣ, Σʸʸ
end

# GMMMap represents a composite type to transform spectral features of a source
# speaker to that of a target speaker based on GMM of source and target joint
# spectral features.
type GMMMap <: FrameByFrameConverter
    params::GMMMapParam
    Eʸ::Matrix{Float64}    # Eq. (11)
    px::GMM

    function GMMMap(weights::Vector{Float64},# shape: (M,)
                    μ::Matrix{Float64},      # shape: (D, M)
                    Σ::Array{Float64,3};     # shape: (D, D, M)
                    swap::Bool=false)
        M = length(weights)

        # Split mean and covariance matrices into source and target
        # speaker's ones
        D = size(μ, 1)>>1 # dimension of feature vector
        μˣ, μʸ, Σˣˣ, Σˣʸ, Σʸˣ, Σʸʸ = split_joint_gmm(μ, Σ)

        # swap src and target parameters
        if swap
            μˣ, μʸ = μʸ, μˣ
            Σˣˣ, Σʸʸ = Σʸʸ, Σˣˣ
            Σˣʸ, Σʸˣ = Σʸˣ, Σˣʸ
        end

        # construct params
        params = GMMMapParam(weights, μˣ, μʸ, Σˣˣ, Σˣʸ, Σʸˣ, Σʸʸ)

        ## pre-allocations
        Eʸ = zeros(D, M)

        # p(x)
        px = GaussianMixtureModel(μˣ, Σˣˣ, weights)

        new(params, Eʸ, px)
    end
end

Base.length(g::GMMMap) = 1 # GMMMap represents `frame-by-frame` converter
dim(g::GMMMap) = size(g.params.μˣ, 1)
ncomponents(g::GMMMap) = length(g.params.weights)
Base.size(g::GMMMap) = (dim(g), length(g))

# Mapping source spectral feature x to target spectral feature y
# so that minimize the mean least squared error.
# More specifically, it returns the value E(p(y|x)].
function fvconvert(g::GMMMap, x::Vector{Float64})
    dim(g) == length(x) || throw(DimensionMismatch("Inconsistent dimentions."))

    μˣ = g.params.μˣ
    μʸ = g.params.μʸ
    ΣʸˣΣˣˣ⁻¹ = g.params.ΣʸˣΣˣˣ⁻¹

    # Eq. (11)
    for m=1:ncomponents(g)
        @inbounds g.Eʸ[:,m] = μʸ[:,m] + (ΣʸˣΣˣˣ⁻¹[:,:,m]) * (x - μˣ[:,m])
    end

    # Eq. (9) p(m|x)
    posterior = predict_proba(g.px, x)

    # Eq. (13)
    g.Eʸ * posterior
end


using Test

@testset "./src/Scenario/QuantumBehavior.jl" begin

using BellScenario: QuantumBehavior

@testset "simple separable cases" begin

    test_cases = [
        ([1;0;0;0],[1;1;1;1;1;1;1;1;1]),
        ([0;1;0;0],[1;1;1;0;0;0;0;0;0]),
        ([0;0;1;0],[1;0;0;1;1;0;0;0;0]),
        ([0;0;0;1],[1;0;0;0;0;0;0;0;0])
    ]

    @testset "ψ = $(case[1])" for case in test_cases
        povm = [[1 0;0 0],[0 0;0 1]]

        behavior = QuantumBehavior.bipartite_scenario([povm,povm], [povm,povm], case[1])

        @test behavior == reshape(case[2],(9,1))
    end
end

@testset "prepare_and_measure()" begin
    @test [1 1 0]' == QuantumBehavior.prepare_and_measure(
        [[1 0;0 0],[0 0;0 1]],
        [[[1 0;0 0],[0 0;0 1]]]
    )
    @test [1 1 0.5 0 0.5]' == QuantumBehavior.prepare_and_measure(
        [[1 0;0 0],[0 0;0 1]],
        [[[1 0;0 0],[0 0;0 1]], [[0.5 0.5;0.5 0.5], [0.5 -0.5;-0.5 0.5]]]
    )
end

end


using PyCall

function series_expansion(x)
    N=100
    cx=x
    sx=(pi/2)*(1/3)*x^3
    for k in 1:N
        cx*=-(4k+1)/((2k+1)*(4k+5))*(pi/2)^2*x^4
        sx*=-(4k+3)/(2k+3)*(pi/2)*x^4
    end
    return cx,sx
end

function continued_fraction(x)
    N=100
    cx=x
    sx=(pi/2)*(1/3)*x^3
    for k in 1:N
        cx*=-(4k+1)/((2k+1)*(4k+5))*(pi/2)^2*x^4
        sx*=-(4k+3)/(2k+3)*(pi/2)*x^4
    end
    return cx,sx
end

function fresnel(x)
    cx,sx=0.0,0.0

    if x==0
        return 0,0
    end

    xa=abs(x)
    if xa<2
        cx,sx=series_expansion(xa)
    elseif xa>=2
        cx,sx=continued_fraction(xa)
    end

    if x<0
        cx,sx=-cx,-sx
    end
    return cx,sx
end

function main_()
    n=1000
    h=0.001
    for i in -n:n
        x=i*h
        cx,sx=fresnel(x)
        print(x,cx,sx)
    end
end

function main()
    xs=linspace(-pi,pi,100)
    ys=sin.(xs)
    @pyimport matplotlib.pyplot as plt
    plt.plot(xs,ys)
    plt.show()
end
main()


using Compat.LinearAlgebra: dot

export elastic_energy_density

elastic_energy_density(σ::EngineeringStress, ϵ::EngineeringStrain) = dot(σ, ϵ) / 2
elastic_energy_density(σ::TensorStress, ε::TensorStrain) = double_contraction(σ, ε) / 2
elastic_energy_density(ε, σ) = elastic_energy_density(ε, σ)
elastic_energy_density(cᵢⱼ::StiffnessMatrix, ϵ::EngineeringStrain) = dot(ϵ, cᵢⱼ, ϵ) / 2
elastic_energy_density(sᵢⱼ::ComplianceMatrix, σ::EngineeringStress) = dot(σ, sᵢⱼ, σ) / 2
elastic_energy_density(cᵢⱼₖₗ::StiffnessTensor, ε::TensorStrain) =
    elastic_energy_density(StiffnessMatrix(cᵢⱼₖₗ), EngineeringStrain(ε))
elastic_energy_density(sᵢⱼₖₗ::ComplianceTensor, σ::TensorStress) =
    elastic_energy_density(ComplianceMatrix(sᵢⱼₖₗ), EngineeringStress(σ))


#RSDeltaSigma: simulateHBF() algorithm
#-------------------------------------------------------------------------------

"""`y = simulateHBF(x, f1, f2, mode=0)`

Simulate a Saramaki half-band filter.

The `f1` and `f2` vectors contain coefficients for the structure.
(`f1` and `f2` can also be struct arrays like those returned from designHBF.m
i.e. struct arrays whose .val fields contain the coeffiecients.)

The `mode` flag determines whether the input is filtered, interpolated,
or decimated according to the following table:
 - `mode = 0`: Plain filtering, no interpolation or decimation.
 - `mode = 1`: The input is interpolated.
 - `mode = 2`: The output is decimated, even samples are taken.
 - `mode = 3`: The output is decimated, odd samples are taken.
"""
function simulateHBF(x, f1, f2, mode::Int=0)
	if isa(f1, Number) && isnan(f1)
		(f1, f2) = exampleHBF(4)
	end
	if isa(f1, Vector{CoeffCSD})
		f1 = values(f1)
	end
	if isa(f2, Vector{CoeffCSD})
		f2 = values(f2)
	end
	x = x[:]
	f1 = f1[:]; f2 = f2[:] #Redundant unless `val`s are not always simple numbers
	n1 = length(f1); n2 = length(f2)
	f2imp = vcat(f2[end:-1:1], f2)
	if 0 == mode #Insert zeros
		f2imp = vcat(f2imp, zeros(2*n2))
		f2imp = f2imp[1:4*n2-1]
	end
	F2 = _tf(f2imp, vcat(1, zeros(length(f2imp)-1)), 1)

	if 0 == mode #Plain
		(up,) = _lsim(F2,x)
		y = 0.5*delay(x,2*n2-1) + f1[1]*up
		for i in 2:n1
			(up,) = _lsim(F2,up)
			(up,) = _lsim(F2,up)
			y = f1[i]*up + delay(y, 4*n2-2)
		end
	elseif 1 == mode #Interpolating
		up = zeros(size(x))
		nz = 2*n2-1
		for i in n1:-1:1
			if i==1
				(up,) = _lsim(F2,up+f1[i]*x)
				nz = n2-1
			else
				(up,) = _lsim(F2,up+f1[i]*x)
				(up,) = _lsim(F2,up)
			end
			x = delay(x,nz)
		end
		y = [2*up'; x']; y = y[:] #Interleave the upper and lower streams

	elseif mode in [2, 3] #Decimating
		x = x[1:2*div(end,2)]
		if 3 == mode
			y = 0.5*x[1:2:end]
			up = x[2:2:end]
			nz = n2-1
		else
			y = 0.5*x[2:2:end]
			up = x[1:2:end]
			nz = n2
		end
		for i in 1:n1
			if i==1
				(up,) = _lsim(F2,up)
			else
				(up,) = _lsim(F2,up)
				(up,) = _lsim(F2,up)
			end
			y = f1[i]*up + delay(y,nz)
			nz = 2*n2-1
		end
	else
		throw("$mode is not a valid value for the mode variable.")
	end
	return y
end

#Last line


import Base.:+
import Base.:*
import Base.:!

export Logic
export Proposition
export True
export False
export T
export F
export ↑
export ⟹
export ↔

## Data Structures ##

"""
    Represents a logical object.
"""
abstract type Logic end

"""
    Represents the value True.

field: value.
"""
struct True <: Logic
    value::True
    True() = new()
end

"""
    Represents the value False.

field: value.
"""
struct False <: Logic
    value::False
    False() = new()
end

"""
    Represents a proposition.

A variable that can take the value `True` or `False`. No others.

field: value.
"""
mutable struct Proposition <: Logic
    value::Logic
    Proposition() = new()
    function Proposition(p::Logic)
        instance = new()
        instance.value = p
        instance
    end
end

## Global Constants ##

const T = True()
const F = False()

## Generic Functions ##

"""
    value(p)

Return the logical value of `p`.
"""
value(p::True) = T
value(p::False) = F
value(p::Logic) = value(p.value)

"""
    show(p)

Print a string representation of the logical value `p`.
"""
Base.show(io::IO, p::True) = print(io, 'T')
Base.show(io::IO, p::False) = print(io, 'F')
Base.show(io::IO, p::Logic) = show(io, value(p))

## Binary Operators ##

"""
    p ↑ q

Not both. Alternative denial. Logical NAND.
"""
↑(p::True, q::True) = F
↑(p::True, q::False) = T
↑(p::False, q::True) = T
↑(p::False, q::False) = T
↑(p::Logic, q::Logic) = value(p) ↑ value(q)

"""
    p * q

Logical conjunction (AND).
"""
*(p::Logic, q::Logic) = (p ↑ q) ↑ (p ↑ q)

"""
    p + q

Logical disjunction (OR).
"""
+(p::Logic, q::Logic) = (p ↑ p) ↑ (q ↑ q)

"""
    p ⟹ q

Logical implication.
"""
⟹(p::Logic, q::Logic) = p ↑ (q ↑ q)

"""
    ↔(p, q)

Logical equality (=).
"""
↔(p::Logic, q::Logic) = (p ↑ q) ↑ ((p ↑ p) ↑ (q ↑ q))

## Unary Operators ##

"""
    !(p)

Negate the proposition `p`. Logical negation.
"""
!(p::Logic) = p ↑ p

"""
    +(p)

Returns a proposition identical to `p`. Logical identity.
"""
+(p::Logic) = p

"""
    True(p)

Returns a Proposition that is True for all `p`. Logical true.
"""
True(p::Logic) = T

"""
    False(p)

Returns a Proposition that is False for all `p`. Logical false.
"""
False(p::Logic) = F


using Boltzmann
using MNIST
using Base.Test

function run_mnist()
    X, y = testdata()  # test data is smaller, no need to downsample
    binarize!(X;threhold=0.01)

    TrainSet = X[:,1:7000]
    ValidSet = X[:,7001:end]

    HiddenUnits = 100;
    Epochs = 10;
    LearningRate = 0.1;

    m_valid = BernoulliRBM(28*28, HiddenUnits, (28,28); momentum=0.5, dataset=TrainSet)
    m = BernoulliRBM(28*28, HiddenUnits, (28,28); momentum=0.5, dataset=TrainSet)

    # Fit using Validation Set
    fit(m_valid, TrainSet;n_iter=Epochs,lr=LearningRate,validation=ValidSet)

    # Fit without Validation Set
    fit(m, TrainSet;n_iter=Epochs,lr=LearningRate)

    return m
end

run_mnist()


using BayesianSumProductNetworks
using Test

@testset "distributions" begin

    # Normal with Normal and Inverse Gamma priors
    H0 = NormalInverseGamma(0.0, 1.0, 1.0, 1.0)
    @test params(H0) == (0.0, 1.0, 1.0, 1.0)

    T = NormalInvGammaSufficientStats()
    @test T.n == 0
    @test T.s == 0
    @test T.sdiff == 0

    # Categorical with Dirichlet prior
    H0 = Dirichlet([0.5, 0.5])
    @test H0.alpha == [0.5, 0.5]

    T = DirichletSufficientStats(2)
    @test T.counts == zeros(Int, 2)

    # Poisson with Gamma prior.
    H0 = Gamma(1.0, 1.0)
    @test H0.α == 1.0
    @test H0.θ == 1.0

    T = GammaSufficienStats()
    @test T.n == 0
    @test T.sn == 0

end


@testset "System Tests" begin
    @testset "Composition System" begin
        @var x y a b

        f = System([y^2 + 2x + 3, x - 1])
        g = System([x + y * a, x - 1 * b], parameters = [a, b])
        @test parameters(g ∘ f) == [a, b]
        @test nparameters(g ∘ f) == 2

        @test parameters(g ∘ g) == [a, b]
        @test nparameters(g ∘ g) == 2

        @test parameters(f ∘ g) == [a, b]
        @test nparameters(f ∘ g) == 2

        @test parameters(f ∘ f) == []
        @test nparameters(f ∘ f) == 0

        @test parameters(f ∘ g ∘ f) == [a, b]
        @test nparameters(f ∘ g ∘ f) == 2
    end
end


export elementary, bi_elementary
export elementary_waypoints, bi_elementary_waypoints

function elementary((x0, y0, θ0)::StaticVector{3,T0}, (xf, yf, θf)::StaticVector{3,Tf}; v=1, λ=1//2) where {T0,Tf}
    ψ=atan(yf - y0, xf - x0)
    @assert adiff(ψ, θ0) ≈ adiff(θf, ψ) "$((x0, y0, θ0)) and $((xf, yf, θf)) are not symmetric for elementary path"
    @assert abs(adiff(ψ, θ0)) < π/2     "$((xf, yf, θf)) is not in front of $((x0, y0, θ0)) for elementary path"
    T = promote_type(T0, Tf)
    α = adiff(θf, θ0)
    d = hypot(xf - x0, yf - y0)
    α ≈ 0 && return (cost=d/v, controls=SVector(zero(VelocityCurvRateStep{T}),
                                                VelocityCurvRateStep{T}(d/v, VelocityCurvRateControl{T}(v, 0)),
                                                zero(VelocityCurvRateStep{T})))
    L = d/D(α, λ)
    σ = 4*α/(L*L*(1 - λ*λ))
    ctrl = SVector(VelocityCurvRateStep{T}(L*(1 - λ)/2, VelocityCurvRateControl{T}(1, σ)),
                   VelocityCurvRateStep{T}(L*λ, VelocityCurvRateControl{T}(1, 0)),
                   VelocityCurvRateStep{T}(L*(1 - λ)/2, VelocityCurvRateControl{T}(1, -σ)))
    (cost=L/v, controls=scalespeed.(ctrl, v))
end

function bi_elementary(q0::StaticVector{3,T0}, qf::StaticVector{3,Tf}; v=1, γ=1//2, λ=1//2) where {T0,Tf}
    T = promote_type(T0, Tf)
    x0, y0, θ0 = q0
    xf, yf, θf = qf
    @assert adiff(θ0, θf) ≈ 0 "angles of $q0 and $qf must be equal for bi-elementary path"
    q0 ≈ qf && return (cost=T(0), controls=zeros(SVector{6,VelocityCurvRateStep{T}}))
    dx = xf - x0
    dy = yf - y0
    ψ  = atan(dy, dx)
    θi = mod2piF(2*ψ - θ0)
    qi = SE2State(x0 + γ*dx, y0 + γ*dy, θi)
    ctrl = [elementary(q0, qi, v=v, λ=λ).controls; elementary(qi, qf, v=v, λ=λ).controls]
    (cost=duration(ctrl), controls=ctrl)
end

function elementary_waypoints(q0::StaticVector, qf::StaticVector, dt_or_N; v=1, λ=1//2)
    waypoints(SimpleCarDynamics{0,1}(), SE2κState(q0), elementary(q0, qf, v=v, λ=λ).controls, dt_or_N)
end

function bi_elementary_waypoints(q0::StaticVector, qf::StaticVector, dt_or_N; v=1, γ=1//2, λ=1//2)
    waypoints(SimpleCarDynamics{0,1}(), SE2κState(q0), bi_elementary(q0, qf, v=v, γ=γ, λ=λ).controls, dt_or_N)
end

@inline function D(α::T, λ) where {T}
    α = abs(α)
    c = α/(1 + λ)
    s = sqrt(α/(T(π)*(1 - λ*λ)))
    sin(c*λ)/c + (cos(α/2)*fresnelC(s*(1 - λ)) + sin(α/2)*fresnelS(s*(1 - λ)))/s
end


@system Plant begin
    DATE ~ hold
    # added for convenience
    DOY(DATE) => Cropbox.Dates.dayofyear(DATE) ~ track::int(u"d")
    # moved from Main
    DOYP: planting_day_of_year => 121 ~ preserve::int(u"d", parameter)

    TMAX: maximum_temperature ~ hold
    TMIN: minimum_tempearture ~ hold
    TMN: mean_temperature ~ hold
    PAR: photosynthetically_active_radiation ~ hold

    SWFAC: soil_water_stress_factor ~ hold

    # distinguish initial values of variables (parameters) from actual variables being updated (i.e. LAI0, N0)
    EMP1: LAI_coeff1 => 0.104 ~ preserve(u"m^2", parameter)
    EMP2: LAI_coeff2 => 0.64 ~ preserve(parameter)
    Fc: canopy_fraction => 0.85 ~ preserve(parameter)
    sla: specific_leaf_area => 0.028 ~ preserve(u"m^2/g")
    INTOT: duration_of_reproductive_stage => 300.0 ~ preserve(u"K*d", parameter)
    LAI0: initial_leaf_area_index => 0.013 ~ preserve(u"m^2/m^2", parameter)
    Lfmax: maximum_leaf_number => 12.0 ~ preserve(parameter)
    N0: initial_leaf_number => 2.0 ~ preserve(parameter)
    nb: LAI_coeff => 5.3 ~ preserve(parameter)
    # correct units for p1 (g/K, not g)
    p1: leaf_senescence_rate => 0.03 ~ preserve(u"g/K", parameter)
    PD: plant_density => 5.0 ~ preserve(u"m^-2", parameter)
    rm: maximum_leaf_appearance_rate => 0.100 ~ preserve(u"d^-1", parameter)
    tb: base_temperature => 10.0 ~ preserve(u"°C", parameter)
    W0: initial_plant_dry_matter => 0.3 ~ preserve(u"g/m^2", parameter)
    Wc0: initial_canopy_dry_matter => 0.045 ~ preserve(u"g/m^2", parameter)
    Wr0: initial_root_dry_matter => 0.255 ~ preserve(u"g/m^2", parameter)

    # handle empirical equation for temperature (nounit)
    PT(nounit(TMIN), nounit(TMAX)): photosynthesis_reduction_factor_for_temp => begin
        1.0 - 0.0025 * ((0.25TMIN + 0.75TMAX) - 26.0)^2
    end ~ track

    # introduce RUE parameter with proper units
    RUE: radiation_use_efficiency => 2.1 ~ preserve(u"g/MJ", parameter)
    Pg(PT, SWFAC, RUE, PAR, PD, Y1, LAI): potential_growth_rate => begin
        PT * SWFAC * RUE * PAR/PD * (1 - ℯ^(-Y1 * LAI))
    end ~ track(u"g/d")

    ROWSPC: row_spacing => 60.0 ~ preserve(u"cm", parameter)
    Y1(ROWSPC, PD): canopy_light_extinction_coeff => begin
        # no need to convert units for ROWSPC
        1.5 - 0.768 * (ROWSPC^2 * PD)^0.1
    end ~ preserve

    # introduce flags for organizing rate calculations depending on development phases
    P(DOY, DOYP): planted => (DOY >= DOYP) ~ flag
    VP(P, N, Lfmax): vegetative_phase => (P && N < Lfmax) ~ flag
    RP(N, Lfmax): reproductive_phase => (N >= Lfmax) ~ flag
    #TODO: development phase code no longer needed
    FL(VP, RP): development_phase_code => (VP ? 1 : RP ? 2 : 0) ~ track

    dN(rm, PT): leaf_number_increase => rm * PT ~ track(u"d^-1", when=VP)
    N(dN): leaf_number ~ accumulate(init=N0, when=VP)

    di(TMN, tb): daily_accumulated_temperature => begin
        tb <= TMN <= 25u"°C" ? TMN - tb : 0
    end ~ track(u"K", when=RP)
    INT(di): accumulated_temperature_during_reproductive_stage ~ accumulate(u"K*d", when=RP)

    _a(EMP2, N, nb) => exp(EMP2 * (N - nb)) ~ track
    dLAI1(SWFAC, PD, EMP1, PT, _a, dN) => begin
        SWFAC * PD * EMP1 * PT * (a/(1+a)) * dN
    end ~ track(u"m^2/m^2/d", when=VP)
    dLAI2(PD, di, p1, sla) => begin
        -PD * di * p1 * sla
    end ~ track(u"m^2/m^2/d", when=RP)
    dLAI(dLAI1, dLAI2): leaf_area_index_increase => dLAI1 + dLAI2 ~ track(u"m^2/m^2/d")
    LAI(dLAI): leaf_area_index ~ accumulate(u"m^2/m^2", init=LAI0)

    E: CH2O_conversion_efficiency => 1.0 ~ preserve(u"g/g", parameter)
    dW(E, Pg, PD) => E * Pg * PD ~ track(u"g/m^2/d")
    W(dW): plant_dry_matter ~ accumulate(u"g/m^2", init=W0, when=P)
    Wc(Fc, dW): canopy_dry_matter => Fc * dW ~ accumulate(u"g/m^2", init=Wc0, when=VP)
    Wr(Fc, dW): root_dry_matter => (1-Fc) * dW ~ accumulate(u"g/m^2", init=Wr0, when=VP)
    Wf(dW): fruit_dry_matter ~ accumulate(u"g/m^2", when=RP)

    endsim(INT, INTOT): end_of_simulation => (INT > INTOT) ~ flag
end


# power functions
function load_pwr_data(set)
    data_net = PowerModels.parse_file("data/$(set[:pwr_case])")
    gen_data = CSV.read("data/gen_data.csv", DataFrame; header=1)
    load_dyn = CSV.read("data/load_factor.csv", DataFrame; header=1)[:,1]
    # Network size
    G = size(gen_data,1)
    N = length(data_net["bus"])
    E = length(data_net["branch"])
    D = length(data_net["load"])

    # order bus indexing
    bus_keys=collect(keys(data_net["bus"]))
    bus_key_dict = Dict()
    for i in 1:N
        push!(bus_key_dict, i => bus_keys[i])
    end
    node(key) = [k for (k,v) in bus_key_dict if v == key][1]

    # Load generation data
    p̅ = zeros(G); p̲ = zeros(G); c1 = zeros(G); c2 = zeros(G); M_p = zeros(N,G)
    for g in 1:G
        p̅[g] = gen_data[g,:P_max]
        p̲[g] = gen_data[g,:P_min] * 0
        c1[g] = gen_data[g,:C]
        c2[g] = gen_data[g,:C_2]
        M_p[gen_data[g,:node],g] = 1
    end

    # Load demand data
    load_key=collect(keys(data_net["load"]))
    d = zeros(D); M_d = zeros(N,D)
    for h in load_key
        d[parse(Int64,h)] = data_net["load"][h]["pd"]*data_net["baseMVA"] + 1e-3
        M_d[node(string(data_net["load"][h]["load_bus"])),parse(Int64,h)] = 1
    end
    load_dyn = ones(set[:T])

    # Load transmission data
    line_key=collect(keys(data_net["branch"]))
    β = zeros(E); f̅ = zeros(E); n_s = trunc.(Int64,zeros(E)); n_r = trunc.(Int64,zeros(E))
    for l in line_key
        β[data_net["branch"][l]["index"]] = -imag(1/(data_net["branch"][l]["br_r"] + data_net["branch"][l]["br_x"]im))
        n_s[data_net["branch"][l]["index"]] = data_net["branch"][l]["f_bus"]
        n_r[data_net["branch"][l]["index"]] = data_net["branch"][l]["t_bus"]
        f̅[data_net["branch"][l]["index"]] = data_net["branch"][l]["rate_a"]*data_net["baseMVA"]
    end
    # merge parallel lines
    ff = zeros(N,N); ββ = zeros(N,N)
    for l in line_key
        ff[node(string(n_s[data_net["branch"][l]["index"]])),node(string(n_r[data_net["branch"][l]["index"]]))] += f̅[data_net["branch"][l]["index"]]
        ff[node(string(n_r[data_net["branch"][l]["index"]])),node(string(n_s[data_net["branch"][l]["index"]]))] += f̅[data_net["branch"][l]["index"]]
        ββ[node(string(n_s[data_net["branch"][l]["index"]])),node(string(n_r[data_net["branch"][l]["index"]]))]  = β[data_net["branch"][l]["index"]]
        ββ[node(string(n_r[data_net["branch"][l]["index"]])),node(string(n_s[data_net["branch"][l]["index"]]))]  = β[data_net["branch"][l]["index"]]
    end
    # find all parallel lines
    parallel_lines = []
    for l in line_key, e in line_key
        if l != e && node(string(n_s[data_net["branch"][l]["index"]])) == node(string(n_s[data_net["branch"][e]["index"]])) && node(string(n_r[data_net["branch"][l]["index"]])) == node(string(n_r[data_net["branch"][e]["index"]]))
            push!(parallel_lines,l)
        end
    end
    # update number of edges
    E = E - Int(length(parallel_lines)/2)
    # get s and r ends of all edge
    n_s = trunc.(Int64,zeros(E)); n_r = trunc.(Int64,zeros(E))
    ff = LowerTriangular(ff)
    for l in 1:E
        n_s[l] = findall(!iszero, ff)[l][1]
        n_r[l] = findall(!iszero, ff)[l][2]
    end
    β = zeros(E); f̅ = zeros(E);
    likely_congested_lines = [2 25 34 76 77 112 116 174]
    for l in 1:E
        β[l] = ββ[n_s[l],n_r[l]]
        l in likely_congested_lines ? f̅[l] = ff[n_s[l],n_r[l]]*3 : f̅[l] = ff[n_s[l],n_r[l]]*3
    end

    # Find reference node
    ref = 0
    for n in 1:N
        if sum(M_p[n,:]) == 0 &&  sum(M_d[n,:]) == 0 == 0
            ref = n
        end
    end

    # Compute PTDF matrix
    B_line = zeros(E,N); B̃_bus = zeros(N,N); B = zeros(N,N)
    for n in 1:N
        for l in 1:E
            if n_s[l] == n
                B[n,n] += β[l]
                B_line[l,n] = β[l]
            end
            if n_r[l] == n
                B[n,n] += β[l]
                B_line[l,n] = -β[l]
            end
        end
    end
    for l in 1:E
        B[Int(n_s[l]),Int(n_r[l])] = - β[l]
        B[Int(n_r[l]),Int(n_s[l])] = - β[l]
    end
    B̃_bus = remove_col_and_row(B,ref)
    B̃_bus = inv(B̃_bus)
    B̃_bus = build_B̆(B̃_bus,ref)
    PTDF = B_line*B̃_bus

    # power-gas data
    gas_bus = [10 12 15 25 26 61 65 66 69]
    power_node = [45 33 36 31 30 40 39 38 25]
    Λ = zeros(48,N)
    Λ = zeros(48,N)
    for i in 1:length(power_node)
        Λ[power_node[i],gas_bus[i]] = 0.25
    end

    @info("done loading power system data")

    # safe network data
    net = Dict(
    # transmission data
    :f̅ => f̅, :n_s => n_s, :n_r => n_r, :T => round.(PTDF,digits=8),
    # load data
    :d => round.(d,digits=5), :M_d => M_d, :k_l => load_dyn,
    # generation data
    :p̅ => round.(p̅,digits=5), :p̲ => round.(p̲,digits=5), :M_p => M_p,
    :c1 => round.(c1,digits=5), :c2 => round.(c2,digits=5), :c̀2 => round.(sqrt.(c2),digits=5),
    # graph data
    :N => N, :E => E, :G => G, :D => D, :ref => ref,
    :gas_bus => [10 12 15 25 26 61 65 66 69],
    :Λ => Λ
    )
    return net
end
function solve_chance_constrained_OPF(net,set,ξ)
    # DC-OPF definition
    model = Model(optimizer_with_attributes(Mosek.Optimizer, "LOG" => 0))
    # model variables
    P =  [@variable(model, [1:net[:G], 1:set[:k_t][t]]) for t = 1:set[:T]]
    v =  [@variable(model, [1:net[:N]]) for t = 1:set[:T]]
    # auxiliary variables
    A =  [@variable(model, [1:net[:N], 1:set[:k_t][t]]) for t = 1:set[:T]]
    B =  [@variable(model, [1:net[:E], 1:set[:k_t][t]]) for t = 1:set[:T]]
    y_f =  [@variable(model, [1:net[:E]]) for t = 1:set[:T]]
    x_f =  [@variable(model, [1:net[:E]]) for t = 1:set[:T]]
    y_p =  [@variable(model, [1:net[:G]]) for t = 1:set[:T]]
    x_p =  [@variable(model, [1:net[:G]]) for t = 1:set[:T]]
    # model objective (exp cost + var of non-CCGT units)
    @objective(model, Min, sum(net[:c1]'P[t]*S(t)*ξ[:μ̂] + 10000 * sum(v[t][i] for i in setdiff(1:net[:N],net[:gas_bus])) for t in 1:set[:T]))
    # prioritize CCGT plants to balance renewables
    @constraint(model, ϕ[t=1:set[:T],i=setdiff(1:net[:N],net[:gas_bus])],
        [v[t][i];ξ[:Σ̅]*(net[:M_p]*P[t]*S(t))[i,:]] in SecondOrderCone())
    # OPF equations
    @constraint(model, α[t=1:set[:T]], A[t] .== (net[:M_p]*P[t] .+ ξ[:M_w]*W(t) .- net[:M_d]*D(t)*net[:k_l][t]))
    @constraint(model, β[t=1:set[:T]], B[t] .== net[:T] * A[t])
    @constraint(model, λ[t=1:set[:T]], ones(net[:N])' * A[t] .== 0)
    # double-sided reformulation of power flow chance constraints
    @constraint(model, μ_1[t=1:set[:T], l=1:net[:E]], [sqrt(set[:ε̅_p]) * (net[:f̅] .- x_f[t])[l]; vcat(ξ[:Σ̅]*(B[t]*S(t))[l,:],y_f[t][l])] in SecondOrderCone())
    @constraint(model, μ_2[t=1:set[:T], l=1:net[:E]], (B[t] * S(t) * ξ[:μ̂])[l] <= y_f[t][l] + x_f[t][l])
    @constraint(model, μ_3[t=1:set[:T], l=1:net[:E]], (B[t] * S(t) * ξ[:μ̂])[l] >= - y_f[t][l] - x_f[t][l])
    @constraint(model, μ_4[t=1:set[:T], l=1:net[:E]], net[:f̅][l] >= x_f[t][l] >= 0)
    @constraint(model, μ_5[t=1:set[:T], l=1:net[:E]], y_f[t][l] >= 0)
    # double-sided generation (reformulated) chance constraints
    @constraint(model, κ_1[t=1:set[:T], i=1:net[:G]], [sqrt(set[:ε̅_p]) * (1/2*(net[:p̅] - net[:p̲]) - x_p[t])[i]; vcat(ξ[:Σ̅]*(P[t]*S(t))[i,:],y_p[t][i])] in SecondOrderCone())
    @constraint(model, κ_2[t=1:set[:T], i=1:net[:G]], (P[t]*S(t)*ξ[:μ̂])[i] - 1/2*(net[:p̅] - net[:p̲])[i] <= y_p[t][i] + x_p[t][i])
    @constraint(model, κ_3[t=1:set[:T], i=1:net[:G]], (P[t]*S(t)*ξ[:μ̂])[i] - 1/2*(net[:p̅] - net[:p̲])[i] >= - y_p[t][i] - x_p[t][i])
    @constraint(model, κ_4[t=1:set[:T], i=1:net[:G]], 1/2*(net[:p̅] - net[:p̲])[i] >= x_p[t][i] >= 0)
    @constraint(model, κ_5[t=1:set[:T], i=1:net[:G]], y_p[t][i] >= 0)
    # solve model
    optimize!(model)
    @info("done solving chance-constrained OPF in $(solve_time(model)) sec. with status $(termination_status(model))")
    sol = Dict(:status => termination_status(model),
                :obj => JuMP.objective_value(model),
                :P => [JuMP.value.(P[t]) for t = 1:set[:T]],
                :CPUtime => solve_time(model))
    return sol
end
function wind_forecast(set)
    # power system data
    w_f = CSV.read("data/wind.csv", DataFrame; header=0)[:,1]
    bus = [3 8 11 20 24 26 31 38 43 49 53]
    W_ = length(bus)
    M_w = zeros(net[:N],W_)
    for i in 1:W_
        M_w[bus[i],i] = 1
    end
    # uncertainty data
    k = set[:k_t][set[:T]]
    # mean and covariance
    μ = ones(k-1)
    Σ = zeros(k-1,k-1)
    for i in 1:k-1
        Σ[i,i] = set[:σ]
    end
    # first two moments of altered dimentions
    μ̂ = vcat(1,μ)
    # E[ξξᵀ] = Cov + μμᵀ
    Σ̂ = zeros(k,k)
    Σ̂[1,1] = 1; Σ̂[2:end,1] .= μ; Σ̂[1,2:end] .= μ;
    Σ̂[2:end,2:end] .=  Σ .+ μ*μ'
    # factorization of E[ξξᵀ]
    Σ̅ = cholesky(Σ̂).L .+ zeros(k,k)
    Σ̅[:,1] .= 0
    ξ̂ = ones(k,set[:Ns])
    ξ̂[2:end,:] = rand(MvNormal(μ,Σ),set[:Ns])
    return Dict(:w_f => w_f, :bus => bus, :W => W_, :M_w => M_w, :μ => μ, :Σ => Σ, :μ̂ => μ̂, :Σ̂ => Σ̂, :Σ̅ => Σ̅, :ξ̂ => ξ̂)
end


using DReal

ctx = global_context()
X = Var(Float64, 0.0, 1.0)
exgold = (X < 0.5) | (X > 0.9)
notex = (!)(ctx, exgold)

ex = exgold
push_ctx!(ctx)
lb_constraint = (>=)(ctx, X, 0.0)
DReal.add!(ctx, lb_constraint)
ub_constraint = (<=)(ctx, X, 1.0)
DReal.add!(ctx, ub_constraint)
DReal.add!(ctx, ex)
result = is_satisfiable(ctx)
pop_ctx!(ctx)

ex = notex
push_ctx!(ctx)
lb_constraint = (>=)(ctx, X, 0.0)
DReal.add!(ctx, lb_constraint)
ub_constraint = (<=)(ctx, X, 1.0)
DReal.add!(ctx, ub_constraint)
DReal.add!(ctx, ex)
result = is_satisfiable(ctx)
pop_ctx!(ctx)

ex = exgold
push_ctx!(ctx)
lb_constraint = (>=)(ctx, X, 0.0)
DReal.add!(ctx, lb_constraint)
ub_constraint = (<=)(ctx, X, 0.5)
DReal.add!(ctx, ub_constraint)
DReal.add!(ctx, ex)
result = is_satisfiable(ctx)
pop_ctx!(ctx)

ex = notex
push_ctx!(ctx)
lb_constraint = (>=)(ctx, X, 0.0)
DReal.add!(ctx, lb_constraint)
ub_constraint = (<=)(ctx, X, 0.5)
DReal.add!(ctx, ub_constraint)
DReal.add!(ctx, ex)

DReal.is_satisfiable()