function cal_flux_residual_kernel(gpuGlobalDataConn, gpuGlobalDataFixedPoint, gpuGlobalDataRest, gpuConfigData, numPoints)
	idx = (blockIdx().x - 1) * blockDim().x + threadIdx().x
	thread_idx = threadIdx().x
	block_dim = blockDim().x

	shared = @cuStaticSharedMem(Float64, 1024)
	flux_shared = @cuStaticSharedMem(Float64, 512)
	if idx > 0 && idx <= numPoints

		flux_shared[thread_idx], flux_shared[thread_idx + block_dim], flux_shared[thread_idx + block_dim * 2],
			flux_shared[thread_idx + block_dim * 3] = 0.0, 0.0, 0.0, 0.0

		if gpuGlobalDataFixedPoint[idx].flag_1 == gpuConfigData[17]
			wall_kernel(gpuGlobalDataConn, gpuGlobalDataFixedPoint, gpuGlobalDataRest, gpuConfigData, shared, flux_shared)
		elseif gpuGlobalDataFixedPoint[idx].flag_1 == gpuConfigData[18]
			interior_kernel(gpuGlobalDataConn, gpuGlobalDataFixedPoint, gpuGlobalDataRest, gpuConfigData, shared, flux_shared)
		elseif gpuGlobalDataFixedPoint[idx].flag_1 == gpuConfigData[19]
			outer_kernel(gpuGlobalDataConn, gpuGlobalDataFixedPoint, gpuGlobalDataRest, gpuConfigData, shared, flux_shared)
		end
	else
		return nothing
	end

	gpuGlobalDataRest[5, idx] = flux_shared[thread_idx]
	gpuGlobalDataRest[6, idx] = flux_shared[thread_idx + block_dim]
	gpuGlobalDataRest[7, idx] = flux_shared[thread_idx + block_dim * 2]
	gpuGlobalDataRest[8, idx] = flux_shared[thread_idx + block_dim * 3]
	sync_threads()
	return nothing
end

@inline function wall_kernel(gpuGlobalDataConn, gpuGlobalDataFixedPoint, gpuGlobalDataRest, gpuConfigData, shared, flux_shared)
	wall_dGx_pos_kernel(gpuGlobalDataConn, gpuGlobalDataFixedPoint, gpuGlobalDataRest, gpuConfigData, shared, flux_shared)

	wall_dGx_neg_kernel(gpuGlobalDataConn, gpuGlobalDataFixedPoint, gpuGlobalDataRest, gpuConfigData, shared, flux_shared)

	wall_dGy_neg_kernel(gpuGlobalDataConn, gpuGlobalDataFixedPoint, gpuGlobalDataRest, gpuConfigData, shared, flux_shared)
	return nothing
end

@inline function interior_kernel(gpuGlobalDataConn, gpuGlobalDataFixedPoint, gpuGlobalDataRest, gpuConfigData, shared, flux_shared)

	interior_dGx_pos_kernel(gpuGlobalDataConn, gpuGlobalDataFixedPoint, gpuGlobalDataRest, gpuConfigData, shared, flux_shared)

	interior_dGx_neg_kernel(gpuGlobalDataConn, gpuGlobalDataFixedPoint, gpuGlobalDataRest, gpuConfigData, shared, flux_shared)

	interior_dGy_pos_kernel(gpuGlobalDataConn, gpuGlobalDataFixedPoint, gpuGlobalDataRest, gpuConfigData, shared, flux_shared)

	interior_dGy_neg_kernel(gpuGlobalDataConn, gpuGlobalDataFixedPoint, gpuGlobalDataRest, gpuConfigData, shared, flux_shared)
	# interior_dG_kernel(gpuGlobalDataConn, gpuGlobalDataFixedPoint, gpuGlobalDataRest, gpuConfigData, shared, flux_shared)
	return nothing
end

@inline function outer_kernel(gpuGlobalDataConn, gpuGlobalDataFixedPoint, gpuGlobalDataRest, gpuConfigData, shared, flux_shared)
	outer_dGx_pos_kernel(gpuGlobalDataConn, gpuGlobalDataFixedPoint, gpuGlobalDataRest, gpuConfigData, shared, flux_shared)
	outer_dGx_neg_kernel(gpuGlobalDataConn, gpuGlobalDataFixedPoint, gpuGlobalDataRest, gpuConfigData, shared, flux_shared)
	outer_dGy_pos_kernel(gpuGlobalDataConn, gpuGlobalDataFixedPoint, gpuGlobalDataRest, gpuConfigData, shared, flux_shared)
	return nothing
end


using AnalogDiscoveryNeuromore

const fs = 512.0
const runForMin = 180
const twoChannels = true
const lowVolt = false

go(fs,runForMin,twoChannels,lowVolt)


if !@isdefined(Jpycachesim)
    include("Jpycachesim.jl")
    using .Jpycachesim
end

#pcs_get_cachesim_from_file("cachesim/cachedef");

# This is my laptop
# l3 = Cache("L3", 12288, 16, 64, "LRU");
# l2 = Cache("L2", 256, 4, 64, "LRU");
# l1 = Cache("L1", 32, 8, 64, "LRU");

al3 = Cache("L3", 32, 1, 64, "LRU");
al2 = Cache("L2", 16, 1, 64, "LRU");
al1 = Cache("L1", 8, 1, 64, "LRU");

bl3 = Cache("L3", 32, 1, 64, "LRU");
bl2 = Cache("L2", 16, 1, 64, "LRU");
bl1 = Cache("L1", 8, 1, 64, "LRU");

c1 = pcs_build_cache([al1,al2,al3]);
c2 = pcs_build_cache([bl1,bl2,bl3]);

pcs_load(0,1,c1);
(m1,h1) = pcs_get_stats(c1);

pcs_load(0,130,c2);
(m2,h2) = pcs_get_stats(c2);

pcs_load(0,1,c1);
(m3,h3) = pcs_get_stats(c1);

println("test stats: should be miss=1, ?, 0, hit = 0, ?, 1");
println("miss= "*string(m1[1])*", "*string(m2[1])*", "*string(m3[1]));
println("hit= "*string(h1[1])*", "*string(h2[1])*", "*string(h3[1]));

pcs_load(0,8,c1);
pcs_store(0,8,c1);
pcs_load(28, 32,c1);

pcs_load(0,128,c2);
pcs_store(0,16,c2);
pcs_load(28, 1024,c2);

pcs_print_stats(c1);
pcs_print_stats(c2);

cobj = unsafe_load(c1);
lev2 = unsafe_load(cobj.load_from);
lev3 = unsafe_load(lev2.load_from);

println("miss: "*string(Int(cobj.MISS.count)));
println("hit: "*string(Int(cobj.HIT.count)));
println("load: "*string(Int(cobj.LOAD.count)));
println("store: "*string(Int(cobj.STORE.count)));


using DataStructures: CircularBuffer
include("AlgorithmicEnv.jl")

"""
Task is to reverse content over the input tape.
http://arxiv.org/abs/1511.07275
"""
mutable struct ReverseEnv <: TapeAlgorithmicEnv
    MIN_REWARD_SHORTFALL_FOR_PROMOTION::Float32
    base::Int
    episode_total_reward::Float32
    reward_shortfalls::CircularBuffer{Float32}
    charmap::Array{Char, 1}
    min_length::Int
    action_space::TupleSpace
    observation_space::Discrete

    MOVEMENTS::NTuple
    READ_HEAD_START::Int

    target::Array{Int8, 1}  # target tape
    input_data::Array{Int8, 1}
    time::Int8
    read_head_position::Int8  # current position of the read head.
    write_head_position::Int8
    last_action
    last_reward::Float32
end

ReverseEnv(base::Int=2) =
    ReverseEnv(-1f-1, # MIN_REWARD_SHORTFALL_FOR_PROMOTION
            base, # last
            0f0, # episode_total_reward
            CircularBuffer{Float32}(50),  # reward_shortfalls
            push!(['A'+i for i=0:base-1], ' '),  # charmap
            1,  # starting min_length
            TupleSpace([Discrete(2), Discrete(2), Discrete(base)]),  # action_space
            Discrete(base+1),  # observation_space
            (:left, :right), 1,  # MOVEMENTS and READ_HEAD_START
            Int8[], Int8[], 0, 1, 1, nothing, 0.0)

target_from_input_data!(env::ReverseEnv) =
    env.target = [char for char in reverse(env.input_data)]


using BayesianQuadrature
using Distributions
using KernelFunctions
using LinearAlgebra
using Random
using Test
const BQ = BayesianQuadrature

include("testing_tools.jl")

@testset "BayesianQuadrature.jl" begin
    include("interface.jl")
    include("models.jl")

    @info "Testing BayesQuads"
    @testset "BayesQuads" begin
        include(joinpath("bayesquads", "abstractbq.jl"))
        include(joinpath("bayesquads", "bayesquad.jl"))
        include(joinpath("bayesquads", "logbayesquad.jl"))
    end

    @info "Testing Samplers"
    @testset "Samplers" begin
        include(joinpath("samplers", "abstractbqsampler.jl"))
        include(joinpath("samplers", "priorsampling.jl"))
    end

    @info "Testing kernel embeddings"
    @testset "Kernel Embeddings" begin
        include(joinpath("kernelembeddings", "kernelembedding.jl"))
    end
end


"""

Construct command line for chain id.

$(SIGNATURES)

### Required arguments
```julia
* `m::SampleModel`                     : SampleModel
* `id::Int`                            : Chain id
```
Not exported
"""
function cmdline(m::SampleModel, id)

    #= cmdline with default parameter values
        `./bernoulli3 num_threads=4
        sample num_chains=4
        num_samples=1000 num_warmup=1000 save_warmup=0
        thin=1
        adapt engaged=1 gamma=0.05 delta=0.8 kappa=0.75
        t0=10.0 init_buffer=75 term_buffer=50 window=25
        algorithm=hmc engine=nuts
        max_depth=10 metric=diag_e stepsize=1.0 stepsize_jitter=1.0
        random seed=-1
        init=bernoulli3_1.init.R
        id=1
        data file=bernoulli3_1.data.R
        output file=bernoulli3_samples_1.csv
        refresh=100`
    =#

    cmd = ``
    # Handle the model name field for unix and windows
    cmd = `$(m.exec_path)`

    if m.use_cpp_chains
        cmd = `$cmd num_threads=$(m.num_threads)`
        cmd = `$cmd sample num_chains=$(m.num_cpp_chains)`
    else
        #cmd = `$cmd sample num_chains=1`
        cmd = `$cmd sample`
    end

    cmd = `$cmd num_samples=$(m.num_samples) num_warmup=$(m.num_warmups)`

    if m.save_warmup
        cmd = `$cmd save_warmup=1`
    else
        cmd = `$cmd save_warmup=0`
    end

    # Common to all models
    cmd = `$cmd thin=$(m.thin)`

    # Adapt section
    if m.engaged
        cmd = `$cmd adapt engaged=1`
    else
        cmd = `$cmd adapt engaged=0`
    end
    cmd = `$cmd gamma=$(m.gamma) delta=$(m.delta) kappa=$(m.kappa)`
    cmd = `$cmd t0=$(m.t0) init_buffer=$(m.init_buffer)`
    cmd = `$cmd term_buffer=$(m.init_buffer) window=$(m.window)`

    # Algorithm section
    cmd = `$cmd algorithm=$(string(m.algorithm)) engine=$(string(m.engine))`
    if m.engine == :nuts
        cmd = `$cmd max_depth=$(m.max_depth)`
    elseif m.engine == :static
        cmd = `$cmd int_time=$(m.int_time)`
    end
    cmd = `$cmd metric=$(string(m.metric)) stepsize=$(m.stepsize)`
    cmd = `$cmd stepsize_jitter=$(m.stepsize_jitter)`

    cmd = `$cmd random seed=$(m.seed)`

    # Init file required?
    if length(m.init_file) > 0 && isfile(m.init_file[id])
      cmd = `$cmd init=$(m.init_file[id])`
    else
      cmd = `$cmd init=$(m.init_bound)`
    end

    cmd = `$cmd id=$(id)`

    # Data file required?
    if length(m.data_file) > 0 && isfile(m.data_file[id])
      cmd = `$cmd data file=$(m.data_file[id])`
    end

    # Output files
    cmd = `$cmd output`
    if length(m.sample_file[id]) > 0
      cmd = `$cmd file=$(m.sample_file[id])`
    end
    if length(m.diagnostic_file) > 0
      cmd = `$cmd diagnostic_file=$(m.diagnostic_file[id])`
    end

    # Refresh rate
    cmd = `$cmd refresh=$(m.refresh)`

    cmd
  end


