export findRelatedFromPotential

"""
    $SIGNATURES
Internal method to set which dimensions should be used as the decision variables for later numerical optimization.
"""
function _setCCWDecisionDimsConv!(ccwl::Union{CommonConvWrapper{F},
                                              CommonConvWrapper{Mixture{N_,F,S,T}}} ) where {N_,F<:Union{AbstractRelativeMinimize, AbstractPrior},S,T}
  #
  p = if ccwl.partial
    Int[ccwl.usrfnc!.partial...]
  else
    Int[1:ccwl.xDim...]
  end

  for thrid in 1:Threads.nthreads()
    length(ccwl.cpt[thrid].p) != length(p) ? resize!(ccwl.cpt[thrid].p, length(p)) : nothing
    ccwl.cpt[thrid].p .= p
  end
  nothing
end

function _setCCWDecisionDimsConv!(ccwl::Union{CommonConvWrapper{F},
                                              CommonConvWrapper{Mixture{N_,F,S,T}}} ) where {N_,F<:AbstractRelativeRoots,S,T}
  #
  for thrid in 1:Threads.nthreads()
    length(ccwl.cpt[thrid].p) != ccwl.xDim ? resize!(ccwl.cpt[thrid].p, ccwl.xDim) : nothing
    ccwl.cpt[thrid].p .= Int[1:ccwl.xDim;]
  end
  nothing
end

"""
    $(SIGNATURES)

Perform the nonlinear numerical operations to approximate the convolution with a particular user defined likelihood function (conditional), which as been prepared in the `frl` object.  This function uses root finding to enforce a non-linear function constraint.

Notes:
- remember this is a deepcopy of original sfidx, since we are generating a proposal distribution and not directly replacing the existing variable belief estimate

Future work:
- once Threads.@threads have been optmized JuliaLang/julia#19967, also see area4 branch
- improve handling of n and particleidx, especially considering future multithreading support
"""
function approxConvOnElements!( ccwl::Union{CommonConvWrapper{F},
                                            CommonConvWrapper{Mixture{N_,F,S,T}}},
                                elements::Union{Vector{Int}, UnitRange{Int}}, ::Type{MultiThreaded}  ) where {N_,F<:AbstractRelative,S,T}
  #
  Threads.@threads for n in elements
    # ccwl.thrid_ = Threads.threadid()
    ccwl.cpt[Threads.threadid()].particleidx = n

    # ccall(:jl_, Nothing, (Any,), "starting loop, thrid_=$(Threads.threadid()), partidx=$(ccwl.cpt[Threads.threadid()].particleidx)")
    _solveCCWNumeric!( ccwl )
  end
  nothing
end

function approxConvOnElements!( ccwl::Union{CommonConvWrapper{F},
                                            CommonConvWrapper{Mixture{N_,F,S,T}}},
                                elements::Union{Vector{Int}, UnitRange{Int}}, ::Type{SingleThreaded}) where {N_,F<:AbstractRelative,S,T}
  #
  for n in elements
    ccwl.cpt[Threads.threadid()].particleidx = n
    _solveCCWNumeric!( ccwl )
  end
  nothing
end

function approxConvOnElements!( ccwl::Union{CommonConvWrapper{F},
                                            CommonConvWrapper{Mixture{N_,F,S,T}}},
                                elements::Union{Vector{Int}, UnitRange{Int}} )  where {N_,F<:AbstractRelative,S,T}
  #
  approxConvOnElements!(ccwl, elements, ccwl.threadmodel)
end

"""
    $(SIGNATURES)

Prepare a common functor computation object `prepareCommonConvWrapper{T}` containing
the user factor functor along with additional variables and information using during
approximate convolution computations.
"""
function prepareCommonConvWrapper!( F_::Type{<:AbstractRelative},
                                    ccwl::CommonConvWrapper{F},
                                    Xi::AbstractVector{<:DFGVariable},
                                    solvefor::Symbol,
                                    N::Int;
                                    needFreshMeasurements::Bool=true,
                                    solveKey::Symbol=:default  ) where {F <: FunctorInferenceType}
  #

  # FIXME, order of fmd ccwl cf are a little weird and should be revised.
  ARR = Array{Array{Float64,2},1}()
  # FIXME maxlen should parrot N (barring multi-/nullhypo issues)
  maxlen, sfidx, manis = prepareparamsarray!(ARR, Xi, solvefor, N, solveKey=solveKey)
  # TODO should be selecting for the correct multihypothesis mode
  ccwl.params = ARR
  # get factor metadata -- TODO, populate, also see #784
  fmd = FactorMetadata(Xi, getLabel.(Xi), ARR, solvefor, nothing)

  # TODO consolidate with ccwl??
  # FIXME do not divert Mixture for sampling
  # cf = _buildCalcFactorMixture(ccwl, fmd, 1, ccwl.measurement, ARR) # TODO perhaps 0 is safer
  cf = CalcFactor( ccwl.usrfnc!, fmd, 0, length(ccwl.measurement), ccwl.measurement, ARR)

  #  get variable node data
  vnds = Xi

  # option to disable fresh samples
  if needFreshMeasurements
    # TODO refactor
    ccwl.measurement = sampleFactor(cf, maxlen)
    # sampleFactor!(ccwl, maxlen, fmd, vnds)
  end

  if ccwl.specialzDim
    ccwl.zDim = ccwl.usrfnc!.zDim[sfidx]
  else
    ccwl.zDim = size(ccwl.measurement[1],1) # TODO -- zDim aspect needs to be reviewed
  end
  ccwl.varidx = sfidx

  ccwl.xDim = size(ccwl.params[sfidx],1)
  # ccwl.xDim = size(ccwl.cpt[1].X,1)
  # info("what? sfidx=$(sfidx), ccwl.xDim = size(ccwl.params[sfidx]) = $(ccwl.xDim), size=$(size(ccwl.params[sfidx]))")
  for thrid in 1:Threads.nthreads()
    cpt_ = ccwl.cpt[thrid]
    cpt_.X = ccwl.params[sfidx]
    resize!(cpt_.p, ccwl.xDim)
    cpt_.p .= 1:ccwl.xDim
    # used in ccw functor for AbstractRelativeMinimize
    # TODO JT - Confirm it should be updated here. Testing in prepgenericconvolution
    resize!(cpt_.res, ccwl.zDim)
    fill!(cpt_.res, 0.0)
    # cpt_.res = zeros(ccwl.xDim)
  end

  return sfidx, maxlen, manis
end

function prepareCommonConvWrapper!( ccwl::Union{CommonConvWrapper{F},
                                                CommonConvWrapper{Mixture{N_,F,S,T}}},
                                    Xi::AbstractVector{<:DFGVariable},
                                    solvefor::Symbol,
                                    N::Int;
                                    needFreshMeasurements::Bool=true,
                                    solveKey::Symbol=:default  ) where {N_,F<:AbstractRelative,S,T}
  #
  prepareCommonConvWrapper!(F, ccwl, Xi, solvefor, N, needFreshMeasurements=needFreshMeasurements, solveKey=solveKey)
end

function generateNullhypoEntropy( val::AbstractMatrix{<:Real},
                                  maxlen::Int,
                                  spreadfactor::Real=10  )
  #
  # covD = sqrt.(vec(Statistics.var(val,dims=2))) .+ 1e-3
  # cVar = diagm((spreadfactor*covD).^2)
  len = size(val, 1)
  cVar = diagm((spreadfactor*ones(len)).^2)
  mu = zeros( len )
  MvNormal( mu, cVar )
end

function calcVariableCovarianceBasic(arr::AbstractMatrix)
  # cannot calculate the stdev from uninitialized state
  msst = Statistics.std(arr, dims=2)
  # FIXME use adaptive scale, see #802
  msst_ = 0 < sum(1e-10 .< msst) ? maximum(msst) : 1.0
  return msst_
end

"""
    $SIGNATURES

Control the amount of entropy to add to null-hypothesis in multihypo case.

Notes:
- FIXME, Currently only supports Euclidean domains.
"""
function calcVariableDistanceExpectedFractional(ccwl::CommonConvWrapper,
                                                sfidx::Int,
                                                certainidx::Vector{Int};
                                                kappa::Float64=3.0  )
  #
  if sfidx in certainidx
    msst_ = calcVariableCovarianceBasic(ccwl.params[sfidx])
    return kappa*msst_
  end
  # @assert !(sfidx in certainidx) "null hypo distance does not work for sfidx in certainidx"

  # get mean of all fractional variables
  uncertainidx = setdiff(1:length(ccwl.params), certainidx)
  uncMeans = zeros(size(ccwl.params[sfidx],1), length(uncertainidx))
  dists = zeros(length(uncertainidx)+length(certainidx))
  dims = size(ccwl.params[sfidx],1)
  count = 0
  for i in uncertainidx
    count += 1
    uncMeans[:,count] = Statistics.mean(ccwl.params[i], dims=2)[:]
  end
  count = 0
  refMean = Statistics.mean(ccwl.params[sfidx], dims=2)[:]
  for i in uncertainidx
    count += 1
    dists[count] = norm(refMean - uncMeans[:,count])
  end
  # also check distance to certainidx for general scale reference (workaround heuristic)
  for cidx in certainidx
    count += 1
    cerMean = Statistics.mean(ccwl.params[cidx], dims=2)[:]
    dists[count] = norm(refMean[1:dims] - cerMean[1:dims])
  end

  push!(dists, 1e-2)
  return kappa*maximum(dists)
end

function addEntropyOnManifoldHack!(addEntr::Union{AbstractMatrix{<:Real},SubArray}, maniAddOps, spreadDist::Real)
  # add 1σ "noise" level to max distance as control
  for i in 1:size(addEntr, 1)
    for j in 1:size(addEntr,2)
      addEntr[i,j] = maniAddOps[i](addEntr[i,j], spreadDist*(rand()-0.5))
    end
  end
  nothing
end

"""
    $(SIGNATURES)

Common function to compute across a single user defined multi-hypothesis ambiguity per factor.  This function dispatches both `AbstractRelativeRoots` and `AbstractRelativeMinimize` factors.
"""
function computeAcrossHypothesis!(ccwl::Union{CommonConvWrapper{F},
                                              CommonConvWrapper{Mixture{N_,F,S,T}}},
                                  allelements::AbstractVector,
                                  activehypo,
                                  certainidx::Vector{Int},
                                  sfidx::Int,
                                  maxlen::Int,
                                  maniAddOps::Tuple;
                                  spreadNH::Real=3.0 ) where {N_,F<:AbstractRelative,S,T}
  #
  count = 0

  # setup the partial or complete decision variable dimensions for this ccwl object
  # NOTE perhaps deconv has changed the decision variable list, so placed here during consolidation phase
  _setCCWDecisionDimsConv!(ccwl)

  # TODO remove assert once all GenericWrapParam has been removed
  # @assert norm(ccwl.certainhypo - certainidx) < 1e-6
  for (hypoidx, vars) in activehypo
    count += 1
    if sfidx in certainidx && hypoidx != 0 || hypoidx in certainidx || hypoidx == sfidx
      # hypo case hypoidx, sfidx = $hypoidx, $sfidx
      for i in 1:Threads.nthreads()  ccwl.cpt[i].activehypo = vars; end
      approxConvOnElements!(ccwl, allelements[count])
    elseif hypoidx != sfidx && hypoidx != 0  # sfidx in uncertnidx
      # multihypo, take other value case
      # sfidx=2, hypoidx=3:  2 should take a value from 3
      # sfidx=3, hypoidx=2:  3 should take a value from 2
        # DEBUG sfidx=2, hypoidx=1 -- bad when do something like multihypo=[0.5;0.5] -- issue 424
      ccwl.params[sfidx][:,allelements[count]] = view(ccwl.params[hypoidx],:,allelements[count])
    elseif hypoidx == 0
      # basically do nothing since the factor is not active for these allelements[count]
      # inject lots of entropy in nullhypo case
      addEntr = view(ccwl.params[sfidx], :, allelements[count])
      # make spread (1σ) equal to mean distance of other fractionals
      spreadDist = calcVariableDistanceExpectedFractional(ccwl, sfidx, certainidx, kappa=spreadNH)
      # ENT = generateNullhypoEntropy(addEntr, maxlen, spreadDist) # TODO
      # add 1σ "noise" level to max distance as control
      addEntropyOnManifoldHack!(addEntr, maniAddOps, spreadDist)
      # for i in 1:size(addEntr, 1)
      #   for j in 1:size(addEntr,2)
      #     # FIXME, should be with ENT
      #     addEntr[i,j] = maniAddOps[i](addEntr[i,j], spreadDist*(rand()-0.5))
      #   end
      # end
    else
      error("computeAcrossHypothesis -- not dealing with multi-hypothesis case correctly")
    end
  end
  nothing
