# handles upper and lower bounds
# Point-Based POMDP Algorithms: Improved Analysis and Implementation
#  search for "blind"

export BlindPolicy, FixedActionSolver

mutable struct BlindPolicy{A} <: Policy
    a::A
    v::Float64
end

function BlindPolicy(pomdp::POMDP)
    action_list = actions(pomdp)
    state_list = ordered_states(pomdp)  # TODO: could it just be states?

    best_action = action_list[1]
    best_worst_reward = -Inf

    for a in action_list
        worst_reward = Inf
        for s in state_list
            r_sa = reward(pomdp, s, a)
            worst_reward = min(r_sa, worst_reward)
        end

        if worst_reward > best_worst_reward
            best_worst_reward = worst_reward
            best_action = a
        end
    end

    v = best_worst_reward / (1.0 - discount(pomdp))

    return BlindPolicy(best_action, v)
end

action(policy::BlindPolicy, b) = policy.a

value(policy::BlindPolicy, b) = policy.v

struct FixedActionSolver <: Solver
    max_iterations::Int64
    tolerance::Float64
end
function FixedActionSolver(;max_iterations::Int=100,tolerance::Float64=1e-3)
    return FixedActionSolver(max_iterations, tolerance)
end

function solve(solver::FixedActionSolver, pomdp::POMDP)

    # convenience variables
    ns = length(states(pomdp))
    na = length(actions(pomdp))
    state_list = ordered_states(pomdp)
    action_list = ordered_actions(pomdp)

    # fill with max_worst_value
    bp = BlindPolicy(pomdp)
    max_worst_value = bp.v
    alphas = ones(ns,na) * max_worst_value
    old_alphas = ones(ns,na) * max_worst_value

    for i = 1:solver.max_iterations
        copyto!(old_alphas, alphas)
        residual = 0.0

        for (ai,a) in enumerate(action_list)
            for (si,s) in enumerate(state_list)
                sp_dist = transition(pomdp, s, a)
                sp_sum = 0.0
                for (spi, sp) in enumerate(state_list)
                    sp_sum += pdf(sp_dist, sp) * old_alphas[spi,ai]
                end
                r = reward(pomdp, s, a)
                alphas[si,ai] = r + discount(pomdp) * sp_sum

                alpha_diff = abs(alphas[si,ai] - old_alphas[si,ai])
                residual = max(alpha_diff, residual)
            end
        end

        residual < solver.tolerance ? break : nothing
    end

    return AlphaVectorPolicy(pomdp, alphas, action_list)
end


module OldED

using LinearAlgebra
using SparseArrays
import Base: view, copy, eltype, size, Array
import Base: +, -, *, /, sum
import LinearAlgebra: mul!

include("Basis.jl")
include("Operation.jl")
include("Operation_construct.jl")
include("Measure.jl")
include("Spin.jl")
#-----------------------------------------------------------------------------------------------------
# Krylov space
#-----------------------------------------------------------------------------------------------------
function reducespace(
    vector_space::AbstractMatrix;
    rtol::Real=1e-3
)
    mat = vector_space' * vector_space
    vals, vecs = eigen(Hermitian(mat))
    pos = vals .> rtol
    vals_pos = vals[pos]
    vecs_pos = vecs[:, pos]
    null_vector_space = vector_space * vecs_pos
    for i = 1:length(vals_pos)
        null_vector_space[:, i] ./= sqrt(vals_pos[i])
    end
    return null_vector_space
end
# Special case for single vector
reducespace(v::AbstractVector) = normalize(v)
#-----------------------------------------------------------------------------------------------------
export krylovspace
function krylovspace(
    H,
    vector_space::AbstractMatrix;
    rtol::Real=1e-3
)
    vs_rank = rank(vector_space)
    cat_space = hcat(vector_space, H * vector_space)
    new_space = reducespace(cat_space, rtol=rtol)
    new_rank = size(new_space, 2)
    while new_rank > vs_rank
        vs_rank = new_rank
        cat_space = hcat(new_space, H * new_space)
        new_space = reducespace(cat_space, rtol=rtol)
        new_rank = size(new_space, 2)
    end
    return new_space
end
# Special case for vector
krylovspace(H, v::AbstractVector; rtol::Real=1e-3) = krylovspace(H, reshape(v, :, 1), rtol=rtol)

end # module ExactDiagonalization


using Pedigrees, HDF5
using Base.Test

## simple example from Mrode(2004)
p = Pedigree([0,0,1,1,4,5],[0,0,2,0,3,2])
Ltrans(p)

fid = h5open(Pkg.dir("Pedigrees","data","pedCows.h5"))
const sire = readmmap(fid["sire"]);
const dam = readmmap(fid["dam"]);
close(fid)

ord,lappt,ss,dd = laporder(sire,dam);
p = Pedigree(ss,dd);
tt = Tinvt(p);
@test all(tt.data.nzval .== -0.5)
@test sort(unique(diff(tt.data.colptr))) == [0,1,2]

Lt = Ltrans(p);
;


macro Ax_max()
	code = quote
		# maximum longitudinal acceleration for given speed
		Ax_max = zeros(length(U),1)
		for i in eachindex(U)
			Ax_max[i,1] = AXC[1]*U[i]^3 + AXC[2]*U[i]^2 + AXC[3]*U[i] + AXC[4]
		end
		Ax_max
	end
	return esc(code)
end


#!/Applications/Julia-0.4.6.app/Contents/Resources/julia/bin/julia
# Mortgage Calculator
# This calculator works in any decimal-based currency.

module Mortgage

export mortgage, mortgagecalc, printsummary, printtable

include("mortgageParameters.jl") # uses dictionaries to expand on input arguments
include("mortgageFunctions.jl")
include("mortgagePrintouts.jl")

using DateParser

"""
    mortgage(<keyword arguments>)

    (principal, rate, amortization, frequency, compounding, startdate, termString, paymentSize, firstPaymentDate, numberOfPayments, accumulatedPrincipal, accumulatedInterest, accumulatedTotal, balance, finalPayment, finalPaymentDate, table) = mortgage(principal, rate, amortization, frequency, compounding, startdate, term, payment)

Return calculated values for mortgages or other loans.

# Arguments
* `principal::Number`: the principal amount of the loan or mortgage, in any decimal-based currency. Default: principal=100000
* `rate::Number`: annual interest rate, in percent, eg 3.26 . Default: rate=10.0
* `amortization::Number`: the number of years over which the entire loan or mortgage would be paid off. Default is amortization=25
* `frequency::String`: "m" for monthly (default); "w" for weekly; "t" for two weeks; "b" for bimonthly. Note that for bimonthly payments, they will occur on the first and the fifteenth of each month, and the mortgage should start on the first or fifteenth of the month.
* `compounding::String`: "s" for none (simple interest; note that interest accrues daily but is not compounded); "m" for monthly (ie American) (this is the default); "c" for Canadian (ie semi-annual. Note that variable rate mortgages often compound monthly - check the fine print.)
* `startdate::String`: start date for the mortgage; default is today's date.
* `term::String`: specify the term in years; use "1/4" for 3 months and "1/2" for 6 months; enter "a" to indicate for the entire amortization interval (ie until paid off); default is 5 years. A term greater than the amortization will generate an error.
* `payment::Number`: payment amount; if not specified, it will be calculated based on the amortization interval. Note that for simple interest mortgages, the payment will be calculated as for monthly compounded mortgages.

# table
This value returned by mortgage() is a one-dimensional array with eight values for each payment. The values are:
	paymentNumber, paymentDate, principalAmount, interestAmount, accumulatedPrincipal, accumulatedInterest, accumulatedTotal, balance.
These values are used by printtable() to print out a listing with one line for each payment.

# Examples
```
julia> mortgage(principal=54000, rate=2.125, amortization=20, frequency="w", compounding="c", startdate="2016-6-29", term="a")

(54000,2.125,20,"w","c",2016-06-29,"until paid off",63.50799378958494,2016-07-06,1044,54000.00000000001,12229.766528153477,66229.76652815354,0,54.43699940601391,2036-06-25,Any[1,2016-07-06,41.61279659644556,21.89519719313937,41.61279659644556,21.89519719313937,63.50799378958494,53958.38720340355,2,2016-07-13  …  66175.32952874753,54.41493596613927,1043,2036-06-25,63.485930349710294,0.022063439874642057,54009.070994383575,12229.766528153477,66238.83752253711,-9.070994383571026])
```
```
julia> printsummary(mortgage(startdate="2016-6-28")...)

Principal: 100000; Annual Interest Rate: 10.0%; Payment frequency: monthly
Compounding: monthly (American) compounding
Your mortgage starts on 2016-06-28 and is amortized over (ie would be fully paid off in) 25.0 years.
Your first payment of 908.70 will be on 2016-07-29.
During the term of 5 years, you will make 60 payments, with a final payment of 908.70 on 2021-06-28.
At the end of the term, the balance remaining will be 94163.77. You will have paid a total of 54522.04
of which 48685.81, or 89.3%, will be interest. This represents 48.7% of the principal amount.
```
```
julia> printtable(mortgage(startdate="2016-6-29", term="1/4")...)

                       Mortgage Schedule of Payments

Mortgage Principal: 100000.00 at 10.00% annual interest, amortized over 25.0 years,
using monthly (American) compounding, to be repaid with 3 payments of 908.70 each,
for a term of 3 months. The mortgage start date is 2016-06-29.

                                                   Accumulated   Accumulated   Accumulated
Payment         Date    Principal     Interest       Principal      Interest         Total         Balance

      1   2016-07-30        75.37       833.33           75.37        833.33        908.70        99924.63
      2   2016-08-30        76.00       832.71          151.36       1666.04       1817.40        99848.64
      3   2016-09-29        76.63       832.07          227.99       2498.11       2726.10        99772.01

At the end of the term, the outstanding balance will be 99772.01.
The last payment of the term will be on 2016-09-29 in the amount of 908.70.
Over the term, you will have paid a total of 2726.10 of which 2498.11, or 91.64%, is interest. This
represents 2.50% of the principal amount.
```

# Usage
Commonly used with printsummary() or printtable(), which are part of this package.
"""
function mortgage(; principal=100000, rate=10.0, amortization=25, frequency="m", compounding="m", startdate=today(), term="5", payment=nothing)
	@show typeof(startdate)
	if typeof(startdate) == AbstractString
		startdate = get(tryparse(Date, startdate))
	end
	# @show startdate
	return mortgagecalc(principal, rate, amortization, frequency, compounding, startdate, term, payment)
end

function mortgagecalc(principal, rate, amortization, frequency, compounding, startdate, term, payment)
	# obtain the length of the term in years
	termLength = termlength(term, amortization)
	# string suitable for printing
	termString = haskey(mortgageTermDescriptors, term) ? mortgageTermDescriptors[term][1] : "$(term) years"
	firstPaymentDate = nextpaymentdate(startdate, frequency, startdate)
	# how many payments will be made over the term of the mortgage
	numberOfPayments = numberofperiods(frequency, termLength, startdate)
	if payment == nothing # if payment size was not specified, calculate it based on amortization and other parameters
		paymentSize = paymentsizecalc(rate, principal, amortization, compounding, frequency, startdate)
	else
		paymentSize = payment
	end
	accumulatedPrincipal, accumulatedInterest, accumulatedTotal, balance, finalPayment, finalPaymentDate, table = dotablerows(principal, rate, frequency, compounding, startdate, firstPaymentDate, paymentSize, numberOfPayments)
	return principal, rate, amortization, frequency, compounding, startdate, termString, paymentSize, firstPaymentDate, numberOfPayments, accumulatedPrincipal, accumulatedInterest, accumulatedTotal, balance, finalPayment, finalPaymentDate, table
end

function main()
	printsummary(mortgage()...)
	printtable(mortgage()...)
end

# uncomment the line below, to run this module from the command line. However, it is preferable
# to use the mortgagecl.jl when one wants to use the command line.
# main()

end # module


module SimMdlPrices

using Parameters
using LinearAlgebra
using QuantEcon
using SparseArrays
using StaticArrays
using Statistics
using Infiltrator
using StatsFuns
using BenchmarkTools
using InteractiveUtils

function simulate_ms_var_1_cond_shocks!(x_mat,s_mat,x0,s0,μ,Φ,Σ,Π,u,ϵ,T_start, g_start)

     simulate_markov_switch_init_cond_shock!(s_mat, s0, Π, u, T_start, g_start)
     simulate_msvar_cond_regime_path_shock!(x_mat, x0, s_mat, μ, Φ, Σ, ϵ,T_start, g_start)

end

function simulate_markov_switch_init_cond_shock!(s_mat,s0, Π, u,T_start,g_start)

     T,G = size(u)
     S = size(Π)[1]

     #s_mat = Array{Int64}(undef,T+1,G)
     if T_start == 1
          s_mat[1,:] .= s0
     end

     #Propogate
     Π₀ = cumsum(Π, dims = 2)
     cnt_g = 1
     for g ∈ g_start:G+g_start-1
          cnt_t  = 1
          for t ∈ T_start:T_start+T-1
               for s ∈ 1:S
                    if u[cnt_t, cnt_g] <= Π₀[s_mat[t, g], s]
                         s_mat[t+1, g] = s
                         break
                    end
               end
               cnt_t += 1
          end
          cnt_g += 1
     end

     return s_mat

end

function simulate_msvar_cond_regime_path_shock!(x_mat,x0,s_mat,μ,Φ,Σ,ϵ,T_start, g_start)

     #Unload and Preallocate
     N, N2 = size(x0)
     T = size(ϵ)[2]
     G = size(ϵ)[3]
     @assert N == size(ϵ)[1]

     if N2==1
          x0 = repeat(x0, 1, G)
      end

     #Propogate
     cnt_g = 1
     for g ∈ g_start:G+g_start-1
          cnt_t = 1
          if T_start==1
               x_mat[:, 1, g] = @view(x0[:, cnt_g])
          end

          for t ∈ T_start:T_start+T-1
               s = s_mat[t+1, g]
               x_mat[:, t+1, g] = μ[s] .+ Φ[s] *  @view(x_mat[:,t,g])  .+ Σ[s] * @view(ϵ[:, cnt_t, cnt_g])
               cnt_t += 1
          end

          cnt_g += 1

     end

end

function  construct_structural_matrices_macro(ϕ,δ,ρ₁,ρ₂,β₁,β₂,α₁,α₂,μ_g,μ_pi,m_g,m_r₁,m_r₂,σ_g,σ_pi,σ_r₁,σ_r₂)

     b0₁,b0₂ = construct_b0_macro_array(ϕ,δ,ρ₁,ρ₂,β₁,β₂)
     bm1₁,bm1₂ = construct_bm1_macro_array(μ_g,μ_pi,ρ₁,ρ₂);
     b1₁,b1₂ = construct_b1_macro_array(μ_g,μ_pi,ϕ,ρ₁,ρ₂,α₁,α₂)
     m0₁,m0₂ = construct_m0_macro_array(m_g,m_r₁,m_r₂)
     Γ₁,Γ₂ = construct_gamma_macro_array(σ_g,σ_pi,σ_r₁,σ_r₂)

     return b0₁,b0₂,bm1₁,bm1₂,b1₁,b1₂,m0₁,m0₂,Γ₁,Γ₂