using Plots
using OceanParameterizations

files =  ["free_convection", "strong_wind", "strong_wind_no_coriolis", "weak_wind_strong_cooling",
          "strong_wind_weak_cooling", "strong_wind_weak_heating"]

## uw profiles unscaled

Ts3 = Dict() # maps file name to T array
for file in files
    𝒟 = data(file, reconstruct_fluxes=true) # <: OceananigansData
    Ts3[file] = 𝒟.uw.coarse
end

Ts3

anim = @animate for i in 1:288
    plot(legend=false, xlabel="uw", ylabel="z", xlims=(-0.0006,0.0001))
    for (file, T) in Ts3
        plot!(T[:,i], collect(1:33), title = "Unscaled", label="$(file)")
    end
end
gif(anim, pwd()*"/uw_reconstructed_with_subgrid.gif", fps=20)

## vw profiles unscaled

Ts3 = Dict() # maps file name to T array
for file in files
    𝒟 = data(file, reconstruct_fluxes=true) # <: OceananigansData
    Ts3[file] = 𝒟.vw.coarse
end

Ts3

anim = @animate for i in 1:288
    plot(legend=false, xlabel="vw", ylabel="z", xlims=(-0.0001,0.0005))
    for (file, T) in Ts3
        plot!(T[:,i], collect(1:33), title = "Unscaled", label="$(file)")
    end
end
gif(anim, pwd()*"/vw_reconstructed_with_subgrid.gif", fps=20)

## wT profiles unscaled

Ts3 = Dict() # maps file name to T array
for file in files
    𝒟 = data(file, reconstruct_fluxes=true) # <: OceananigansData
    Ts3[file] = 𝒟.wT.coarse
end

Ts3

anim = @animate for i in 1:288
    plot(legend=false, xlabel="wT", ylabel="z", xlims=(-0.00009,0.00006))
    for (file, T) in Ts3
        plot!(T[:,i], collect(1:33), title = "Unscaled", label="$(file)")
    end
end
gif(anim, pwd()*"/wT_reconstructed_with_subgrid.gif", fps=20)


# create a border of a fixed size and fill it with a constant value
using Images, ImageView

pad_top = 25
pad_bottom = 25
pad_color = RGB4{N0f8}(0.5,0.5,0.) # border color

img = load("sample-images/cats-3061372_640.jpg");
img = padarray(img, Fill(pad_color, (pad_top, pad_bottom)))
img = parent(img) # reset indices to start from 1
imshow(img)

# create a border of a fixed size based on a content of an image
using Images, ImageView

pad_top = 25
pad_bottom = 25

img = load("sample-images/cats-3061372_640.jpg");
img = padarray(img, Pad(:reflect, pad_top, pad_bottom))
img = parent(img) # reset indices to start from 1
imshow(img)


@recipe function f(fc::FORECAST;
                   titlefont  = font(8, "Courier"),
                   xtickfont  = font(3, "Courier"),
                   ytickfont  = font(3, "Courier"),
                   yguidefont = font(5, "Courier"),
                   xguidefont = font(5, "Courier"),
                   legendfont = font(5, "Courier"),
                   xlims = nothing,
                   ylims = nothing,
                   markershape = :circle,
                   markersize = 2,
                   markerstrokewidth = 0,
                   title = fc.call,
                   xguide = "Time",
                   legend = nothing)

    # Default values
    titlefont  := titlefont
    xtickfont  := xtickfont
    ytickfont  := ytickfont
    yguidefont := yguidefont
    xguidefont := xguidefont
    legendfont := legendfont
    title      := title
    xguide     := xguide
    markershape := markershape
    markersize := markersize
    markerstrokewidth := markerstrokewidth

    # Making the forecast to occupy the last 1/4 of xlims
    sx = size(fc.model.x,1)
    sm = size(fc.mean,1)
    ts = vcat(fc.model.x[:,1],fc.mean[:,1])
    xr1 = max(1,sx-3*size(fc.mean,1))
    xr2 = sx+sm
    xr = eltype(ts) in [Date, DateTime, Time] ?
        (ts[xr1], ts[xr2]) : (Dates.value(ts[xr1]), Dates.value(ts[xr2]))
    xlims := isnothing(xlims) ? xr : xlims
    xend = Array(fc.model.x[xr1:end,2:end])
    my = minimum([minimum(xend),minimum(Array(fc.lower[:,2:end]))])
    My = maximum([maximum(xend),maximum(Array(fc.upper[:,2:end]))])
    ypad = 0.1 * maximum([abs(My),abs(my)])
    ylims := isnothing(ylims) ? (my-ypad,My+ypad) : ylims

    xts = fc.model.x
    names_x = names(xts)[2:end]
    x = Array(xts[:,2:end])
    ts = xts[:,1]
    name_ts = names(xts)[1]

    legend := isnothing(legend) ? (size(x,2) <= 4 ? :top : :outerright) : legend

    @series begin
        label := permutedims(fc.model.varnames)
        color_palette := :tab10
        seriescolor := reshape(collect(1:size(x,2)),1,size(x,2))
        seriestype := :line

        ts,x
    end

    alpha_dimming = 2
    if size(x,2) == 1
        @series begin
            label := nothing
            color_palette := :tab10
            seriescolor := reshape(collect(1:size(x,2)),1,size(x,2))
            linestyle := :dot
            seriesalpha := 0
            fillalpha := 1/(alpha_dimming*size(x,2))

            rmean = Array(fc.mean[:,2:end])
            lower1 = Array(fc.lower[:,2:div(end-1,2)+1])
            upper1 = Array(fc.upper[:,2:div(end-1,2)+1])

            ribbon :=  (rmean - lower1, upper1 - rmean)

            fc.mean[:,1], Array(fc.mean[:,2:end])

        end
    end

    @series begin
        label := nothing
        color_palette := :tab10
        seriescolor := reshape(collect(1:size(x,2)),1,size(x,2))
        seriestype := :line
        linestyle := :dot
        seriesalpha := 0
        fillalpha := 1/(alpha_dimming*size(x,2))

        rmean = Array(fc.mean[:,2:end])
        lower2 = Array(fc.lower[:,div(end-1,2)+2:end])
        upper2 = Array(fc.upper[:,div(end-1,2)+2:end])

        ribbon :=  (rmean - lower2, upper2 - rmean)

        fc.mean[:,1], Array(fc.mean[:,2:end])
    end

    @series begin

        label := nothing
        color_palette := :tab10
        seriescolor := reshape(collect(1:size(x,2)),1,size(x,2))
        seriestype := :line
        linestyle := :dot
        seriesalpha := 1

        fc.mean[:,1], Array(fc.mean[:,2:end])
    end

end


# make sure to include this file in src/GeophysOperator.jl
# AFTER FooPhysics.jl has been included
module FooPhysicsInversion
using ..AbstractOperator
# using AbstractOperator (above) says that transD_GP can
# use this kind of physics, through a misfit function

import ..AbstractOperator.get_misfit
# get_misfit (above) extends all the other misfit functions
# in transD_GP. Because we are extending a method
# we must use import instead of using.

import ..Model, ..Options
# the above line now recognizes GP models and McMC options defined in src/TransD_GP_MCMC.jl

# The above three lines are the bare minimum we need to use transD_GP

# But wait! we need to specialize this inversion module
# for FooPhysics ... so let's do that. This is basically the
# geophysics "forward call"
using ..FooPhysics
import ..FooPhysics.returnphysics!

# Now let's actually define our FooInversion operator and for simplicity
# say it is a subtype of 1D operator (operators are defined in src/AbstractOperator.jl)
mutable struct FooInversion<:Operator1D
    d # some observations data
    F # some forward physics struct
end

# some constructor for FooInversion
function FooInversion()
    FooInversion([0, 0.], FooPhysics.Foo(20, rand(1:100))) # return value
end

# Now let's define (extend) a misfit function for
# a 1D model parameter vector m, the physics for which
# can be computed through methods in FooPhysics
# using an operator of type Foo, using McMC prior bounds
# and options stored in a struct of type Options
function get_misfit(m::Model, opt::Options, foo::FooInversion)
    get_misfit(m.fstar, opt, foo) # dispatches to helper function below
end
# above defined function and type signature MUST be defined

function get_misfit(x::AbstractArray, opt::Options, foo::FooInversion)
    # m.fstar is the 1D array interpolated by the GP mean
    # at locations opt.xall
    returnphysics!(foo.F, x) # think of this as F(x)
    chi2by2 = 0*foo.d[1] - 0*foo.F.somefield[1]
end
# this misfit function returns the same value for very call
# so we're sampling the prior
# this type signature is easier to debug than feeding in a GP model

end


module NARemovers

using Random
using DataFrames

using AutoMLPipeline.AbsTypes
using AutoMLPipeline.BaseFilters
using AutoMLPipeline.Utils

import AutoMLPipeline.AbsTypes: fit!, transform!
export fit!, transform!
export NARemover

"""
    NARemover(
      Dict(
        :name => "nadetect",
        :acceptance => 0.10 # tolerable NAs percentage
      )
    )

Removes columns with NAs greater than acceptance rate.
This assumes that it processes columns of features.
The output column should not be part of input to avoid
it being excluded if it fails the acceptance critera.

Implements `fit!` and `transform!`.
"""
mutable struct NARemover <: Transformer
  name::String
  model::Dict
  args::Dict

  function NARemover(args::Dict = Dict())
	 default_args = Dict(
								:name => "nadetect",
								:acceptance => 0.10
								)
	 cargs=nested_dict_merge(default_args,args)
	 cargs[:name] = cargs[:name]*"_"*randstring(3)
	 new(cargs[:name],Dict(),cargs)
  end
end

"""
NARemover(acceptance::Float64)

Helper function for NARemover.
"""
NARemover(acceptance::Float64) = NARemover(Dict(:acceptance => acceptance))

"""
    fit!(nad::NARemover,features::DataFrame,labels::Vector=[])

Checks and exit of df is empty

# Arguments
- `nad::NARemover`: custom type
- `features::DataFrame`: input
- `labels::Vector=[]`:
"""
function fit!(nad::NARemover, features::DataFrame, labels::Vector=[])
  if features == DataFrame()
	 error("empty dataframe")
  end
  nad.model = nad.args
end

"""
    transform!(nad::NARemover,nfeatures::DataFrame)

Removes columns with NAs greater than acceptance rate.

# Arguments
- `nad::NARemover`: custom type
- `nfeatures::DataFrame`: input
"""
function transform!(nad::NARemover, nfeatures::DataFrame)
  features = deepcopy(nfeatures)
  if features == DataFrame()
	 error("empty dataframe")
  end
  sz = nrow(features)
  tol = nad.model[:acceptance]
  colnames = []
  for (colname,dat) in collect(pairs(eachcol(features)))
	 if sum(ismissing.(dat)) < tol*sz
		push!(colnames,colname)
	 end
  end
  xtr =  features[:,colnames]
  return xtr
end

end


"""
    DistributedODEIntegrator <: AbstractODEIntegrator

A simplified variant of [`ODEIntegrator`](https://github.com/SciML/OrdinaryDiffEq.jl/blob/6ec5a55bda26efae596bf99bea1a1d729636f412/src/integrators/type.jl#L77-L123).

"""
mutable struct DistributedODEIntegrator{algType,uType,tType} <: DiffEqBase.AbstractODEIntegrator{algType,true,uType,tType}
    prob
    alg::algType
    u::uType
    dt::tType
    t::tType
    tstop::tType
    step::Int
    stepstop::Int # -1
    adjustfinal::Bool
    callback::DiffEqBase.CallbackSet
    u_modified::Bool
    cache
end

# called by DiffEqBase.init and solve (see below)
function DiffEqBase.__init(
    prob::DiffEqBase.AbstractODEProblem,
    alg::DistributedODEAlgorithm,
    args...;
    dt,  # required
    stepstop=-1,
    adjustfinal=false,
    callback=nothing,
    kwargs...)

    u = prob.u0
    t = prob.tspan[1]
    tstop = prob.tspan[2]

    callbackset = DiffEqBase.CallbackSet(callback)
    isempty(callbackset.continuous_callbacks) || error("Continuous callbacks are not supported")
    integrator = DistributedODEIntegrator(prob, alg, u, dt, t, tstop, 0, stepstop, adjustfinal, callbackset, false, cache(prob, alg; dt=dt, kwargs...))

    DiffEqBase.initialize!(callbackset,u,t,integrator)
    return integrator
end