end
  # elseif hypoidx == sfidx
  #   # multihypo, do conv case, hypoidx == sfidx
  #   ah = sort(union([sfidx;], certainidx))
  #   @assert norm(ah - vars) < 1e-10
  #   for i in 1:Threads.nthreads()  ccwl.cpt[i].activehypo = ah; end
  #   approxConvOnElements!(ccwl, allelements[count])

"""
    $(SIGNATURES)

Multiple dispatch wrapper for `<:AbstractRelativeRoots` types, to prepare and execute the general approximate convolution with user defined factor residual functions.  This method also supports multihypothesis operations as one mechanism to introduce new modality into the proposal beliefs.

Planned changes will fold null hypothesis in as a standard feature and no longer appear as a separate `InferenceType`.
"""
function evalPotentialSpecific( Xi::AbstractVector{<:DFGVariable},
                                ccwl::CommonConvWrapper{T},
                                solvefor::Symbol,
                                T_::Type{<:AbstractRelative},
                                measurement::Tuple=(zeros(0,100),);
                                needFreshMeasurements::Bool=true,
                                solveKey::Symbol=:default,
                                N::Int=size(measurement[1],2),
                                spreadNH::Real=3.0,
                                dbg::Bool=false  ) where {T <: AbstractFactor}
  #

  # Prep computation variables
  # FIXME #1025, should FMD be built here?
  sfidx, maxlen, manis = prepareCommonConvWrapper!(ccwl, Xi, solvefor, N, needFreshMeasurements=needFreshMeasurements, solveKey=solveKey)
  # check for user desired measurement values
  if 0 < size(measurement[1],1)
    ccwl.measurement = measurement
  end

  # Check which variables have been initialized
  isinit = map(x->isInitialized(x), Xi)

  # get manifold add operations
  # TODO, make better use of dispatch, see JuliaRobotics/RoME.jl#244
  addOps, d1, d2, d3 = buildHybridManifoldCallbacks(manis)

  # assemble how hypotheses should be computed
  _, allelements, activehypo, mhidx = assembleHypothesesElements!(ccwl.hypotheses, maxlen, sfidx, length(Xi), isinit, ccwl.nullhypo )
  certainidx = ccwl.certainhypo

  # perform the numeric solutions on the indicated elements
  # error("ccwl.xDim=$(ccwl.xDim)")
  computeAcrossHypothesis!(ccwl, allelements, activehypo, certainidx, sfidx, maxlen, addOps, spreadNH=spreadNH)

  return ccwl.params[ccwl.varidx]
end

# TODO `measurement` might not be properly wired up yet
function evalPotentialSpecific( Xi::AbstractVector{<:DFGVariable},
                                ccwl::CommonConvWrapper{T},
                                solvefor::Symbol,
                                T_::Type{<:AbstractPrior},
                                measurement::Tuple=(zeros(0,0),);
                                needFreshMeasurements::Bool=true,
                                solveKey::Symbol=:default,
                                N::Int=size(measurement[1],2),
                                dbg::Bool=false,
                                spreadNH::Real=3.0 ) where {T <: AbstractFactor}
  #
  # FIXME, NEEDS TO BE CLEANED UP AND WORK ON MANIFOLDS PROPER
  fnc = ccwl.usrfnc!
  sfidx = 1
  oldVal = getVal(Xi[sfidx], solveKey=solveKey)
  nn = maximum([N; size(measurement[1],2); size(oldVal,2); size(ccwl.params[sfidx],2)]) # (N <= 0 ? size(getVal(Xi[1]),2) : N)
  vnds = Xi # (x->getSolverData(x)).(Xi)
  # FIXME better standardize in-place operations (considering solveKey)
  if needFreshMeasurements
    cf = CalcFactor( ccwl.usrfnc!, _getFMdThread(ccwl), 0, length(ccwl.measurement), ccwl.measurement, ccwl.params)
    ccwl.measurement = sampleFactor(cf, nn)
    # fmd = FactorMetadata(Xi, getLabel.(Xi), ccwl.params, solvefor, nothing)
    # sampleFactor!(ccwl, nn, fmd, vnds)
  end
  # Check which variables have been initialized
  isinit = map(x->isInitialized(x), Xi)
  _, allelements, activehypo, mhidx = assembleHypothesesElements!(ccwl.hypotheses, nn, sfidx, length(Xi), isinit, ccwl.nullhypo )
  # get solvefor manifolds
  manis = getManifolds(Xi[sfidx])
  addOps, d1, d2, d3 = buildHybridManifoldCallbacks(manis)
  # two cases on how to use the measurement
  nhmask = mhidx .== 0
  ahmask = mhidx .== 1
  # generate nullhypo samples
  # inject lots of entropy in nullhypo case
  # make spread (1σ) equal to mean distance of other fractionals
  # FIXME better standardize in-place operations (considering solveKey)
  addEntr = if size(oldVal,2) == nn
    deepcopy(oldVal)  #ccwl.params[sfidx])
  else
    ret = zeros(size(oldVal,1),nn)
    ret[:,1:size(oldVal,2)] .= oldVal #ccwl.params[sfidx]
    ret
  end
  # @show nn, size(addEntr), size(nhmask), size(oldVal)
  addEntrNH = view(addEntr, :, nhmask)
  spreadDist = spreadNH*calcVariableCovarianceBasic(addEntr)
  # ENT = generateNullhypoEntropy(addEntr, nn, spreadDist)
  if !ccwl.partial
      addEntr[:,ahmask] = ccwl.measurement[1][:,ahmask]
      # ongoing part of RoME.jl #244
      addEntropyOnManifoldHack!(addEntrNH, addOps, spreadDist)
    # return ccwl.measurement[1]
  else
    i = 0
    for dimnum in fnc.partial
      i += 1
      addEntr[dimnum,ahmask] = ccwl.measurement[1][i,ahmask]
      addEntrNHp = view(addEntr, dimnum, nhmask)
      # ongoing part of RoME.jl #244
      addEntropyOnManifoldHack!(addEntrNHp, addOps[dimnum:dimnum], spreadDist)
    end
  end
  return addEntr
end

function evalPotentialSpecific( Xi::AbstractVector{<:DFGVariable},
                                ccwl::CommonConvWrapper{Mixture{N_,F,S,T}},
                                solvefor::Symbol,
                                measurement::Tuple=(zeros(0,0),);
                                needFreshMeasurements::Bool=true,
                                solveKey::Symbol=:default,
                                N::Int=size(measurement[1],2),
                                dbg::Bool=false,
                                spreadNH::Real=3.0 ) where {N_,F<:FunctorInferenceType,S,T}
  #
  evalPotentialSpecific(Xi,
                        ccwl,
                        solvefor,
                        F,
                        measurement;
                        needFreshMeasurements=needFreshMeasurements,
                        solveKey=solveKey,
                        N=N,
                        dbg=dbg,
                        spreadNH=spreadNH )
end

function evalPotentialSpecific( Xi::AbstractVector{<:DFGVariable},
                                ccwl::CommonConvWrapper{F},
                                solvefor::Symbol,
                                measurement::Tuple=(zeros(0,0),);
                                needFreshMeasurements::Bool=true,
                                solveKey::Symbol=:default,
                                N::Int=size(measurement[1],2),
                                dbg::Bool=false,
                                spreadNH::Real=3.0 ) where {F <: FunctorInferenceType}
  #
  evalPotentialSpecific(Xi,
                        ccwl,
                        solvefor,
                        F,
                        measurement;
                        needFreshMeasurements=needFreshMeasurements,
                        solveKey=solveKey,
                        N=N,
                        dbg=dbg,
                        spreadNH=spreadNH )
end

"""
    $(SIGNATURES)

Single entry point for evaluating factors from factor graph, using multiple dispatch to locate the correct `evalPotentialSpecific` function.
"""
function evalFactor(dfg::AbstractDFG,
                    fct::DFGFactor,
                    solvefor::Symbol,
                    measurement::Tuple=(zeros(0,100),);
                    needFreshMeasurements::Bool=true,
                    solveKey::Symbol=:default,
                    N::Int=size(measurement[1],2),
                    dbg::Bool=false  )
  #

  ccw = _getCCW(fct)
  # TODO -- this build up of Xi is excessive and could happen at addFactor time
  variablelist = getVariableOrder(fct)
  Xi = getVariable.(dfg, variablelist)

  # setup operational values before compute (likely to be refactored)
  for i in 1:Threads.nthreads()
    ccw.cpt[i].factormetadata.variablelist = variablelist
    ccw.cpt[i].factormetadata.solvefor = solvefor
  end

  return evalPotentialSpecific( Xi, ccw, solvefor, measurement, needFreshMeasurements=needFreshMeasurements,
                                solveKey=solveKey, N=N, dbg=dbg, spreadNH=getSolverParams(dfg).spreadNH )
  #
end

function approxConv(dfg::AbstractDFG,
                    fc::DFGFactor,
                    target::Symbol,
                    measurement::Tuple=(zeros(0,0),);
                    solveKey::Symbol=:default,
                    N::Int=size(measurement[1],2) )
  #
  v1 = getVariable(dfg, target)
  N = N == 0 ? getNumPts(v1) : N
  return evalFactor(dfg, fc, v1.label, measurement, solveKey=solveKey, N=N)
end

"""
    $SIGNATURES

Calculate the sequential series of convolutions in order as listed by `fctLabels`, and starting from the
value already contained in the first variable.

Notes
- The ultimate `target` variable must be given to allow path discovery through n-ary factors.
- Fresh starting point will be used if first element in `fctLabels` is a unary `<:AbstractPrior`.
- This function will not change any values in `dfg`, and might have slightly less speed performance to meet this requirement.
- pass in `tfg` to get a recoverable result of all convolutions in the chain.
- `setPPE` and `setPPEmethod` can be used to store PPE information in temporary `tfg`

DevNotes
- TODO strong requirement that this function is super efficient on single factor/variable case!
- FIXME must consolidate with `accumulateFactorMeans`
- TODO `solveKey` not fully wired up everywhere yet
  - tfg gets all the solveKeys inside the source `dfg` variables
- TODO add a approxConv on PPE option
  - Consolidate with [`accumulateFactorMeans`](@ref), `approxConvBinary`

Related

[`approxDeconv`](@ref), `LightDFG.findShortestPathDijkstra`, [`evalFactor`](@ref)
"""
function approxConv(dfg::AbstractDFG,
                    from::Symbol,
                    target::Symbol,
                    measurement::Tuple=(zeros(0,0),);
                    solveKey::Symbol=:default,
                    N::Int = size(measurement[1],2),
                    tfg::AbstractDFG = initfg(),
                    setPPEmethod::Union{Nothing, Type{<:AbstractPointParametricEst}}=nothing,
                    setPPE::Bool= setPPEmethod !== nothing,
                    path::AbstractVector{Symbol}=Symbol[]  )
  #
  @assert isVariable(dfg, target) "approxConv(dfg, from, target,...) where `target`=$target must be a variable in `dfg`"

  if from in ls(dfg, target)
    # direct request
    # TODO avoid this allocation for direct cases ( dfg, :x1x2f1, :x2[/:x1] )
    path = Symbol[from; target]
    varLbls = Symbol[target;]
  else
    # must first discover shortest factor path in dfg
    # TODO DFG only supports LightDFG.findShortestPathDijkstra at the time of writing (DFG v0.10.9)
    path = 0 == length(path) ? findShortestPathDijkstra(dfg, from, target) : path
    @assert path[1] == from "sanity check that shortest path function is working as expected"

    # list of variables
    fctMsk = isFactor.(dfg, path)
    # which factors in the path
    fctLbls = path[fctMsk]
    # must still add
    varLbls =  union(lsf.(dfg, fctLbls)...)
    neMsk = exists.(tfg, varLbls) .|> x-> xor(x,true)
    # put the non-existing variables into the temporary graph `tfg`
    # bring all the solveKeys too
    addVariable!.(tfg, getVariable.(dfg, varLbls[neMsk]))
    # variables adjacent to the shortest path should be initialized from dfg
    setdiff(varLbls, path[xor.(fctMsk,true)]) .|> x->initManual!(tfg, x, getBelief(dfg, x))
  end

  # find/set the starting point
  idxS = 1
  pts = if varLbls[1] == from
    # starting from a variable
    pts0 = getBelief(dfg, varLbls[1]) |> getPoints
  else
    # chain would start one later
    idxS += 1
    # get the factor
    fct0 = getFactor(dfg,from)
    # get the Matrix{<:Real} of projected points
    pts1 = approxConv(dfg, fct0, path[2], measurement, solveKey=solveKey, N=N)
    length(path) == 2 ? (return pts1) : pts1
  end
  # didn't return early so shift focus to using `tfg` more intensely
  initManual!(tfg, varLbls[1], pts)
  # use in combination with setPPE and setPPEmethod keyword arguments
  ppemethod = setPPEmethod === nothing ? MeanMaxPPE : setPPEmethod
  !setPPE ? nothing : setPPE!(tfg, varLbls[1], solveKey, ppemethod)

  # do chain of convolutions
  for idx in idxS:length(path)
    if fctMsk[idx]
      # this is a factor path[idx]
      fct = getFactor(dfg, path[idx])
      addFactor!(tfg, fct)
      pts = approxConv(tfg, fct, path[idx+1], solveKey=solveKey, N=N)
      initManual!(tfg, path[idx+1], pts)
      !setPPE ? nothing : setPPE!(tfg, path[idx+1], solveKey, ppemethod)
    end
  end

  # return target variable values
  return getBelief(tfg, target) |> getPoints