end

function construct_b0_macro_array(ϕ,δ,ρ₁,ρ₂,β₁,β₂)
     b0₁ = @SMatrix [1. 0. ϕ; -δ  1. 0.; -(1. - ρ₁)*β₁ 0. 1.]
     b0₂ = @SMatrix [1. 0. ϕ; -δ  1. 0.; -(1. - ρ₂)*β₂ 0. 1.]

     return b0₁,b0₂
end

function construct_bm1_macro_array(μ_g, μ_pi, ρ₁, ρ₂)
     bm1₁ = @SMatrix [(1. - μ_g) 0. 0.; 0. (1. - μ_pi) 0.; 0. 0. ρ₁]
     bm1₂ = @SMatrix [(1. - μ_g) 0. 0.; 0. (1. - μ_pi) 0.; 0. 0. ρ₂]

     return bm1₁,bm1₂
end

function construct_b1_macro_array(μ_g,μ_pi,ϕ,ρ₁,ρ₂,α₁,α₂)

     b1₁ = @SMatrix [μ_g ϕ 0.; 0. μ_pi 0.; 0. (1. - ρ₁)*α₁ 0.]
     b1₂ = @SMatrix [μ_g ϕ 0.; 0. μ_pi 0.; 0. (1. - ρ₂)*α₂ 0.]

     return b1₁,b1₂
end

function construct_m0_macro_array(m_g,m_r₁,m_r₂)

     m0₁ = SMatrix{3,1,Float64}([m_g 0. m_r₁])
     m0₂ = SMatrix{3,1,Float64}([m_g 0. m_r₂]);

     return m0₁,m0₂
end

function construct_gamma_macro_array(σ_g, σ_pi, σ_r₁, σ_r₂)

     Γ₁ = @SMatrix [σ_g  0.  0.; 0. σ_pi  0.; 0. 0. σ_r₁]
     Γ₂ = @SMatrix [σ_g  0.  0.; 0. σ_pi  0.; 0. 0. σ_r₂]

     return Γ₁,Γ₂
end

function transform_struct_to_rf(b0₁, b0₂, bm1₁, bm1₂, b1₁, b1₂, m0₁, m0₂, Γ₁, Γ₂, π_m, π_d, Π_0, Π_X)

     maxₖ = 1000 #Maximum iterations for REE

     # Compute composite Markov transition matrix and its ergodic distribution
     Π = kron(π_m, π_d)
     mc = MarkovChain(Π)
     q = stationary_distributions(mc)[1]

     #Compute forward-looking RE equilibrium
     F1₁ = b0₁ \ bm1₁
     F1₂ = b0₂ \ bm1₂
     A1₁ = b0₁ \ b1₁
     A1₂ = b0₂ \ b1₂

     Σ1_11 = b0₁ \ Γ₁
     Σ1_12 = b0₁ \ Γ₂
     Σ1_21 = b0₂ \ Γ₁
     Σ1_22 = b0₂ \ Γ₂

     m1₁ = b0₁ \ m0₁
     m1₂ = b0₂ \ m0₂

     #Compute forward solution
     Φ, μ, Σ, det_dum = get_forward_solution_msre(π_m, A1₁, A1₂, F1₁, F1₂, m1₁, m1₂, Σ1_11, Σ1_12, Σ1_21,
                                                                                Σ1_22, maxₖ)

     #Define covariance "cell"
     cov = [Σ[1] * Σ[1]', Σ[2] * Σ[2]', Σ[3] * Σ[3]', Σ[4] * Σ[4]']

     #Compute risk-neautral dynamic
     μ_Q = [ μ[1] - cov[1] * Π_0,
                   μ[2] - cov[2] * Π_0,
                   μ[3] - cov[3] * Π_0,
                   μ[4] - cov[4] * Π_0 ]

     Φ_Q = [ Φ[1] - cov[1] * Π_X,
                    Φ[2] - cov[2] * Π_X,
                    Φ[3] - cov[3] * Π_X,
                    Φ[4] - cov[4] * Π_X ]

     return μ, Φ, μ_Q, Φ_Q, Σ, cov, Π, q, det_dum
end

function get_forward_solution_msre(π_m, A1₁, A1₂, F1₁, F1₂, m1₁, m1₂, Σ1_11, Σ1_12, Σ1_21, Σ1_22, maxₖ)

     Φ, k_term = get_rf_Φ(π_m, F1₁, F1₂, A1₁, A1₂, maxₖ = maxₖ)

     μ, Σ =  compute_μ_Σ_fmmsre(Φ, A1₁, A1₂, m1₁, m1₂, Σ1_11, Σ1_12, Σ1_21, Σ1_22, π_m)

     if k_term == maxₖ
          det_dum = 0
     else
          det_dum = 1
     end

     Φ_out = [Φ[1], Φ[1], Φ[2], Φ[2]]
     μ_out = [μ[1], μ[1], μ[2], μ[2]]
     return Φ_out, μ_out, Σ, det_dum
end

function get_rf_Φ(π_m, F1₁, F1₂, A1₁, A1₂; maxₖ = 1000, tolK = 0.000001)

     B₁ = F1₁
     B₂ = F1₂
     Ωkm1₁ = B₁
     Ωkm1₂ = B₂
     Ωkm1 = [Ωkm1₁,Ωkm1₂]
     global k_term = 0
     global Ωk₁ = similar(Ωkm1₁)
     global Ωk₂ = similar(Ωkm1₁)
     function f(Ωkm1,maxₖ,A1₁, A1₂,π_m,B₁,B₂,tolK)
               for i ∈ 1:maxₖ
                    #update Φₖ
                    Ωk₁ = (I - A1₁ * (π_m[1,1] .* Ωkm1[1] + π_m[1,2] .* Ωkm1[2] ) ) \ B₁
                    Ωk₂ = (I - A1₂ * (π_m[2,1] .* Ωkm1[1] + π_m[2,2] .* Ωkm1[2] ) ) \ B₂

                    #Check for convergence
                    dist = max( maximum(abs.( Ωk₁ - Ωkm1[1] )), maximum(abs.( Ωk₂ - Ωkm1[2] )))

                    #update values or break
                    if (dist <= tolK) || (i==maxₖ)
                         k_term = i
                         break
                    else
                         Ωkm1[1] = Ωk₁
                         Ωkm1[2] = Ωk₂
                    end

               end
          return Ωk₁, Ωk₂, k_term
     end

     Ωk₁,Ωk₂,k_term = f(Ωkm1,maxₖ,A1₁, A1₂,π_m,B₁,B₂,tolK)
     Φ = [ Ωk₁, Ωk₂ ]

     return Φ, k_term

end

function  compute_μ_Σ_fmmsre(Φ, A1₁, A1₂, m1₁, m1₂, Σ1_11, Σ1_12, Σ1_21, Σ1_22, π_m)

     N = size(A1₁)[1]

     m1 = [m1₁; m1₂]
     A₁_star = ( I - A1₁ * ( π_m[1,1]  .*  Φ[1] + π_m[1,2] .* Φ[2] ) )
     A₂_star = ( I - A1₂ * ( π_m[2,1]  .*  Φ[1] + π_m[2,2] .* Φ[2] ) )

     A_star = SMatrix{6,6}([(A₁_star-π_m[1,1].*A1₁) -π_m[1,2].*A1₁;
                     -π_m[2,1].*A1₂ (A₂_star - π_m[2,2].*A1₂) ])

     μ_vec = A_star \ m1
     μ₁ =SMatrix{3,1}(μ_vec[1:N])
     μ₂ = SMatrix{3,1}(μ_vec[N+1:end])

     μ = [μ₁, μ₂]

     Σ_11 = A₁_star \ Σ1_11
     Σ_12 = A₁_star \ Σ1_12
     Σ_21 = A₂_star \ Σ1_21
     Σ_22 = A₂_star \ Σ1_22

     Σ = [Σ_11, Σ_12, Σ_21, Σ_22]

      return μ , Σ
end

@with_kw struct ModelStructuralParams

     Π_0::SMatrix{3,1,Float64,3}
     Π_X::SMatrix{3,3,Float64,9}
     m_g::Float64
     m_r₁::Float64
     m_r₂::Float64
     ϕ::Float64
     δ::Float64
     μ_g::Float64
     μ_pi::Float64
     ρ₁::Float64
     ρ₂::Float64
     β₁::Float64
     β₂::Float64
     α₁::Float64
     α₂::Float64
     σ_g::Float64
     σ_pi::Float64
     σ_r₁::Float64
     σ_r₂::Float64
     σ_y::Float64
     γ_gₐ::Float64
     γ_piₐ::Float64
     γ_cₐ::Float64
     γ_gᵢ::Float64
     γ_piᵢ::Float64
     γ_cᵢ::Float64
     γ_gₒ::Float64
     γ_piₒ::Float64
     γ_cₒ::Float64
     ρₐ::Float64
     ρᵢ::Float64
     ρₒ::Float64
     σ_wₐ::Float64
     σ_zₐ::Float64
     σ_νₐ::Float64
     σ_qₐ::Float64
     σ_wᵢ::Float64
     σ_zᵢ::Float64
     σ_νᵢ::Float64
     σ_qᵢ::Float64
     σ_wₒ::Float64
     σ_zₒ::Float64
     σ_νₒ::Float64
     σ_qₒ::Float64
     λ::Float64
     π_m::SMatrix{2, 2, Float64, 4}
     π_d::SMatrix{2, 2, Float64, 4}

end

@with_kw struct ModelReducedFormParams

     μ::Vector{SMatrix{3,1,Float64,3}}
     μ_Q::Vector{SMatrix{3,1,Float64,3}}
     μ_ν::Vector{SMatrix{6,1,Float64,6}}
     μ_Q_ν::Vector{SMatrix{6,1,Float64,6}}
     Φ::Vector{SMatrix{3,3,Float64,9}}
     Φ_Q::Vector{SMatrix{3,3,Float64,9}}
     Φ_ν::Vector{SMatrix{6,6,Float64,36}}
     Φ_Q_ν::Vector{SMatrix{6,6,Float64,36}}
     Σ::Vector{SMatrix{3,3,Float64,9}}
     Σ_ν::Vector{SMatrix{6,6,Float64,36}}
     cov::Vector{SMatrix{3,3,Float64,9}}
     cov_ν::Vector{SMatrix{6,6,Float64,36}}
     Π::SMatrix{4,4,Float64,16}
     q::Vector{Float64}
     Δ::SMatrix{3, 7, Float64, 21}
     Δ_Q::SMatrix{3, 7, Float64, 21}
     σ_w::SMatrix{3, 1, Float64, 3}
     σ_z::SMatrix{3, 1, Float64, 3}
     σ_q::SMatrix{3, 1, Float64, 3}
     σ_ν::SMatrix{3, 1, Float64, 3}
     det_dum::Int64

end

function construct_structural_parameters(params)

     PiX = @SMatrix [ params[4] 0. 0.; params[7] params[5] 0.; params[8] 0. params[6] ]
     Pi0 = SMatrix{3,1}([params[1] params[2] params[3]])
     beta1 = (1-params[16]) \ params[18]
     beta2 = (1-params[17]) \ params[19]
     alpha1 = (1-params[16])\params[20]
     alpha2 = (1-params[17])\params[21]
     pi_m = @SMatrix [ params[52] 1-params[52]; 1-params[53] params[53] ]
     pi_d = @SMatrix [ params[54] 1-params[54]; 1-params[55] params[55] ]

     msp = ModelStructuralParams(Π_0 = Pi0, Π_X = PiX, m_g = params[9], m_r₁ = params[10],
     m_r₂ = params[11], ϕ = params[12], δ = params[13], μ_g = params[14], μ_pi = params[15], ρ₁ = params[16],
     ρ₂ = params[17], β₁ = beta1, β₂ = beta2, α₁ = alpha1, α₂ = alpha2, σ_g = params[22], σ_pi = params[23],
     σ_r₁ = params[24], σ_r₂ = params[25], σ_y = params[26], γ_gₐ = params[27], γ_piₐ = params[28],
     γ_cₐ = params[29], γ_gᵢ = params[30], γ_piᵢ = params[31], γ_cᵢ = params[32], γ_gₒ = params[33],
     γ_piₒ = params[34], γ_cₒ = params[35], ρₐ = params[36], ρᵢ = params[37], ρₒ = params[38], σ_wₐ = params[39],
     σ_zₐ = params[40], σ_νₐ = params[41], σ_qₐ = params[42], σ_wᵢ = params[43], σ_zᵢ = params[44],
     σ_νᵢ = params[45], σ_qᵢ = params[46], σ_wₒ = params[47], σ_zₒ = params[48], σ_νₒ = params[49],
     σ_qₒ = params[50], λ = params[51], π_m = pi_m, π_d = pi_d)

     return msp

end

function params_to_rf(params)

     msp = construct_structural_parameters(params)

     @unpack Π_0, Π_X, m_g, m_r₁, m_r₂, ϕ, δ, μ_g, μ_pi, ρ₁, ρ₂, β₁, β₂, α₁, α₂, σ_g, σ_pi, σ_r₁, σ_r₂, σ_y, γ_gₐ,
     γ_piₐ, γ_cₐ, γ_gᵢ, γ_piᵢ, γ_cᵢ, γ_gₒ, γ_piₒ, γ_cₒ, ρₐ, ρᵢ, ρₒ, σ_wₐ, σ_zₐ, σ_νₐ, σ_qₐ, σ_wᵢ, σ_zᵢ, σ_νᵢ, σ_qᵢ, σ_wₒ, σ_zₒ,
     σ_νₒ, σ_qₒ, λ, π_m, π_d = msp

     b0₁,b0₂,bm1₁,bm1₂,b1₁,b1₂,m0₁,m0₂,Γ₁,Γ₂ =
          construct_structural_matrices_macro(ϕ, δ, ρ₁, ρ₂, β₁, β₂, α₁, α₂, μ_g, μ_pi, m_g, m_r₁, m_r₂, σ_g, σ_pi, σ_r₁,
                                                                       σ_r₂)

     μ, Φ, μ_Q, Φ_Q, Σ, cov, Π, q, det_dum =
          transform_struct_to_rf(b0₁, b0₂, bm1₁, bm1₂, b1₁, b1₂, m0₁, m0₂, Γ₁, Γ₂, π_m, π_d, Π_0, Π_X)

     μ_ν , Φ_ν, μ_Q_ν, Φ_Q_ν, Σ_ν, cov_ν, Δ, Δ_Q, σ_w, σ_z, σ_q, σ_ν =
          augment_macro_fsmsre_nu(μ, Φ, μ_Q, Φ_Q, Σ, γ_gₐ, γ_gᵢ, γ_gₒ, γ_piₐ, γ_piᵢ, γ_piₒ, γ_cₐ, γ_cᵢ, γ_cₒ, ρₐ, ρᵢ, ρₒ,
                                                        λ, σ_wₐ, σ_wᵢ, σ_wₒ, σ_zₐ, σ_zᵢ, σ_zₒ,σ_qₐ, σ_qᵢ, σ_qₒ, σ_νₐ, σ_νᵢ, σ_νₒ)

     mrfp = ModelReducedFormParams(μ = μ, μ_ν = μ_ν, μ_Q = μ_Q, μ_Q_ν = μ_Q_ν, Φ = Φ, Φ_ν = Φ_ν,
                                                               Φ_Q = Φ_Q, Φ_Q_ν = Φ_Q_ν, Σ = Σ, Σ_ν = Σ_ν, cov = cov,
                                                               cov_ν = cov_ν, Π = Π, q = q, det_dum = det_dum, Δ = Δ, Δ_Q = Δ_Q,
                                                               σ_w = σ_w, σ_z = σ_z, σ_q = σ_q, σ_ν = σ_ν)

     return mrfp