# called by DiffEqBase.solve
function DiffEqBase.__solve(
    prob::DiffEqBase.AbstractODEProblem,
    alg::DistributedODEAlgorithm,
    args...;
    kwargs...)

    integrator = DiffEqBase.__init(prob, alg, args...; kwargs...)
    DiffEqBase.solve!(integrator)
    return integrator.u # ODEProblem returns a Solution objec
end

# either called directly (after init), or by solv e (via __solve)
function DiffEqBase.solve!(integrator::DistributedODEIntegrator)
    while integrator.t < integrator.tstop
        if integrator.adjustfinal && integrator.t + integrator.dt > integrator.tstop
            adjust_dt!(integrator, integrator.tstop - integrator.t)
        end
        DiffEqBase.step!(integrator)

        if integrator.step == integrator.stepstop
            break
        end
    end

    if isdefined(DiffEqBase, :finalize!)
        DiffEqBase.finalize!(integrator.callback, integrator.u, integrator.t, integrator)
    end
end

# either called directly, or via solve!
function DiffEqBase.step!(integrator::DistributedODEIntegrator)
    step_u!(integrator) # solvers need to define this interface
    integrator.t += integrator.dt
    integrator.step += 1

    # apply callbacks
    discrete_callbacks = integrator.callback.discrete_callbacks
    for callback in discrete_callbacks
        if callback.condition(integrator.u,integrator.t,integrator)
            callback.affect!(integrator)
        end
    end
end

# solvers need to define this interface
step_u!(integrator) = step_u!(integrator, integrator.cache)

function adjust_dt!(integrator::DistributedODEIntegrator, dt)
    # TODO: figure out interface for recomputing other objects (linear operators, etc)
    integrator.dt = dt
end

# not sure what this should do?
# defined as default initialize: https://github.com/SciML/DiffEqBase.jl/blob/master/src/callbacks.jl#L3
DiffEqBase.u_modified!(i::DistributedODEIntegrator,bool) = nothing


function initws(res_x::Int, res_y::Int)
    clearws()

    ds_width_meters, ds_height_meters, ds_width_px, ds_height_px = inqdspsize()
    ds_px_width_meters = ds_width_meters / ds_width_px
    ds_px_height_meters = ds_height_meters / ds_height_px
    ws_width_meters = res_x * ds_px_width_meters
    ws_height_meters = res_y * ds_px_height_meters

    setwsviewport(0., ws_width_meters, 0., ws_height_meters)
    setwswindow(0., 1., 0., 1.)
    setviewport(0., 1., 0., 1.)
    setwindow(0., 1., 0., 1.)

    updatews()
end

function render(gr_img::GRImage)
    clearws()
    drawimage(0., 1., 0., 1., gr_img.res_x, gr_img.res_y, gr_img.abgr, 0)
    updatews()
end


"""
Submodule providing helper functions for generating plots.
"""
module Plotter
using ..CarrierCapture: Potential, conf_coord

export plot_pots, plot_pot!, plot_ccs, plot_cc!

using Plots, LaTeXStrings

"""
    plot_pot!(pot; lplt_wf = false, plt = nothing, color = Nothing, label = "", scale_factor = 2e-2)

Plots `Potential` with its' wave functions if `lplt_wf` is `true`.
"""
function plot_pot!(pot::Potential; lplt_wf = false, plt = nothing, color = Nothing, label = "", scale_factor = 2e-2)
    if plt == nothing
        plt = plot()
    end
    label = if label == "" pot.name else label end
    color = if color == nothing "black" else color end

    # plot wave functions
    if lplt_wf
        ϵ = pot.ϵ; χ = pot.χ
        for i = 1:length(ϵ)
            plot!(plt, pot.Q, χ[i, :] * scale_factor .+ ϵ[i], lw = 0,
                  fillrange = [χ[i, :] * 0 .+ ϵ[i], χ[i, :] * scale_factor .+ ϵ[i]],
                  color = color, alpha = 0.5, label = "")
        end
    end

    # plot function
    plot!(plt, pot.Q, pot.E, lw = 4, color = color, label = label)

    # plot data
    if size(pot.QE_data)[1] > 1
        scatter!(plt, pot.QE_data.Q, pot.QE_data.E, color = color, label = "")
    end
    xaxis!(L"\ Q (amu^{1\/2} Å) \ (^{}$$"); yaxis!(L"\ Energy  (eV) \ (^{}$$")

    return plt
end

"""
    plot_cc!(cc; plt = nothing, color = nothing, label = "")

Plots capture coefficient `cc.capt_coeff` as a function of `1000/T`.
"""
function plot_cc!(cc::conf_coord; plt = nothing, color = nothing, label = "")
    if plt == nothing
        plt = plot()
    end
    label = if label == "" cc.name else label end
    # color = if color==Nothing "black" else color end
    plot!(plt, 1000 ./ cc.temperature, cc.capt_coeff, lw = 4, label = label)
    xaxis!(L"\ 1000\/T (K^{-1}) \ (^{}$$"); yaxis!(L"C (cm^{3}\/s) \ (^{}$$", :log10)
    return plt
end

end


#=
Description
    Module for POMDP type objects with state, action, and
    observation space, etc. defined.
=#

module POMDPs

export POMDP

type POMDP

    states          :: Matrix
    actions         :: Array
    observations    :: Matrix
    term_state      :: Vector{Float64}

    function POMDP(states::Matrix, actions::Array, observations::Matrix,
                   term_state::Vector{Float64})
        return new(states, actions, observations, term_state)
    end # function POMDP

end # type POMDP

end # module POMDPs


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

__precompile__(true)

module SolidStateDetectors

using LinearAlgebra
using Random
using Statistics

using ArraysOfArrays
using Interpolations
using IntervalSets
using JSON
using LaTeXStrings
using NamedTupleTools
using ParallelProcessingTools
using ProgressMeter
using RecipesBase
using StaticArrays
using Unitful
using YAML

import Clustering
import Distributions
import Tables
import TypedTables

import Base: size, sizeof, length, getindex, setindex!, axes, getproperty,
             range, ndims, eachindex, enumerate, iterate, IndexStyle, eltype, in
import Base: show, print, println, display, +, -, &
import Base.convert

const SSD = SolidStateDetectors; export SSD
export SolidStateDetector
export SSD_examples

export Grid, CylindricalPoint, CartesianPoint

export ElectricPotential, PointTypes, ChargeDensity, DielectricDistribution, WeightingPotential, ElectricField
export apply_initial_state!
export calculate_electric_potential!, calculate_weighting_potential!, calculate_electric_field!
export update_till_convergence!, refine!
export set_charge_drift_model!, apply_charge_drift_model!
export get_active_volume
export generate_charge_signals, generate_charge_signals!
export VacuumChargeDriftModel, ADLChargeDriftModel
export Simulation, simulate!
export Event, drift_charges!

const SSDFloat = Union{Float16, Float32, Float64}

struct ConfigFileError <: Exception
    msg::AbstractString
end
Base.showerror(io::IO, e::ConfigFileError) = print(io, "ConfigFileError: ", e.msg)

include("Geometries/Geometries.jl")

include("Axes/DiscreteAxis.jl")
include("World/World.jl")
include("Grids/Grids.jl")

include("Types/Types.jl")

include("MaterialProperties/MaterialProperties.jl")
include("Config/Config.jl")
include("ChargeDensityModels/ChargeDensityModels.jl")
include("SolidStateDetector/DetectorGeometries.jl")
include("GeometryRounding.jl")

include("PotentialSimulation/PotentialSimulation.jl")

include("ElectricField/ElectricField.jl")

include("ChargeDriftModels/ChargeDriftModels.jl")
include("ChargeDrift/ChargeDrift.jl")
include("SignalGeneration/SignalGeneration.jl")

include("ChargeStatistics/ChargeStatistics.jl")
include("ChargeClustering/ChargeClustering.jl")

include("Simulation/Simulation.jl")
include("Event/Event.jl")

include("IO/IO.jl")

include("examples.jl")

end # module


@latexrecipe function f(func::DiffEqBase.AbstractParameterizedFunction)
    return latexify(func.sys)
end


export check_inv, world_similar, almost_same

@nospecialize
"""
    check_inv(f, args; atol::Real=1e-8, verbose::Bool=false, kwargs...)

Return true if `f(args..., kwargs...)` is reversible.
"""
function check_inv(f, args; atol::Real=1e-8, verbose::Bool=false, kwargs...)
    args0 = deepcopy(args)
    args_ = f(args...; kwargs...)
    args = length(args) == 1 ? (args_,) : args_
    args_ = (~f)(args...; kwargs...)
    args = length(args) == 1 ? (args_,) : args_
    world_similar(args0, args, atol=atol, verbose=verbose)
end

function world_similar(a, b; atol::Real=1e-8, verbose::Bool=false)
    for (xa, xb) in zip(a, b)
        if !almost_same(xa, xb; atol=atol)
            verbose && println("$xa does not match $xb")
            return false
        end
    end
    return true
end
@specialize

"""
    almost_same(a, b; atol=GLOBAL_ATOL[], kwargs...) -> Bool

Return true if `a` and `b` are almost same w.r.t. `atol`.
"""
function almost_same(a::T, b::T; atol=GLOBAL_ATOL[], kwargs...) where T <: AbstractFloat
    a === b || abs(b - a) < atol
end

function almost_same(a::TA, b::TB; kwargs...) where {TA, TB}
    false
end

function almost_same(a::T, b::T; kwargs...) where {T<:Dict}
    length(a) != length(b) && return false
    for (k, v) in a
        haskey(b, k) && almost_same(v, b[k]; kwargs...) || return false
    end
    return true
end

@generated function almost_same(a::T, b::T; kwargs...) where T
    nf = fieldcount(a)
    if isprimitivetype(T)
        :(a === b)
    else
        quote
            res = true
            @nexprs $nf i-> res = res && almost_same(getfield(a, i), getfield(b, i); kwargs...)
            res
        end
    end
end

almost_same(x::T, y::T; kwargs...) where T<:AbstractArray = all(almost_same.(x, y; kwargs...))
almost_same(x::FastStack, y::FastStack; kwargs...) where T<:AbstractArray = all(almost_same.(x.data[1:x.top[]], y.data[1:y.top[]]; kwargs...))


import DataFrames.DataFrame
import DataArrays.skipmissing

include("clusters.jl")

sim_param = setup_sim_param_model()
add_param_fixed(sim_param,"num_targets_sim_pass_one",150061) #9)   # For "observed" data, use a realistic number of targets (after any cuts you want to perform)
add_param_fixed(sim_param,"max_incl_sys",80.0) #degrees; 0 (deg) for isotropic system inclinations; set closer to 90 (deg) for more transiting systems

const max_incl_sys = get_real(sim_param,"max_incl_sys")
cos_factor = cos(max_incl_sys*pi/180) #factor to divide the number of targets in simulation by to get the actual number of targets needed (with an isotropic distribution of system inclinations) to produce as many transiting systems for a single observer

##### To load the DR25 catalog and compute arrays of multiplicities, periods, period ratios, transit durations, transit depths, period-normalized transit duration ratios (xi), and transit depth ratios:

Q1Q17_DR25 = CSV.read("q1_q17_dr25_koi.tab_selectcols_new.csv", header=19, nullable=true)
Q1Q17_DR25_stellar = CSV.read("q1_q17_dr25_stellar_koi.tab_selectcols.csv", header=30, nullable=true)
Q1Q17_DR25_stellar_all = CSV.read("q1_q17_dr25_stellar_koi.tab_all.csv", header=1, nullable=true)

N_Kepler_targets = sum((Q1Q17_DR25_stellar_all[:teff] .> 4000.) .& (Q1Q17_DR25_stellar_all[:teff] .< 7000.) .& (Q1Q17_DR25_stellar_all[:logg] .> 4.))
println("Total number of Kepler targets satisfying our cuts: ", N_Kepler_targets)

table_confirmed = Q1Q17_DR25[(Q1Q17_DR25[:koi_disposition] .== "CONFIRMED") .| (Q1Q17_DR25[:koi_disposition] .== "CANDIDATE"), :] #Table containing only the confirmed and candidate objects
table_stellar = Q1Q17_DR25_stellar

table_confirmed = table_confirmed[(table_confirmed[:koi_period] .> get_real(sim_param,"min_period")) .& (table_confirmed[:koi_period] .< get_real(sim_param,"max_period")), :] #to make additional cuts in period P to be comparable to our simulated sample
table_confirmed = table_confirmed[(table_confirmed[:koi_prad] .> get_real(sim_param,"min_radius")/ExoplanetsSysSim.earth_radius) .& (table_confirmed[:koi_prad] .< get_real(sim_param,"max_radius")/ExoplanetsSysSim.earth_radius) .& (.~ismissing.(table_confirmed[:koi_prad])), :] #to make additional cuts in planetary radii to be comparable to our simulated sample