end

## ====================================================================================
## TODO better consolidate below with existing functions
## ====================================================================================

# TODO should this be consolidated with regular approxConv?
# TODO, perhaps pass Xi::Vector{DFGVariable} instead?
function approxConvBinary(arr::Array{Float64,2},
                          meas::FunctorInferenceType,
                          outdims::Int,
                          fmd::FactorMetadata,
                          measurement::Tuple=(zeros(0,size(arr,2)),);
                          varidx::Int=2,
                          N::Int=size(arr,2),
                          vnds=DFGVariable[] )
  #
  # N = N == 0 ? size(arr,2) : N
  pts = zeros(outdims,N);
  t = Array{Array{Float64,2},1}()
  push!(t,arr)
  push!(t,pts)

  fmd.arrRef = t

  # TODO consolidate with ccwl??
  # FIXME do not divert Mixture for sampling
  # cf = _buildCalcFactorMixture(ccwl, fmd, 1, ccwl.measurement, ARR) # TODO perhaps 0 is safer
  # FIXME 0, 0, ()
  cf = CalcFactor( meas, fmd, 0, 0, (), t)

  measurement = size(measurement[1],2) == 0 ? sampleFactor(cf, N) : measurement
  # measurement = size(measurement[1],2) == 0 ? sampleFactor(meas, N, fmd, vnds) : measurement

  zDim = size(measurement[1],1)
  ccw = CommonConvWrapper(meas, t[varidx], zDim, t, fmd, varidx=varidx, measurement=measurement)  # N=> size(measurement[1],2)

  for n in 1:N
    ccw.cpt[Threads.threadid()].particleidx = n
    _solveCCWNumeric!( ccw )
  end
  return pts
end

"""
    $SIGNATURES

Calculate both measured and predicted relative variable values, starting with `from` at zeros up to `to::Symbol`.

Notes
- assume single variable separators only.
"""
function accumulateFactorChain( dfg::AbstractDFG,
                                from::Symbol,
                                to::Symbol,
                                fsyms::Vector{Symbol}=findFactorsBetweenNaive(dfg, from, to);
                                initval=zeros(size(getVal(dfg, from))))

  # get associated variables
  svars = union(ls.(dfg, fsyms)...)

  # use subgraph copys to do calculations
  tfg_meas = buildSubgraph(dfg, [svars;fsyms])
  tfg_pred = buildSubgraph(dfg, [svars;fsyms])

  # drive variable values manually to ensure no additional stochastics are introduced.
  nextvar = from
  initManual!(tfg_meas, nextvar, initval)
  initManual!(tfg_pred, nextvar, initval)

  # nextfct = fsyms[1] # for debugging
  for nextfct in fsyms
    nextvars = setdiff(ls(tfg_meas,nextfct),[nextvar])
    @assert length(nextvars) == 1 "accumulateFactorChain requires each factor pair to separated by a single variable"
    nextvar = nextvars[1]
    meas, pred = approxDeconv(dfg, nextfct) # solveFactorMeasurements
    pts_meas = approxConv(tfg_meas, nextfct, nextvar, (meas,ones(Int,100),collect(1:100)))
    pts_pred = approxConv(tfg_pred, nextfct, nextvar, (pred,ones(Int,100),collect(1:100)))
    initManual!(tfg_meas, nextvar, pts_meas)
    initManual!(tfg_pred, nextvar, pts_pred)
  end
  return getVal(tfg_meas,nextvar), getVal(tfg_pred,nextvar)
end

"""
    $(SIGNATURES)

Compute proposal belief on `vertid` through `fct` representing some constraint in factor graph.
Always full dimension variable node -- partial constraints will only influence subset of variable dimensions.
The remaining dimensions will keep pre-existing variable values.

Notes
- fulldim is true when "rank-deficient" -- TODO swap to false (or even float)
"""
function findRelatedFromPotential(dfg::AbstractDFG,
                                  fct::DFGFactor,
                                  target::Symbol,
                                  measurement::Tuple=(zeros(0,0),);
                                  N::Int=size(measurement[1],2),
                                  solveKey::Symbol=:default,
                                  dbg::Bool=false  )
  #

  # # assuming it is properly initialized TODO
  pts = evalFactor(dfg, fct, target, solveKey=solveKey, N=N, dbg=dbg);
  # pts = approxConv(dfg, fct, target, measurement, N=N, solveKey=solveKey)

  # # determine if evaluation is "dimension-deficient"
  # solvable dimension
  inferdim = getFactorSolvableDim(dfg, fct, target)
  # zdim = getFactorDim(fct)
  # vdim = getVariableDim(DFG.getVariable(dfg, target))

  # TODO -- better to upsample before the projection
  Ndim = size(pts,1)
  Npoints = size(pts,2)
  # Assume we only have large particle population sizes, thanks to addNode!
  manis = getManifolds(dfg, target)
  # manis = getSofttype(DFG.getVariable(dfg, target)).manifolds # older
  proposal = AMP.manikde!(pts, manis)

  # FIXME consolidate with approxConv method instead
  if Npoints != N # this is where we control the overall particle set size
      proposal = resample(proposal,N)
  end
  return (proposal, inferdim)
end

"""
    $SIGNATURES

Compute the proposals of a destination vertex for each of `factors` and place the result
as belief estimates in both `dens` and `partials` respectively.

Notes
- TODO: also return if proposals were "dimension-deficient" (aka ~rank-deficient).
"""
function proposalbeliefs!(dfg::AbstractDFG,
                          destvertlabel::Symbol,
                          factors::AbstractVector{<:DFGFactor},
                          dens::Vector{BallTreeDensity},
                          partials::Dict{Int, Vector{BallTreeDensity}},
                          measurement::Tuple=(zeros(0,0),);
                          solveKey::Symbol=:default,
                          N::Int=100,
                          dbg::Bool=false  )
  #
  inferddimproposal = Vector{Float64}(undef, length(factors))
  for (count,fct) in enumerate(factors)
    data = getSolverData(fct)
    p, inferd = findRelatedFromPotential(dfg, fct, destvertlabel, measurement, N=N, dbg=dbg, solveKey=solveKey)
    if _getCCW(data).partial   # partial density
      pardims = _getCCW(data).usrfnc!.partial
      for dimnum in pardims
        if haskey(partials, dimnum)
          push!(partials[dimnum], marginal(p,[dimnum]))
        else
          partials[dimnum] = BallTreeDensity[marginal(p,[dimnum])]
        end
      end
    else # add onto full density list
      push!(dens, p)
    end
    inferddimproposal[count] = inferd
  end
  inferddimproposal
end

#


# This file is a part of BAT.jl, licensed under the MIT License (MIT).

using RandomNumbers.Random123: Philox4x, Threefry4x
using RandomNumbers.Random123: random123_r, gen_seed

const Random123_UInt = Union{UInt32, UInt64}
const Random123RNG4x = Union{Philox4x, Threefry4x}

abstract type AbstractRNGSeed end

export AbstractRNGSeed

AbstractRNGSeed() = Philox4xSeed()

struct Philox4xSeed{T<:Random123_UInt} <: AbstractRNGSeed
    seed::NTuple{2,T}

    Philox4xSeed{T}(seed::NTuple{2,T}) where {T<:Random123_UInt} = new{T}(seed)
    Philox4xSeed{T}() where {T<:Random123_UInt} = new{T}(gen_seed(T, 2))
end

export Philox4xSeed

# (::Type{Philox4xSeed{T}})() where {T<:Random123_UInt} = Philox4xSeed(gen_seed(T, 2))

Philox4xSeed() = Philox4xSeed{UInt64}()