end

function augment_macro_fsmsre_nu(μ, Φ, μ_Q, Φ_Q, Σ, γ_gₐ, γ_gᵢ, γ_gₒ, γ_piₐ, γ_piᵢ, γ_piₒ, γ_cₐ, γ_cᵢ, γ_cₒ, ρₐ, ρᵢ,
                                                            ρₒ, λ, σ_wₐ, σ_wᵢ, σ_wₒ, σ_zₐ, σ_zᵢ, σ_zₒ, σ_qₐ, σ_qᵢ, σ_qₒ, σ_νₐ, σ_νᵢ, σ_νₒ)

     S = size(μ)[1]
     N = size(μ[1])[1]
     Δ = @SMatrix [γ_gₐ γ_piₐ 0.; γ_gᵢ γ_piᵢ 0.; γ_gₒ γ_piₒ 0. ]
     ρ = @SMatrix [ρₐ 0. 0.; 0. ρᵢ 0.; 0. 0. ρₒ]
     C = SMatrix{3,1}([γ_cₐ, γ_cᵢ, γ_cₒ])
     σ_z = SMatrix{3,1}([σ_zₐ, σ_zᵢ, σ_zₒ])
     C_Q = C - λ.*σ_z
     σ2_ν = @SMatrix [(σ_wₐ^2 + σ_zₐ^2) σ_zₐ*σ_zᵢ σ_zₐ*σ_zₒ;
                                    σ_zₐ*σ_zᵢ (σ_wᵢ^2 + σ_zᵢ^2) σ_zᵢ*σ_zₒ;
                                    σ_zₐ*σ_zₒ  σ_zᵢ*σ_zₒ (σ_wₒ^2 + σ_zₒ^2)]
     σ_ν = cholesky(σ2_ν).L

     μ_ν = [ [μ[1]; Δ*μ[1]+C], [μ[2]; Δ*μ[2]+C], [μ[3]; Δ*μ[3]+C], [μ[4]; Δ*μ[4]+C] ]

     μ_Q_ν = [ [μ_Q[1]; Δ*μ_Q[1]+C_Q], [μ_Q[2]; Δ*μ_Q[2]+C_Q], [μ_Q[3]; Δ*μ_Q[3]+C_Q],
                       [μ_Q[4];Δ*μ_Q[4]+C_Q] ]

     Φ_ν = [ SMatrix{6,6,Float64,36}([Φ[1] zeros(N,N); Δ*Φ[1] ρ]),
                  SMatrix{6,6,Float64,36}([Φ[2] zeros(N,N); Δ*Φ[2] ρ]),
                  SMatrix{6,6,Float64,36}( [Φ[3] zeros(N,N); Δ*Φ[3] ρ]),
                  SMatrix{6,6,Float64,36}([Φ[4] zeros(N,N); Δ*Φ[4] ρ])]

     Φ_Q_ν = [ SMatrix{6,6,Float64,36}([Φ_Q[1] zeros(N,N); Δ*Φ_Q[1] ρ]),
                       SMatrix{6,6,Float64,36}([Φ_Q[2] zeros(N,N); Δ*Φ_Q[2] ρ]),
                       SMatrix{6,6,Float64,36}([Φ_Q[3] zeros(N,N); Δ*Φ_Q[3] ρ]),
                       SMatrix{6,6,Float64,36}([Φ_Q[4] zeros(N,N); Δ*Φ_Q[4] ρ])]

     Σ_ν = [ SMatrix{6,6,Float64,36}([Σ[1] zeros(N,N); Δ*Σ[1] σ_ν]),
                 SMatrix{6,6,Float64,36}( [Σ[2] zeros(N,N); Δ*Σ[2] σ_ν]),
                 SMatrix{6,6,Float64,36}([Σ[3] zeros(N,N); Δ*Σ[3] σ_ν]),
                 SMatrix{6,6,Float64,36}([Σ[4] zeros(N,N); Δ*Σ[4] σ_ν])]

     cov_ν = [Σ_ν[1]*(Σ_ν[1]'),Σ_ν[2]*(Σ_ν[2]'),Σ_ν[3]*(Σ_ν[3]'),Σ_ν[4]*(Σ_ν[4]')]

     Δ_tmp = @SMatrix [γ_gₐ γ_piₐ 0. ρₐ 0. 0. γ_cₐ;
                                       γ_gᵢ γ_piᵢ 0. 0. ρᵢ 0. γ_cᵢ;
                                       γ_gₒ γ_piₒ 0. 0. 0. ρₒ γ_cₒ];

     Δ_Q = @SMatrix [γ_gₐ γ_piₐ 0. ρₐ 0. 0. (γ_cₐ-σ_zₐ*λ);
                                   γ_gᵢ γ_piᵢ 0. 0. ρᵢ 0. (γ_cᵢ-σ_zᵢ*λ);
                                   γ_gₒ γ_piₒ 0. 0. 0. ρₒ (γ_cₒ-σ_zₒ*λ) ];

    σ_w = SMatrix{3, 1, Float64, 3}([σ_wₐ; σ_wᵢ; σ_wₒ])
    σ_z = SMatrix{3, 1, Float64, 3}([σ_zₐ; σ_zᵢ; σ_zₒ])
    σ_q = SMatrix{3, 1, Float64, 3}([σ_qₐ; σ_qᵢ; σ_qₒ])
    σ_ν = SMatrix{3, 1, Float64, 3}([σ_νₐ; σ_νᵢ; σ_νₒ])

     return μ_ν , Φ_ν, μ_Q_ν, Φ_Q_ν, Σ_ν, cov_ν, Δ_tmp, Δ_Q, σ_w, σ_z, σ_q, σ_ν
end

function split_shock_mat(u_mat, ϵ_mat, w_mat, z_mat, g)

     gk = size(u_mat)[2]
     #T = size(u_mat)[1]
     k = Int(gk / g)

     u = Vector{Array{Float64,2}}(undef, g)
     ϵ = Vector{Array{Float64,3}}(undef, g)
     w = Vector{Array{Float64,3}}(undef, g)
     z = Vector{Array{Float64,2}}(undef, g)

     for gg ∈ 1:g
          u[gg] = u_mat[:,1+(gg-1)*k:gg*k]
          ϵ[gg] = ϵ_mat[:,:,1+(gg-1)*k:gg*k]
          w[gg] = w_mat[:,:,1+(gg-1)*k:gg*k]
          z[gg] = z_mat[:,1+(gg-1)*k:gg*k]
     end

     return u, ϵ, w, z
end