#To make cuts based on stellar properties of T_eff and logg:
teff_confirmed = zeros(Int64, size(table_confirmed,1)) #list to be filled with the T_eff (K) for the objects
logg_confirmed = zeros(Float64, size(table_confirmed,1)) #list to be filled with the logg(cgs) for the objects
cdpp5_confirmed = zeros(Float64, size(table_confirmed,1)) #list to be filled with the RMS CDPP 5h values for the objects
cdpp6_confirmed = zeros(Float64, size(table_confirmed,1)) #list to be filled with the RMS CDPP 6h values for the objects
for (i,KepID) in enumerate(table_confirmed[:kepid])
    teff_confirmed[i] = table_stellar[:teff][table_stellar[:kepid] .== KepID, :][1]
    logg_confirmed[i] = table_stellar[:logg][table_stellar[:kepid] .== KepID, :][1]
    cdpp5_confirmed[i] = table_stellar[:rrmscdpp05p0][table_stellar[:kepid] .== KepID, :][1]
    cdpp6_confirmed[i] = table_stellar[:rrmscdpp06p0][table_stellar[:kepid] .== KepID, :][1]
end

cdpp_cut = 250.
println("Fraction of CONFIRMED and CANDIDATE planets after CDPP cut: ", size(table_confirmed[(teff_confirmed .> 4000.) .& (teff_confirmed .< 7000.) .& (logg_confirmed .> 4.) .& (cdpp5_confirmed .< cdpp_cut), :], 1), "/", size(table_confirmed[(teff_confirmed .> 4000.) .& (teff_confirmed .< 7000.) .& (logg_confirmed .> 4.), :], 1))

table_confirmed = table_confirmed[(teff_confirmed .> 4000.) .& (teff_confirmed .< 7000.) .& (logg_confirmed .> 4.) .& (cdpp5_confirmed .< cdpp_cut), :]

KOI_systems = [x[1:6] for x in table_confirmed[:kepoi_name]]
checked_bools = zeros(size(table_confirmed,1)) #0's denote KOI that were not checked yet; 1's denote already checked KOI

M_confirmed = Int64[] #list to be filled with the planet multiplicities of the systems
R_confirmed = Float64[] #list to be filled with period ratios of adjacent planet pairs
xi_confirmed = Float64[] #list to be filled with the period-normalized transit duration ratios of adjacent planet pairs
D_ratio_confirmed = Float64[] #list to be filled with the transit depth ratios of adjacent planet pairs
P_confirmed = table_confirmed[:koi_period] #array of the periods (days)
P_confirmed = collect(skipmissing(P_confirmed))
t_D_confirmed = table_confirmed[:koi_duration] #array of the transit durations (hrs)
t_D_confirmed = collect(skipmissing(t_D_confirmed))
D_confirmed = table_confirmed[:koi_depth]/(1e6) #array of the transit depths (fraction)
D_confirmed = D_confirmed[ismissing.(D_confirmed) .== false] #to get rid of NA values