function (rngseed::Philox4xSeed{T})() where {T}
    rng = Philox4x{T,10}(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
    srand(rng, rngseed.seed)
end

struct Threefry4xSeed{T<:Random123_UInt} <: AbstractRNGSeed
    seed::NTuple{4,T}

    Threefry4xSeed{T}(seed::NTuple{4,T}) where {T<:Random123_UInt} = new{T}(seed)
    Threefry4xSeed{T}() where {T<:Random123_UInt} = new{T}(gen_seed(T, 4))
end

export Threefry4xSeed

Threefry4xSeed() = Threefry4xSeed{UInt64}()

function (rngseed::Threefry4xSeed{T})() where {T}
    rng = Threefry4x{T,20}(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
    srand(rng, rngseed.seed)
end

reset_rng_counters!(rng::AbstractRNG, tags::Integer...) = reset_rng_counters!(rng, tags)

# ToDo: More flexible tagging scheme, allow for more/less than 3 tags and
# support counter-based RNGs with more/less than 4 counters.

function reset_rng_counters!(rng::Random123RNG4x, tags::NTuple{3,Integer})
    rng.ctr3 += Base.Threads.threadid()
    rng.ctr4 = tags[1]
    rng.ctr3 = tags[2]
    rng.ctr2 = tags[3]
    rng.ctr1 = zero(rng.ctr1)
    random123_r(rng)
end

struct MersenneTwisterSeed{R<:AbstractRNG} <: AbstractRNGSeed
    rng::R
end

export MersenneTwisterSeed

MersenneTwisterSeed() = MersenneTwisterSeed(RandomDevice())

(rngseed::MersenneTwisterSeed)() =
    MersenneTwister(rand(rngseed.rng, UInt64))

# ToDo (maybe): Implement tagging (resp. multiple streams) for Mersenne
# Twister via skip-ahead


# const VTKtypemap = Dict{DataType, Int}(P1=>1, L2=>3, T3=>5,
#     Q4=>9, T4=>10, H8=>12, Q8=>23,
#     L3=>21, T6=>22,
#     T10=>24, H20=>25)
const _VTK_TYPE_MAP = Dict{AbsShapeDesc, Int}(P1=>1, L2=>3, T3=>5, Q4=>9, T4=>10, H8=>12, T6=>22, Q8=>23)

"""
    vtkwrite(filename, connectivity)

Write VTK file with the mesh.

- `connectivity` = incidence relation of the type `d -> 0`. It must have a "geom"
  attribute for access to the locations of all the vertices that the
  connectivity incidence relation references.
- `data` = array of named tuples. Names of attributes of either the left or the
  right shape collection of the `connectivity` incidence relation. Property names: `:name` required, name of the attribute. `:allxyz` optional, pad the attribute to be a three-dimensional quantity (in the global Cartesian coordinate system).
"""
function vtkwrite(filename, connectivity, data = [])
    locs = attribute(connectivity.right, "geom")
    # Fill in a matrix of point coordinates
    points = fill(zero(eltype(locs[1])), length(locs[1]), length(locs))
    for i in 1:length(locs)
        points[:, i] .= locs[i]
    end #
    # Figure out the cell type
    celltype = WriteVTK.VTKCellTypes.VTKCellType(_VTK_TYPE_MAP[shapedesc(connectivity.left)])
    # Prepare an array of the cells
    cells = [MeshCell(celltype, [j for j in connectivity[i]]) for i in 1:nrelations(connectivity)]
    vtkfile = vtk_grid(filename, points, cells, compress=3)
    for nt in data
        an = nt.name # name of the shape collection
        pn = propertynames(nt)
        allxyz = (:allxyz in pn ? nt.allxyz : false)
        if an in keys(connectivity.right.attributes)
            a = attribute(connectivity.right, an)
            nc = length(a[1])
            ncz = ((nc < 3) && allxyz ? ncz = 3 : nc)
            pdata = fill(0.0, ncz, length(a))
            for j in 1:nc
                for i in 1:length(a)
                    pdata[j, i] = a[i][j]
                end
            end
            vtkfile[an] = pdata
        elseif an in keys(connectivity.left.attributes)
            a = attribute(connectivity.left, an)
            nc = length(a[1])
            cdata = fill(0.0, nc, length(a))
            for j in 1:nc
                for i in 1:length(a)
                    cdata[j, i] = a[i][j]
                end
            end
            vtkfile[an] = cdata
        end
    end
    return vtk_save(vtkfile)
end

"""
    export_MESH(meshfile, mesh)

Import vertices and shapes in the MESH format.

# Output
Data dictionary, with keys
- "`vertices`" (vertices),
- "`shapes`" (array of shape collections).
"""
function export_MESH(meshfile, connectivity)
    meshfilebase, ext = splitext(meshfile)
    # Name of the shape descriptor
    t = shapedesc(connectivity.left).name
    datinfo = [meshfilebase * "-xyz.dat", t, meshfilebase * "-conn.dat"]
    locs = attribute(connectivity.right, "geom")
    open(datinfo[1], "w") do file
        X = [locs[idx] for idx in 1:length(locs)]
        writedlm(file, X, ' ')
    end
    open(datinfo[3], "w") do file
        c = [connectivity[idx] for idx in 1:nrelations(connectivity)]
        writedlm(file, c, ' ')
    end
    open(meshfilebase * ".mesh", "w") do file
        writedlm(file, datinfo)
    end

    return true
end


# MIT license
# Copyright (c) Microsoft Corporation. All rights reserved.
# See LICENSE in the project root for full license information.

module Repeat

export basismatrix

using Base: offset_if_vec
using StaticArrays:SVector,MVector,SMatrix,MMatrix
using DataFrames:DataFrame
using Colors
import LazySets

include("Lattice.jl")
include("HexagonalLattice.jl")
include("RectangularLattice.jl")
include("Array.jl")
include("Cluster.jl")
include("Multilens/Lenslets.jl")

end #module
export Repeat


# with xr and xs
function fdtdKernel!{T}(n::Int,
		     p_out::Array{T},
		     p0::Array{T,3},
		     p1::Array{T,3},
		     p2::Array{T,3},
		     xr::Vector{Tuple{Int,Int,Int}},
		     xs::Vector{Tuple{Int,Int,Int}},
		     s::Array{T},
		     d1::T,
		     d2::T,
		     d3::T,
		     d4::T,
		     q::Vector{T},
		     G::Array{UInt8,3} )
	for i = 1:size(p0,3), m = 1:size(p0,2), l = 1:size(p0,1)
		if G[l,m,i] == 0x01
			air!(l,m,i,d1,d2,d3,d4,p0,p1,p2)
			#walls
		elseif G[l,m,i] == 0x02
			l1!(l,m,i,d1,d2,d3,d4,p0,p1,p2,q)
		elseif G[l,m,i] == 0x03
			le!(l,m,i,d1,d2,d3,d4,p0,p1,p2,q)
		elseif G[l,m,i] == 0x04
			m1!(l,m,i,d1,d2,d3,d4,p0,p1,p2,q)
		elseif G[l,m,i] == 0x05
			me!(l,m,i,d1,d2,d3,d4,p0,p1,p2,q)
		elseif G[l,m,i] == 0x06
			i1!(l,m,i,d1,d2,d3,d4,p0,p1,p2,q)
		elseif G[l,m,i] == 0x07
			ie!(l,m,i,d1,d2,d3,d4,p0,p1,p2,q)

			#edges
		elseif G[l,m,i] == 0x08
			l1m1!(l,m,i,d1,d2,d3,d4,p0,p1,p2,q)
		elseif G[l,m,i] == 0x09
			l1me!(l,m,i,d1,d2,d3,d4,p0,p1,p2,q)
		elseif G[l,m,i] == 0x0a
			l1i1!(l,m,i,d1,d2,d3,d4,p0,p1,p2,q)
		elseif G[l,m,i] == 0x0b
			l1ie!(l,m,i,d1,d2,d3,d4,p0,p1,p2,q)

		elseif G[l,m,i] == 0x0c
			lem1!(l,m,i,d1,d2,d3,d4,p0,p1,p2,q)
		elseif G[l,m,i] == 0x0d
			leme!(l,m,i,d1,d2,d3,d4,p0,p1,p2,q)
		elseif G[l,m,i] == 0x0e
			lei1!(l,m,i,d1,d2,d3,d4,p0,p1,p2,q)
		elseif G[l,m,i] == 0x0f
			leie!(l,m,i,d1,d2,d3,d4,p0,p1,p2,q)

		elseif G[l,m,i] == 0x10
			m1i1!(l,m,i,d1,d2,d3,d4,p0,p1,p2,q)
		elseif G[l,m,i] == 0x11
			m1ie!(l,m,i,d1,d2,d3,d4,p0,p1,p2,q)
		elseif G[l,m,i] == 0x12
			mei1!(l,m,i,d1,d2,d3,d4,p0,p1,p2,q)
		elseif G[l,m,i] == 0x13
			meie!(l,m,i,d1,d2,d3,d4,p0,p1,p2,q)

			#corners
		elseif G[l,m,i] == 0x14
			l1m1i1!(l,m,i,d1,d2,d3,d4,p0,p1,p2,q)
		elseif G[l,m,i] == 0x15
			l1m1ie!(l,m,i,d1,d2,d3,d4,p0,p1,p2,q)
		elseif G[l,m,i] == 0x16
			l1mei1!(l,m,i,d1,d2,d3,d4,p0,p1,p2,q)
		elseif G[l,m,i] == 0x17
			l1meie!(l,m,i,d1,d2,d3,d4,p0,p1,p2,q)

		elseif G[l,m,i] == 0x18
			lem1i1!(l,m,i,d1,d2,d3,d4,p0,p1,p2,q)
		elseif G[l,m,i] == 0x19
			lem1ie!(l,m,i,d1,d2,d3,d4,p0,p1,p2,q)
		elseif G[l,m,i] == 0x1a
			lemei1!(l,m,i,d1,d2,d3,d4,p0,p1,p2,q)
		elseif G[l,m,i] == 0x1b
			lemeie!(l,m,i,d1,d2,d3,d4,p0,p1,p2,q)
		end
		## add source
		for k in eachindex(xs)
			if xs[k][1] == l && xs[k][2] == m && xs[k][3] == i
				p2[l,m,i] += s[n,k]
			end
		end
		## get pressure
		for k in eachindex(xr)
			if xr[k][1] == l && xr[k][2] == m && xr[k][3] == i
				p_out[n,k] = p2[l,m,i]
			end
		end
	end
end

# full and xs
function fdtdKernel!{T}(n::Int,
		     p_out::Array{T,4}, #full output
		     p0::Array{T,3},
		     p1::Array{T,3},
		     p2::Array{T,3},
		     xs::Vector{Tuple{Int,Int,Int}},
		     s::Array{T},
		     d1::T,
		     d2::T,
		     d3::T,
		     d4::T,
		     q::Vector{T},
		     G::Array{UInt8,3} )
	for i = 1:size(p0,3), m = 1:size(p0,2), l = 1:size(p0,1)
		if G[l,m,i] == 0x01
			air!(l,m,i,d1,d2,d3,d4,p0,p1,p2)

			#walls
		elseif G[l,m,i] == 0x02
			l1!(l,m,i,d1,d2,d3,d4,p0,p1,p2,q)
		elseif G[l,m,i] == 0x03
			le!(l,m,i,d1,d2,d3,d4,p0,p1,p2,q)
		elseif G[l,m,i] == 0x04
			m1!(l,m,i,d1,d2,d3,d4,p0,p1,p2,q)
		elseif G[l,m,i] == 0x05
			me!(l,m,i,d1,d2,d3,d4,p0,p1,p2,q)
		elseif G[l,m,i] == 0x06
			i1!(l,m,i,d1,d2,d3,d4,p0,p1,p2,q)
		elseif G[l,m,i] == 0x07
			ie!(l,m,i,d1,d2,d3,d4,p0,p1,p2,q)

			#edges
		elseif G[l,m,i] == 0x08
			l1m1!(l,m,i,d1,d2,d3,d4,p0,p1,p2,q)
		elseif G[l,m,i] == 0x09
			l1me!(l,m,i,d1,d2,d3,d4,p0,p1,p2,q)
		elseif G[l,m,i] == 0x0a
			l1i1!(l,m,i,d1,d2,d3,d4,p0,p1,p2,q)
		elseif G[l,m,i] == 0x0b
			l1ie!(l,m,i,d1,d2,d3,d4,p0,p1,p2,q)

		elseif G[l,m,i] == 0x0c
			lem1!(l,m,i,d1,d2,d3,d4,p0,p1,p2,q)
		elseif G[l,m,i] == 0x0d
			leme!(l,m,i,d1,d2,d3,d4,p0,p1,p2,q)
		elseif G[l,m,i] == 0x0e
			lei1!(l,m,i,d1,d2,d3,d4,p0,p1,p2,q)
		elseif G[l,m,i] == 0x0f
			leie!(l,m,i,d1,d2,d3,d4,p0,p1,p2,q)

		elseif G[l,m,i] == 0x10
			m1i1!(l,m,i,d1,d2,d3,d4,p0,p1,p2,q)
		elseif G[l,m,i] == 0x11
			m1ie!(l,m,i,d1,d2,d3,d4,p0,p1,p2,q)
		elseif G[l,m,i] == 0x12
			mei1!(l,m,i,d1,d2,d3,d4,p0,p1,p2,q)
		elseif G[l,m,i] == 0x13
			meie!(l,m,i,d1,d2,d3,d4,p0,p1,p2,q)

			#corners
		elseif G[l,m,i] == 0x14
			l1m1i1!(l,m,i,d1,d2,d3,d4,p0,p1,p2,q)
		elseif G[l,m,i] == 0x15
			l1m1ie!(l,m,i,d1,d2,d3,d4,p0,p1,p2,q)
		elseif G[l,m,i] == 0x16
			l1mei1!(l,m,i,d1,d2,d3,d4,p0,p1,p2,q)
		elseif G[l,m,i] == 0x17
			l1meie!(l,m,i,d1,d2,d3,d4,p0,p1,p2,q)

		elseif G[l,m,i] == 0x18
			lem1i1!(l,m,i,d1,d2,d3,d4,p0,p1,p2,q)
		elseif G[l,m,i] == 0x19
			lem1ie!(l,m,i,d1,d2,d3,d4,p0,p1,p2,q)
		elseif G[l,m,i] == 0x1a
			lemei1!(l,m,i,d1,d2,d3,d4,p0,p1,p2,q)
		elseif G[l,m,i] == 0x1b
			lemeie!(l,m,i,d1,d2,d3,d4,p0,p1,p2,q)
		end
		## add source
		for k in eachindex(xs)
			if xs[k][1] == l && xs[k][2] == m && xs[k][3] == i
				p2[l,m,i] += s[n,k]
			end
		end
		p_out[l,m,i,n] = p2[l,m,i]
	end
end

# xr and full s
function fdtdKernel!{T}(n::Int,
		     p_out::Array{T},
		     p0::Array{T,3},
		     p1::Array{T,3},
		     p2::Array{T,3},
		     xr::Vector{Tuple{Int,Int,Int}},
		     s::Array{T,4},
		     d1::T,
		     d2::T,
		     d3::T,
		     d4::T,
		     q::Vector{T},
		     G::Array{UInt8,3} )
	for i = 1:size(p0,3), m = 1:size(p0,2), l = 1:size(p0,1)
		if G[l,m,i] == 0x01
			air!(n,l,m,i,d1,d2,d3,d4,p0,p1,p2,s)
			#walls
		elseif G[l,m,i] == 0x02
			l1!(n,l,m,i,d1,d2,d3,d4,p0,p1,p2,q,s)
		elseif G[l,m,i] == 0x03
			le!(n,l,m,i,d1,d2,d3,d4,p0,p1,p2,q,s)
		elseif G[l,m,i] == 0x04
			m1!(n,l,m,i,d1,d2,d3,d4,p0,p1,p2,q,s)
		elseif G[l,m,i] == 0x05
			me!(n,l,m,i,d1,d2,d3,d4,p0,p1,p2,q,s)
		elseif G[l,m,i] == 0x06
			i1!(n,l,m,i,d1,d2,d3,d4,p0,p1,p2,q,s)
		elseif G[l,m,i] == 0x07
			ie!(n,l,m,i,d1,d2,d3,d4,p0,p1,p2,q,s)

			#edges
		elseif G[l,m,i] == 0x08
			l1m1!(n,l,m,i,d1,d2,d3,d4,p0,p1,p2,q,s)
		elseif G[l,m,i] == 0x09
			l1me!(n,l,m,i,d1,d2,d3,d4,p0,p1,p2,q,s)
		elseif G[l,m,i] == 0x0a
			l1i1!(n,l,m,i,d1,d2,d3,d4,p0,p1,p2,q,s)
		elseif G[l,m,i] == 0x0b
			l1ie!(n,l,m,i,d1,d2,d3,d4,p0,p1,p2,q,s)

		elseif G[l,m,i] == 0x0c
			lem1!(n,l,m,i,d1,d2,d3,d4,p0,p1,p2,q,s)
		elseif G[l,m,i] == 0x0d
			leme!(n,l,m,i,d1,d2,d3,d4,p0,p1,p2,q,s)
		elseif G[l,m,i] == 0x0e
			lei1!(n,l,m,i,d1,d2,d3,d4,p0,p1,p2,q,s)
		elseif G[l,m,i] == 0x0f
			leie!(n,l,m,i,d1,d2,d3,d4,p0,p1,p2,q,s)

		elseif G[l,m,i] == 0x10
			m1i1!(n,l,m,i,d1,d2,d3,d4,p0,p1,p2,q,s)
		elseif G[l,m,i] == 0x11
			m1ie!(n,l,m,i,d1,d2,d3,d4,p0,p1,p2,q,s)
		elseif G[l,m,i] == 0x12
			mei1!(n,l,m,i,d1,d2,d3,d4,p0,p1,p2,q,s)
		elseif G[l,m,i] == 0x13
			meie!(n,l,m,i,d1,d2,d3,d4,p0,p1,p2,q,s)

			#corners
		elseif G[l,m,i] == 0x14
			l1m1i1!(n,l,m,i,d1,d2,d3,d4,p0,p1,p2,q,s)
		elseif G[l,m,i] == 0x15
			l1m1ie!(n,l,m,i,d1,d2,d3,d4,p0,p1,p2,q,s)
		elseif G[l,m,i] == 0x16
			l1mei1!(n,l,m,i,d1,d2,d3,d4,p0,p1,p2,q,s)
		elseif G[l,m,i] == 0x17
			l1meie!(n,l,m,i,d1,d2,d3,d4,p0,p1,p2,q,s)

		elseif G[l,m,i] == 0x18
			lem1i1!(n,l,m,i,d1,d2,d3,d4,p0,p1,p2,q,s)
		elseif G[l,m,i] == 0x19
			lem1ie!(n,l,m,i,d1,d2,d3,d4,p0,p1,p2,q,s)
		elseif G[l,m,i] == 0x1a
			lemei1!(n,l,m,i,d1,d2,d3,d4,p0,p1,p2,q,s)
		elseif G[l,m,i] == 0x1b
			lemeie!(n,l,m,i,d1,d2,d3,d4,p0,p1,p2,q,s)
		end
		## get pressure
		for k in eachindex(xr)
			if xr[k][1] == l && xr[k][2] == m && xr[k][3] == i
				p_out[n,k] = p2[l,m,i]
			end
		end
	end
end

# full p and full s
function fdtdKernel!{T}(n::Int,
		     p_out::Array{T,4}, #full output
		     p0::Array{T,3},
		     p1::Array{T,3},
		     p2::Array{T,3},
		     s::Array{T,4},
		     d1::T,
		     d2::T,
		     d3::T,
		     d4::T,
		     q::Vector{T},
		     G::Array{UInt8,3} )
	for i = 1:size(p0,3), m = 1:size(p0,2), l = 1:size(p0,1)
		if G[l,m,i] == 0x01
			air!(n,l,m,i,d1,d2,d3,d4,p0,p1,p2,s)
			#walls
		elseif G[l,m,i] == 0x02
			l1!(n,l,m,i,d1,d2,d3,d4,p0,p1,p2,q,s)
		elseif G[l,m,i] == 0x03
			le!(n,l,m,i,d1,d2,d3,d4,p0,p1,p2,q,s)
		elseif G[l,m,i] == 0x04
			m1!(n,l,m,i,d1,d2,d3,d4,p0,p1,p2,q,s)
		elseif G[l,m,i] == 0x05
			me!(n,l,m,i,d1,d2,d3,d4,p0,p1,p2,q,s)
		elseif G[l,m,i] == 0x06
			i1!(n,l,m,i,d1,d2,d3,d4,p0,p1,p2,q,s)
		elseif G[l,m,i] == 0x07
			ie!(n,l,m,i,d1,d2,d3,d4,p0,p1,p2,q,s)

			#edges
		elseif G[l,m,i] == 0x08
			l1m1!(n,l,m,i,d1,d2,d3,d4,p0,p1,p2,q,s)
		elseif G[l,m,i] == 0x09
			l1me!(n,l,m,i,d1,d2,d3,d4,p0,p1,p2,q,s)
		elseif G[l,m,i] == 0x0a
			l1i1!(n,l,m,i,d1,d2,d3,d4,p0,p1,p2,q,s)
		elseif G[l,m,i] == 0x0b
			l1ie!(n,l,m,i,d1,d2,d3,d4,p0,p1,p2,q,s)

		elseif G[l,m,i] == 0x0c
			lem1!(n,l,m,i,d1,d2,d3,d4,p0,p1,p2,q,s)
		elseif G[l,m,i] == 0x0d
			leme!(n,l,m,i,d1,d2,d3,d4,p0,p1,p2,q,s)
		elseif G[l,m,i] == 0x0e
			lei1!(n,l,m,i,d1,d2,d3,d4,p0,p1,p2,q,s)
		elseif G[l,m,i] == 0x0f
			leie!(n,l,m,i,d1,d2,d3,d4,p0,p1,p2,q,s)

		elseif G[l,m,i] == 0x10
			m1i1!(n,l,m,i,d1,d2,d3,d4,p0,p1,p2,q,s)
		elseif G[l,m,i] == 0x11
			m1ie!(n,l,m,i,d1,d2,d3,d4,p0,p1,p2,q,s)
		elseif G[l,m,i] == 0x12
			mei1!(n,l,m,i,d1,d2,d3,d4,p0,p1,p2,q,s)
		elseif G[l,m,i] == 0x13
			meie!(n,l,m,i,d1,d2,d3,d4,p0,p1,p2,q,s)

			#corners
		elseif G[l,m,i] == 0x14
			l1m1i1!(n,l,m,i,d1,d2,d3,d4,p0,p1,p2,q,s)
		elseif G[l,m,i] == 0x15
			l1m1ie!(n,l,m,i,d1,d2,d3,d4,p0,p1,p2,q,s)
		elseif G[l,m,i] == 0x16
			l1mei1!(n,l,m,i,d1,d2,d3,d4,p0,p1,p2,q,s)
		elseif G[l,m,i] == 0x17
			l1meie!(n,l,m,i,d1,d2,d3,d4,p0,p1,p2,q,s)

		elseif G[l,m,i] == 0x18
			lem1i1!(n,l,m,i,d1,d2,d3,d4,p0,p1,p2,q,s)
		elseif G[l,m,i] == 0x19
			lem1ie!(n,l,m,i,d1,d2,d3,d4,p0,p1,p2,q,s)
		elseif G[l,m,i] == 0x1a
			lemei1!(n,l,m,i,d1,d2,d3,d4,p0,p1,p2,q,s)
		elseif G[l,m,i] == 0x1b
			lemeie!(n,l,m,i,d1,d2,d3,d4,p0,p1,p2,q,s)
		end
		p_out[l,m,i,n] = p2[l,m,i]
	end
end


"""
This file initializes the ClimateERA module by setting and determining the
ECMWF reanalysis parameters to be analyzed and the regions upon which the data
are to be extracted from.  Functionalities include:
    - Setting up of reanalysis module type
    - Setting up of reanalysis parameters to be analyzed
    - Setting up of time steps upon which data are to be downloaded
    - Setting up of region of analysis based on ClimateEasy

"""

function gnssancread(fnc::AbstractArray,epar::Dict,nhr::Integer,ii::Integer,yr::Integer)

    nhr = daysinyear(yr-1) * nhr;

    dyr1 = erancread(fnc[ii],epar); dim = size(dyr1)

    if !(ii==1); d2 = erancread(fnc[ii-1],epar);
    else;        d2 = ones(dim[1],dim[2],nhr) * NaN;
    end

    return cat(d2,d1,dims=3);

end

function gnssancsave(dysm::AbstractArray,ddhr::AbstractArray,dvar::AbstractArray,
                    fnc::AbstractString,nlon::Integer,nlat::Integer,nhr::Integer)

    fnc = replace(fnc,"era"=>"gnssa");
    if isfile(fnc)
       @info "$(Dates.now()) - Unfinished netCDF file $(fnc) detected.  Deleting."
       rm(fnc);
    end

    nccreate(fnc,"longitude")

end


module BIREFRINGENT
using ..AGFFileReader: Glass, GlassID, AGF
export CDSE, BATIO3, SRMOO4_E, AGASS3_E, TAS, CALCITE, CAWO4_E, MGF2_E, TE, MGF2, CUGAS2_E, CAWO4, CDS, LIYF4_E, QUARTZ_E, TEO2_E, BATIO3_E, LIYF4, AGGAS2, CDSE_E, ADP_E, TE_E, LAF3, LAF3_E, TEO2, BBO, BBO_E, KDP, ZNGEP2_E, LINBO3_E, ALN_E, CAMOO4, CDS_E, AL2O3_E, TAS_E, CAMOO4_E, PBMOO4_E, KDP_E, YVO4, ZNO_E, AL2O3, PBMOO4, CUGAS2, SRMOO4, ADP, ALN, BEO, AGGASE2, ZNGEP2, CDGEAS2, AGGAS2_E, CALCITE_E, CDGEAS2_E, LIO3, ZNO, AGASS3, LIO3_E, LINBO3, QUARTZ, BEO_E, AGGASE2_E, YVO4_E

const CDSE = Glass(GlassID(AGF, 935), 9, 4.2243, 1.768, 0.227, 3.12, 3380.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 22.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 4.9, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.0, -1.0, -1.0, 0, 0.0, 0, 1.0, 0)
const BATIO3 = Glass(GlassID(AGF, 936), 2, 4.187, 0.049729, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.4, 0.7, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 0.0, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 2.427266, -1.0, -1.0, 0, 12.296883, 0, 1.0, 0)
const SRMOO4_E = Glass(GlassID(AGF, 937), 2, 2.4923, 0.02214144, 0.105, 20.647936, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.45, 2.4, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 0.0, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.913456, -1.0, -1.0, 0, 28.381356, 0, 1.0, 0)
const AGASS3_E = Glass(GlassID(AGF, 938), 7, 6.364, 0.342, 0.09, 0.0011, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.59, 4.6, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 0.0, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.0, -1.0, -1.0, 0, 0.0, 0, 1.0, 0)
const TAS = Glass(GlassID(AGF, 939), 2, 10.21, 0.197136, 0.522, 625.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 2.0, 12.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 0.0, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.0, -1.0, -1.0, 0, 0.0, 0, 1.0, 0)
const CALCITE = Glass(GlassID(AGF, 940), 6, 0.8559, 0.00345744, 0.8391, 0.019881, 0.0009, 0.038809, 0.6845, 49.070025, 0.0, 0.0, 0.2, 2.2, 1.05e-6, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 0.0, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.658643, -1.0, -1.0, 0, 48.541403, 0, 2.711, 0)
const CAWO4_E = Glass(GlassID(AGF, 941), 2, 2.6041, 0.01901641, 4.1237, 456.719641, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.45, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 0.0, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.937211, -1.0, -1.0, 0, 33.304949, 0, 1.0, 0)
const MGF2_E = Glass(GlassID(AGF, 942), 2, 0.41344023, 0.00135737865, 0.50497499, 0.00823767166, 2.4904862, 565.10774, 0.0, 0.0, 0.0, 0.0, 0.2, 7.04, -2.3e-6, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 0.0, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.389557, -1.0, -1.0, 0, 104.857428, 0, 1.0, 0)
const TE = Glass(GlassID(AGF, 943), 9, 18.5346, 4.3289, 3.981, 3.78, 11813.0, 0.0, 0.0, 0.0, 0.0, 0.0, 4.0, 14.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 27.5, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.0, -1.0, -1.0, 0, 0.0, 0, 1.0, 0)
const MGF2 = Glass(GlassID(AGF, 944), 2, 0.48755108, 0.0018821784, 0.39875031, 0.00895188847, 2.3120353, 566.135591, 0.0, 0.0, 0.0, 0.0, 0.2, 7.04, -2.5e-7, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 9.4, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.377744, -1.0, -1.0, 0, 106.217612, 0, 3.171, 0)
const CUGAS2_E = Glass(GlassID(AGF, 945), 9, 4.3165, 1.8692, 0.1364, 1.7575, 738.43, 0.0, 0.0, 0.0, 0.0, 0.0, 0.55, 11.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 0.0, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.0, -1.0, -1.0, 0, 0.0, 0, 1.0, 0)
const CAWO4 = Glass(GlassID(AGF, 946), 2, 2.5493, 0.01814409, 0.92, 116.964225, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.45, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 0.0, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.920414, -1.0, -1.0, 0, 34.953592, 0, 1.0, 0)
const CDS = Glass(GlassID(AGF, 947), 2, 3.9658282, 0.0558036869, 0.18113874, 0.233146066, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.51, 1.4, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 0.0, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.0, -1.0, -1.0, 0, 0.0, 0, 1.0, 0)
const LIYF4_E = Glass(GlassID(AGF, 948), 9, 1.31021, 0.84903, 0.00876, 0.53607, 134.9566, 0.0, 0.0, 0.0, 0.0, 0.0, 0.23, 2.6, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 0.0, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.476472, -1.0, -1.0, 0, 88.935059, 0, 1.0, 0)
const QUARTZ_E = Glass(GlassID(AGF, 949), 1, 2.38421862, -0.0120653449, 0.011013843, 0.00012886313, 1.68747314e-7, 4.92022338e-8, 0.0, 0.0, 0.0, 0.0, 0.18, 0.71, -1.638e-5, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 0.0, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.553398, -1.0, -1.0, 0, 68.797763, 0, 0.0, 0)
const TEO2_E = Glass(GlassID(AGF, 950), 2, 2.823, 0.01800964, 1.542, 0.06922161, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.4, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 0.0, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 2.430453, -1.0, -1.0, 0, 15.474561, 0, 1.0, 0)
const BATIO3_E = Glass(GlassID(AGF, 951), 2, 4.064, 0.044521, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.4, 0.7, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 0.0, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 2.380271, -1.0, -1.0, 0, 13.963107, 0, 1.0, 0)
const LIYF4 = Glass(GlassID(AGF, 952), 9, 1.38757, 0.70757, 0.00931, 0.18849, 50.99741, 0.0, 0.0, 0.0, 0.0, 0.0, 0.23, 2.6, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 0.0, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.453776, -1.0, -1.0, 0, 93.549426, 0, 1.0, 0)
const AGGAS2 = Glass(GlassID(AGF, 953), 9, 3.628, 2.1686, 0.1003, 2.7153, 950.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.49, 12.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 0.0, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.0, -1.0, -1.0, 0, 0.0, 0, 1.0, 0)
const CDSE_E = Glass(GlassID(AGF, 954), 9, 4.2009, 1.8875, 0.2171, 3.6461, 3629.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 22.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 0.0, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.0, -1.0, -1.0, 0, 0.0, 0, 1.0, 0)
const ADP_E = Glass(GlassID(AGF, 955), 2, 1.162166, 0.0085932421, 12.01997, 831.8239, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.4, 1.06, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 0.0, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.478797, -1.0, -1.0, 0, 61.616939, 0, 1.0, 0)
const TE_E = Glass(GlassID(AGF, 956), 9, 29.5222, 9.3068, 2.5766, 9.235, 13521.0, 0.0, 0.0, 0.0, 0.0, 0.0, 4.0, 14.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 0.0, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.0, -1.0, -1.0, 0, 0.0, 0, 1.0, 0)
const LAF3 = Glass(GlassID(AGF, 957), 2, 1.5376, 0.00776161, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.35, 0.7, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 0.0, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.604046, -1.0, -1.0, 0, 80.83154, 0, 1.0, 0)
const LAF3_E = Glass(GlassID(AGF, 958), 2, 1.5149, 0.00770884, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.35, 0.7, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 0.0, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.596715, -1.0, -1.0, 0, 81.256833, 0, 1.0, 0)
const TEO2 = Glass(GlassID(AGF, 959), 2, 2.584, 0.01800964, 1.157, 0.06959044, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.4, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 0.0, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 2.274935, -1.0, -1.0, 0, 16.22277, 0, 1.0, 0)
const BBO = Glass(GlassID(AGF, 960), 7, 2.7405, 0.0184, 0.0179, 0.0155, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.22, 1.06, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 0.0, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.670737, -1.0, -1.0, 0, 52.593907, 0, 1.0, 0)
const BBO_E = Glass(GlassID(AGF, 961), 7, 2.373, 0.0128, 0.0156, 0.0044, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.22, 1.06, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 0.0, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.552518, -1.0, -1.0, 0, 61.321496, 0, 1.0, 0)
const KDP = Glass(GlassID(AGF, 962), 2, 1.256618, 0.0084478168, 33.89909, 1113.904, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.4, 1.06, -9.718e-5, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 22.0, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.509182, -1.0, -1.0, 0, 56.224997, 0, 2.339, 0)
const ZNGEP2_E = Glass(GlassID(AGF, 963), 9, 4.6332, 5.3422, 0.1426, 1.458, 662.55, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 12.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 0.0, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.0, -1.0, -1.0, 0, 0.0, 0, 1.0, 0)
const LINBO3_E = Glass(GlassID(AGF, 964), 9, 2.32468, 2.2565, 0.0441, 14.503, 671.587225, 0.0, 0.0, 0.0, 0.0, 0.0, 0.4, 3.1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 0.0, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 2.214539, -1.0, -1.0, 0, 20.455966, 0, 1.0, 0)
const ALN_E = Glass(GlassID(AGF, 965), 9, 3.0729, 1.6173, 0.030485, 4.139, 225.9009, 0.0, 0.0, 0.0, 0.0, 0.0, 0.22, 5.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 0.0, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 2.200116, -1.0, -1.0, 0, 44.15554, 0, 1.0, 0)
const CAMOO4 = Glass(GlassID(AGF, 966), 2, 2.784, 0.02199289, 1.2425, 134.003776, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.45, 3.8, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 0.0, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.992539, -1.0, -1.0, 0, 28.832428, 0, 1.0, 0)
const CDS_E = Glass(GlassID(AGF, 967), 2, 3.97478769, 0.0502969611, 0.26680809, 0.218030956, 0.00074077, 0.259235138, 0.0, 0.0, 0.0, 0.0, 0.51, 1.4, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 0.0, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.0, -1.0, -1.0, 0, 0.0, 0, 1.0, 0)
const AL2O3_E = Glass(GlassID(AGF, 968), 2, 1.5039759, 0.00548026323, 0.55069141, 0.0147994281, 6.5927379, 402.89514, 0.0, 0.0, 0.0, 0.0, 0.2, 5.5, 2.2e-5, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 0.0, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.760096, -1.0, -1.0, 0, 72.988091, 0, 1.0, 0)
const TAS_E = Glass(GlassID(AGF, 969), 2, 8.993, 0.197136, 0.308, 625.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 2.0, 12.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 0.0, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.0, -1.0, -1.0, 0, 0.0, 0, 1.0, 0)
const CAMOO4_E = Glass(GlassID(AGF, 970), 2, 2.8045, 0.02377764, 1.0055, 134.003776, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.45, 3.8, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 0.0, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 2.002336, -1.0, -1.0, 0, 26.628162, 0, 1.0, 0)
const PBMOO4_E = Glass(GlassID(AGF, 971), 2, 3.52555, 0.03222025, 0.2066, 0.105865637, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.44, 1.08, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 0.0, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 2.277375, -1.0, -1.0, 0, 15.77258, 0, 1.0, 0)
const KDP_E = Glass(GlassID(AGF, 972), 2, 1.131091, 0.00814598, 5.75675, 811.7537, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.4, 1.06, -0.0001008, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 39.200001, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.468324, -1.0, -1.0, 0, 68.984949, 0, 2.339, 0)
const YVO4 = Glass(GlassID(AGF, 973), 7, 3.77834, 0.069736, 0.04724, 0.0108133, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.63, 1.55, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 0.0, -1.0, 0, -1.0, [], 1.0, -1.0, -1.0, 0, 0.0, 0, 1.0, 0)
const ZNO_E = Glass(GlassID(AGF, 974), 8, 2.80333, 0.9447, 0.09024016, 0.00714, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.45, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 0.0, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 2.019875, -1.0, -1.0, 0, 12.436859, 0, 1.0, 0)
const AL2O3 = Glass(GlassID(AGF, 975), 2, 1.4313493, 0.0052799261, 0.65054713, 0.0142382647, 5.3414021, 325.017834, 0.0, 0.0, 0.0, 0.0, 0.2, 5.5, 1.8e-5, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 6.65, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.768172, -1.0, -1.0, 0, 72.308589, 0, 1.0, 0)
const PBMOO4 = Glass(GlassID(AGF, 976), 2, 3.54642, 0.0342916324, 0.5827, 0.11400077, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.44, 1.08, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 0.0, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 2.40988, -1.0, -1.0, 0, 10.949945, 0, 1.0, 0)
const CUGAS2 = Glass(GlassID(AGF, 977), 9, 3.9064, 2.3065, 0.1149, 1.5479, 738.43, 0.0, 0.0, 0.0, 0.0, 0.0, 0.55, 11.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 0.0, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.0, -1.0, -1.0, 0, 0.0, 0, 1.0, 0)
const SRMOO4 = Glass(GlassID(AGF, 978), 2, 2.4839, 0.02105401, 0.1015, 21.187609, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.45, 2.4, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 0.0, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.908806, -1.0, -1.0, 0, 29.94765, 0, 1.0, 0)
const ADP = Glass(GlassID(AGF, 979), 2, 1.29899, 0.0089232927, 43.17364, 1188.531, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.4, 1.06, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 0.0, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.523454, -1.0, -1.0, 0, 52.25678, 0, 1.0, 0)
const ALN = Glass(GlassID(AGF, 980), 9, 3.1399, 1.3786, 0.02941, 3.861, 225.9009, 0.0, 0.0, 0.0, 0.0, 0.0, 0.22, 5.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 0.0, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 2.154291, -1.0, -1.0, 0, 50.733102, 0, 1.0, 0)
const BEO = Glass(GlassID(AGF, 981), 2, 1.92274, 0.0062536464, 1.24209, 94.3443116, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.44, 7.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 0.0, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.718619, -1.0, -1.0, 0, 93.251706, 0, 1.0, 0)
const AGGASE2 = Glass(GlassID(AGF, 982), 9, 4.6453, 2.2057, 0.1879, 1.8377, 1600.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.73, 13.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 0.0, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.0, -1.0, -1.0, 0, 0.0, 0, 1.0, 0)
const ZNGEP2 = Glass(GlassID(AGF, 983), 9, 4.4733, 5.2658, 0.1338, 1.4909, 662.55, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 12.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 0.0, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.0, -1.0, -1.0, 0, 0.0, 0, 1.0, 0)
const CDGEAS2 = Glass(GlassID(AGF, 984), 9, 10.1064, 2.2988, 1.0872, 1.6247, 1370.0, 0.0, 0.0, 0.0, 0.0, 0.0, 2.4, 11.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 0.0, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.0, -1.0, -1.0, 0, 0.0, 0, 1.0, 0)
const AGGAS2_E = Glass(GlassID(AGF, 985), 9, 4.0172, 1.5274, 0.131, 2.1699, 950.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.49, 12.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 0.0, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.0, -1.0, -1.0, 0, 0.0, 0, 1.0, 0)
const CALCITE_E = Glass(GlassID(AGF, 986), 2, 1.0856, 0.0062362609, 0.0988, 0.020164, 0.317, 131.515024, 0.0, 0.0, 0.0, 0.0, 0.2, 3.3, 2.59e-5, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 0.0, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.486494, -1.0, -1.0, 0, 79.064587, 0, 2.711, 0)
const CDGEAS2_E = Glass(GlassID(AGF, 987), 9, 11.8018, 1.2152, 2.6971, 1.6922, 1370.0, 0.0, 0.0, 0.0, 0.0, 0.0, 2.4, 11.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 0.0, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.0, -1.0, -1.0, 0, 0.0, 0, 1.0, 0)
const LIO3 = Glass(GlassID(AGF, 988), 9, 2.03132, 1.37623, 0.0350823, 1.06745, 169.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.5, 5.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 0.0, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.0, -1.0, -1.0, 0, 0.0, 0, 1.0, 0)
const ZNO = Glass(GlassID(AGF, 989), 8, 2.81418, 0.87968, 0.09253764, 0.00711, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.45, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 6.5, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 2.003385, -1.0, -1.0, 0, 12.424016, 0, 1.0, 0)
const AGASS3 = Glass(GlassID(AGF, 990), 7, 7.483, 0.474, 0.09, 0.0019, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.63, 4.6, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 0.0, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.0, -1.0, -1.0, 0, 0.0, 0, 1.0, 0)
const LIO3_E = Glass(GlassID(AGF, 991), 9, 1.83086, 1.08807, 0.031381, 0.554582, 158.76, 0.0, 0.0, 0.0, 0.0, 0.0, 0.5, 5.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 0.0, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.0, -1.0, -1.0, 0, 0.0, 0, 1.0, 0)
const LINBO3 = Glass(GlassID(AGF, 992), 9, 2.39198, 2.51118, 0.047089, 7.1333, 272.316004, 0.0, 0.0, 0.0, 0.0, 0.0, 0.4, 3.1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 0.0, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 2.300158, -1.0, -1.0, 0, 18.711921, 0, 1.0, 0)
const QUARTZ = Glass(GlassID(AGF, 993), 1, 2.35676495, -0.0113996924, 0.0108741656, 3.32066914e-5, 1.08609346e-5, -3.10123984e-7, 0.0, 0.0, 0.0, 0.0, 0.18, 0.71, -1.487e-5, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 0.0, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.544296, -1.0, -1.0, 0, 70.132045, 0, 0.0, 0)
const BEO_E = Glass(GlassID(AGF, 994), 2, 1.96939, 0.00737881, 1.67389, 109.824112, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.44, 7.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 0.0, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.734106, -1.0, -1.0, 0, 79.335058, 0, 1.0, 0)
const AGGASE2_E = Glass(GlassID(AGF, 995), 9, 5.2912, 1.397, 0.2845, 1.9282, 1600.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.73, 13.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 0.0, -1.0, 0, -1.0, [(0.334, 1.0, 25.0), (0.35, 1.0, 25.0), (0.365, 1.0, 25.0), (0.37, 1.0, 25.0), (0.38, 1.0, 25.0), (0.39, 1.0, 25.0), (0.4, 1.0, 25.0), (0.42, 1.0, 25.0), (0.46, 1.0, 25.0), (0.5, 1.0, 25.0), (0.66, 1.0, 25.0), (1.06, 1.0, 25.0), (1.529, 1.0, 25.0), (2.325, 1.0, 25.0)], 1.0, -1.0, -1.0, 0, 0.0, 0, 1.0, 0)
const YVO4_E = Glass(GlassID(AGF, 996), 7, 4.59905, 0.110534, 0.04813, 0.0122676, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.63, 1.55, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, -1.0, -1.0, 0.0, -1.0, 0, -1.0, [], 1.0, -1.0, -1.0, 0, 0.0, 0, 1.0, 0)
end # module


module sbp

include("config.jl")
include("grid.jl")
include("operator.jl")
include("metrics.jl")
include("sparse.jl")
include("vtk.jl")
include("test.jl")
include("source.jl")
include("table.jl")
include("lmv.jl")
include("mms.jl")
include("boundary_operator.jl")

# Time integrators
include("time_integrators/lsrk4.jl")
include("time_integrators/staggered_rk4.jl")

# Operators
include("../operators/oreilly_petersson_2019/operators.jl")
include("../operators/strand_1994/operators.jl")

# Staggered operators and discretizations
include("staggered.jl")
include("staggered/acoustic.jl")

# Collocated operators and discretizations
include("collocated.jl")
include("collocated/acoustic.jl")

end


#!usr/bin/env julia

include("call_cc.jl")

@show @call_cc cc begin
    5*6+cc(7)
end # ⇒ 9

block(content::Function, except::Function) =
    (@call_cc success begin
        except(@call_cc fail begin
            success(content(fail))
        end)
    end)

block(
    raise -> begin
        function safe_div(a, b)
            if b == 0
                raise("divide by zero")
            else
                div(a, b)
            end
        end
        @show safe_div(3/4)
        @show safe_div(9/6)
        @show safe_div(2/0)
    end,
    errmsg -> begin
        print_with_color(:red, errmsg)
    end
)


# Histogram (Bar) plots
# Vertical bars

function PlotBars(label_id, values::AbstractArray{T}, count::Integer, width::Real = 0.67, shift::Real = 0.0, offset::Integer = 0, stride::Integer = sizeof(T)) where {T<:ImPlotData}
    ImPlot_PlotBars(label_id, values, count, width, shift, offset, stride)
end

function PlotBars(label_id, x::AbstractArray{T}, y::AbstractArray{T}, count::Integer, width::Real, offset::Integer = 0, stride::Integer = sizeof(T)) where {T<:ImPlotData}
    ImPlot_PlotBars(label_id, x, y, count, width, offset, stride)
end

function PlotBars(label_id, values::AbstractArray{T}, count::Integer, width::Real = 0.67, shift::Real = 0.0, offset::Integer = 0, stride::Integer = sizeof(Float64)) where {T<:Real}
    ImPlot_PlotBars(label_id, Float64.(values), count, width, shift, offset, stride)
end

function PlotBars(label_id, x::AbstractArray{T}, y::AbstractArray{T}, count::Integer, width::Real, offset::Integer = 0, stride::Integer = sizeof(Float64)) where {T<:Real}
    ImPlot_PlotBars(label_id, Float64.(x), Float64.(y), count, width, offset, stride)
end

function PlotBars(values::AbstractArray{T}; count::Integer = length(values),
                  label_id::String = "", width = 0.67, shift = 0.0, offset::Integer = 0,
                  stride::Integer = 1) where {T <: ImPlotData}

    ImPlot_PlotBars(label_id, values, count, width, shift, offset, stride * sizeof(T))
end

function PlotBars(x::AbstractArray{T}, y::AbstractArray{T};
                  count::Integer = min(length(x),length(y)), label_id::String = "",
                  width = 0.67, offset::Integer = 0, stride::Integer = 1) where {T <: ImPlotData}

    ImPlot_PlotBars(label_id, x, y, count, width, offset, stride * sizeof(T))
end

function PlotBars(x::AbstractArray{T1}, y::AbstractArray{T2};
                  kwargs...) where {T1<:Real, T2<:Real}

    PlotBars(promote(x, y)..., kwargs...)
end

# Horizontal bars

function PlotBarsH(label_id, values::AbstractArray{T}, count::Integer, height::Real = 0.67, shift::Real = 0.0, offset::Integer = 0, stride::Integer = sizeof(T)) where {T<:ImPlotData}
    ImPlot_PlotBarsH(label_id, values, count, height, shift, offset, stride)
end

function PlotBarsH(label_id, x::AbstractArray{T}, y::AbstractArray{T}, count::Integer, height::Real, offset::Integer = 0, stride::Integer = sizeof(T)) where {T<:ImPlotData}
    ImPlot_PlotBarsH(label_id, x, y, count, height, offset, stride)
end

function PlotBarsH(label_id, values::AbstractArray{T}, count::Integer, height::Real = 0.67, shift::Real = 0.0, offset::Integer = 0, stride::Integer = sizeof(Float64)) where {T<:Real}
    ImPlot_PlotBarsH(label_id, Float64.(values), count, height, shift, offset, stride)
end

function PlotBarsH(label_id, x::AbstractArray{T}, y::AbstractArray{T}, count::Integer, height::Real, offset::Integer = 0, stride::Integer = sizeof(Float64)) where {T<:Real}
    ImPlot_PlotBarsH(label_id, Float64.(x), Float64.(y), count, height, offset, stride)
end

function PlotBarsH(values::AbstractArray{T}; count::Integer = length(values),
                  label_id::String = "", width = 0.67, shift = 0.0, offset::Integer = 0,
                  stride::Integer = 1) where {T <: ImPlotData}

    ImPlot_PlotBarsH(label_id, values, count, width, shift, offset, stride * sizeof(T))
end

function PlotBarsH(x::AbstractArray{T}, y::AbstractArray{T};
                  count::Integer = min(length(x),length(y)), label_id::String = "",
                  width = 0.67, offset::Integer = 0, stride::Integer = 1) where {T<:ImPlotData}

    ImPlot_PlotBarsH(label_id, x, y, count, width, offset, stride * sizeof(T))
end

function PlotBarsH(x::AbstractArray{T1}, y::AbstractArray{T2};
                   kwargs...) where {T1<:Real, T2<:Real}

    PlotBarsH(promote(x, y)..., kwargs...)
end

PlotBarsG(label_id, getter, data, count, width, offset = 0) =
ImPlot_PlotBarsG(label_id, getter, data, count, offset)


@inline function DiffEqBase.change_t_via_interpolation!(integrator::SDEIntegrator,t,modify_save_endpoint::Type{Val{T}}=Val{false}) where T
  # Can get rid of an allocation here with a function
  # get_tmp_arr(integrator.cache) which gives a pointer to some
  # cache array which can be modified.
  if integrator.tdir*t < integrator.tdir*integrator.tprev
    error("Current interpolant only works between tprev and t")
  elseif t != integrator.t
    if typeof(integrator.u) <: AbstractArray
      integrator(integrator.u,t)
    else
      integrator.u = integrator(t)
    end
    integrator.dtnew = integrator.t - t
    reject_step!(integrator,t-integrator.tprev) #this only changes dt and noise, so no interpolation problems
    integrator.dt = integrator.dtnew
    integrator.sqdt = sqrt(abs(integrator.dt))
    integrator.t = t

    # reeval_internals_due_to_modification!(integrator) # Not necessary for linear interp
    if T
      solution_endpoint_match_cur_integrator!(integrator)
    end
  end
end

function (integrator::SDEIntegrator)(t,deriv::Type=Val{0};idxs=nothing)
  current_interpolant(t,integrator,idxs,deriv)
end

(integrator::SDEIntegrator)(val::AbstractArray,t::Union{Number,AbstractArray},deriv::Type=Val{0};idxs=nothing) = current_interpolant!(val,t,integrator,idxs,deriv)

function u_modified!(integrator::SDEIntegrator,bool::Bool)
  integrator.u_modified = bool
end

get_proposed_dt(integrator::SDEIntegrator) = ifelse(integrator.opts.adaptive, integrator.dtpropose, integrator.dtcache)
set_proposed_dt!(integrator::SDEIntegrator,dt::Number) = (integrator.dtpropose = dt; integrator.dtcache = dt)

function set_proposed_dt!(integrator::SDEIntegrator,integrator2::SDEIntegrator)
  integrator.dtpropose = integrator2.dtpropose
  integrator.dtcache = integrator2.dtcache
  integrator.qold = integrator2.qold
  integrator.erracc = integrator2.erracc
  integrator.dtacc = integrator2.dtacc
end

#TODO: Bigger caches for most algorithms
@inline DiffEqBase.get_tmp_cache(integrator::SDEIntegrator) =
  get_tmp_cache(integrator, integrator.alg, integrator.cache)
# avoid method ambiguity
for typ in (StochasticDiffEqAlgorithm,StochasticDiffEqNewtonAdaptiveAlgorithm)
  @eval @inline DiffEqBase.get_tmp_cache(integrator::SDEIntegrator, alg::$typ, cache::StochasticDiffEqConstantCache) = nothing
end
@inline DiffEqBase.get_tmp_cache(integrator::SDEIntegrator, alg, cache) = (cache.tmp,)
@inline DiffEqBase.get_tmp_cache(integrator::SDEIntegrator, alg::StochasticDiffEqNewtonAdaptiveAlgorithm, cache) =
    (cache.tmp, cache.atmp)
@inline DiffEqBase.get_tmp_cache(integrator::SDEIntegrator, alg::StochasticCompositeAlgorithm, cache) =
    get_tmp_cache(integrator, alg.algs[1], cache.caches[1])

full_cache(integrator::SDEIntegrator) = full_cache(integrator.cache)
full_cache(integrator::StochasticCompositeCache) = Iterators.flatten(full_cache(c) for c in integrator.caches)

ratenoise_cache(integrator::SDEIntegrator) = ratenoise_cache(integrator.cache)
ratenoise_cache(integrator::StochasticCompositeCache) = Iterators.flatten(ratenoise_cache(c) for c in integrator.caches)

rand_cache(integrator::SDEIntegrator) = rand_cache(integrator.cache)
rand_cache(integrator::StochasticCompositeCache) = Iterators.flatten(rand_cache(c) for c in integrator.caches)

jac_iter(integrator::SDEIntegrator) = jac_iter(integrator.cache)
jac_iter(integrator::StochasticCompositeCache) = Iterators.flatten(jac_iter(c) for c in integrator.caches)

@inline function add_tstop!(integrator::SDEIntegrator,t)
  integrator.tdir * (t - integrator.t) < 0 && error("Tried to add a tstop that is behind the current time. This is strictly forbidden")
  push!(integrator.opts.tstops, integrator.tdir * t)
end

function DiffEqBase.add_saveat!(integrator::SDEIntegrator,t)
  integrator.tdir * (t - integrator.t) < 0 && error("Tried to add a saveat that is behind the current time. This is strictly forbidden")
  push!(integrator.opts.saveat, integrator.tdir * t)
end

resize_non_user_cache!(integrator::SDEIntegrator,i::Int) = resize_non_user_cache!(integrator,integrator.cache,i)
deleteat_non_user_cache!(integrator::SDEIntegrator,i) = deleteat_non_user_cache!(integrator,integrator.cache,i)
addat_non_user_cache!(integrator::SDEIntegrator,i) = addat_non_user_cache!(integrator,integrator.cache,i)
resize!(integrator::SDEIntegrator,i::Int) = resize!(integrator,integrator.cache,i)

function resize!(integrator::SDEIntegrator,cache,i)
  # This has to go first!
  resize_non_user_cache!(integrator,cache,i)
  for c in full_cache(integrator)
    resize!(c,i)
  end
  for c in ratenoise_cache(integrator)
    resize!(c,i)
  end
end

function resize_noise!(integrator,cache,bot_idx,i)
  for c in integrator.W.S₁
    resize!(c[2],i)
    if alg_needs_extra_process(integrator.alg)
      resize!(c[3],i)
    end
    if i >= bot_idx # fill in rands
      fill_new_noise_caches!(integrator,c,c[1],bot_idx:i)
    end
  end
  for c in integrator.W.S₂
    resize!(c[2],i)
    if alg_needs_extra_process(integrator.alg)
      resize!(c[3],i)
    end
    if i >= bot_idx # fill in rands
      fill_new_noise_caches!(integrator,c,c[1],bot_idx:i)
    end
  end
  resize!(integrator.W.dW,i)
  integrator.W.dW[end] = zero(eltype(integrator.u))
  resize!(integrator.W.dWtilde,i)
  integrator.W.dWtilde[end] = zero(eltype(integrator.u))
  resize!(integrator.W.dWtmp,i)
  integrator.W.dWtmp[end] = zero(eltype(integrator.u))
  resize!(integrator.W.curW,i)
  integrator.W.curW[end] = zero(eltype(integrator.u))
  DiffEqNoiseProcess.resize_stack!(integrator.W,i)

  if alg_needs_extra_process(integrator.alg)
    resize!(integrator.W.dZ,i)
    integrator.W.dZ[end] = zero(eltype(integrator.u))
    resize!(integrator.W.dZtilde,i)
    integrator.W.dZtilde[end] = zero(eltype(integrator.u))
    resize!(integrator.W.dZtmp,i)
    integrator.W.dZtmp[end] = zero(eltype(integrator.u))
    resize!(integrator.W.curZ,i)
    integrator.W.curZ[end] = zero(eltype(integrator.u))
  end
  if i >= bot_idx # fill in rands
    fill!(@view(integrator.W.curW[bot_idx:i]),zero(eltype(integrator.u)))
    if alg_needs_extra_process(integrator.alg)
      fill!(@view(integrator.W.curZ[bot_idx:i]),zero(eltype(integrator.u)))
    end
  end
end

@inline function fill_new_noise_caches!(integrator,c,scaling_factor,idxs)
  if isinplace(integrator.W)
    integrator.W.dist(@view(c[2][idxs]),integrator.W,scaling_factor,integrator.u,integrator.p,integrator.t,integrator.W.rng)
    if alg_needs_extra_process(integrator.alg)
      integrator.W.dist(@view(c[3][idxs]),integrator.W,scaling_factor,integrator.u,integrator.p,integrator.t,integrator.W.rng)
    end
  else
    c[2][idxs] .= integrator.noise(length(idxs),integrator,scaling_factor)
    if alg_needs_extra_process(integrator.alg)
      c[3][idxs] .= integrator.noise(length(idxs),integrator,scaling_factor)
    end
  end

end

function resize_non_user_cache!(integrator::SDEIntegrator,cache,i)
  bot_idx = length(integrator.u) + 1
  if is_diagonal_noise(integrator.sol.prob)
    resize_noise!(integrator,cache,bot_idx,i)
    for c in rand_cache(integrator)
      resize!(c,i)
    end
  end
end

function deleteat!(integrator::SDEIntegrator,idxs)
  deleteat_non_user_cache!(integrator,cache,idxs)
  for c in full_cache(integrator)
    deleteat!(c,idxs)
  end
  for c in ratenoise_cache(integrator)
    deleteat!(c,idxs)
  end
end

function addat!(integrator::SDEIntegrator,idxs)
  addat_non_user_cache!(integrator,cache,idxs)
  for c in full_cache(integrator)
    addat!(c,idxs)
  end
  for c in ratenoise_cache(integrator)
    addat!(c,idxs)
  end
end

function deleteat_non_user_cache!(integrator::SDEIntegrator,cache,idxs)
  if is_diagonal_noise(integrator.sol.prob)
    deleteat_noise!(integrator,cache,idxs)
    for c in rand_cache(integrator)
      deleteat!(c,idxs)
    end
  end
end

function addat_non_user_cache!(integrator::SDEIntegrator,cache,idxs)
  if is_diagonal_noise(integrator.sol.prob)
    addat_noise!(integrator,cache,idxs)
    for c in rand_cache(integrator)
      addat!(c,idxs)
    end
  end
end

function deleteat_noise!(integrator,cache,idxs)
  for c in integrator.W.S₁
    deleteat!(c[2],idxs)
    if alg_needs_extra_process(integrator.alg)
      deleteat!(c[3],idxs)
    end
  end
  for c in integrator.W.S₂
    deleteat!(c[2],idxs)
    if alg_needs_extra_process(integrator.alg)
      deleteat!(c[3],idxs)
    end
  end
  deleteat!(integrator.W.dW,idxs)
  deleteat!(integrator.W.dWtilde,idxs)
  deleteat!(integrator.W.dWtmp,idxs)
  deleteat!(integrator.W.curW,idxs)
  DiffEqNoiseProcess.resize_stack!(integrator.W,length(integrator.u))

  if alg_needs_extra_process(integrator.alg)
    deleteat!(integrator.W.curZ,idxs)
    deleteat!(integrator.W.dZtmp,idxs)
    deleteat!(integrator.W.dZtilde,idxs)
    deleteat!(integrator.W.dZ,idxs)
  end
end

function addat_noise!(integrator,cache,idxs)
  for c in integrator.W.S₁
    addat!(c[2],idxs)
    if alg_needs_extra_process(integrator.alg)
      addat!(c[3],idxs)
    end
    fill_new_noise_caches!(integrator,c,c[1],idxs)
  end
  for c in integrator.W.S₂
    addat!(c[2],idxs)
    if alg_needs_extra_process(integrator.alg)
      addat!(c[3],idxs)
    end
    fill_new_noise_caches!(integrator,c,c[1],idxs)
  end

  addat!(integrator.W.dW,idxs)
  integrator.W.dW[idxs] .= zero(eltype(integrator.u))
  addat!(integrator.W.curW,idxs)
  integrator.W.curW[idxs] .= zero(eltype(integrator.u))
  if alg_needs_extra_process(integrator.alg)
    addat!(integrator.W.dZ,idxs)
    integrator.W.dZ[idxs] .= zero(eltype(integrator.u))
    addat!(integrator.W.curZ,idxs)
    integrator.W.curZ[idxs] .= zero(eltype(integrator.u))
  end

  i = length(integrator.u)
  resize!(integrator.W.dWtilde,i)
  resize!(integrator.W.dWtmp,i)
  DiffEqNoiseProcess.resize_stack!(integrator.W,i)
  if alg_needs_extra_process(integrator.alg)
    resize!(integrator.W.dZtmp,i)
    resize!(integrator.W.dZtilde,i)
  end

  # fill in rands
  fill!(@view(integrator.W.curW[idxs]),zero(eltype(integrator.u)))
  if alg_needs_extra_process(integrator.alg)
    fill!(@view(integrator.W.curZ[idxs]),zero(eltype(integrator.u)))
  end
end

function terminate!(integrator::SDEIntegrator, retcode = :Terminated)
  integrator.sol = DiffEqBase.solution_new_retcode(integrator.sol, retcode)
  integrator.opts.tstops.valtree = typeof(integrator.opts.tstops.valtree)()
end

DiffEqBase.has_reinit(integrator::SDEIntegrator) = true
function DiffEqBase.reinit!(integrator::SDEIntegrator,u0 = integrator.sol.prob.u0;
  t0 = integrator.sol.prob.tspan[1], tf = integrator.sol.prob.tspan[2],
  erase_sol = true,
  tstops = integrator.opts.tstops_cache,
  saveat = integrator.opts.saveat_cache,
  d_discontinuities = integrator.opts.d_discontinuities_cache,
  reinit_cache = true,reinit_callbacks = true,
  initialize_save = true,
  reset_dt = (integrator.dtcache == zero(integrator.dt)) && integrator.opts.adaptive)

  if isinplace(integrator.sol.prob)
    recursivecopy!(integrator.u,u0)
    recursivecopy!(integrator.uprev,integrator.u)
  else
    integrator.u = u0
    integrator.uprev = integrator.u
  end

  integrator.t = t0
  integrator.tprev = t0

  tType = typeof(integrator.t)
  tspan = (tType(t0), tType(tf))
  integrator.opts.tstops = OrdinaryDiffEq.initialize_tstops(tType, tstops, d_discontinuities, tspan)
  integrator.opts.saveat = OrdinaryDiffEq.initialize_saveat(tType, saveat, tspan)
  integrator.opts.d_discontinuities = OrdinaryDiffEq.initialize_d_discontinuities(tType, d_discontinuities, tspan)

  if erase_sol
    if integrator.opts.save_start
      resize_start = 1
    else
      resize_start = 0
    end
    resize!(integrator.sol.u,resize_start)
    resize!(integrator.sol.t,resize_start)
    if integrator.sol.u_analytic !== nothing
      resize!(integrator.sol.u_analytic,0)
    end
    if typeof(integrator.alg) <: StochasticDiffEqCompositeAlgorithm
      resize!(integrator.sol.alg_choice,resize_start)
    end
    integrator.saveiter = resize_start
  end
  integrator.iter = 0
  integrator.success_iter = 0

  # full re-initialize the PI in timestepping
  integrator.qold = integrator.opts.qoldinit
  integrator.q11 = typeof(integrator.t)(1)

  if reset_dt
    auto_dt_reset!(integrator)
  end

  if reinit_callbacks
    initialize_callbacks!(integrator, initialize_save)
  end

  if reinit_cache
    initialize!(integrator,integrator.cache)
  end

  reinit!(integrator.W,integrator.dt)
end

function DiffEqBase.auto_dt_reset!(integrator::SDEIntegrator)
  integrator.dt = sde_determine_initdt(integrator.u,integrator.t,
  integrator.tdir,integrator.opts.dtmax,integrator.opts.abstol,integrator.opts.reltol,
  integrator.opts.internalnorm,integrator.sol.prob,get_current_alg_order(integrator.alg, integrator.cache),
  integrator)
end

@inline function DiffEqBase.get_du(integrator::SDEIntegrator)
  (integrator.u - integrator.uprev) / integrator.dt
end

@inline function DiffEqBase.get_du!(out,integrator::SDEIntegrator)
  @.. out = (integrator.u - integrator.uprev) / integrator.dt
end

function DiffEqBase.set_t!(integrator::SDEIntegrator, t::Real)
  if integrator.opts.save_everystep
    error("Integrator time cannot be reset unless it is initialized",
          " with save_everystep=false")
  end
  if !isdtchangeable(integrator.alg)
    reinit!(integrator, integrator.u;
            t0 = t,
            reset_dt = false,
            reinit_callbacks = false,
            reinit_cache = false)
  else
    integrator.t = t
  end
end

function DiffEqBase.set_u!(integrator::SDEIntegrator, u)
  if integrator.opts.save_everystep
    error("Integrator state cannot be reset unless it is initialized",
          " with save_everystep=false")
  end
  integrator.u = u
  u_modified!(integrator, true)
end