function simulate_nu_cond_x_i_shock_rn!(ν_mat,x_mat,init_ν,mrfp,w,z,T_start, g_start)

     N = size(x_mat)[1]
     n_ts = size(w)[2]
     G = size(w)[3]
     N_re, N2 = size(init_ν)
     @unpack σ_w, σ_z, Δ_Q = mrfp

     if N2==1
          init_ν = repeat(init_ν,1,G)
     end

     ## Scale systematic shocks by std. dev. loading
     w_mat = repeat(σ_w, 1, n_ts, G) .* w

     tmp_z_mat = Array{Float64}(undef, N_re, n_ts, G)
     for g ∈ 1:G
          tmp_z_mat[:, :, g] = repeat(z[:,g]', N_re)
     end

     z_mat = repeat(σ_z, 1, n_ts, G) .* tmp_z_mat
     wz_mat = w_mat + z_mat

     cnt_g = 1
     for g=g_start:G+g_start-1
          if T_start == 1
               ν_mat[:,1,g] = @view(init_ν[:, cnt_g])
          end
          wz_path = @view(wz_mat[:, :, cnt_g])
          ##Create X_mat = [x_t; nu_{t-1}; 1]'; an (N + N_re + 1) x n_ts Array
          X_mat = Array{Float64}(undef, N+N_re+1, n_ts+1)
          X_mat[1:N, 1:end-1] = @view(x_mat[:, T_start+1:T_start+n_ts, g])
          X_mat[end,:] = ones(1, n_ts+1)
          X_mat[N+1:N+N_re, 1] = @view(ν_mat[:, 1, g])

          ##Compute nu_t iteratively
          cnt = 1
          for i=T_start:n_ts+T_start-1

               X_mat[N+1:N+N_re, cnt+1] = Δ_Q * @view(X_mat[:, cnt]) + @view(wz_path[: ,cnt])
               cnt +=1

          end

          ν_mat[:, T_start+1:T_start+n_ts, g] = @view(X_mat[N+1:N+N_re, 2:end])
          cnt_g += 1

     end

     return ν_mat
end

function compute_mc_real_estate_Q_cond_x_i_nofull!( Q_mc, η_mc, Q_std_mc, η_std_mc,
                                                                                Q_std_prep, η_std_prep,x_mat, ν_mat,T_bar,G)
     T = size(x_mat)[2] - 1
     n_η = T_bar
     N_re = size(ν_mat)[1]

     # Preallocate
     Δ_ν = [0. 0. -1. 1. 0. 0.;
                 0. 0. -1. 0. 1. 0.;
                 0. 0. -1. 0. 0. 1.]

     m1_η_sim = Array{Float64}(undef, N_re, T_bar, G)
     x_path_mat = [x_mat; ν_mat]

     for g=1:G

          x_path = @view(x_path_mat[:,1:T_bar,g])
          m1_η_sim[:, :, g] = cumsum(Δ_ν * x_path, dims = 2)

     end
     η_sim = exp.(m1_η_sim)
     η_mc[:,1:T] .= dropdims(mean(η_sim[:,:,1:G], dims = 3), dims = 3)
     Q_mc .= sum(η_mc[:,1:T], dims = 2)
     #Get std mat
     N_std = Int64(floor(G / 30))
     for i=1:30

          η_std_prep[:, 1:T_bar, i] =  dropdims(mean(@view(η_sim[:, :, 1+(i-1)*N_std:i*N_std]), dims = 3), dims = 3)
          Q_std_prep[:, i] = sum(@view(η_std_prep[:, 1:n_η, i]), dims = 2)

     end

     η_std_mc .= dropdims(std(η_std_prep, dims = 3), dims = 3) ./ sqrt(30)
     Q_std_mc .= std(Q_std_prep, dims = 2) ./ sqrt(30)

end

function compute_mc_term_structure_cond_x(x_mat, maturity_mat)
     G = size(x_mat)[3]
     N_terms = size(maturity_mat)[2]

     δ = [0. 0. -1.]
     ts_sim = Array{Float64}(undef, N_terms, G)
     m1_ts_sim = Array{Float64}(undef, N_terms,G)
     m2_ts_sim = Array{Float64}(undef, N_terms,G)

     for g=1:G
          x_path = @view(x_mat[:, :, g])
          for n=1:N_terms
             m1_ts_sim[n,g] = sum(δ *@view(x_path[:, 1:maturity_mat[n]]), dims = 2)[1]
          end
     end
     m2_ts_sim = m1_ts_sim.^2
     ts_sim = exp.(m1_ts_sim)

     ts_mc = -log.(mean(ts_sim, dims = 2)) ./ maturity_mat'
     m1_ts_mc = mean(m1_ts_sim, dims = 2);
     m2_ts_mc = mean(m2_ts_sim, dims = 2);

     N_std = Int64(floor(G/30))
     ts_std_prep = Array{Float64}(undef, N_terms, 30)
     for i=1:30
          ts_std_prep[:, i] =  -log.(mean(@view(ts_sim[:, 1+(i-1)*N_std:i*N_std]), dims = 2)) ./ maturity_mat'
     end
     ts_std_mc = std(ts_std_prep, dims = 2) ./ sqrt(30)

     return ts_mc, ts_std_mc, m1_ts_mc, m2_ts_mc, ts_std_prep
end

function   get_total_mean_and_std!(Q_mc_tot, η_mc_tot, Q_std_mc_tot, Q_mc_cell, eta_mc_cell,
                                                            Q_std_prep_cell)

     Q_mc_tot .= compute_mean_over_subsamples(Q_mc_cell)
     η_mc_tot .= compute_mean_over_subsamples(eta_mc_cell)

     Q_std_mc_tot .= compute_std_over_subsamples(Q_std_prep_cell)
     #η_std_mc_tot .= compute_std_over_subsamples(η_std_prep_cell)

end

function compute_std_over_subsamples(in_cell_std)
     L = size(in_cell_std)[1]
     N1, N2 = size(in_cell_std[1])

     if L>1
          in_std_mat = dropdims(sum(reshape(reduce(hcat, in_cell_std), N1, N2, L), dims = 3) ./ L, dims = 3)
      else
          in_std_mat = in_cell_std[1]
      end

     b = size(in_std_mat)[2]
     out_std_mat = std(in_std_mat, dims = 2) ./ sqrt(b)

     return out_std_mat

end

function compute_mean_over_subsamples(in_cell)

     n,m = size(in_cell[1])
     L = size(in_cell)[1]

     if m==1
         mean_mat = mean(reduce(hcat,in_cell), dims = 2)
     else
         mean_mat =  dropdims(sum(reshape(reduce(hcat, in_cell), n, m, L), dims = 3) ./ L, dims = 3)
     end

     return mean_mat

end

function simulate_model_prices_cond_shock_acc_ts(x_init, ν_init, mrfp, T_sim, u, ϵ, w, z, tol, del, η_tol, t, s,
                                                                                n_grps, L)
     # Unload inputs
     N_macro = 3
     N_re = 3
     M = size(u)[1]
     G = size(ϵ)[3]
     @assert floor(G / L)== (G/L)
     gg = Int64(G / L)

     @unpack μ_Q, Φ_Q, Σ, Π = mrfp

     Q_mc = Array{Float64}(undef, N_re, 1)
     Q_std_mc = Array{Float64}(undef, N_re, 1)
     #η_std_mc = Array{Float64}(undef, N_re, T_sim)
     η_mc= Array{Float64}(undef, N_re, T_sim)
     x_mat = Array{Float64}(undef, N_macro, T_sim+1, G)
     ν_mat = Array{Float64}(undef, N_re, T_sim + 1, G)
     s_mat = Array{Int64}(undef, T_sim+1, G)

     u_cell, ϵ_cell, w_cell, z_cell  = split_shock_mat(u, ϵ, w, z, L)

     X = Array(Array(x_init[:, t]')')
     ν = Array(Array(ν_init[:, t]')')
     T_bar = [0]

     outer_while!(Q_mc, η_mc, Q_std_mc, x_mat, ν_mat, s_mat, T_bar, T_sim, n_grps, gg, X, ν, s, μ_Q,
                                    Φ_Q, Σ, Π, u_cell, ϵ_cell, w_cell, z_cell, mrfp, η_tol, N_re, del, L, tol)

     return Q_mc, η_mc, Q_std_mc, s_mat, x_mat, ν_mat, T_bar

end

function inner_while!(x_mat,s_mat, ν_mat, T_bar, Q_mc_tmp, η_mc_tmp, Q_std_mc_tmp, η_std_mc_tmp,
                                   Q_std_prep, η_std_prep, l, n_grps, T_tmp, μ_Q, Φ_Q, Σ, Π, u_cell, ϵ_cell, w_cell,z_cell,
                                   mrfp, η_tol,gg)

     trig_2 = 0
     cnt_2 = 1
     while (trig_2==0)&&(cnt_2<=n_grps)

          tmp_ndx = (1+(cnt_2-1)*T_tmp):(cnt_2*T_tmp)
          g_start = 1 + (l-1)*gg
          #Simulate model for length T_tmp
          simulate_ms_var_1_cond_shocks!(x_mat, s_mat, x_mat[:, tmp_ndx[1], :],
               s_mat[tmp_ndx[1], :], μ_Q, Φ_Q, Σ, Π, u_cell[l][tmp_ndx, :], ϵ_cell[l][: ,tmp_ndx, :],tmp_ndx[1],g_start)

          #Simulate \nu
          simulate_nu_cond_x_i_shock_rn!(ν_mat, x_mat, ν_mat[:, tmp_ndx[1], :], mrfp, w_cell[l][:, tmp_ndx, :],
                                                                 z_cell[l][tmp_ndx, :],tmp_ndx[1],g_start)

          #Simulate Q
          compute_mc_real_estate_Q_cond_x_i_nofull!(Q_mc_tmp, η_mc_tmp, Q_std_mc_tmp, η_std_mc_tmp,
                                                                                Q_std_prep, η_std_prep , x_mat[:, 1:tmp_ndx[end]+1, :],
                                                                                ν_mat[:, 1:tmp_ndx[end]+1, :],tmp_ndx[end],l*gg)
          #Check that all η_{T_sim,x_t)< cutoff for all t
          if (~any(η_mc_tmp[:, tmp_ndx[end]] .>= η_tol)) || (cnt_2==n_grps)

               #Set loop trigger and T_bar
               T_bar[1]  = cnt_2 * T_tmp
               trig_2 = 1

          else

               cnt_2 +=  1

          end
     end

     return

end

function outer_while!(Q_mc, η_mc, Q_std_mc, x_mat, ν_mat, s_mat, T_bar, T_sim, n_grps, gg, X, ν, s,
                                        μ_Q, Φ_Q, Σ, Π, u_cell, ϵ_cell, w_cell, z_cell, mrfp, η_tol, N_re, del, L, tol)

     T_tmp = Int64(T_sim ./ n_grps)
     G = gg * L
     x_mat[:, 1, :] = repeat(X, 1, 1, G)
     ν_mat[:, 1, :] = repeat(ν, 1, 1, G)
     s_mat[1, :] .= s

     trig = 0
     l = 1

     η_std_prep = Array{Float64}(undef, N_re, T_sim, 30)
     Q_std_prep = Array{Float64}(undef, N_re, 30)
     Q_mc_tmp = Array{Float64}(undef, N_re, 1)
     η_mc_tmp = Array{Float64}(undef, N_re, T_sim)
     Q_std_mc_tmp = Array{Float64}(undef, N_re, 1)
     η_std_mc_tmp = Array{Float64}(undef, N_re, T_sim)
     Q_mc_tot = Array{Float64}(undef, N_re,1)
     η_mc_tot = Array{Float64}(undef, N_re, T_sim)
     Q_std_mc_tot = Array{Float64}(undef, N_re, 1)
     #η_std_mc_tot = Array{Float64}(undef, N_re, T_sim)
     Q_mc_cell = Vector{Array{Float64,2}}(undef, L)
     Q_std_mc_cell = Vector{Array{Float64,2}}(undef, L)
     η_mc_cell = Vector{Array{Float64,2}}(undef, L)
     η_std_mc_cell = Vector{Array{Float64,2}}(undef, L)
     Q_std_prep_cell = Vector{Array{Float64,2}}(undef, L)
     η_std_prep_cell = Vector{Array{Float64,3}}(undef, L)

     while ((trig==0) && (l<=L)) #early simulation check

          if l==1

               #Detect early truncation point, T_bar
               inner_while!(x_mat, s_mat, ν_mat, T_bar, Q_mc_tmp, η_mc_tmp, Q_std_mc_tmp, η_std_mc_tmp,
                                        Q_std_prep, η_std_prep, l, n_grps, T_tmp, μ_Q, Φ_Q, Σ, Π, u_cell, ϵ_cell, w_cell, z_cell,
                                         mrfp, η_tol,gg)
               TT_bar = T_bar[1]

          else
               TT_bar = T_bar[1]
               g_start = 1 + (l-1)*gg
               #Run simulation for T_bar periods
               # Simulate Macro model
               simulate_ms_var_1_cond_shocks!(x_mat, s_mat,X, s, μ_Q, Φ_Q, Σ,Π,u_cell[l][1:TT_bar, :],
                                                                                ϵ_cell[l][:, 1:TT_bar, :],1,g_start)

               #Simulate \nu
               simulate_nu_cond_x_i_shock_rn!(ν_mat, x_mat, ν, mrfp ,w_cell[l][:, 1:TT_bar, :], z_cell[l][1:TT_bar, :],
                                                                      1, g_start)

               #Approximate real estate Q
               compute_mc_real_estate_Q_cond_x_i_nofull!(Q_mc_tmp, η_mc_tmp, Q_std_mc_tmp, η_std_mc_tmp,
                                                                                Q_std_prep, η_std_prep, x_mat[:,1:TT_bar+1,:],
                                                                                ν_mat[:,1:TT_bar+1,:],TT_bar, l * gg)
          end

          Q_mc_cell[l] = copy(Q_mc_tmp)
          η_mc_cell[l] = copy(η_mc_tmp)
          Q_std_mc_cell[l] = copy(Q_std_mc_tmp)
          η_std_mc_cell[l] = copy(η_std_mc_tmp)
          Q_std_prep_cell[l] = copy(Q_std_prep)
          η_std_prep_cell[l] = copy(η_std_prep)

          #Compute total mean and variance
          get_total_mean_and_std!(Q_mc_tot, η_mc_tot, Q_std_mc_tot, Q_mc_cell[1:l], η_mc_cell[1:l],
                                                    Q_std_prep_cell[1:l])

          #Check condition
          if (!any((2 .* (1. .- normcdf.((1.2 .* tol .* Q_mc_tot) ./ Q_std_mc_tot)) .> del)) || (l==L) )

               trig = 1;
               println("l: $l")
               println("T_bar: $T_bar")

               Q_mc .= Q_mc_tot
               η_mc .= η_mc_tot
               Q_std_mc .= Q_std_mc_tot
               #η_std_mc[:, 1:T_bar] = η_std_mc_tot

          else

              l = l+1;

          end
     end

     return

end

export simulate_markov_switch_init_cond_shock, simulate_ms_var_1_cond_shocks,
     simulate_msvar_cond_regime_path_shock, construct_gamma_macro_array, construct_m0_macro_array,
     construct_b1_macro_array, construct_bm1_macro_array, construct_b0_macro_array,
     construct_structural_matrices_macro, transform_struct_to_rf, get_forward_solution_msre, get_rf_Φ,
     compute_drift_sigma_fmmsre, compute_μ_Σ_fmmsre, construct_structural_parameters, params_to_rf,
     augment_macro_fsmsre_nu, simulate_nu_cond_x_i_shock_rn, compute_mc_real_estate_Q_cond_x_i_nofull,
     compute_mc_term_structure_cond_x, compute_std_over_subsamples, compute_mean_over_subsamples,
     simulate_model_prices_cond_shock_acc_ts, split_shock_mat

end


struct Poisson <: Distribution{Int} end

"""
    poisson(lambda::Real)

Sample an `Int` from the Poisson distribution with rate `lambda`.
"""
const poisson = Poisson()

function logpdf(::Poisson, x::Int, lambda::Real)
    x < 0 ? -Inf : x * log(lambda) - lambda - loggamma(x+1)
end

function logpdf_grad(::Poisson, x::Int, lambda::Real)
    (nothing, x/lambda - 1)
end

function random(::Poisson, lambda::Real)
    rand(Distributions.Poisson(lambda))
end

(::Poisson)(lambda) = random(Poisson(), lambda)
is_discrete(::Poisson) = true

has_output_grad(::Poisson) = false
has_argument_grads(::Poisson) = (false,)

export poisson


include(joinpath(@__DIR__, "../../src/FLS.jl"))

####################
# REFLEXIVE GRAPHS #
####################

reflgraphschema = @acset LabeledGraph begin
    V = 2
    E = 4
    vlabel = [:V, :E]
    elabel = [:id, :refl, :src, :tgt]
    src = [1,1,2,2]
    tgt = [1,2,1,1]
end

reflgraph = FLS(:reflgraph, reflgraphschema, [], [
    (:srcid, [:refl, :src], []),
    (:tgtid, [:refl, :tgt], [])])


## hack attempts to provide some of SymPy's simplify routines.
## could be seriously improved

## + expand  (built in)
##
## + expand_log
## + expand_power_exp
## + expand_power_base
## + expand_trig

## + logcombine (logsimp)
## + powsimp
## + powdenest
## * combsimp
## + trigsimp

## * factor (over Q)
## * collect
## * coeffs
## * cancel
## * apart

### --------------------------------------------------

export trig_simp, expand_trig

get_fun(ex::BasicType{Val{:Add}}) = :(+)
get_fun(ex::BasicType{Val{:Sub}}) = :(-)
get_fun(ex::BasicType{Val{:Mul}}) = :(*)
get_fun(ex::BasicType{Val{:Div}}) = :(/)
get_fun(ex::BasicType{Val{:Pow}}) = :(^)
#for t in (:Symbol, :Integer, :Rational, :Complex) ...
get_fun(ex::BasicType{Val{:Symbol}}) = :identity
get_fun(ex::BasicType{Val{:Integer}}) = :identity
get_fun(ex::BasicType{Val{:Rational}}) = :identity
get_fun(ex::BasicType{Val{:Complex}}) = :identity

function get_fun(u::BasicType)
    ex = Basic(u)
    as = args(ex)
    fn = head(ex) |> string |> lowercase |> Symbol
    fn
end

abstract type AbstractSimplify end

## identities

## rewrite product of sin and cos

## sin(x)*cos(y) = 1/2 * (sin(x+y) + sin(x-y)) ...
function sc(ex)
    @vars u_ v_ x___
    out = replace(ex,
                  sin(u_)*cos(v_)*x___,
                  (isempty(x___)?Basic(1):prod(x___)) * (sin(expand(v_ + u_)) + sin(expand(u_ - v_))))
    expand(out)
end

function ss(ex)
    @vars u_ v_ x___
    out = replace(ex,
                  sin(u_)*sin(v_)*x___,
                  x___*(1//2)*(cos(expand(u_ - v_)) - cos(expand(u_ + v_))))
    expand(out)
end

function cc(ex)
    @vars u_ v_ x___
    out = replace(ex,
                  cos(u_)*cos(v_)*x___,
                  x___ * (1//2)*(cos(expand(u_ + v_)) + cos(expand(u_ - v_))))
    expand(out)
end

## hyperbolic
function shch(ex)
    @vars u_ v_ x___
    out = replace(ex,
                  sinh(u_)*cosh(v_)*x___,
                  x___*(1//2)*(sinh(expand(v_ + u_)) + sinh(expand(u_ - v_))))
    expand(out)
end

function shsh(ex)
    @vars u_ v_ x___
    out = replace(ex,
                  sinh(u_)*sinh(v_)*x___,
                  x___*(1//2)*(cosh(expand(u_ + v_))- cosh(expand(u_ - v_)) ))
    expand(out)
end

function chch(ex)
    @vars u_ v_ x___
    out = replace(ex,
                  cosh(u_)*cosh(v_)*x___,
                  x___ * (1//2)*(cosh(expand(u_ + v_)) + cosh(expand(u_ - v_))))
    expand(out)
end

### expand trig -- sin(x+y) -> sin(x)*sin(y) + cos(x)*cos(y)
function sxy(ex)
    @vars x_ y_ x___
    ex = replace(ex, sin(x_ + y_) * x___ => x___ * (sin(x_)*cos(y_) + sin(y_) * cos(x_)))
#    m = match(ex, sin(x_*y_))
#    !ismatch(m) && return ex
    #    is XX Number(x_) or Number(y_) and integer and agreater 2 then write as 2x + (n-2)x and expand#...
    ex
end

function cxy(ex)
    @vars x_ y_ x___
    replace(ex, cos(x_ + y_) * x___ => x___ * (cos(x_)*cos(y_) - sin(x_) * sin(y_)))
end

##################################################

##
## The basic _simp functions walk the syntax tree
## the specialized types then define what happens
## when the walking occurs with overrides as needed
function _simp(T::AbstractSimplify, ex::BasicType{Val{:Add}})
    sum(_simp(T, a) for a in args(Basic(ex)))
end

function _simp(T::AbstractSimplify, ex::BasicType{Val{:Mul}})
    prod(_simp(T, a) for a in args(Basic(ex)))
end

## "canonicalize" trig, hyperbolic, to use sin,cos or sinh, cosh
## Could make this a separate simplification, but that would
## involve walking the syntax tree again
_simp(::AbstractSimplify, ex::BasicType{Val{:Tan}}) = (u = args(Basic(ex));sin(u...)/cos(u...))
_simp(::AbstractSimplify, ex::BasicType{Val{:Cot}}) = (u = args(Basic(ex));cos(u...)/sin(u...))
_simp(::AbstractSimplify, ex::BasicType{Val{:Sec}}) = (u = args(Basic(ex));inv(cos(u...)))
_simp(::AbstractSimplify, ex::BasicType{Val{:Csc}}) = (u = args(Basic(ex));inv(sin(u...)))

_simp(::AbstractSimplify, ex::BasicType{Val{:Tanh}}) = (u = args(Basic(ex));sinh(u...)/cosh(u...))
_simp(::AbstractSimplify, ex::BasicType{Val{:Coth}}) = (u = args(Basic(ex));cosh(u...)/sinh(u...))
_simp(::AbstractSimplify, ex::BasicType{Val{:Sech}}) = (u = args(Basic(ex));inv(cosh(u...)))
_simp(::AbstractSimplify, ex::BasicType{Val{:Csch}}) = (u = args(Basic(ex));inv(sinh(u...)))

## leaves -- when args(ex) = []
function _simp(::AbstractSimplify, ex::T) where {T <: SymEngine.BasicNumber}
    Basic(ex)
end

function _simp(::AbstractSimplify, ex::BasicType{Val{:Symbol}})
    Basic(ex)
end

## catch all
function _simp(T::AbstractSimplify, ex::BasicType)
#    println("_simp: $ex $(typeof(ex))")
    eval(Expr(:call, get_fun(ex), broadcast(_simp, T, args(Basic(ex)))...))
end
_simp(T::AbstractSimplify, ex::Basic) = _simp(T, BasicType(ex))

## iterate until a fixed point
function _fixed_point(T::AbstractSimplify, ex, n=20)
    for i in 1:n
        ex1 = _simp(T, BasicType(ex))
        ex1 == ex && return expand(ex)
        ex = ex1
    end
    expand(ex)
end

##################################################

## check that walking works
struct BlankSimp <: AbstractSimplify end

##################################################
## TrigSimp
struct TrigSimp <: AbstractSimplify end

"""
    trig_simp

Expand trig products such as `sin(x)*cos(y)` into sums using double angle
formulas
"""
trig_simp(ex::Basic) = _fixed_point(TrigSimp(), ex)

function _simp(T::TrigSimp, ex::BasicType{Val{:Pow}})
    a,b = args(Basic(ex))
    if  head(b) == :Integer && b > 1
        @vars u_
        op = head(a)
        op == :Sin  && return replace(a^2, sin(u_)^2,   1//2*(1 - cos(2*u_)))  * a^(b-2)
        op == :Cos  && return replace(a^2, cos(u_)^2,   1//2*(1 + cos(2*u_)))  * a^(b-2)
        op == :Sinh && return replace(a^2, sinh(u_)^2, -1//2*(1 - cosh(2*u_))) * a^(b-2)
        op == :Cosh && return replace(a^2, cosh(u_)^2,  1//2*(1 + cosh(2*u_))) * a^(b-2)
    elseif head(b) == :Integer && b <= -1
        return _simp(T, a)^b
    end
    return ex
end

## products apply the simplification rules for sin*cos,...
function _simp(T::TrigSimp, ex::BasicType{Val{:Mul}})
    v = Basic(ex)
    u = v |> cc |> ss |> sc |> chch |> shsh |> shch
    u = expand(u)
    head(u) != :Mul && return u
    out = Basic(1)

    ## out =prod(_simp(T, a) for a in args(Basic(u)))
    for a in args(Basic(u))
        out = out * _simp(T, a)
    end
    return out
end

##################################################
## expand trig
struct ExpandTrig <: AbstractSimplify end

"""
    expand_trig

Apply double angle formulas to express as products of sines and cosines
"""
expand_trig(ex::Basic) = _fixed_point(ExpandTrig(), ex)

_simp(T::ExpandTrig, ex::BasicType{Val{:Sin}}) = sxy(Basic(ex))
_simp(T::ExpandTrig, ex::BasicType{Val{:Cos}}) = cxy(Basic(ex))

##################################################


module test_suite

using Test,
    BasisFunctions, BasisFunctions.Test

using DoubleFloats,
    FastTransforms,
    FFTW,
    LinearAlgebra,
    Random,
    StaticArrays,
    DomainSets,
    DomainIntegrals

import BasisFunctions.Test:
    supports_approximation,
    supports_interpolation,
    suitable_function,
    suitable_interpolation_grid

⊕ = BasisFunctions.:⊕

linspace(a,b,c) = range(a, stop=b, length=c)

my_rand = rand
Random.seed!(1234)

BF = BasisFunctions
const show_timings = false

# const LargeFloat = Double64
const LargeFloat = BigFloat

##########
# Testing
##########
ENV["JULIA_DEBUG"]="all"
include("test_scenariolist.jl")

Delimit("Utilities")
include("util/test_utilities.jl")
include("util/test_pgfplots.jl")

Delimit("Operators")
include("test_operators.jl")

Delimit("Dictionaries")
include("dictionaries/test_dictionaries.jl")
include("dictionaries/test_pwconstants.jl")

Delimit("Check evaluations, interpolations, extensions, expansions")
include("test_fourier.jl")
include("test_chebyshev.jl")
include("test_ops.jl")
include("test_gram.jl")

Delimit("Generic OPS")
include("test_generic_OPS.jl")

Delimit("Plots")
include("test_plots.jl")

println(" All tests passed!")

end # module


#=
demo_HDP_Gaussian1DGaussian1D

A demo for Hierarchical Dirichlet Process mixture models with Gaussian1DGaussian1D
Bayesian components.

28/07/2015
Adham Beyki, odinay@gmail.com
=#

using BNP

## --- synthesizing the data --- ##
true_gg = 2.0
true_aa = 0.5
n_groups = 100
n_group_j   = 100 * ones(Int, n_groups)
KK_truncation = 25

beta_tilde = rand(Distributions.Beta(1, true_gg), KK_truncation)
beta_tilde[KK_truncation] = 1.0
my_beta = BNP.stick_breaking(beta_tilde)

pi_tilde = zeros(Float64, n_groups, KK_truncation)
my_pi = zeros(Float64, n_groups, KK_truncation)
for jj = 1:n_groups
    for kk = 1:KK_truncation-1
        gamma1 = true_aa * my_beta[kk]
        gamma2 = true_aa * (1 - sum(my_beta[1:kk]))
        pi_tilde[jj, kk] = rand(Distributions.Beta(gamma1, gamma2))
    end
    pi_tilde[jj, KK_truncation] = 1.0
    my_pi[jj, :] = BNP.stick_breaking(pi_tilde[jj, :][:])
end

true_zz = Array(Vector{Int}, n_groups)
for jj = 1:n_groups
    true_zz[jj] = zeros(Int, n_group_j[jj])
    for ii = 1:n_group_j[jj]
        kk = sample(my_pi[jj, :][:])
        true_zz[jj][ii] = kk
    end
end

zz_flat = Int[]
for jj = 1:n_groups
	append!(zz_flat, true_zz[jj])
end

zz_unq = sort(unique(zz_flat))
true_KK = length(zz_unq)
for jj = 1:n_groups
	for kk = 1:true_KK
		idx = find(x -> x==zz_unq[kk], true_zz[jj])
		true_zz[jj][idx] = kk
	end
end

vv = 0.001          # fixed variance
ss = 2
true_atoms = [Gaussian1D(ss*kk, vv) for kk = 1:true_KK]

xx = Array(Vector{Float64}, n_groups)
true_nn = zeros(Int, n_groups, true_KK)
for jj = 1:n_groups
    xx[jj] = zeros(Float64, n_group_j[jj])
    for ii = 1:n_group_j[jj]
        kk = true_zz[jj][ii]
        xx[jj][ii] = sample(true_atoms[kk])
        true_nn[jj, kk] += 1
    end
end

m0 = mean(mean(xx))
v0 = 10.0
q0 = Gaussian1DGaussian1D(m0, v0, vv)

# constructing the HDP model
hdp_KK_init = 1
KK_truncation = 15
hdp_gg = 1.0
hdp_g1 = 0.1
hdp_g2 = 0.1
hdp_aa = 1.0
hdp_a1 = 0.1
hdp_a2 = 0.1
hdp = HDP(q0, hdp_KK_init, hdp_gg, hdp_g1, hdp_g2, hdp_aa, hdp_a1, hdp_a2)

# sampling
zz = Array(Vector{Int}, n_groups)
for jj = 1:n_groups
    zz[jj] = ones(Int, n_group_j[jj])
    zz[jj] = rand(1:hdp.KK, n_group_j[jj])
end

n_burnins   = 0
n_lags      = 0
n_samples   = 500
sample_hyperparam = true
n_internals = 10
store_every = 1000
results_path = ""
filename    = "demo_HDP_Gaussian1DGaussian1D_"

KK_list, KK_dict = truncated_gibbs_sampler(hdp, KK_truncation, xx, zz,
    n_burnins, n_lags, n_samples, sample_hyperparam, n_internals, store_every, results_path, filename)

KK_hist = hist(KK_list, 0.5:maximum(KK_list)+0.5)[2]
candidate_KK = indmax(KK_hist)
posterior_components, nn = posterior(hdp, xx, KK_dict[candidate_KK], candidate_KK)


# Monte Carlo estimation of the out-of-sample cost of a given first-stage decision

function estimateSolnQuality(z_soln::Array{Float64},covariate_obs::Array{Float64},degree::Float64,numMCScenarios::Int64,numMCReplicates::Int64,coeff_true::Array{Float64,2})

	objEstimates = zeros(Float64,numMCReplicates)

	for mc = 1:numMCReplicates
		srand(randomSeeds_MC[mc])
		demand_scen_MC = generateTrueCondScenarios(numMCScenarios,covariate_obs,degree,coeff_true)
		objEstimates[mc] = estimateCostOfSoln(z_soln,demand_scen_MC)
	end

	return objEstimates
end


using BSplines
using Test
using LinearAlgebra
using SparseArrays

function vecdist(a::AbstractVector, b::AbstractVector,
                 ϵ = eps(eltype(a)))
    δ = √(sum(abs2, a-b))
    δ, δ/√(sum(abs2, a .+ ϵ))
end

@testset "Knot sets" begin
    @testset "Linear" begin
        t = LinearKnotSet(7, 0, 1, 10)
        @test order(t) == 7
        @test numintervals(t) == 10
        @test length(t) == 23
        @test first(t) == 0
        @test last(t) == 1
        for i = 1:7
            @test t[i] == 0
            @test t[end-i+1] == 1
        end
        tt = range(0, stop=1, length=11)
        for i = 7:17
            @test t[i] == tt[i-6]
        end

        ttt = collect(t)
        @test length(t) == length(ttt)
        for i ∈ eachindex(t)
            @test t[i] == ttt[i]
        end

        @test eltype(t) <: Real
    end

    @testset "Exponential" begin
        t = ExpKnotSet(7, -8, 1, 31)
        @test order(t) == 7
        @test numintervals(t) == 31
        @test first(t) == 0
        @test last(t) == 10

        t′ = ExpKnotSet(7, -8, 1, 31, include0=false)
        @test order(t′) == 7
        @test numintervals(t′) == 31
        @test first(t′) == 1e-8
        @test last(t′) == 10

        t′′ = ExpKnotSet(7, -8, 1, 31, base=2, include0=false)
        @test order(t′′) == 7
        @test numintervals(t′′) == 31
        @test first(t′′) == 1/256
        @test last(t′′) == 2
    end
end

@testset "Quadrature" begin
    t = LinearKnotSet(1, 0, 1, 2)
    x,w = BSplines.lgwt(t)
    @test all(w .== 1/4)
    @test x == [-1,1,-1,1]/(4*√3) + [1,1,3,3]/4
end

@testset "Basis" begin
    k = 3
    t = LinearKnotSet(k, 0, 1, 2)
    basis = BSplines.Basis(t)
    @testset "Eval on subintervals" begin
        x₁ = range(-1,stop=-0.5,length=10)
        Bᵢ₁ = basis(x₁)
        @test norm(Bᵢ₁) == 0

        function testbasis(x)
            Bᵢ = basis(x)
            B̃ = spzeros(Float64, length(x), 4)
            B̃[:,1] = (x .>= 0) .* (x .< 0.5) .* ((2x .- 1).^2)
            B̃[:,2] = (x .>= 0) .* (x .< 0.5) .* (2/3*(1 .- (3x .- 1).^2)) +
                (x .>= 0.5) .* (x .< 1)  .* (2*(x .- 1).^2)
            B̃[:,3] = (x .>= 0) .* (x .< 0.5) .* (2*x.^2) +
                (x .>= 0.5) .* (x .< 1)  .* (2/3*(1 .- (3x .- 2).^2))
            B̃[:,4] = (x .>= 0.5) .* (x .< 1) .* ((2x .- 1).^2)
            for j = 1:4
                δ,δr = vecdist(Bᵢ[:,j],B̃[:,j])
                @test δ < 1e-15
                @test δr < 1e-15
            end
        end
        testbasis(range(0,stop=1,length=50))
        testbasis(range(-0.5,stop=0.6,length=40))
        testbasis(range(0.5,stop=1.6,length=40))
    end

    @testset "Scalar operators" begin
        @testset "Overlap matrix" begin
            B = basis(I)
            Br = [ 1/10  7/120  1/120      0
                  7/120    1/6   1/10  1/120
                  1/120   1/10    1/6  7/120
                      0  1/120  7/120   1/10]
            @test norm(B-Br) < 1e-15
        end
        @testset "Position operator" begin
            B = basis(x->x)
            Br = [ 1/120   1/96   1/480       0
                    1/96  7/120    1/20   1/160
                   1/480   1/20  13/120  23/480
                       0  1/160  23/480  11/120]
            @test norm(B-Br) < 1e-15
        end
    end

    @testset "Derivative operators" begin
        # References generated using AnalyticBSplines.jl
        ∂n = [derop(basis, n) for n ∈ 1:k-1]
        ∂ref = [[ -1//2    5//12   1//12   0
                  -5//12   0//1    1//3   1//12
                  -1//12  -1//3    0//1   5//12
                    0     -1//12  -5//12  1//2],
                [ 4//3  -2//1   2//3   0
                  2//1  -8//3   0//1  2//3
                  2//3   0//1  -8//3  2//1
                   0     2//3  -2//1  4//3]]
        for n ∈ 1:k-1
            @test norm(∂n[n]-∂ref[n]) < 1e-15
        end

        k′ = 7
        t′ = LinearKnotSet(k′, 0, 1, 3)
        basis′ = BSplines.Basis(t′)

        ∂′n = [derop(basis′, n) for n ∈ 1:k′-1]
        ∂′ref = [
 [    -1//2          10625//29568     283021//2395008    23827//1197504     479//199584        5//27216          1//149688         0                0
  -10625//29568          0//1          35867//177408    118891//1064448   26167//709632    11017//1419264     7141//8515584       1//946176         0
 -283021//2395008   -35867//177408         0//1           5129//39424     11645//101376   245743//4257792    20455//1216512    7141//8515584       1//149688
  -23827//1197504  -118891//1064448    -5129//39424          0//1         11035//118272   218689//2128896   245743//4257792   11017//1419264       5//27216
    -479//199584    -26167//709632    -11645//101376    -11035//118272        0//1         11035//118272     11645//101376    26167//709632      479//199584
      -5//27216     -11017//1419264  -245743//4257792  -218689//2128896  -11035//118272        0//1           5129//39424    118891//1064448   23827//1197504
      -1//149688     -7141//8515584   -20455//1216512  -245743//4257792  -11645//101376    -5129//39424          0//1         35867//177408   283021//2395008
        0               -1//946176     -7141//8515584   -11017//1419264  -26167//709632  -118891//1064448   -35867//177408        0//1         10625//29568
        0                 0               -1//149688        -5//27216      -479//199584   -23827//1197504  -283021//2395008  -10625//29568         1//2],
[    90//11      -27843//2464      169651//66528      2347//4752        377//5544         7//1188           1//4158            0                0
  16509//2464     -4413//616      -104639//133056   191011//266112    72047//177408  118771//1064448    31501//2128896        3//78848          0
 169651//66528  -104639//133056    -46861//22176    -16405//29568     58403//177408   45935//118272    121843//709632     31501//2128896       1//4158
   2347//4752    191011//266112    -16405//29568     -3083//3168     -36415//88704    39203//177408     45935//118272    118771//1064448       7//1188
    377//5544     72047//177408     58403//177408   -36415//88704     -5807//7392    -36415//88704      58403//177408     72047//177408      377//5544
      7//1188    118771//1064448    45935//118272    39203//177408   -36415//88704    -3083//3168      -16405//29568     191011//266112     2347//4752
      1//4158     31501//2128896   121843//709632    45935//118272    58403//177408  -16405//29568     -46861//22176    -104639//133056   169651//66528
       0              3//78848      31501//2128896  118771//1064448   72047//177408  191011//266112   -104639//133056     -4413//616       16509//2464
       0               0                1//4158          7//1188        377//5544      2347//4752      169651//66528     -27843//2464         90//11],
[  -108//1     41193//224    -178259//2016    10723//1008     41//24        43//252        1//126          0             0
 -23049//224     162//1       -78221//1344   -13759//2688   4773//1792    2023//1536    5039//21504       9//7168        0
 -93901//2016  78221//1344         0//1       -9741//896    -905//256     4079//3584   10065//7168     5039//21504      1//126
 -10723//1008  13759//2688      9741//896         0//1     -4621//896    -5055//1792    4079//3584     2023//1536      43//252
    -41//24    -4773//1792       905//256      4621//896       0//1      -4621//896     -905//256      4773//1792      41//24
    -43//252   -2023//1536     -4079//3584     5055//1792   4621//896        0//1      -9741//896    -13759//2688   10723//1008
     -1//126   -5039//21504   -10065//7168    -4079//3584    905//256     9741//896        0//1      -78221//1344   93901//2016
       0          -9//7168     -5039//21504   -2023//1536  -4773//1792   13759//2688   78221//1344     -162//1      23049//224
       0            0             -1//126       -43//252     -41//24    -10723//1008  178259//2016   -41193//224      108//1],
[  1080//1    -224775//112    413855//336     -58255//168     1045//28       185//42         5//21            0              0
 138105//112   -62505//28     851525//672    -354385//1344  -16085//896    56255//5376   34625//10752      135//3584         0
 232415//336  -781435//672     57055//112       7365//448   -45905//896   -25215//1792   25535//3584     34625//10752       5//21
  32465//168  -354385//1344     7365//448       8255//112      595//64     -3815//128   -25215//1792     56255//5376      185//42
   1045//28    -16085//896    -45905//896        595//64      5055//112      595//64    -45905//896     -16085//896      1045//28
    185//42     56255//5376   -25215//1792     -3815//128      595//64      8255//112     7365//448    -354385//1344    32465//168
      5//21     34625//10752   25535//3584    -25215//1792  -45905//896     7365//448    57055//112    -781435//672    232415//336
       0          135//3584    34625//10752    56255//5376  -16085//896  -354385//1344  851525//672     -62505//28     138105//112
       0             0             5//21         185//42      1045//28    -58255//168   413855//336    -224775//112      1080//1],
[   -7290//1    1578285//112   -1077255//112    203895//56     -26055//28       1395//14         45//7             0              0
 -1170045//112    40095//2     -3001995//224   2125575//448   -866295//896     -3375//256    127305//3584      3645//3584         0
  -873225//112  3274155//224     -18225//2     1109295//448     26325//896   -421605//1792   -10125//512     127305//3584       45//7
  -158535//56   2228985//448   -1109295//448         0//1      202095//448     26325//896   -421605//1792     -3375//256      1395//14
   -19305//28    866295//896     -26325//896   -202095//448         0//1      202095//448     26325//896    -866295//896     19305//28
    -1395//14      3375//256     421605//1792   -26325//896   -202095//448         0//1     1109295//448   -2228985//448    158535//56
      -45//7    -127305//3584     10125//512    421605//1792   -26325//896  -1109295//448     18225//2     -3274155//224    873225//112
         0        -3645//3584   -127305//3584     3375//256    866295//896  -2125575//448   3001995//224     -40095//2     1170045//112
         0             0            -45//7       -1395//14      26055//28    -203895//56    1077255//112   -1578285//112      7290//1],
[174960//7    -98415//2      70605//2     -107595//7      38070//7       -9180//7       1080//7            0            0
  98415//2   -677970//7    1944135//28    -241245//8    1185435//112    -79245//32    105435//448     10935//448        0
 115155//2  -3158865//28   1117395//14   -1852875//56    157545//16    -176175//224  -419985//448    105435//448    1080//7
 215595//7   -474525//8    2229525//56    -186705//14     26325//56     325215//112  -176175//224    -79245//32    13500//7
  72090//7  -2080485//112   157545//16      26325//56    -57105//14      26325//56    157545//16   -2080485//112   72090//7
  13500//7    -79245//32   -176175//224    325215//112    26325//56    -186705//14   2229525//56    -474525//8    215595//7
   1080//7    105435//448  -419985//448   -176175//224   157545//16   -1852875//56   1117395//14   -3158865//28   115155//2
       0       10935//448   105435//448    -79245//32   1185435//112   -241245//8    1944135//28    -677970//7     98415//2
       0            0         1080//7       -9180//7      38070//7     -107595//7      70605//2      -98415//2    174960//7]]

        for n ∈ 1:k′-1
            @test norm(∂′n[n]-∂′ref[n])/abs(1e-16 + norm(∂′ref[n])) < 1e-14
        end
    end

    @testset "Dirichlet0 conditions" begin
        k = 3
        t = LinearKnotSet(k, 0, 2, 4)
        basis = BSplines.Basis(t, 0, 0)

        ∂n = [derop(basis, n) for n ∈ 1:k-1]
        ∂ref = [[ 0    0       0       0      0     0
                  0   0//1    3//8    1//24   0     0
                  0  -3//8    0//1    5//12  1//24  0
                  0  -1//24  -5//12   0//1   3//8   0
                  0    0     -1//24  -3//8   0//1   0
                  0    0       0       0      0     0],
                [ 0    0      0      0      0    0
                  0  -8//3   1//3   1//3    0    0
                  0   1//3  -2//1   2//3   1//3  0
                  0   1//3   2//3  -2//1   1//3  0
                  0    0     1//3   1//3  -8//3  0
                  0    0      0      0      0    0]]
        for n ∈ 1:k-1
            @test norm(∂n[n]-∂ref[n]) < 1e-15
        end
    end
end

@testset "Splines" begin
    k = 7
    t = LinearKnotSet(k, 0, 1, 10)
    basis = BSplines.Basis(t)
    x = range(first(t), stop=last(t), length=101)[1:end-1] # Due to issue #2
    B = basis(x)
    for n = 1:k-1
        c = rand(n)
        f = x -> sum(c[i]*x^(i-1) for i in eachindex(c))
        S = Spline(f, basis)
        @test norm(S(B) - f.(x))/abs(1e-16 + norm(f.(x))) < 1e-14
    end
end


# ## Getting started

using MLJ, RDatasets, PrettyPrinting, Random

# We start by generating a 2D cloud of points

Random.seed!(3203)
X = randn(20, 2)
y = vcat(-ones(10), ones(10))

X = MLJ.table(X)
y = categorical(y);

# Let's fit a SVM classifier

@load SVC pkg=LIBSVM

svc_mdl = SVC()
svc = machine(svc_mdl, X, y)

fit!(svc);

# As usual we can check how it performs

ypred = predict(svc, X)
misclassification_rate(ypred, y)

# Not bad.

# ### Basic tuning

# As usual we could tune the model, for instance the penalty encoding the tradeoff between margin width and misclassification:

rc = range(svc_mdl, :cost, lower=0.1, upper=5)
tm = TunedModel(model=svc_mdl, ranges=[rc], tuning=Grid(resolution=10),
                resampling=CV(nfolds=3, rng=33), measure=misclassification_rate)
mtm = machine(tm, X, y)

fit!(mtm)

ypred = predict(mtm, X)
misclassification_rate(ypred, y)

# You could also change the kernel etc.


function main()
    n = 0
    check = 0

    while (check == 0)
        check = 1
        n += 20 * 19
        for m in 19:-1:1
            if (n % m != 0)
                check = 0
                break
            end
        end
    end

    println(n)
end

main()


#=
2D advection on unstructured grid using FV
=#

### If the Finch package has already been added, use this line #########
using Finch # Note: to add the package, first do: ]add "https://github.com/paralab/Finch.git"

### If not, use these four lines (working from the examples directory) ###
# if !@isdefined(Finch)
#     include("../Finch.jl");
#     using .Finch
# end
##########################################################################

init_finch("FVadvection2d");

useLog("FVadvection2dlog")

# Configuration setup
domain(2)
solverType(FV)
timeStepper(RK4)

# Mesh
# This rectangle covers [0, 0.1]x[0, 0.3]
# Uncomment the desired mesh.
mesh("utriangle.msh")  # Using triangles
#mesh("uquad.msh")     # Using quads

add_boundary_ID(2, (x,y) -> (x >= 0.1));
add_boundary_ID(3, (x,y) -> (y <= 0));
add_boundary_ID(4, (x,y) -> (y >= 0.3));

# Variables and BCs
u = variable("u", location=CELL)
boundary(u, 1, FLUX, 0) # x=0
boundary(u, 2, NO_BC) # x=1
boundary(u, 3, FLUX, "sin(10*pi*x)*sin(2*pi*t)^2") # y=0
boundary(u, 4, NO_BC) # y=1

# Time interval and initial condition
T = 0.7;
timeInterval(T)
initial(u, "0")

# The flux and source terms of the conservation equation
# F and S in the following equation:
# Dt(int(u dx)) = int(S dx) - int(F.n ds)
coefficient("a", [0.1, 1], type=VECTOR) # advection velocity
# The "upwind" function applies upwinding to the term (a.n)*u with flow velocity a.
# The optional third parameter is for tuning. Default upwind = 0, central = 1. Choose something between these.
flux(u, "upwind(a,u)")
# Note that there is no source() for this problem

#exportCode("fvad2dcode") # uncomment to export generated code to a file
#importCode("fvad2dcode") # uncomment to import code from a file

solve(u)

finalize_finch()

##### Uncomment below to plot

# xy = Finch.fv_info.cellCenters

# using Plots
# pyplot();
# display(plot(xy[1,:], xy[2,:], u.values[:], st=:surface))


using Dates
using ArgParse
using Printf
using Random
using GaussianProcesses

# before_all = Dates.now()

include("../../btg.jl")

###
###  Manually enter parsed args entries so we can run this file as a script
###
##
parsed_args = Dict()
push!(parsed_args, "randseed" => 1234)
push!(parsed_args, "lmax" => 0.5)
push!(parsed_args, "lmin" => 0.2)
push!(parsed_args, "noiselevel" => 1)
push!(parsed_args, "ntest" => 200)
push!(parsed_args, "ntrain" => 80)
push!(parsed_args, "p" => 1)

randseed = parsed_args["randseed"]; rng = MersenneTwister(randseed)
Random.seed!(randseed);

function covariate_fun(x, p)
    n = size(x, 1)
    d = size(x, 2)
    if p == 1
        return ones(n, 1)
    elseif p == 1 + d
        return hcat(ones(n), x)
    else
        throw(ArgumentError("Only support constant or linear convariate."))
    end
end

true_fun(x, s) = sin(x) + s*randn() + 3
true_fun_noise(x) = max(true_fun(x, 1e-1), 0)^1/3
true_fun_0(x) = true_fun(x, 0.)^1/3

# training set
n_train = parsed_args["ntrain"]
data = range(-pi, stop=pi, length=n_train)
#data = sort((rand(Float64, (n_train, )) .- 0.5)*2*pi)
target = (sin.(data) .+ 0.1 * parsed_args["noiselevel"].* randn(rng, n_train) .+ 3).^(1/3)
x = reshape(data, n_train, 1)
Fx = covariate_fun(x, parsed_args["p"])
max_train = maximum(target)
y = target ./ max_train
trainingData0 = trainingData(x, Fx, y)
d = getDimension(trainingData0); n = getNumPts(trainingData0); p = getCovDimension(trainingData0)
# testing set
n_test = parsed_args["ntest"]
x_test = range(-pi, stop=pi, length=n_test)
#x_test = (rand(Float64, (n_test, )) .- 0.5)*2*pi
x_test = sort(reshape(x_test, n_test, 1), dims=1)
Fx_test = covariate_fun(x_test, parsed_args["p"])
y_test_true = (sin.(x_test) .+ 3).^(1/3)
elapsedmin = 0

#parameter setting
# myquadtype = parsed_args["sparse"] ? ["SparseCarlo", "SparseCarlo"] : ["QuasiMonteCarlo", "QuasiMonteCarlo"]
myquadtype = ["Gaussian", "Gaussian"]
rangeλ = [-20.0 20.0]
#rangeλ = reshape([0.1], 1, 1)
lmin = parsed_args["lmin"]
lmax = parsed_args["lmax"]
#rangeθ = [1/lmax^2 1/lmin^2]
rangeθ = reshape([1.0], 1, 1)
@info "rangeθ, rangel:" rangeθ, [lmin lmax]
# rangeθ = [0.111 25]
# build btg model

function load_synthetic_btg()
    return btg(trainingData0, rangeθ, rangeλ; priorθ = inverseUniform(rangeθ), quadtype = myquadtype)
end

function getseed()
    return randseed
end


using LinearAlgebra
using ContinuousDPs
using QuantEcon: PFI, VFI, solve, stationary_values
using BasisMatrices: Basis, ChebParams, SplineParams, LinParams, nodes
using Test

include("test_cdp.jl")
include("test_lq_approx.jl")


using StochasticDiffEq, DiffEqDevTools, Test
using Random

f_linear_iip(du,u,p,t) = @.(du = 1.01*u)
σ_linear_iip(du,u,p,t) = @.(du = 0.87*u)

linear_analytic(u0,p,t,W) = @.(u0*exp(0.63155t+0.87W))

prob_sde_linear_iip = SDEProblem(SDEFunction(f_linear_iip,σ_linear_iip,
                             analytic=linear_analytic),σ_linear_iip,[1/2],(0.0,1.0))

Random.seed!(100)
dts = 1 .//2 .^(7:-1:3) #14->7 good plot

prob = prob_sde_linear_iip
println("EM")
sim  = test_convergence(dts,prob,EM(),save_everystep=false,trajectories=Int(1e4),
                        weak_timeseries_errors=false)
@test abs(sim.𝒪est[:weak_final]-1) < 0.3
#@test abs(sim.𝒪est[:weak_l2]-1) < 0.3
#@test abs(sim.𝒪est[:weak_l∞]-1) < 0.3
println("SimplifiedEM")
sim  = test_convergence(dts,prob,SimplifiedEM(),save_everystep=false,trajectories=Int(5e5),
                        weak_timeseries_errors=false)
@test abs(sim.𝒪est[:weak_final]-1) < 0.3
#@test abs(sim.𝒪est[:weak_l2]-1) < 0.3
#@test abs(sim.𝒪est[:weak_l∞]-1) < 0.35
println("RKMil")
sim = test_convergence(dts,prob,RKMil(),save_everystep=false,trajectories=Int(1e4),
                        weak_timeseries_errors=false)
@test abs(sim.𝒪est[:weak_final]-1) < 0.3
#@test abs(sim.𝒪est[:weak_l2]-1) < 0.3
#@test abs(sim.𝒪est[:weak_l∞]-1) < 0.3
println("RKMilCommute")
sim = test_convergence(dts,prob,RKMilCommute(),save_everystep=false,trajectories=Int(1e4),
                        weak_timeseries_errors=false)
@test abs(sim.𝒪est[:weak_final]-1) < 0.3
#@test abs(sim.𝒪est[:weak_l2]-1) < 0.3
#@test abs(sim.𝒪est[:weak_l∞]-1) < 0.3
println("RKMilGeneral")
sim = test_convergence(dts,prob,RKMilGeneral(),save_everystep=false,trajectories=Int(1e4),
                        weak_timeseries_errors=false)
@test abs(sim.𝒪est[:weak_final]-1) < 0.3
#@test abs(sim.𝒪est[:weak_l2]-1) < 0.3
#@test abs(sim.𝒪est[:weak_l∞]-1) < 0.3
println("SROCK1")
sim = test_convergence(dts,prob,SROCK1(),save_everystep=false,trajectories=Int(4e4),
                        weak_timeseries_errors=false)
@test abs(sim.𝒪est[:weak_final]-1) < 0.3
#@test abs(sim.𝒪est[:weak_l2]-1) < 0.3
#@test abs(sim.𝒪est[:weak_l∞]-1) < 0.3
println("SROCK2")
dts = 1 .//2 .^(8:-1:1) #14->7 good plot
sim = test_convergence(dts,prob,SROCK2(),save_everystep=false,trajectories=Int(5e5),
                        weak_timeseries_errors=false)
@show sim.𝒪est[:weak_final]
@test abs(sim.𝒪est[:weak_final]-2) < 0.35
#@test abs(sim.𝒪est[:weak_l2]-2) < 0.3
#@test abs(sim.𝒪est[:weak_l∞]-2) < 0.3
dts = 1 .//2 .^(7:-1:2) #14->7 good plot
println("SROCKEM")
sim = test_convergence(dts,prob,SROCKEM(strong_order_1=false),save_everystep=false,trajectories=Int(1e4),
                        weak_timeseries_errors=false)
@test abs(sim.𝒪est[:weak_final]-1) < 0.3
#@test abs(sim.𝒪est[:weak_l2]-1) < 0.3
#@test abs(sim.𝒪est[:weak_l∞]-1) < 0.3
println("SROCKEM")
sim = test_convergence(dts,prob,SROCKEM(),save_everystep=false,trajectories=Int(1e4),
                        weak_timeseries_errors=false)
@test abs(sim.𝒪est[:weak_final]-1) < 0.3
#@test abs(sim.𝒪est[:weak_l2]-1) < 0.3
#@test abs(sim.𝒪est[:weak_l∞]-1) < 0.3
println("SKSROCK")
sim = test_convergence(dts,prob,SKSROCK(),save_everystep=false,trajectories=Int(5e4),
                        weak_timeseries_errors=false)
@test abs(sim.𝒪est[:weak_final]-1) < 0.3
#@test abs(sim.𝒪est[:weak_l2]-1) < 0.3
#@test abs(sim.𝒪est[:weak_l∞]-1) < 0.3

#omitting tests for incomplete methods
# sim = test_convergence(dts,prob,TangXiaoSROCK2(version_num=1),save_everystep=false,trajectories=Int(4e4),
#                           weak_timeseries_errors=false)
# @test abs(sim.𝒪est[:weak_final]-2) < 0.3
# #@test abs(sim.𝒪est[:weak_l2]-2) < 0.3
# #@test abs(sim.𝒪est[:weak_l∞]-2) < 0.3
# sim = test_convergence(dts,prob,TangXiaoSROCK2(version_num=2),save_everystep=false,trajectories=Int(4e4),
#                           weak_timeseries_errors=false)
# @test abs(sim.𝒪est[:weak_final]-2) < 0.3
# #@test abs(sim.𝒪est[:weak_l2]-2) < 0.3
# #@test abs(sim.𝒪est[:weak_l∞]-2) < 0.3
# sim = test_convergence(dts,prob,TangXiaoSROCK2(version_num=3),save_everystep=false,trajectories=Int(4e4),
#                           weak_timeseries_errors=false)
# @test abs(sim.𝒪est[:weak_final]-2) < 0.3
# #@test abs(sim.𝒪est[:weak_l2]-2) < 0.3
# #@test abs(sim.𝒪est[:weak_l∞]-2) < 0.3
# sim = test_convergence(dts,prob,TangXiaoSROCK2(version_num=4),save_everystep=false,trajectories=Int(4e4),
#                           weak_timeseries_errors=false)
# @test abs(sim.𝒪est[:weak_final]-2) < 0.3
# #@test abs(sim.𝒪est[:weak_l2]-2) < 0.3
# #@test abs(sim.𝒪est[:weak_l∞]-2) < 0.3
# sim = test_convergence(dts,prob,TangXiaoSROCK2(version_num=5),save_everystep=false,trajectories=Int(4e4),
#                           weak_timeseries_errors=false)
# @test abs(sim.𝒪est[:weak_final]-2) < 0.3
# #@test abs(sim.𝒪est[:weak_l2]-2) < 0.3
# #@test abs(sim.𝒪est[:weak_l∞]-2) < 0.3

println("WangLi3SMil_A")
sim = test_convergence(dts,prob,WangLi3SMil_A(),save_everystep=false,trajectories=Int(1e4),
                        weak_timeseries_errors=false)
@test abs(sim.𝒪est[:weak_final]-1) < 0.3
#@test abs(sim.𝒪est[:weak_l2]-1) < 0.3
#@test abs(sim.𝒪est[:weak_l∞]-1) < 0.3
println("WangLi3SMil_B")
sim = test_convergence(dts,prob,WangLi3SMil_B(),save_everystep=false,trajectories=Int(1e4),
                        weak_timeseries_errors=false)
@test abs(sim.𝒪est[:weak_final]-1) < 0.3
#@test abs(sim.𝒪est[:weak_l2]-1) < 0.3
#@test abs(sim.𝒪est[:weak_l∞]-1) < 0.3
println("WangLi3SMil_C")
sim = test_convergence(dts,prob,WangLi3SMil_C(),save_everystep=false,trajectories=Int(1e4),
                        weak_timeseries_errors=false)
@test abs(sim.𝒪est[:weak_final]-1) < 0.3
#@test abs(sim.𝒪est[:weak_l2]-1) < 0.3
#@test abs(sim.𝒪est[:weak_l∞]-1) < 0.3
println("WangLi3SMil_D")
sim = test_convergence(dts,prob,WangLi3SMil_D(),save_everystep=false,trajectories=Int(1e4),
                        weak_timeseries_errors=false)
@test abs(sim.𝒪est[:weak_final]-1) < 0.3
#@test abs(sim.𝒪est[:weak_l2]-1) < 0.3
#@test abs(sim.𝒪est[:weak_l∞]-1) < 0.3
println("WangLi3SMil_E")
sim = test_convergence(dts,prob,WangLi3SMil_E(),save_everystep=false,trajectories=Int(1e4),
                        weak_timeseries_errors=false)
@test abs(sim.𝒪est[:weak_final]-1) < 0.3
#@test abs(sim.𝒪est[:weak_l2]-1) < 0.3
#@test abs(sim.𝒪est[:weak_l∞]-1) < 0.3
println("WangLi3SMil_F")
sim = test_convergence(dts,prob,WangLi3SMil_F(),save_everystep=false,trajectories=Int(1e4),
                        weak_timeseries_errors=false)
@test abs(sim.𝒪est[:weak_final]-1) < 0.3
#@test abs(sim.𝒪est[:weak_l2]-1) < 0.3
#@test abs(sim.𝒪est[:weak_l∞]-1) < 0.3
println("SRI")
dts = 1 .//2 .^(8:-1:2)
sim = test_convergence(dts,prob,SRI(),save_everystep=false,trajectories=Int(2e4),
                        weak_timeseries_errors=false)
@test abs(sim.𝒪est[:weak_final]-2) < 0.35
#@test abs(sim.𝒪est[:weak_l2]-2) < 0.3
#@test abs(sim.𝒪est[:weak_l∞]-2) < 0.3
println("SRIW1")
sim = test_convergence(dts,prob,SRIW1(),save_everystep=false,trajectories=Int(1e5),
                        weak_timeseries_errors=false)
@test abs(sim.𝒪est[:weak_final]-2) < 0.3
#@test abs(sim.𝒪est[:weak_l2]-2) < 0.3
#@test abs(sim.𝒪est[:weak_l∞]-2) < 0.3


module Utils

include("utils/factor.jl")
include("utils/cutils.jl")
include("utils/simple_graph.jl")

# Helper functions for normalize; may be useful elsewhere
function _entries(xs::Array)
    ys = map(_entries, xs)
    result = []
    for y in ys
        append!(result, y)
    end
    return result
end

_entries(d::Dict) = _entries(collect(map(_entries, values(d))))

_entries(xs::Tuple) = _entries([y for y in xs])

_entries(x::Number) = [x]

_entries(::Nothing) = []

_transform(f::Function, xs::Array) = [_transform(f,x) for x in xs]

_transform(f::Function, d::Dict) = Dict([(k,_transform(f,x)) for (k,x) in d])

_transform(f::Function, xs::Tuple) = tuple([_transform(f,x) for x in xs]...)

_transform(f::Function, x::Number) = f(x)

_transform(::Function, ::Nothing) = nothing

"""
    normalize(xs)

Normalize an array of non-negative reals to sum to 1
"""
function normalize(xs)
    tot = 0.0
    ys = _entries(xs)
    for y in ys
        if y < 0
            error("Negative probability in $(ys)")
        end
        tot += y
    end
    # In the case of learning, it is legitimately possible that a case never
    # happens, so all statistics are zero. Therefore we accept this case.
    if tot == 0.0
        f = x -> 1.0 / length(ys)
    else
        f = x -> x / tot
    end
    return _transform(f, xs)
end

end


export OperatorFunction

abstract OperatorFunction{BT,FF,T} <: Operator{T}

immutable ConcreteOperatorFunction{BT<:Operator,FF,T} <: OperatorFunction{BT,FF,T}
    op::BT
    f::FF
end

ConcreteOperatorFunction(op::Operator,f::Function) =
    ConcreteOperatorFunction{typeof(op),typeof(f),eltype(op)}(op,f)
OperatorFunction(op::Operator,f::Function) = ConcreteOperatorFunction(op,f)

for op in (:domainspace,:rangespace,:domain,:bandinds)
    @eval begin
        $op(OF::ConcreteOperatorFunction) = $op(OF.op)
    end
end

function getindex(OF::ConcreteOperatorFunction,k::Integer,j::Integer)
    @assert isdiag(OF.op)
    if k==j
        OF.f(OF.op[k,k])::eltype(OF)
    else
        zero(eltype(OF))
    end
end

function Base.convert{T}(::Type{Operator{T}},D::ConcreteOperatorFunction)
    if T==eltype(D)
        D
    else
        ConcreteOperatorFunction{typeof(D.op),T}(D.op,D.f)
    end
end

for OP in (:(Base.inv),:(Base.sqrt))
    @eval begin
        $OP(D::DiagonalOperator) = OperatorFunction(D,$OP)
        $OP(C::ConstantTimesOperator) = $OP(C.λ)*$OP(C.op)
        function $OP(D::ConcreteOperatorFunction)
            @assert isdiag(D)
            OperatorFunction(D.op,x->$OP(D.f(x)))
        end
        $OP(A::Operator) = isdiag(A) ? OperatorFunction(A,$OP) :
                                error("Not implemented.")
    end
end

Base.sqrt(S::SpaceOperator) = SpaceOperator(sqrt(S.op),S.domainspace,S.rangespace)
Base.inv(S::SpaceOperator) = SpaceOperator(inv(S.op),S.rangespace,S.domainspace)


using LinearAlgebra
using IterativeSolvers
import LinearAlgebra: ldiv!

struct Projector{VT<:AbstractVector}
    v::VT
end

ldiv!(p::Projector, x) = x - p.v*(p.v'*x)
ldiv!(x, p::Projector) = x - p.v'*(x*p.v)

gs(A::AbstractMatrix) = (ei = eigen(A); (ei.values[1], ei.vectors[:,1]))
function gs_backward(A, e, v, de, dv)
    #x = gmres((I-v*v')*(e*I-A), (I-v*v')*dv)
    x = gmres((e*I-A)*(I-v*v'), dv)'
    #x = gmres(e*I-A, dv, Pl=Projector(v))
    #x = gmres(e*I-A, (I-v*v')*dv, Pl=Projector(v))
    #x = gmres(e*I-A, (I-v*v')*dv)
    #(de*v + (I-v*v')*pinv(e*I-A)*dv)*v'
    (de*v + x)*v'
end

function num_diff(A, de, dv)
    δ = 1e-5
    A = A |> copy
    dA = zeros(n, n)
    for i = 1:n
        for j = 1:n
            A[i,j]+=δ/2
            pos = gs(A)
            A[i,j]-=δ
            neg = gs(A)
            A[i,j]+=δ/2
            grad = (pos.-neg)./δ
            dA[i,j] = grad[1]*de + sum(grad[2].*dv)
        end
    end
    dA
end

n=3
A = randn(n,n); A += A'
e,v = gs(A)
de = randn()
dv = randn(n)
de = 0
dv = [1.0, 0,0]
dA = gs_backward(A, e, v, de, dv)

@show num_diff(A, e, v, de, dv)
@show dA

using Test
@test isapprox(dA .|> abs, num_diff() .|> abs)


srand(1)
@time for i = 1:8
    n = prod(1:i)
    a = sprand(n, n, 0.1 / n)
    for j in 1:i
        @test sumabs2(SparseMatrixCSC(SparseMatrixBSC(a, j, i)) - a) == 0
        v = rand(size(a, 2))
        @test SparseMatrixBSC(a, j, i) * v == a * v
    end
end


# Contour + heatmap

using CairoMakie
let
    f(x,y) = (x + 2y^2) * abs(sin(y) + cos(x))
    x = y = 1:0.2:20
    z = [f(x,y) for x in x, y in y]

    fig = Figure(resolution=(800,400), fontsize = 22)
    axs = [Axis(fig[1, j], aspect = 1, xlabel = "x", ylabel = j ==1 ? "y" : "")
        for j in 1:2]
    p1 = CairoMakie.heatmap!(axs[1], x, y, z, colormap = :plasma)
#     CairoMakie.contour!(axs[2], x, y, z; color = :black, levels = 100:1:101)
    CairoMakie.heatmap!(axs[2], x, y, z; colormap = (:plasma, 0.5))
    CairoMakie.contour!(axs[2], x, y, z; color = :white, levels = 100:1:101)
    Colorbar(fig[1,3], p1, width = 20, ticksize=20, tickalign = 1)
    [limits!(axs[i], 1,20,1,20) for i in 1:2]
    display(fig)
end


## VISIBLE SECTION
#

function flock!(Ba, Bb; W, nfun=neighbors_n, tau=0.1)
  Ba0 = deepcopy(Ba)
  Bb0 = deepcopy(Bb)
  @simd for bi in Ba
    deli = neighbors(bi, Ba0)

    si = seperate(bi, deli)
    ai = align(bi, deli)
    ci = cohere(bi, deli)
    ev = evade(bi, Bb0, c=tau)
    bp = bound_position(bi)

    bi.vel += bi.ws*si + bi.wa*ai + bi.wc*ci + bi.we*ev + bi.wb*bp
    limit_velocity!(bi)
    bi.pos += tau*bi.vel
  end
  @simd for bi in Bb
    si = seperate(bi, Bb0)
    pu = pursuit(bi, Ba0, c=tau)
    bp = bound_position(bi)

    bi.vel += bi.ws*si + bi.wp*pu + bi.wb*bp
    limit_velocity!(bi)
    bi.pos += tau*bi.vel
  end
end

function pursuit(bi, O; c)
  j, cg = 0, zeros(dim(bi))
  for bj in O
    if dis(bi, bj) < bi.dp
      cg += bj.pos + c*dis(bi, bj)*bj.vel
      j += 1
    end
  end
  if j != 0
    pit = cg/j - bi.pos
    dv = (pit/norm(pit))*bi.vmax - bi.vel
    return dv
  else
    dv = cg #zeros(dim(bi))
    return dv
  end
end

function evade(bi, O; c)
  bj = partialsort(collect(O), 1, by = bj -> dis(bi, bj))
  if dis(bi, bj) < bi.de
    pit = (bj.pos + c*dis(bi, bj)*bj.vel) - bi.pos
    dv = -(pit/norm(pit))*bi.vmax - bi.vel
    return dv
  else
    dv = zeros(dim(bi))
    return dv
  end
end

## HIDDEN SECTION
#

function initialize_pureva(;
  dim=2, nbb=2,
  nba=100, dv=5., av=3.05, ds=dv/2., nc=4, de=3*dv, dp=3*dv,
  w0=1., ws=.1, wa=.05, wc=.01, ww=0., wb=.1, wp=.01, we=.1,
  vmax=5., vmin=.4*vmax,
  bmin=-(200/dim^2)*ones(dim), bmax=(200/dim^2)*ones(dim)
)
  Random.seed!(0)
  pfun = x -> bmin + x .* (bmax .- bmin)
  vfun = x -> (x ./ norm(x)) .* vmax / 2
  Bfun = nb -> [Boid(i, pfun(rand(dim)), vfun(rand(dim)*2 - ones(dim)),
    dv, av, ds, w0, ws, wa, wc, vmax, vmin, bmin, bmax, ww, wb, wp, we, nc, dp, de) for i = 1:nb]
  if dim == 2
    Wxy = B -> Node(collect(Point2f0(b.pos) for b in B))
  else
    Wxy = B -> Node(collect(Point3f0(b.pos) for b in B))
  end
  B = map(Bfun, [nba, nbb])
  W = World()
  W.xy = map(Wxy, B)
  W.sc = scatter(W.xy[1])
  scatter!(W.xy[2], color = "red")
  return (B[1], B[2], W)
end

function draw!(W, Ba, Bb; t, dim=2, fps=60)
  if dim == 2
    Wxy = B -> collect(Point2f0(b.pos) for b in B)
  else
    Wxy = B -> collect(Point3f0(b.pos) for b in B)
  end
  W.xy[1][] = Wxy(Ba)
  W.xy[2][] = Wxy(Bb)
  dt = time() - t
  if dt > 1/fps
    sleep(1/fps)
    return time()
  else
    sleep(1/fps - dt)
    return time()
  end
  #sleep(1/fps)
  #return time()
end

function run_pureva(; nba=100, nbb=2, nfun=neighbors_n, nmax=1000, dim=2)
  Ba, Bb, W = initialize_pureva(nba=nba, nbb=nbb, dim=dim)
  t = time()
  display(W.sc)

  try
    @fastmath for _ = 1:nmax
      t = draw!(W, Ba, Bb, t=t, dim=dim)
      #stat!(W, Ba, Bb)
      flock!(Ba, Bb, W=W, nfun=nfun)
    end
  catch InterruptException
    println("\nSimulation interrupted.")
  end
end


function test_inner_product_layer(backend::Backend, n_input, T, eps)
  println("-- Testing InnerProductLayer on $(typeof(backend)){$T}...")

  ############################################################
  # Prepare Data for Testing
  ############################################################
  # inner-product layer should be able to take blobs with
  # different num, but the input feature dimensions should
  # be the same
  batch_size   = [abs(rand(Int)) % 50 + 1 for i = 1:n_input]
  orig_dim_all = (10, 20, 3)
  orig_dim     = prod(orig_dim_all)
  target_dim   = 30

  X = [rand(T, orig_dim_all..., n) for n in batch_size]
  W = rand(T, orig_dim, target_dim)
  b = rand(T, target_dim)

  ############################################################
  # Setup
  ############################################################
  layer = InnerProductLayer(name="ip", output_dim=target_dim,
      tops=Array{Symbol}(n_input), bottoms=Array{Symbol}(n_input))
  inputs = Blob[make_blob(backend, x) for x in X]
  diffs = Blob[make_blob(backend, x) for x in X]

  println("    > Setup")
  state  = setup(backend, layer, inputs, diffs)

  @test length(state.W) == length(W)
  @test length(state.b) == length(b)
  copy!(state.W, W)
  copy!(state.b, b)

  println("    > Forward")
  forward(backend, state, inputs)

  X2 = [reshape(X[i], orig_dim, batch_size[i]) for i = 1:n_input]
  res = [W' * x2 .+ b for x2 in X2]

  res_layer = [similar(x) for x in res]
  for i = 1:n_input
    copy!(res_layer[i], state.blobs[i])
    @test all(-eps .< res_layer[i] - res[i] .< eps)
  end

  println("    > Backward")
  top_diff = [rand(T, size(state.blobs_diff[i])) for i = 1:n_input]
  for i = 1:n_input
    copy!(state.blobs_diff[i], top_diff[i])
  end
  backward(backend, state, inputs, diffs)

  top_diff = [reshape(top_diff[i], target_dim, batch_size[i]) for i = 1:n_input]
  bias_grad = similar(b)
  copy!(bias_grad, state.∇b)
  bias_grad_expected = sum([sum(top_diff[i],2) for i = 1:n_input])
  @test all(-eps .< vec(bias_grad) - vec(bias_grad_expected) .< eps)

  X_mat = [reshape(X[i], orig_dim, batch_size[i]) for i = 1:n_input]
  weight_grad = similar(W)
  copy!(weight_grad, state.∇W)
  weight_grad_expected = sum([X_mat[i]*top_diff[i]' for i = 1:n_input])
  @test all(-eps .< vec(weight_grad) - vec(weight_grad_expected) .< eps)

  back_grad = [similar(X_mat[i]) for i = 1:n_input]
  for i = 1:n_input
    copy!(back_grad[i], diffs[i])
    @test all(-eps .< vec(back_grad[i]) - vec(W * top_diff[i]) .< eps)
  end

  shutdown(backend, state)
end
function test_inner_product_layer(backend::Backend)
  test_inner_product_layer(backend, 3, Float32, 1e-3)
  test_inner_product_layer(backend, 3, Float64, 1e-10)
end

if test_cpu
  test_inner_product_layer(backend_cpu)
end
if test_gpu
  test_inner_product_layer(backend_gpu)
end


using Test
using UncertainData
using Distributions
using StaticArrays
using StatsBase
using KernelDensity

#############################################
# UncertainValues module
#############################################
@testset "Uncertain values" begin
    @testset "Assign distributions" begin
        include("uncertain_values/test_assign_distributions.jl")

        @testset "Uncertain values" begin
            include("uncertain_values/test_uncertain_values.jl")
        end

        @testset "minmax" begin
            include("uncertain_values/test_minmax.jl")
        end

        @testset "CertainValue" begin
            include("uncertain_values/test_certain_values.jl")
        end

        @testset "Merging" begin
            include("uncertain_values/test_merging.jl")
        end
    end
end

@testset "Populations" begin
    include("uncertain_values/populations/test_UncertainScalarPopulation.jl")
    include("uncertain_values/populations/test_ConstrainedUncertainScalarPopulation.jl")
end

#############################################
# UncertainDatasets module
#############################################
@testset "Uncertain datasets" begin
    include("uncertain_datasets/test_uncertain_datasets.jl")
end

#############################################
# Resampling uncertain values
#############################################
@testset "Uncertain values" begin
    include("resampling/test_resampling_uncertain_values.jl")
end
#############################################
# Resampling uncertain tuples
#############################################
@testset "Uncertain tuples" begin
    include("resampling/test_resampling_uncertain_tuples.jl")
end

#############################################
# Resampling uncertain datasets
#############################################

# Resampling schemes
@testset "Resampling" begin

    @testset "BinnedResampling" begin
        include("resampling/resampling_schemes/test_BinnedResampling.jl")
    end

    @testset "BinnedWeightedResampling" begin
        include("resampling/resampling_schemes/test_BinnedWeightedResampling.jl")
    end

    @testset "BinnedMeanResampling" begin
        include("resampling/resampling_schemes/test_BinnedMeanResampling.jl")
    end

    @testset "BinnedMeanWeightedResampling" begin
        include("resampling/resampling_schemes/test_BinnedMeanWeightedResampling.jl")
    end

    @testset "ConstrainedIndexValueResampling" begin
        include("resampling/resampling_schemes/test_ConstrainedIndexValueResampling.jl")
    end

    @testset "ContrainedValueResampling" begin
        include("resampling/resampling_schemes/test_ConstrainedValueResampling.jl")
    end

    @testset "SequentialResampling" begin
        include("resampling/resampling_schemes/test_SequentialResampling.jl")
    end

    @testset "SequentialInterpolatedResampling" begin
        include("resampling/resampling_schemes/test_SequentialInterpolatedResampling.jl")
    end

    @testset "RandomSequences" begin
        include("resampling/resampling_schemes/test_RandomSequences.jl")
    end

    # Define an array of uncertain values `uvals` that we can construct datasets from.
    include("resampling/define_uncertain_values.jl")

    @testset "Vectors of uncertain values" begin

        # Resampling vectors of uncertain values
        #---------------------------------------
        include("resampling/uncertain_vectors/test_resampling_vectors.jl")
        include("resampling/uncertain_vectors/test_resampling_vectors_apply_funcs.jl")
        include("resampling/uncertain_vectors/test_resampling_vectors_constraints.jl")
    end

    # Resampling uncertain datasets
    #-------------------------------
    @testset "UncertainDataset" begin
        include("resampling/uncertain_datasets/test_resampling_datasets.jl")
    end

    # Resampling uncertain value datasets
    #-------------------------------------
    @testset "UncertainValueDataset" begin
        include("resampling/uncertain_datasets/test_resampling_datasets_uncertainvaluedataset.jl")
        include("resampling/uncertain_datasets/test_resampling_datasets_uncertainvaluedataset_apply_funcs.jl")
        include("resampling/uncertain_datasets/test_resampling_datasets_uncertainvaluedataset_constraints.jl")
    end
    # Resampling uncertain index datasets
    #-------------------------------------
    @testset "UncertainIndexDataset" begin
        include("resampling/uncertain_datasets/test_resampling_datasets_uncertainindexdataset.jl")
        include("resampling/uncertain_datasets/test_resampling_datasets_uncertainindexdataset_apply_funcs.jl")
        include("resampling/uncertain_datasets/test_resampling_datasets_uncertainindexdataset_constraints.jl")
    end

    # Resampling uncertain index-value datasets
    #-------------------------------------
    @testset "UncertainIndexValueDataset" begin
        include("resampling/uncertain_datasets/test_resampling_uncertainindexvaluedataset.jl")
        include("resampling/uncertain_datasets/test_resampling_with_schemes.jl")
    end

    # Special resampling constraints
    #-----------------------------
    @testset "Special resampling constraints" begin
        @testset "Sequential" begin
            include("resampling/uncertain_datasets/sequential/test_resampling_sequential_increasing.jl")
            include("resampling/uncertain_datasets/sequential/test_resampling_sequential_decreasing.jl")
        end
    end

    # Resampling inplace.
    #-----------------------------
    @testset "Inplace resampling" begin
        include("resampling/test_resampling_inplace.jl")
    end

    #############################################
    # Resampling uncertain datasets element-wise
    #############################################
    @testset "Element-wise" begin
        include("resampling/uncertain_datasets/test_resampling_abstractuncertainvaluedataset_elwise.jl")
    end
end

############################################
# Interpolation and binning
#############################################
@testset "Interpolation/binning" begin
    include("generic_interpolation/test_findall_nan_chunks.jl")
    include("generic_interpolation/test_interpolate_nans.jl")
    include("resampling/uncertain_datasets/test_interpolation.jl")
    include("binning/test_binning.jl")

    # Interpolation with resampling schemes
    @testset "Intp w/ resampling scheme" begin
    include("resampling/binning/test_bin_BinnedResampling.jl")
    include("resampling/binning/test_bin_BinnedWeightedResampling.jl")
    end
end

#############################
# Mathematics
#############################
@testset "Mathematics" begin
    include("mathematics/test_mathematics.jl")
end

############################################
# Uncertain statistics
#############################################
@testset "Statistics" begin
    include("uncertain_statistics/uncertain_values/test_core_stats_values_point_estimates.jl")
    include("uncertain_statistics/uncertain_values/test_core_stats_values_pair_estimates.jl")

    include("uncertain_statistics/uncertain_datasets/test_core_stats_datasets_single_dataset_estimates.jl")
    include("uncertain_statistics/uncertain_datasets/test_core_stats_datasets_pairwise_estimates.jl")

    include("uncertain_statistics/test_hypothesistests.jl")
end
#include("uncertain_statistics/test_hypothesistests_timeseries.jl")

#########################################
# Resampling with constraints and models
#########################################

# #####################
# # Resampling schemes
# #####################
# include("resampling/resampling_schemes/test_ConstrainedResampling.jl")

# #######################
# # Sampling constraints
# #######################
@testset "Resampling with constraints" begin
    include("sampling_constraints/test_sampling_constraints.jl")
    include("sampling_constraints/test_constrain_certainvalue.jl")
    include("sampling_constraints/test_constrain_population.jl")
    include("sampling_constraints/test_constrain_uncertainvalues.jl")
    include("sampling_constraints/test_constrain_uncertainvalues_kde.jl")
    include("sampling_constraints/test_constrain_uncertaindatasets.jl")
    include("sampling_constraints/test_constrain_uncertainvaluedatasets.jl")
    include("sampling_constraints/test_constrain_uncertainindexdatasets.jl")

    include("sampling_constraints/test_constrain_with_schemes.jl")

end