for i in 1:length(KOI_systems)
    if checked_bools[i] == 0 #if the KOI has not been checked (included while looking at another planet in the same system)
        system_i = (1:length(KOI_systems))[KOI_systems .== KOI_systems[i]]
        checked_bools[system_i] = 1

        #To get the periods and transit durations in this system:
        system_P = table_confirmed[:koi_period][system_i] #periods of all the planets in this system
        system_t_D = table_confirmed[:koi_duration][system_i] #transit durations of all the planets in this system
        system_D = table_confirmed[:koi_depth][system_i] #transit depths (in ppm) of all the planets in this system
        system_sort_i = sortperm(system_P) #indices that would sort the periods of the planets in this system
        system_P = system_P[system_sort_i] #periods of all the planets in this system, sorted
        system_t_D = system_t_D[system_sort_i] #transit durations of all the planets in this system, sorted by period
        system_D = system_D[system_sort_i] #transit depths of all the planets in this system, sorted by period

        #To count the total number of planets in this system:
        push!(M_confirmed, length(system_P))

        #To compute the period ratios, period-normalized transit duration ratios, and transit depth ratios in this system:
        system_R = system_P[2:end]./system_P[1:end-1] #period ratios of all the adjacent planet pairs in this system
        system_D_ratio = system_D[2:end]./system_D[1:end-1] #transit depth ratios of all the adjacent planet pairs in this system
        system_xi = (system_t_D[1:end-1]./system_t_D[2:end]).*(system_P[2:end]./system_P[1:end-1]).^(1//3) #period-normalized transit duration ratios of all the adjacent planet pairs in this system

        append!(R_confirmed, system_R)
        append!(D_ratio_confirmed, system_D_ratio)
        append!(xi_confirmed, system_xi)
    end
end

##### To run a set of simulations on a grid of two parameters, saving KS statistics:

#=
#To run our model on a 2D grid of 'power_law_P' and 'power_law_r':

power_law_P_range = linspace(-0.5, 0.5, 11)
power_law_r_range = linspace(-3., -1.2, 11)

model_fit_stats = zeros(6,length(power_law_P_range),length(power_law_r_range)) #3D array to be filled with all the statistics of the model fits: number of observed planets, and KS distances for multiplicities, periods, period ratios, transit depths, and period-normalized transit duration ratios
model_fit_stats_fields = ["Number of observed planets", "KS multiplicities", "KS periods", "KS period ratios", "KS transit depths", "KS period-normalized transit duration ratios"] #names of the fields associated with 'model_fit_stats' array

sim_param_default = setup_sim_param_model() #To set up all the default parameters

for (i,power_law_P) in enumerate(power_law_P_range)
    for (j,power_law_r) in enumerate(power_law_r_range)
        tic()
        sim_param_here = deepcopy(sim_param_default) #To set up all the default parameters
        add_param_fixed(sim_param_here,"power_law_P",power_law_P) #To replace the 'power_law_P' parameter
        add_param_fixed(sim_param_here,"power_law_r",power_law_r) #To replace the 'power_law_r' parameter

        cat_phys = generate_kepler_physical_catalog(sim_param_here)
        cat_phys_cut = ExoplanetsSysSim.generate_obs_targets(cat_phys,sim_param_here)
        cat_obs = observe_kepler_targets_single_obs(cat_phys_cut,sim_param_here)
        summary_stat = calc_summary_stats_model(cat_obs,sim_param_here)

        M_cat_obs = ones(Int64,0) #array to be filled with the number of transiting planets in each simulated system
        for k in 1:get_int(sim_param_here,"max_tranets_in_sys")
            append!(M_cat_obs, k*ones(Int64, summary_stat.stat["num n-tranet systems"][k]))
        end

        model_fit_stats[1,i,j] = summary_stat.stat["num_tranets"]
        model_fit_stats[2,i,j] = ksstats_ints(M_cat_obs, M_confirmed)[5]
        model_fit_stats[3,i,j] = ksstats(summary_stat.stat["P list"], P_confirmed)[5]
        model_fit_stats[4,i,j] = ksstats(summary_stat.stat["period_ratio_list"], R_confirmed)[5]
        model_fit_stats[5,i,j] = ksstats(summary_stat.stat["depth list"], D_confirmed)[5]
        model_fit_stats[6,i,j] = ksstats(summary_stat.stat["duration_ratio_list"], xi_confirmed)[5]

        println("# power_law_P, power_law_r: ", get_real(sim_param_here,"power_law_P"), ", ",  get_real(sim_param_here,"power_law_r"))
        toc()
    end
end
=#

#=
f = open("Model_stats_P_r_grid.out", "w")
println(f,"# Model statistics on a grid of (power_law_P, power_law_r) = (", power_law_P_range, ", ",  power_law_r_range, ")")
println(f,"# num_targets_sim_pass_one: ", get_int(sim_param_default,"num_targets_sim_pass_one"))
println(f,"# log_rate_clusters: ", get_real(sim_param_default,"log_rate_clusters"))
println(f,"# log_rate_planets_per_cluster: ", get_real(sim_param_default,"log_rate_planets_per_cluster"))
println(f,"# sigma_incl: ", get_real(sim_param_default,"sigma_incl"))
println(f,"# sigma_incl_near_mmr: ", get_real(sim_param_default,"sigma_incl_near_mmr"))
println(f,"# sigma_hk: ", get_real(sim_param_default,"sigma_hk"))
println(f,"# num_mutual_hill_radii: ", get_real(sim_param_default,"num_mutual_hill_radii"))
println(f,"# mr_power_index: ", get_real(sim_param_default,"mr_power_index"))
println(f,"# mr_max_mass: ", get_real(sim_param_default,"mr_max_mass"))
println(f,"# sigma_logperiod_per_pl_in_cluster: ", get_real(sim_param_default,"sigma_logperiod_per_pl_in_cluster"))
for i in 1:size(model_fit_stats)[1]
    println(f,"# ", model_fit_stats_fields[i], ":")
    println(f,model_fit_stats[i,:,:]')
end
close(f)
=#

#=
#To run our model on a 2D grid of 'num_mutual_hill_radii' and 'sigma_logperiod_per_pl_in_cluster':

num_mutual_hill_radii_range = linspace(5., 20., 11)
sigma_logperiod_per_pl_in_cluster_range = linspace(0.05, 0.5, 11)

model_fit_stats = zeros(8,length(num_mutual_hill_radii_range),length(sigma_logperiod_per_pl_in_cluster_range)) #3D array to be filled with all the statistics of the model fits: number of observed planets, and KS distances for multiplicities, periods, period ratios, transit durations, period-normalized transit duration ratios, transit depths, and transit depth ratios
model_fit_stats_fields = ["Number of observed planets", "KS multiplicities", "KS periods", "KS period ratios", "KS transit durations", "KS period-normalized transit duration ratios", "KS transit depths", "KS transit depth ratios"] #names of the fields associated with 'model_fit_stats' array

for (i,num_mutual_hill_radii) in enumerate(num_mutual_hill_radii_range)
    for (j,sigma_logperiod_per_pl_in_cluster) in enumerate(sigma_logperiod_per_pl_in_cluster_range)
        tic()
        sim_param_here = deepcopy(sim_param) #To set up all the default parameters
        add_param_fixed(sim_param_here,"num_mutual_hill_radii",num_mutual_hill_radii) #To replace the 'num_mutual_hill_radii' parameter
        add_param_fixed(sim_param_here,"sigma_logperiod_per_pl_in_cluster",sigma_logperiod_per_pl_in_cluster) #To replace the 'sigma_logperiod_per_pl_in_cluster' parameter

        cat_phys = generate_kepler_physical_catalog(sim_param_here)
        cat_phys_cut = ExoplanetsSysSim.generate_obs_targets(cat_phys,sim_param_here)
        cat_obs = observe_kepler_targets_single_obs(cat_phys_cut,sim_param_here)
        summary_stat = calc_summary_stats_model(cat_obs,sim_param_here)

        M_cat_obs = ones(Int64,0) #array to be filled with the number of transiting planets in each simulated system
        for k in 1:get_int(sim_param_here,"max_tranets_in_sys")
            append!(M_cat_obs, k*ones(Int64, summary_stat.stat["num n-tranet systems"][k]))
        end

        model_fit_stats[1,i,j] = summary_stat.stat["num_tranets"]
        model_fit_stats[2,i,j] = ksstats_ints(M_cat_obs, M_confirmed)[5]
        model_fit_stats[3,i,j] = ksstats(summary_stat.stat["P list"], P_confirmed)[5]
        model_fit_stats[4,i,j] = ksstats(summary_stat.stat["period_ratio_list"], R_confirmed)[5]
        model_fit_stats[5,i,j] = ksstats(summary_stat.stat["duration list"].*24, t_D_confirmed)[5] #transit durations in simulations are in days, while in the Kepler catalog are in hours
        model_fit_stats[6,i,j] = ksstats(summary_stat.stat["duration_ratio_list"], xi_confirmed)[5]
        model_fit_stats[7,i,j] = ksstats(summary_stat.stat["depth list"], D_confirmed)[5]
        model_fit_stats[8,i,j] = ksstats(summary_stat.stat["radius_ratio_list"].^2, D_ratio_confirmed)[5] #simulations save radius ratios while we computed transit duration ratios from the Kepler catalog

        println("# num_mutual_hill_radii, sigma_logperiod_per_pl_in_cluster: ", get_real(sim_param_here,"num_mutual_hill_radii"), ", ",  get_real(sim_param_here,"sigma_logperiod_per_pl_in_cluster"))
        toc()
    end
end
=#

#=
f = open("Model_stats_Nhill_sigmaP_grid.out", "w")
println(f,"# Model statistics on a grid of (num_mutual_hill_radii, sigma_logperiod_per_pl_in_cluster) = (", num_mutual_hill_radii_range, ", ",  sigma_logperiod_per_pl_in_cluster_range, ")")
println(f, "# num_targets_sim_pass_one: ", get_int(sim_param,"num_targets_sim_pass_one"))
println(f, "# max_incl_sys: ", get_real(sim_param,"max_incl_sys"))
println(f, "# log_rate_clusters: ", get_real(sim_param,"log_rate_clusters"))
println(f, "# max_clusters_in_sys: ", get_int(sim_param,"max_clusters_in_sys"))
println(f, "# log_rate_planets_per_cluster: ", get_real(sim_param,"log_rate_planets_per_cluster"))
println(f, "# max_planets_in_clusters: ", get_int(sim_param,"max_planets_in_cluster"))
println(f, "# power_law_P: ", get_real(sim_param,"power_law_P"))
println(f, "# min_period: ", get_real(sim_param,"min_period"))
println(f, "# max_period: ", get_real(sim_param,"max_period"))
println(f, "# power_law_r1: ", get_real(sim_param,"power_law_r1"))
println(f, "# power_law_r2: ", get_real(sim_param,"power_law_r2"))
println(f, "# min_radius (R_earth): ", get_real(sim_param,"min_radius")/ExoplanetsSysSim.earth_radius)
println(f, "# max_radius (R_earth): ", get_real(sim_param,"max_radius")/ExoplanetsSysSim.earth_radius)
println(f, "# break_radius (R_earth): ", get_real(sim_param,"break_radius")/ExoplanetsSysSim.earth_radius)
println(f, "# sigma_incl: ", get_real(sim_param,"sigma_incl"))
println(f, "# sigma_incl_near_mmr: ", get_real(sim_param,"sigma_incl_near_mmr"))
println(f, "# sigma_hk: ", get_real(sim_param,"sigma_hk"))
#####println(f, "# num_mutual_hill_radii: ", get_real(sim_param,"num_mutual_hill_radii"))
println(f, "# mr_power_index: ", get_real(sim_param,"mr_power_index"))
println(f, "# mr_max_mass (M_earth): ", get_real(sim_param,"mr_max_mass")/ExoplanetsSysSim.earth_mass)
println(f, "# sigma_log_radius_in_cluster: ", get_real(sim_param,"sigma_log_radius_in_cluster"))
#####println(f, "# sigma_logperiod_per_pl_in_cluster: ", get_real(sim_param,"sigma_logperiod_per_pl_in_cluster"))
for i in 1:size(model_fit_stats)[1]
    println(f,"# ", model_fit_stats_fields[i], ":")
    println(f,model_fit_stats[i,:,:]')
end
close(f)
=#

#=
#To run our model on a 2D grid of 'sigma_incl' and 'sigma_hk':

sigma_incl_range = linspace(0., 5., 11)
sigma_hk_range = linspace(0., 0.2, 11)

model_fit_stats = zeros(8,length(sigma_incl_range),length(sigma_hk_range)) #3D array to be filled with all the statistics of the model fits: number of observed planets, and KS distances for multiplicities, periods, period ratios, transit durations, period-normalized transit duration ratios, transit depths, and transit depth ratios
model_fit_stats_fields = ["Number of observed planets", "KS multiplicities", "KS periods", "KS period ratios", "KS transit durations", "KS period-normalized transit duration ratios", "KS transit depths", "KS transit depth ratios"] #names of the fields associated with 'model_fit_stats' array

for (i,sigma_incl) in enumerate(sigma_incl_range)
    for (j,sigma_hk) in enumerate(sigma_hk_range)
        tic()
        sim_param_here = deepcopy(sim_param) #To set up all the default parameters
        add_param_fixed(sim_param_here,"sigma_incl",sigma_incl) #To replace the 'sigma_incl' parameter
        add_param_fixed(sim_param_here,"sigma_hk",sigma_hk) #To replace the 'sigma_hk' parameter

        cat_phys = generate_kepler_physical_catalog(sim_param_here)
        cat_phys_cut = ExoplanetsSysSim.generate_obs_targets(cat_phys,sim_param_here)
        cat_obs = observe_kepler_targets_single_obs(cat_phys_cut,sim_param_here)
        summary_stat = calc_summary_stats_model(cat_obs,sim_param_here)

        M_cat_obs = ones(Int64,0) #array to be filled with the number of transiting planets in each simulated system
        for k in 1:get_int(sim_param_here,"max_tranets_in_sys")
            append!(M_cat_obs, k*ones(Int64, summary_stat.stat["num n-tranet systems"][k]))
        end

        model_fit_stats[1,i,j] = summary_stat.stat["num_tranets"]
        model_fit_stats[2,i,j] = ksstats_ints(M_cat_obs, M_confirmed)[5]
        model_fit_stats[3,i,j] = ksstats(summary_stat.stat["P list"], P_confirmed)[5]
        model_fit_stats[4,i,j] = ksstats(summary_stat.stat["period_ratio_list"], R_confirmed)[5]
        model_fit_stats[5,i,j] = ksstats(summary_stat.stat["duration list"].*24, t_D_confirmed)[5] #transit durations in simulations are in days, while in the Kepler catalog are in hours
        model_fit_stats[6,i,j] = ksstats(summary_stat.stat["duration_ratio_list"], xi_confirmed)[5]
        model_fit_stats[7,i,j] = ksstats(summary_stat.stat["depth list"], D_confirmed)[5]
        model_fit_stats[8,i,j] = ksstats(summary_stat.stat["radius_ratio_list"].^2, D_ratio_confirmed)[5] #simulations save radius ratios while we computed transit duration ratios from the Kepler catalog

        println("# sigma_incl, sigma_hk: ", get_real(sim_param_here,"sigma_incl"), ", ",  get_real(sim_param_here,"sigma_hk"))
        toc()
    end
end
=#

#=
f = open("Model_stats_sigma_incl_sigma_hk_grid.out", "w")
println(f,"# Model statistics on a grid of (sigma_incl, sigma_hk) = (", sigma_incl_range, ", ",  sigma_hk_range, ")")
println(f, "# num_targets_sim_pass_one: ", get_int(sim_param,"num_targets_sim_pass_one"))
println(f, "# max_incl_sys: ", get_real(sim_param,"max_incl_sys"))
println(f, "# log_rate_clusters: ", get_real(sim_param,"log_rate_clusters"))
println(f, "# max_clusters_in_sys: ", get_int(sim_param,"max_clusters_in_sys"))
println(f, "# log_rate_planets_per_cluster: ", get_real(sim_param,"log_rate_planets_per_cluster"))
println(f, "# max_planets_in_clusters: ", get_int(sim_param,"max_planets_in_cluster"))
println(f, "# power_law_P: ", get_real(sim_param,"power_law_P"))
println(f, "# min_period: ", get_real(sim_param,"min_period"))
println(f, "# max_period: ", get_real(sim_param,"max_period"))
println(f, "# power_law_r1: ", get_real(sim_param,"power_law_r1"))
println(f, "# power_law_r2: ", get_real(sim_param,"power_law_r2"))
println(f, "# min_radius (R_earth): ", get_real(sim_param,"min_radius")/ExoplanetsSysSim.earth_radius)
println(f, "# max_radius (R_earth): ", get_real(sim_param,"max_radius")/ExoplanetsSysSim.earth_radius)
println(f, "# break_radius (R_earth): ", get_real(sim_param,"break_radius")/ExoplanetsSysSim.earth_radius)
#####println(f, "# sigma_incl: ", get_real(sim_param,"sigma_incl"))
println(f, "# sigma_incl_near_mmr: ", get_real(sim_param,"sigma_incl_near_mmr"))
#####println(f, "# sigma_hk: ", get_real(sim_param,"sigma_hk"))
println(f, "# num_mutual_hill_radii: ", get_real(sim_param,"num_mutual_hill_radii"))
println(f, "# mr_power_index: ", get_real(sim_param,"mr_power_index"))
println(f, "# mr_max_mass (M_earth): ", get_real(sim_param,"mr_max_mass")/ExoplanetsSysSim.earth_mass)
println(f, "# sigma_log_radius_in_cluster: ", get_real(sim_param,"sigma_log_radius_in_cluster"))
println(f, "# sigma_logperiod_per_pl_in_cluster: ", get_real(sim_param,"sigma_logperiod_per_pl_in_cluster"))
for i in 1:size(model_fit_stats)[1]
    println(f,"# ", model_fit_stats_fields[i], ":")
    println(f,model_fit_stats[i,:,:]')
end
close(f)
=#

#=
#To run our model on a 2D grid of 'mr_power_index' and 'sigma_log_radius_in_cluster':

mr_power_index_range = linspace(1.5, 3.5, 11)
sigma_log_radius_in_cluster_range = linspace(0.1, 1.0, 11)

model_fit_stats = zeros(8,length(mr_power_index_range),length(sigma_log_radius_in_cluster_range)) #3D array to be filled with all the statistics of the model fits: number of observed planets, and KS distances for multiplicities, periods, period ratios, transit durations, period-normalized transit duration ratios, transit depths, and transit depth ratios
model_fit_stats_fields = ["Number of observed planets", "KS multiplicities", "KS periods", "KS period ratios", "KS transit durations", "KS period-normalized transit duration ratios", "KS transit depths", "KS transit depth ratios"] #names of the fields associated with 'model_fit_stats' array

for (i,max_radius) in enumerate(mr_power_index_range)
    for (j,mr_power_index) in enumerate(sigma_log_radius_in_cluster_range)
        tic()
        sim_param_here = deepcopy(sim_param) #To set up all the default parameters
        add_param_fixed(sim_param_here,"mr_power_index",mr_power_index) #To replace the 'mr_power_index' parameter
        add_param_fixed(sim_param_here,"sigma_log_radius_in_cluster",sigma_log_radius_in_cluster) #To replace the 'sigma_log_radius_in_cluster' parameter

        cat_phys = generate_kepler_physical_catalog(sim_param_here)
        cat_phys_cut = ExoplanetsSysSim.generate_obs_targets(cat_phys,sim_param_here)
        cat_obs = observe_kepler_targets_single_obs(cat_phys_cut,sim_param_here)
        summary_stat = calc_summary_stats_model(cat_obs,sim_param_here)

        M_cat_obs = ones(Int64,0) #array to be filled with the number of transiting planets in each simulated system
        for k in 1:get_int(sim_param_here,"max_tranets_in_sys")
            append!(M_cat_obs, k*ones(Int64, summary_stat.stat["num n-tranet systems"][k]))
        end

        model_fit_stats[1,i,j] = summary_stat.stat["num_tranets"]
        model_fit_stats[2,i,j] = ksstats_ints(M_cat_obs, M_confirmed)[5]
        model_fit_stats[3,i,j] = ksstats(summary_stat.stat["P list"], P_confirmed)[5]
        model_fit_stats[4,i,j] = ksstats(summary_stat.stat["period_ratio_list"], R_confirmed)[5]
        model_fit_stats[5,i,j] = ksstats(summary_stat.stat["duration list"].*24, t_D_confirmed)[5] #transit durations in simulations are in days, while in the Kepler catalog are in hours
        model_fit_stats[6,i,j] = ksstats(summary_stat.stat["duration_ratio_list"], xi_confirmed)[5]
        model_fit_stats[7,i,j] = ksstats(summary_stat.stat["depth list"], D_confirmed)[5]
        model_fit_stats[8,i,j] = ksstats(summary_stat.stat["radius_ratio_list"].^2, D_ratio_confirmed)[5] #simulations save radius ratios while we computed transit duration ratios from the Kepler catalog

        println("# mr_power_index, sigma_log_radius_in_cluster: ", get_real(sim_param_here,"mr_power_index"), ", ",  get_real(sim_param_here,"sigma_log_radius_in_cluster"))
        toc()
    end
end
=#

#=
f = open("Model_stats_mr_sigmaR_grid.out", "w")
println(f,"# Model statistics on a grid of (mr_power_index,sigma_log_radius_in_cluster) = (", mr_power_index_range, ", ",  sigma_log_radius_in_cluster_range, ")")
println(f, "# num_targets_sim_pass_one: ", get_int(sim_param,"num_targets_sim_pass_one"))
println(f, "# max_incl_sys: ", get_real(sim_param,"max_incl_sys"))
println(f, "# log_rate_clusters: ", get_real(sim_param,"log_rate_clusters"))
println(f, "# max_clusters_in_sys: ", get_int(sim_param,"max_clusters_in_sys"))
println(f, "# log_rate_planets_per_cluster: ", get_real(sim_param,"log_rate_planets_per_cluster"))
println(f, "# max_planets_in_clusters: ", get_int(sim_param,"max_planets_in_cluster"))
println(f, "# power_law_P: ", get_real(sim_param,"power_law_P"))
println(f, "# min_period: ", get_real(sim_param,"min_period"))
println(f, "# max_period: ", get_real(sim_param,"max_period"))
println(f, "# power_law_r1: ", get_real(sim_param,"power_law_r1"))
println(f, "# power_law_r2: ", get_real(sim_param,"power_law_r2"))
println(f, "# min_radius (R_earth): ", get_real(sim_param,"min_radius")/ExoplanetsSysSim.earth_radius)
println(f, "# max_radius (R_earth): ", get_real(sim_param,"max_radius")/ExoplanetsSysSim.earth_radius)
println(f, "# break_radius (R_earth): ", get_real(sim_param,"break_radius")/ExoplanetsSysSim.earth_radius)
println(f, "# sigma_incl: ", get_real(sim_param,"sigma_incl"))
println(f, "# sigma_incl_near_mmr: ", get_real(sim_param,"sigma_incl_near_mmr"))
println(f, "# sigma_hk: ", get_real(sim_param,"sigma_hk"))
println(f, "# num_mutual_hill_radii: ", get_real(sim_param,"num_mutual_hill_radii"))
#####println(f, "# mr_power_index: ", get_real(sim_param,"mr_power_index"))
println(f, "# mr_max_mass (M_earth): ", get_real(sim_param,"mr_max_mass")/ExoplanetsSysSim.earth_mass)
#####println(f, "# sigma_log_radius_in_cluster: ", get_real(sim_param,"sigma_log_radius_in_cluster"))
println(f, "# sigma_logperiod_per_pl_in_cluster: ", get_real(sim_param,"sigma_logperiod_per_pl_in_cluster"))
for i in 1:size(model_fit_stats)[1]
    println(f,"# ", model_fit_stats_fields[i], ":")
    println(f,model_fit_stats[i,:,:]')
end
close(f)
=#

#=
#To run our model on a 2D grid of 'log_rate_clusters' and 'log_rate_planets_per_cluster':

log_rate_clusters_range = log.(linspace(1., 5., 11))
log_rate_planets_per_cluster_range = log.(linspace(1., 5., 11))

model_fit_stats = zeros(9,length(log_rate_clusters_range),length(log_rate_planets_per_cluster_range)) #3D array to be filled with all the statistics of the model fits: number of observed planets, and KS distances for multiplicities, periods, period ratios, transit durations, period-normalized transit duration ratios, transit depths, and transit depth ratios
model_fit_stats_fields = ["Number of observed planets", "Number of systems with observed singles", "KS multiplicities", "KS periods", "KS period ratios", "KS transit durations", "KS period-normalized transit duration ratios", "KS transit depths", "KS transit depth ratios"] #names of the fields associated with 'model_fit_stats' array

for (i,log_rate_clusters) in enumerate(log_rate_clusters_range)
    for (j,log_rate_planets_per_cluster) in enumerate(log_rate_planets_per_cluster_range)
        tic()
        sim_param_here = deepcopy(sim_param) #To set up all the default parameters
        add_param_fixed(sim_param_here,"log_rate_clusters",log_rate_clusters) #To replace the 'log_rate_clusters' parameter
        add_param_fixed(sim_param_here,"log_rate_planets_per_cluster",log_rate_planets_per_cluster) #To replace the 'log_rate_planets_per_cluster' parameter

        cat_phys = generate_kepler_physical_catalog(sim_param_here)
        cat_phys_cut = ExoplanetsSysSim.generate_obs_targets(cat_phys,sim_param_here)
        cat_obs = observe_kepler_targets_single_obs(cat_phys_cut,sim_param_here)
        summary_stat = calc_summary_stats_model(cat_obs,sim_param_here)

        M_cat_obs = ones(Int64,0) #array to be filled with the number of transiting planets in each simulated system
        for k in 1:get_int(sim_param_here,"max_tranets_in_sys")
            append!(M_cat_obs, k*ones(Int64, summary_stat.stat["num n-tranet systems"][k]))
        end

        model_fit_stats[1,i,j] = summary_stat.stat["num_tranets"]
        model_fit_stats[2,i,j] = summary_stat.stat["num n-tranet systems"][1]
        model_fit_stats[3,i,j] = ksstats_ints(M_cat_obs, M_confirmed)[5]
        model_fit_stats[4,i,j] = ksstats(summary_stat.stat["P list"], P_confirmed)[5]
        model_fit_stats[5,i,j] = ksstats(summary_stat.stat["period_ratio_list"], R_confirmed)[5]
        model_fit_stats[6,i,j] = ksstats(summary_stat.stat["duration list"].*24, t_D_confirmed)[5] #transit durations in simulations are in days, while in the Kepler catalog are in hours
        model_fit_stats[7,i,j] = ksstats(summary_stat.stat["duration_ratio_list"], xi_confirmed)[5]
        model_fit_stats[8,i,j] = ksstats(summary_stat.stat["depth list"], D_confirmed)[5]
        model_fit_stats[9,i,j] = ksstats(summary_stat.stat["radius_ratio_list"].^2, D_ratio_confirmed)[5] #simulations save radius ratios while we computed transit duration ratios from the Kepler catalog

        println("# exp(log_rate_clusters), exp(log_rate_planets_per_cluster): ", exp(get_real(sim_param_here,"log_rate_clusters")), ", ",  exp(get_real(sim_param_here,"log_rate_planets_per_cluster")))
        toc()
    end
end
=#

#=
f = open("Model_stats_Nc_Np_grid.out", "w")
println(f,"# Model statistics on a grid of (log_rate_clusters, log_rate_planets_per_cluster) = (", log_rate_clusters_range, ", ",  log_rate_planets_per_cluster_range, ")")
println(f, "# num_targets_sim_pass_one: ", get_int(sim_param,"num_targets_sim_pass_one"))
println(f, "# max_incl_sys: ", get_real(sim_param,"max_incl_sys"))
#####println(f, "# log_rate_clusters: ", get_real(sim_param,"log_rate_clusters"))
println(f, "# max_clusters_in_sys: ", get_int(sim_param,"max_clusters_in_sys"))
#####println(f, "# log_rate_planets_per_cluster: ", get_real(sim_param,"log_rate_planets_per_cluster"))
println(f, "# max_planets_in_clusters: ", get_int(sim_param,"max_planets_in_cluster"))
println(f, "# power_law_P: ", get_real(sim_param,"power_law_P"))
println(f, "# min_period: ", get_real(sim_param,"min_period"))
println(f, "# max_period: ", get_real(sim_param,"max_period"))
println(f, "# power_law_r1: ", get_real(sim_param,"power_law_r1"))
println(f, "# power_law_r2: ", get_real(sim_param,"power_law_r2"))
println(f, "# min_radius (R_earth): ", get_real(sim_param,"min_radius")/ExoplanetsSysSim.earth_radius)
println(f, "# max_radius (R_earth): ", get_real(sim_param,"max_radius")/ExoplanetsSysSim.earth_radius)
println(f, "# break_radius (R_earth): ", get_real(sim_param,"break_radius")/ExoplanetsSysSim.earth_radius)
println(f, "# sigma_incl: ", get_real(sim_param,"sigma_incl"))
println(f, "# sigma_incl_near_mmr: ", get_real(sim_param,"sigma_incl_near_mmr"))
println(f, "# sigma_hk: ", get_real(sim_param,"sigma_hk"))
println(f, "# num_mutual_hill_radii: ", get_real(sim_param,"num_mutual_hill_radii"))
println(f, "# mr_power_index: ", get_real(sim_param,"mr_power_index"))
println(f, "# mr_max_mass (M_earth): ", get_real(sim_param,"mr_max_mass")/ExoplanetsSysSim.earth_mass)
println(f, "# sigma_log_radius_in_cluster: ", get_real(sim_param,"sigma_log_radius_in_cluster"))
println(f, "# sigma_logperiod_per_pl_in_cluster: ", get_real(sim_param,"sigma_logperiod_per_pl_in_cluster"))
for i in 1:size(model_fit_stats)[1]
    println(f,"# ", model_fit_stats_fields[i], ":")
    println(f,model_fit_stats[i,:,:]')
end
close(f)
=#

##### The following optimization code is for exploring our updated model with a broken power law for the planet radii distribution instead of a single power law:

#=
#To run our model on a 2D grid of 'power_law_r1' and 'power_law_r2':

power_law_r1_range = linspace(-6., 3., 11)
power_law_r2_range = linspace(-6., 3., 11)

model_fit_stats = zeros(8,length(power_law_r1_range),length(power_law_r2_range)) #3D array to be filled with all the statistics of the model fits: number of observed planets, and KS distances for multiplicities, periods, period ratios, transit durations, period-normalized transit duration ratios, transit depths, and transit depth ratios
model_fit_stats_fields = ["Number of observed planets", "KS multiplicities", "KS periods", "KS period ratios", "KS transit durations", "KS period-normalized transit duration ratios", "KS transit depths", "KS transit depth ratios"] #names of the fields associated with 'model_fit_stats' array

for (i,power_law_r1) in enumerate(power_law_r1_range)
    for (j,power_law_r2) in enumerate(power_law_r2_range)
        tic()
        sim_param_here = deepcopy(sim_param) #To set up all the default parameters
        add_param_fixed(sim_param_here,"power_law_r1",power_law_r1) #To replace the 'power_law_r1' parameter
        add_param_fixed(sim_param_here,"power_law_r2",power_law_r2) #To replace the 'power_law_r2' parameter

        cat_phys = generate_kepler_physical_catalog(sim_param_here)
        cat_phys_cut = ExoplanetsSysSim.generate_obs_targets(cat_phys,sim_param_here)
        cat_obs = observe_kepler_targets_single_obs(cat_phys_cut,sim_param_here)
        summary_stat = calc_summary_stats_model(cat_obs,sim_param_here)

        M_cat_obs = ones(Int64,0) #array to be filled with the number of transiting planets in each simulated system
        for k in 1:get_int(sim_param_here,"max_tranets_in_sys")
            append!(M_cat_obs, k*ones(Int64, summary_stat.stat["num n-tranet systems"][k]))
        end

        model_fit_stats[1,i,j] = summary_stat.stat["num_tranets"]
        model_fit_stats[2,i,j] = ksstats_ints(M_cat_obs, M_confirmed)[5]
        model_fit_stats[3,i,j] = ksstats(summary_stat.stat["P list"], P_confirmed)[5]
        model_fit_stats[4,i,j] = ksstats(summary_stat.stat["period_ratio_list"], R_confirmed)[5]
        model_fit_stats[5,i,j] = ksstats(summary_stat.stat["duration list"].*24, t_D_confirmed)[5] #transit durations in simulations are in days, while in the Kepler catalog are in hours
        model_fit_stats[6,i,j] = ksstats(summary_stat.stat["duration_ratio_list"], xi_confirmed)[5]
        model_fit_stats[7,i,j] = ksstats(summary_stat.stat["depth list"], D_confirmed)[5]
        model_fit_stats[8,i,j] = ksstats(summary_stat.stat["radius_ratio_list"].^2, D_ratio_confirmed)[5] #simulations save radius ratios while we computed transit duration ratios from the Kepler catalog

        println("# power_law_r1, power_law_r2: ", get_real(sim_param_here,"power_law_r1"), ", ",  get_real(sim_param_here,"power_law_r2"))
        toc()
    end
end
=#

#=
f = open("Model_stats_r1_r2_rbreak2_grid.out", "w")
println(f,"# Model statistics on a grid of (power_law_r1, power_law_r2) = (", power_law_r1_range, ", ",  power_law_r2_range, ")")
println(f, "# num_targets_sim_pass_one: ", get_int(sim_param,"num_targets_sim_pass_one"))
println(f, "# max_incl_sys: ", get_real(sim_param,"max_incl_sys"))
println(f, "# log_rate_clusters: ", get_real(sim_param,"log_rate_clusters"))
println(f, "# max_clusters_in_sys: ", get_int(sim_param,"max_clusters_in_sys"))
println(f, "# log_rate_planets_per_cluster: ", get_real(sim_param,"log_rate_planets_per_cluster"))
println(f, "# max_planets_in_clusters: ", get_int(sim_param,"max_planets_in_cluster"))
println(f, "# power_law_P: ", get_real(sim_param,"power_law_P"))
println(f, "# min_period: ", get_real(sim_param,"min_period"))
println(f, "# max_period: ", get_real(sim_param,"max_period"))
#####println(f, "# power_law_r1: ", get_real(sim_param,"power_law_r1"))
#####println(f, "# power_law_r2: ", get_real(sim_param,"power_law_r2"))
println(f, "# min_radius (R_earth): ", get_real(sim_param,"min_radius")/ExoplanetsSysSim.earth_radius)
println(f, "# max_radius (R_earth): ", get_real(sim_param,"max_radius")/ExoplanetsSysSim.earth_radius)
println(f, "# break_radius (R_earth): ", get_real(sim_param,"break_radius")/ExoplanetsSysSim.earth_radius)
println(f, "# sigma_incl: ", get_real(sim_param,"sigma_incl"))
println(f, "# sigma_incl_near_mmr: ", get_real(sim_param,"sigma_incl_near_mmr"))
println(f, "# sigma_hk: ", get_real(sim_param,"sigma_hk"))
println(f, "# num_mutual_hill_radii: ", get_real(sim_param,"num_mutual_hill_radii"))
println(f, "# mr_power_index: ", get_real(sim_param,"mr_power_index"))
println(f, "# mr_max_mass (M_earth): ", get_real(sim_param,"mr_max_mass")/ExoplanetsSysSim.earth_mass)
println(f, "# sigma_log_radius_in_cluster: ", get_real(sim_param,"sigma_log_radius_in_cluster"))
println(f, "# sigma_logperiod_per_pl_in_cluster: ", get_real(sim_param,"sigma_logperiod_per_pl_in_cluster"))
for i in 1:size(model_fit_stats)[1]
    println(f,"# ", model_fit_stats_fields[i], ":")
    println(f,model_fit_stats[i,:,:]')
end
close(f)
=#

##### The following optimization code is for exploring our updated model with clustered (lognormal) planet radii, but still a single power law:

#=
#To run our model on a 2D grid of 'power_law_r' and 'sigma_log_radius_in_cluster':

power_law_r_range = linspace(-3., -1.2, 11)
sigma_log_radius_in_cluster_range = linspace(0.05, 0.5, 11)

model_fit_stats = zeros(7,length(power_law_r_range),length(sigma_log_radius_in_cluster_range)) #3D array to be filled with all the statistics of the model fits: number of observed planets, number of systems with observed singles, and KS distances for multiplicities, periods, period ratios, transit depths, and period-normalized transit duration ratios
model_fit_stats_fields = ["Number of observed planets", "Number of systems with observed singles", "KS multiplicities", "KS periods", "KS period ratios", "KS transit depths", "KS period-normalized transit duration ratios"] #names of the fields associated with 'model_fit_stats' array

sim_param_default = setup_sim_param_model() #To set up all the default parameters

for (i,power_law_r) in enumerate(power_law_r_range)
    for (j,sigma_log_radius_in_cluster) in enumerate(sigma_log_radius_in_cluster_range)
        tic()
        sim_param_here = deepcopy(sim_param_default) #To set up all the default parameters
        add_param_fixed(sim_param_here,"power_law_r",power_law_r) #To replace the 'power_law_r' parameter
        add_param_fixed(sim_param_here,"sigma_log_radius_in_cluster",sigma_log_radius_in_cluster) #To replace the 'sigma_log_radius_in_cluster' parameter

        cat_phys = generate_kepler_physical_catalog(sim_param_here)
        cat_phys_cut = ExoplanetsSysSim.generate_obs_targets(cat_phys,sim_param_here)
        cat_obs = observe_kepler_targets_single_obs(cat_phys_cut,sim_param_here)
        summary_stat = calc_summary_stats_model(cat_obs,sim_param_here)

        M_cat_obs = ones(Int64,0) #array to be filled with the number of transiting planets in each simulated system
        for k in 1:get_int(sim_param_here,"max_tranets_in_sys")
            append!(M_cat_obs, k*ones(Int64, summary_stat.stat["num n-tranet systems"][k]))
        end

        model_fit_stats[1,i,j] = summary_stat.stat["num_tranets"]
        model_fit_stats[2,i,j] = summary_stat.stat["num n-tranet systems"][1]
        model_fit_stats[3,i,j] = ksstats_ints(M_cat_obs, M_confirmed)[5]
        model_fit_stats[4,i,j] = ksstats(summary_stat.stat["P list"], P_confirmed)[5]
        model_fit_stats[5,i,j] = ksstats(summary_stat.stat["period_ratio_list"], R_confirmed)[5]
        model_fit_stats[6,i,j] = ksstats(summary_stat.stat["depth list"], D_confirmed)[5]
        model_fit_stats[7,i,j] = ksstats(summary_stat.stat["duration_ratio_list"], xi_confirmed)[5]

        println("# power_law_r, sigma_log_radius_in_cluster: ", get_real(sim_param_here,"power_law_r"), ", ",  get_real(sim_param_here,"sigma_log_radius_in_cluster"))
        toc()
    end
end
=#

#=
f = open("Model_stats_r_sigma_r_grid.out", "w")
println(f,"# Model statistics on a grid of (power_law_r, sigma_log_radius_in_cluster) = (", power_law_r_range, ", ",  sigma_log_radius_in_cluster_range, ")")
println(f,"# num_targets_sim_pass_one: ", get_int(sim_param_default,"num_targets_sim_pass_one"))
println(f,"# log_rate_clusters: ", get_real(sim_param_default,"log_rate_clusters"))
println(f,"# log_rate_planets_per_cluster: ", get_real(sim_param_default,"log_rate_planets_per_cluster"))
println(f,"# power_law_P: ", get_real(sim_param_default,"power_law_P"))
println(f,"# max_radius: ", get_real(sim_param_default,"max_radius"))
println(f,"# sigma_incl: ", get_real(sim_param_default,"sigma_incl"))
println(f,"# sigma_incl_near_mmr: ", get_real(sim_param_default,"sigma_incl_near_mmr"))
println(f,"# sigma_hk: ", get_real(sim_param_default,"sigma_hk"))
println(f,"# num_mutual_hill_radii: ", get_real(sim_param_default,"num_mutual_hill_radii"))
println(f,"# mr_power_index: ", get_real(sim_param_default,"mr_power_index"))
println(f,"# mr_max_mass: ", get_real(sim_param_default,"mr_max_mass"))
println(f,"# sigma_logperiod_per_pl_in_cluster: ", get_real(sim_param_default,"sigma_logperiod_per_pl_in_cluster"))
for i in 1:size(model_fit_stats)[1]
    println(f,"# ", model_fit_stats_fields[i], ":")
    println(f,model_fit_stats[i,:,:]')
end
close(f)
=#


module Formulas

export @~,
       Term,
       Terms,
       Formula

# package code goes here
include("formula.jl")
include("terms.jl")

end # module


"""
This function creates the model for a full themal dispatch formulation depending on combination of devices, device_formulation and system_formulation
"""
function construct_device!(ps_m::CanonicalModel,
                           device::Type{T},
                           device_formulation::Type{D},
                           system_formulation::Type{S},
                           sys::PSY.System,
                           time_range::UnitRange{Int64};
                           kwargs...) where {T <: PSY.ThermalGen,
                                             D <: AbstractThermalFormulation,
                                             S <: PM.AbstractPowerFormulation}

    parameters = get(kwargs, :parameters, true)

    if isempty(keys(ps_m.initial_conditions))
        @warn("Initial Conditions not provided, this can lead to infeasible problem formulations")
    end

    #Variables
    activepower_variables(ps_m, sys.generators.thermal, time_range);

    reactivepower_variables(ps_m, sys.generators.thermal, time_range);

    commitment_variables(ps_m, sys.generators.thermal, time_range)

    #Constraints
    activepower_constraints(ps_m, sys.generators.thermal, device_formulation, system_formulation, time_range)

    reactivepower_constraints(ps_m, sys.generators.thermal, device_formulation, system_formulation, time_range)

    commitment_constraints(ps_m, sys.generators.thermal, device_formulation, system_formulation, time_range, parameters)

    ramp_constraints(ps_m, sys.generators.thermal, device_formulation, system_formulation, time_range, parameters)

    time_constraints(ps_m, sys.generators.thermal, device_formulation, system_formulation, time_range, parameters)

    #Cost Function
    cost_function(ps_m, sys.generators.thermal, device_formulation, system_formulation)

    return

end

"""
This function creates the model for a full themal dispatch formulation depending on combination of devices, device_formulation and system_formulation
"""
function construct_device!(ps_m::CanonicalModel,
                           device::Type{T},
                           device_formulation::Type{D},
                           system_formulation::Type{S},
                           sys::PSY.System,
                           time_range::UnitRange{Int64};
                           kwargs...) where {T <: PSY.ThermalGen,
                                             D <: AbstractThermalFormulation,
                                             S <: PM.AbstractActivePowerFormulation}

    parameters = get(kwargs, :parameters, true)

    if isempty(keys(ps_m.initial_conditions))
        @warn("Initial Conditions not provided, this can lead to infeasible problem formulations")
    end

    #Variables
    activepower_variables(ps_m, sys.generators.thermal, time_range);

    commitment_variables(ps_m, sys.generators.thermal, time_range)

    #Constraints
    activepower_constraints(ps_m, sys.generators.thermal, device_formulation, system_formulation, time_range)

    commitment_constraints(ps_m, sys.generators.thermal, device_formulation, system_formulation, time_range, parameters)

    ramp_constraints(ps_m, sys.generators.thermal, device_formulation, system_formulation, time_range, parameters)

    time_constraints(ps_m, sys.generators.thermal, device_formulation, system_formulation, time_range, parameters)

    #Cost Function
    cost_function(ps_m, sys.generators.thermal, device_formulation, system_formulation)

    return

end

"""
This function creates the model for a full themal dispatch formulation depending on combination of devices, device_formulation and system_formulation
"""
function construct_device!(ps_m::CanonicalModel,
                           device::Type{T},
                           device_formulation::Type{PSI.ThermalRampLimited},
                           system_formulation::Type{S},
                           sys::PSY.System,
                           time_range::UnitRange{Int64};
                           kwargs...) where {T <: PSY.ThermalGen,
                                             S <: PM.AbstractPowerFormulation}

    if isempty(keys(ps_m.initial_conditions))
        @warn("Initial Conditions not provided, this can lead to infeasible problem formulations")
    end

    parameters = get(kwargs, :parameters, true)

    #Variables
    activepower_variables(ps_m, sys.generators.thermal, time_range);

    reactivepower_variables(ps_m, sys.generators.thermal, time_range);

    #Constraints
    activepower_constraints(ps_m, sys.generators.thermal, device_formulation, system_formulation, time_range)

    reactivepower_constraints(ps_m, sys.generators.thermal, device_formulation, system_formulation, time_range)

    ramp_constraints(ps_m, sys.generators.thermal, device_formulation, system_formulation, time_range, parameters)

    #Cost Function
    cost_function(ps_m, sys.generators.thermal, device_formulation, system_formulation)

    return

end

"""
This function creates the model for a full themal dispatch formulation depending on combination of devices, device_formulation and system_formulation
"""
function construct_device!(ps_m::CanonicalModel,
                           device::Type{T},
                           device_formulation::Type{ThermalRampLimited},
                           system_formulation::Type{S},
                           sys::PSY.System,
                           time_range::UnitRange{Int64};
                           kwargs...) where {T <: PSY.ThermalGen,
                                             S <: PM.AbstractActivePowerFormulation}

    if isempty(keys(ps_m.initial_conditions))
        @warn("Initial Conditions not provided, this can lead to infeasible problem formulations")
    end

    parameters = get(kwargs, :parameters, true)

    #Variables
    activepower_variables(ps_m, sys.generators.thermal, time_range);

    #Constraints
    activepower_constraints(ps_m, sys.generators.thermal, device_formulation, system_formulation, time_range)

    ramp_constraints(ps_m, sys.generators.thermal, device_formulation, system_formulation, time_range, parameters)

    #Cost Function
    cost_function(ps_m, sys.generators.thermal, device_formulation, system_formulation)

    return

end

function construct_device!(ps_m::CanonicalModel,
                           device::Type{T},
                           device_formulation::Type{D},
                           system_formulation::Type{S},
                           sys::PSY.System,
                           time_range::UnitRange{Int64};
                           kwargs...) where {T<: PSY.ThermalGen,
                                             D <: AbstractThermalDispatchForm,
                                             S <: PM.AbstractPowerFormulation}

    #Variables
    activepower_variables(ps_m, sys.generators.thermal, time_range);

    reactivepower_variables(ps_m, sys.generators.thermal, time_range);

    #Constraints
    activepower_constraints(ps_m, sys.generators.thermal, device_formulation, system_formulation, time_range)

    reactivepower_constraints(ps_m, sys.generators.thermal, device_formulation, system_formulation, time_range)

    #Cost Function
    cost_function(ps_m, sys.generators.thermal, device_formulation, system_formulation)

    return

end

function construct_device!(ps_m::CanonicalModel,
                           device::Type{T},
                           device_formulation::Type{D},
                           system_formulation::Type{S},
                           sys::PSY.System,
                           time_range::UnitRange{Int64};
                           kwargs...) where {T<: PSY.ThermalGen,
                                             D <: AbstractThermalDispatchForm,
                                             S <: PM.AbstractActivePowerFormulation}

    #Variables
    activepower_variables(ps_m, sys.generators.thermal, time_range);

    #Constraints
    activepower_constraints(ps_m, sys.generators.thermal, device_formulation, system_formulation, time_range)

    #Cost Function
    cost_function(ps_m, sys.generators.thermal, device_formulation, system_formulation)

    return

end


immutable EdgeIterState
    s::Int  # src vertex
    di::Int # index into adj of dest vertex
    fin::Bool
end

type EdgeIter
    m::Int
    adj::Vector{Vector{Int}}
    directed::Bool
end

eltype(::Type{EdgeIter}) = Edge

EdgeIter(g::Graph) = EdgeIter(ne(g), g.fadjlist, false)
EdgeIter(g::DiGraph) = EdgeIter(ne(g), g.fadjlist, true)

function _next(eit::EdgeIter, state::EdgeIterState = EdgeIterState(1,1,false), first::Bool = true)
    s = state.s
    di = state.di
    if !first
        di += 1
    end
    fin = state.fin
    while s <= length(eit.adj)
        arr = eit.adj[s]
        while di <= length(arr)
            if eit.directed || s <= arr[di]
                return EdgeIterState(s, di, fin)
            end
            di += 1
        end
        s += 1
        di = 1
    end
    fin = true
    return EdgeIterState(s, di, fin)
end

start(eit::EdgeIter) = _next(eit)
done(eit::EdgeIter, state::EdgeIterState) = state.fin
length(eit::EdgeIter) = eit.m

function next(eit::EdgeIter, state)
    edge = Edge(state.s, eit.adj[state.s][state.di])
    return(edge, _next(eit, state, false))
end

function _isequal(e1::EdgeIter, e2)
    for e in e2
        s, d = e
        found = length(searchsorted(e1.adj[s], d)) > 0
        if !e1.directed
            found = found || length(searchsorted(e1.adj[d],s)) > 0
        end
        !found && return false
    end
    return true
end
==(e1::EdgeIter, e2::AbstractArray{Edge,1}) = _isequal(e1, e2)
==(e1::AbstractArray{Edge,1}, e2::EdgeIter) = _isequal(e2, e1)
==(e1::EdgeIter, e2::Set{Edge}) = _isequal(e1, e2)
==(e1::Set{Edge}, e2::EdgeIter) = _isequal(e2, e1)

function ==(e1::EdgeIter, e2::EdgeIter)
    length(e1.adj) == length(e2.adj) || return false
    e1.directed == e2.directed || return false
    for i in 1:length(e1.adj)
        e1.adj[i] == e2.adj[i] || return false
    end
    return true
end

show(io::IO, eit::EdgeIter) = write(io, "EdgeIter $(eit.m)")
show(io::IO, s::EdgeIterState) = write(io, "EdgeIterState [$(s.s), $(s.di), $(s.fin)]")


"""
# plot_model_coef

Multiple regression coefficient plot for multiple models.

$(SIGNATURES)

### Required arguments
```julia
* `models`                             : Vector of `SampleModel`s to compare
* `pars`                               : Vector of parameters to include in comparison
```
### Optional arguments
```julia
* `fig=""`                             : File to store plot
* `title=""`                           : Title for plot
```

### Return values
```julia
* `(s, f)`                             : (particles, plot)
```

"""
function plot_model_coef(models::Vector{SampleModel},
  pars::Vector{Symbol}; fig="", title="")

  mnames = [models[i].name for i in 1:length(models)]
  for i in 1:length(mnames)
    if length(mnames[i]) > 9
      mnames[i] = mnames[i][1:9]
    end
  end

  s = Vector{NamedTuple}(undef, length(models))
  for (mindx, mdl) in enumerate(models)
    df = read_samples(mdl, :dataframe)
    m, l, u = estimparam(df)
    d = Dict{Symbol, NamedTuple}()
    for (indx, par) in enumerate(names(df))
        d[Symbol(par)] = (mean=m[indx], lower=l[indx], upper=u[indx])
    end
    s[mindx] =   (; d...)
  end

  plot_model_coef(s, pars; mnames, fig, title)
end

"""
# plot_model_coef

Multiple regression coefficient plot for a single model.

$(SIGNATURES)

### Required arguments
```julia
* `model`                              : SampleModel to display
* `pars`                               : Vector of parameters to include in comparison
* `fig`                                : File to store the produced plot
```
### Optional arguments
```julia
* `title=""`                           : Title for plot
```

### Return values
```julia
* `(s, f)`                             : (particles, plot)
```

"""
function plot_model_coef(model::SampleModel, pars::Vector{Symbol};
  fig="", title="")
  plot_model_coef([model], pars; fig, title)
end

function plot_models(models::Vector{SampleModel}, type::Symbol;
  fig="", title = uppercase(String(type)))

  mnames = [models[i].name for i in 1:length(models)]
  for i in 1:length(mnames)
    if length(mnames[i]) > 9
      mnames[i] = mnames[i][1:9]
    end
  end

  df_waic = compare(models, type)
  plot_models(df_waic, type; fig, title)
end

export
  plot_model_coef,
  plot_models


#######################################################################################
# GPU-crutch solve method
# Makes the simplest possible method for GPU-compatibility
# Out of place only
#######################################################################################
struct GPUSimpleTsit5 <: DiffEqBase.AbstractODEAlgorithm end
export GPUSimpleTsit5

@muladd function DiffEqBase.solve(prob::ODEProblem,
                          alg::GPUSimpleTsit5;
                          dt = 0.1f0)
  @assert !isinplace(prob)
  u0 = prob.u0
  tspan = prob.tspan
  f = prob.f
  p = prob.p
  t = tspan[1]
  tf = prob.tspan[2]
  ts = tspan[1]:dt:tspan[2]
  us = MVector{length(ts),typeof(u0)}(undef)
  us[1] = u0
  u = u0
  k7 = f(u, p, t)

  cs, as, btildes, rs = _build_atsit5_caches(eltype(u0))
  c1, c2, c3, c4, c5, c6 = cs
  a21, a31, a32, a41, a42, a43, a51, a52, a53, a54,
  a61, a62, a63, a64, a65, a71, a72, a73, a74, a75, a76 = as
  btilde1, btilde2, btilde3, btilde4, btilde5, btilde6, btilde7 = btildes

  # FSAL
  for i in 2:length(ts)
      uprev = u; k1 = k7; t = ts[i]
      tmp = uprev+dt*a21*k1
      k2 = f(tmp, p, t+c1*dt)
      tmp = uprev+dt*(a31*k1+a32*k2)
      k3 = f(tmp, p, t+c2*dt)
      tmp = uprev+dt*(a41*k1+a42*k2+a43*k3)
      k4 = f(tmp, p, t+c3*dt)
      tmp = uprev+dt*(a51*k1+a52*k2+a53*k3+a54*k4)
      k5 = f(tmp, p, t+c4*dt)
      tmp = uprev+dt*(a61*k1+a62*k2+a63*k3+a64*k4+a65*k5)
      k6 = f(tmp, p, t+dt)
      u = uprev+dt*(a71*k1+a72*k2+a73*k3+a74*k4+a75*k5+a76*k6)
      k7 = f(u, p, t+dt)
      us[i] = u
  end

  sol = DiffEqBase.build_solution(prob,alg,ts,SArray(us),
                                  k = nothing, destats = nothing,
                                  calculate_error = false)
  DiffEqBase.has_analytic(prob.f) && DiffEqBase.calculate_solution_errors!(sol;timeseries_errors=true,dense_errors=false)
  sol
end

#######################################################################################
# GPU-crutch solve method
# Makes the simplest possible adaptive method for GPU-compatibility
# Out of place only
#######################################################################################
struct GPUSimpleATsit5 end
export GPUSimpleATsit5

@muladd function DiffEqBase.solve(prob::ODEProblem,
                          alg::GPUSimpleATsit5;
                          dt = 0.1f0,saveat = nothing,
                          save_everystep = true,
                          abstol = 1f-6, reltol = 1f-3)
  @assert !isinplace(prob)
  u0 = prob.u0
  tspan = prob.tspan
  f = prob.f
  p = prob.p

  t = tspan[1]
  tf = prob.tspan[2]

  if saveat === nothing
    ts = Vector{eltype(dt)}(undef,1)
    ts[1] = prob.tspan[1]
    us = Vector{typeof(u0)}(undef,0)
    push!(us,recursivecopy(u0))
  else
    ts = saveat
    cur_t = 1
    us = MVector{length(ts),typeof(u0)}(undef)
    if prob.tspan[1] == ts[1]
      cur_t += 1
      us[1] = u0
    end
  end

  u = u0
  qold = qoldinit
  k7 = f(u, p, t)

  cs, as, btildes, rs = _build_atsit5_caches(eltype(u0))
  c1, c2, c3, c4, c5, c6 = cs
  a21, a31, a32, a41, a42, a43, a51, a52, a53, a54,
  a61, a62, a63, a64, a65, a71, a72, a73, a74, a75, a76 = as
  btilde1, btilde2, btilde3, btilde4, btilde5, btilde6, btilde7 = btildes

  # FSAL
  while t < tspan[2]
      uprev = u
      k1 = k7
      EEst = Inf

      while EEst > 1
        dt < 1e-14 && error("dt<dtmin")

        tmp = uprev+dt*a21*k1
        k2 = f(tmp, p, t+c1*dt)
        tmp = uprev+dt*(a31*k1+a32*k2)
        k3 = f(tmp, p, t+c2*dt)
        tmp = uprev+dt*(a41*k1+a42*k2+a43*k3)
        k4 = f(tmp, p, t+c3*dt)
        tmp = uprev+dt*(a51*k1+a52*k2+a53*k3+a54*k4)
        k5 = f(tmp, p, t+c4*dt)
        tmp = uprev+dt*(a61*k1+a62*k2+a63*k3+a64*k4+a65*k5)
        k6 = f(tmp, p, t+dt)
        u = uprev+dt*(a71*k1+a72*k2+a73*k3+a74*k4+a75*k5+a76*k6)
        k7 = f(u, p, t+dt)

        tmp = dt*(btilde1*k1+btilde2*k2+btilde3*k3+btilde4*k4+
                     btilde5*k5+btilde6*k6+btilde7*k7)
        tmp = tmp./(abstol.+max.(abs.(uprev),abs.(u))*reltol)
        EEst = DiffEqBase.ODE_DEFAULT_NORM(tmp, t)

        if iszero(EEst)
          q = inv(qmax)
        else
          @fastmath q11 = EEst^beta1
          @fastmath q = q11/(qold^beta2)
        end

        if EEst > 1
          dt = dt/min(inv(qmin),q11/gamma)
        else # EEst <= 1
          @fastmath q = max(inv(qmax),min(inv(qmin),q/gamma))
          qold = max(EEst,qoldinit)
          dtold = dt
          dt = dt/q #dtnew
          dt = min(abs(dt),abs(tf-t-dtold))
          told = t
          if (tf - t - dtold) < 1e-14
            t = tf
          else
            t += dtold
          end

          if saveat === nothing && save_everystep
            push!(us,recursivecopy(u))
            push!(ts,t)
          else saveat !== nothing
            while cur_t <= length(ts) && ts[cur_t] <= t
              savet = ts[cur_t]
              θ = (savet - told)/dtold
              b1θ, b2θ, b3θ, b4θ, b5θ, b6θ, b7θ = bθs(rs, θ)
              us[cur_t] = uprev + dtold*(
                   b1θ*k1 + b2θ*k2 + b3θ*k3 + b4θ*k4 + b5θ*k5 + b6θ*k6 + b7θ*k7)
              cur_t += 1
            end
          end

        end
      end
  end

  if saveat === nothing && !save_everystep
      push!(us,u)
      push!(ts,t)
  end
  sol = DiffEqBase.build_solution(prob,alg,ts,us,
                                  calculate_error = false)
  DiffEqBase.has_analytic(prob.f) && DiffEqBase.calculate_solution_errors!(sol;timeseries_errors=true,dense_errors=false)
  sol
end