function applycnsics!(system::CVSystem,old=Dict("a"=>0),restart="no")
    if restart == "no"
        system.cns.H[1] = 1/system.heart.activation.th[1]*minTos;
        system.cns.Emaxlv[1] = system.heart.lv.Emax[1];
        system.cns.Emaxrv[1] = system.heart.rv.Emax[1];

        system.cns.R2L[1] = system.branches.term[13].R[2];
        system.cns.R3L[1] = system.branches.term[13].R[3];
        system.cns.R2U[1] = system.branches.term[54].R[2];
        system.cns.R3U[1] = system.branches.term[54].R[3];
        system.cns.C4L[1] = system.branches.term[13].C[4];
        system.cns.C5L[1] = system.branches.term[13].C[5];
        system.cns.C4U[1] = system.branches.term[54].C[4];
        system.cns.C5U[1] = system.branches.term[54].C[5];
        system.cns.V4L[1] = system.branches.term[13].V0[4];
        system.cns.V5L[1] = system.branches.term[13].V0[5];
        system.cns.V4U[1] = system.branches.term[54].V0[4];
        system.cns.V5U[1] = system.branches.term[54].V0[5];

        system.cns.gammalv = 0.8*system.cns.Emaxlv[1];
        system.cns.alphalv = 1.6*system.cns.Emaxlv[1] - system.cns.gammalv;
        system.cns.gammarv = 0.8*system.cns.Emaxrv[1];
        system.cns.alpharv = 1.6*system.cns.Emaxrv[1] - system.cns.gammarv;
        system.cns.gammaR2L = 0.6*system.cns.R2L[1];
        system.cns.alphaR2L = 2.2*system.cns.R2L[1] - system.cns.gammaR2L;
        system.cns.gammaR3L = 0.6*system.cns.R3L[1];
        system.cns.alphaR3L = 2.2*system.cns.R3L[1] - system.cns.gammaR3L;
        system.cns.gammaR2U = 0.6*system.cns.R2U[1];
        system.cns.alphaR2U = 2.2*system.cns.R2U[1] - system.cns.gammaR2U;
        system.cns.gammaR3U = 0.6*system.cns.R3U[1];
        system.cns.alphaR3U = 2.2*system.cns.R3U[1] - system.cns.gammaR3U;
        system.cns.gammaC4L = 1.1*system.cns.C4L[1];
        system.cns.alphaC4L = -0.7*system.cns.C4L[1] + system.cns.gammaC4L;
        system.cns.gammaC5L = 1.1*system.cns.C5L[1];
        system.cns.alphaC5L = -0.7*system.cns.C5L[1] + system.cns.gammaC5L;
        system.cns.gammaC4U = 1.1*system.cns.C4U[1];
        system.cns.alphaC4U = -0.7*system.cns.C4U[1] + system.cns.gammaC4U;
        system.cns.gammaC5U = 1.1*system.cns.C5U[1];
        system.cns.alphaC5U = -0.7*system.cns.C5U[1] + system.cns.gammaC5U;
        system.cns.gammaV4L = 1.05*system.cns.V4L[1];
        system.cns.alphaV4L = -0.85*system.cns.V4L[1] + system.cns.gammaV4L;
        system.cns.gammaV5L = 1.05*system.cns.V5L[1];
        system.cns.alphaV5L = -0.85*system.cns.V5L[1] + system.cns.gammaV5L;
        system.cns.gammaV4U = 1.05*system.cns.V4U[1];
        system.cns.alphaV4U = -0.85*system.cns.V4U[1] + system.cns.gammaV4U;
        system.cns.gammaV5U = 1.05*system.cns.V5U[1];
        system.cns.alphaV5U = -0.85*system.cns.V5U[1] + system.cns.gammaV5U;

        system.cns.ns = [0.25];
        system.cns.np = [0.25];
    elseif restart == "yes"
        system.cns.H[1] = old["H"][end];
        system.cns.Emaxlv[1] = old["Emaxlv"][end];
        system.cns.Emaxrv[1] = old["Emaxrv"][end];

        system.cns.R2L[1] = old["R2L"][end];
        system.cns.R3L[1] = old["R3L"][end];
        system.cns.R2U[1] = old["R2U"][end];
        system.cns.R3U[1] = old["R3U"][end];
        system.cns.C4L[1] = old["C4L"][end];
        system.cns.C5L[1] = old["C5L"][end];
        system.cns.C4U[1] = old["C4U"][end];
        system.cns.C5U[1] = old["C5U"][end];
        system.cns.V4L[1] = old["V4L"][end];
        system.cns.V5L[1] = old["V5L"][end];
        system.cns.V4U[1] = old["V4U"][end];
        system.cns.V5U[1] = old["V5U"][end];

        system.cns.gammalv = old["gammalv"];
        system.cns.alphalv = old["alphalv"];
        system.cns.gammarv = old["gammarv"];
        system.cns.alpharv = old["alpharv"];
        system.cns.gammaR2L = old["gammaR2L"];
        system.cns.alphaR2L = old["alphaR2L"];
        system.cns.gammaR3L = old["gammaR3L"];
        system.cns.alphaR3L = old["alphaR3L"];
        system.cns.gammaR2U = old["gammaR2U"];
        system.cns.alphaR2U = old["alphaR2U"];
        system.cns.gammaR3U = old["gammaR3U"];
        system.cns.alphaR3U = old["alphaR3U"];
        system.cns.gammaC4L = old["gammaC4L"];
        system.cns.alphaC4L = old["alphaC4L"];
        system.cns.gammaC5L = old["gammaC5L"];
        system.cns.alphaC5L = old["alphaC5L"];
        system.cns.gammaC4U = old["gammaC4U"];
        system.cns.alphaC4U = old["alphaC4U"];
        system.cns.gammaC5U = old["gammaC5U"];
        system.cns.alphaC5U = old["alphaC5U"];
        system.cns.gammaV4L = old["gammaV4L"];
        system.cns.alphaV4L = old["alphaV4L"];
        system.cns.gammaV5L = old["gammaV5L"];
        system.cns.alphaV5L = old["alphaV5L"];
        system.cns.gammaV4U = old["gammaV4U"];
        system.cns.alphaV4U = old["alphaV4U"];
        system.cns.gammaV5U = old["gammaV5U"];
        system.cns.alphaV5U = old["alphaV5U"];

        system.cns.ns = [old["ns"][end]];
        system.cns.np = [old["np"][end]];
    end
    system.cns.alphaH = 2.1563;
    system.cns.betaH = 0.5313;
    system.cns.gammaH = 0.8438;
end


println(1 -2 3)


# Function to merge eventlist and write merged event file
# Author: Gulab Dewangan
# Date : 29 October 2018
#

using DataFrames, FITSIO, FITSIO.Libcfitsio

"""
    sxt_l2evtlist_merge(l2evtfilelist, merged_evtfile)

Merge the level2 orbit-wise event files in FITS format from the AstroSat/SXT instrument.

...
# Arguments
- `l2evtfilelist::String`: An ascii file listing the level2 event files for different orbits of a given SXT observation.
- `merged_evtfile::String`: Name of the merged event file to be created.
...
"""
function sxt_l2evtlist_merge(l2evtfilelist::String, merged_evtfile::String)
   evtfiles = readlines(l2evtfilelist)
   # Read fits event files
   f=Array{FITS}(undef, length(evtfiles))
   for i=1:length(evtfiles)
     f[i]=FITS(evtfiles[i], "r+")
     write_key(f[i][2],"TFORM10","1I")
     write_key(f[i][4],"TFORM5","1I")
     close(f[i])
   end
   f=FITS.(evtfiles, "r+")
   evtlist=Array{TableHDU}(undef, length(evtfiles))
   badpixlist=Array{TableHDU}(undef,length(evtfiles))
   gtilist=Array{TableHDU}(undef,length(evtfiles))
   evt_df=Array{DataFrame}(undef, length(evtfiles))
   badpix_df=Array{DataFrame}(undef, length(evtfiles))
   gti_df=Array{DataFrame}(undef, length(evtfiles))
   for i=1:length(f)
     evtlist[i]=f[i][2]
     gtilist[i]=f[i][3]
     badpixlist[i]=f[i][4]
     evt_df[i]=sxtevtlist2df(evtlist[i])
     badpix_df[i]=sxtbadpixlist2df(badpixlist[i])
     gti_df[i]=gti2df(gtilist[i])
     write_key(f[i][2],"TFORM10","16X")
     write_key(f[i][4],"TFORM5","16X")
    # close(f)
   end
   close.(f)
   # Merge event lists and badpixel lists
   evtlist_merged=vcat(evt_df...)
   gtilist_merged = vcat(gti_df...)
   gtilist_unique = gti_unique_or(gtilist_merged)
   badpixlist_merged=vcat(badpix_df...)
   # Retain only unique events or badpixels
   sort!(evtlist_merged, [:TIME, :RAWX, :RAWY])
   unique!(evtlist_merged, [:CCDFrame,:RAWX, :RAWY])
   sort!(badpixlist_merged, [:RAWX, :RAWY])
   unique!(badpixlist_merged)

   # Convert dataframes to vectors to be able to write to fits files
   (evtlist_col_names, evtlist_col_vals) = sxtevt_df2vec(evtlist_merged)
   (badpix_col_names,badpix_col_vals) = sxtbadpix_df2vec(badpixlist_merged)
   (gti_start, gti_stop) = (gtilist_unique[:,:START], gtilist_unique[:,:STOP])
   exposure_sec = sum(gti_stop .- gti_start)

   # Create GTIs
   # evttimes = evtlist_merged[:TIME]
   # (gti_start,gti_stop) = buildgti(evttimes)
   # exptime_sec=sum(gti_stop .- gti_start)

   gti_col_names=["START","STOP"]
   gti_col_vals=[gti_start,gti_stop]

   # Read headers from the first evtfile
   # ff=FITS(evtfiles[1])
   # primary_hdr=read_header(ff[1])
   # evt_hdr=read_header(ff[2])
   # gti_hdr=read_header(ff[3])
   # badpix_header=read_header(ff[4])
   # close(ff)
   #
   # # Update header keywords for merged eventfile
   # evt_hdr["EXPOSURE"]=exptime_sec
   # evt_hdr["ONTIME"]=exptime_sec
   # evt_hdr["LIVETIME"]=exptime_sec

   # Write the merged event file

  ff=fits_create_file(merged_evtfile)
  evtlist_coldef=[("TIME", "1D", "seconds"),("CCDFrame", "1J", " "),("X", "1I", "pixel"),("Y", "1I", "pixel"),("RAWX", "1I", "pixel"),("RAWY", "1I", "pixel"),("DETX", "1I", "pixel"),("DETY", "1I", "pixel"),("PHAS", "9I", "channel"),("STATUS", "16X", " "), ("PHA", "1J", " "), ("GRADE", "1I", " "),  ("PixsAbove", "1I", " "), ("PI", "1J", "channel"), ("RA", "D", " "),("Dec", "D", " "), ("PHASO", "9I", " ")]
  fits_create_binary_tbl(ff,0,evtlist_coldef,"EVENTS")
  for i=1:length(evtlist_col_names)
    fits_write_col(ff,i,1,1,evtlist_col_vals[i])
  end

  gti_coldef=[("START", "1D", "s"),("STOP", "1D", "s")]
  fits_create_binary_tbl(ff,0,gti_coldef,"STDGTI")
  for i=1:length(gti_col_names)
    fits_write_col(ff,i,1,1,gti_col_vals[i])
  end

  badpix_coldef=[("RAWX", "1I", " "),("RAWY", "1I"," "),("TYPE", "1I", " "),("YEXTENT", "1I"," "), ("BADFLAG", "16I", " ")]
  fits_create_binary_tbl(ff,0,badpix_coldef,"BADPIX ")
  for i=1:length(badpix_col_names)
    fits_write_col(ff,i,1,1,badpix_col_vals[i])
  end
fits_close_file(ff)

# Determine start-stop keywords from the list of event files
  ef = Array{FITS}(undef, length(evtfiles))
  ef_evtheader = Array{FITSHeader}(undef, length(evtfiles))
  ef = FITS.(evtfiles, "r")
  for n=1:length(ef)
     ef_evtheader[n] = read_header(ef[n][2])
     close(ef[n])
   end

   # Determine keyword values
   tstart = Array{Float64}(undef,length(evtfiles))
   tstop = Array{Float64}(undef,length(evtfiles))
   date_obs = Array{String}(undef,length(evtfiles))
   date_end = Array{String}(undef,length(evtfiles))
   time_obs = Array{String}(undef,length(evtfiles))
   time_end = Array{String}(undef,length(evtfiles))
   mjd_obs = Array{Float64}(undef,length(evtfiles))
  for i=1:length(evtfiles)
    tstart[i] = ef_evtheader[i]["TSTART"]
    tstop[i] = ef_evtheader[i]["TSTOP"]
    date_obs[i] = ef_evtheader[i]["DATE-OBS"]
    date_end[i] = ef_evtheader[i]["DATE-END"]
    time_obs[i] = ef_evtheader[i]["TIME-OBS"]
    time_end[i] = ef_evtheader[i]["TIME-END"]
    mjd_obs[i] = ef_evtheader[i]["MJD-OBS"]
  end
  tstart_merged = minimum(tstart)
  tstop_merged = maximum(tstop)
  mjd_obs_merged = minimum(mjd_obs)
  telapse_merged = tstop_merged - tstart_merged
  date_obs_merged = date_obs[findall(x ->x==tstart_merged, tstart)][1]
  date_end_merged = date_end[findall(x ->x==tstop_merged, tstop)][1]
  time_obs_merged = time_obs[findall(x ->x==tstart_merged, tstart)][1]
  time_end_merged = time_end[findall(x ->x==tstop_merged, tstop)][1]

  # Write header keywords from the first eventfile
  ef1=FITS(readlines(l2evtfilelist)[1])
  mef=FITS(merged_evtfile,"r+")
  # ef1h1=read_header(ef1[1])
  # ef1h2=read_header(ef1[2])
  # ef1h3=read_header(ef1[3])
  # ef1h4=read_header(ef1[4])
  ef1_header=[read_header(ef1[i]) for i=1:4]
  for i=1:4
    for j=1:length(ef1_header[i].keys)
      if ef1_header[i].keys[j] in ["SIMPLE", "BITPIX", "NAXIS", "EXTEND", "NAXIS1", "NAXIS2", "PCOUNT", "GCOUNT", "TFIELDS", "XTENSION", "BITPIX", "TFORM1", "TFORM2", "TFORM3", "TFORM4", "TFORM5", "TFORM6", "TFORM7", "TFORM8",  "TFORM9", "TFORM10", "TFORM11", "TFORM12", "TFORM13", "TFORM14","TFORM15", "TFORM16", "TFORM17", "TOTAL2K", "ERROR2K", "FILIN001", "EXPOSURE"]
#        println(ef1_header[i].keys[j] * "Not updated")
      else
        write_key(mef[i],ef1_header[i].keys[j],ef1_header[i].values[j],ef1_header[i].comments[j])
#        println(ef1_header[i].keys[j] * " updated.")
      end
      write_key(mef[i],"EXPOSURE",exposure_sec,"Exposure time")
      write_key(mef[i],"LIVETIME",exposure_sec,"On source time")
      write_key(mef[i],"ONTIME",exposure_sec,"On source time")
      write_key(mef[i], "TSTART",tstart_merged,"time start")
      write_key(mef[i], "TSTOP",tstop_merged ,"time stop")
      write_key(mef[i], "DATE-OBS",date_obs_merged,"start date of observation")
      write_key(mef[i], "DATE-END",date_end_merged,"end date of observation")
      write_key(mef[i], "TIME-OBS",time_obs_merged,"start time of observation")
      write_key(mef[i], "TIME-END",time_end_merged,"end time of observation")
      write_key(mef[i], "TELAPSE", telapse_merged, "Total elapsed time")
      write_key(mef[i], "MJD-OBS", mjd_obs_merged, "MJD of observation start")
  end
end
close(ef1)
close(mef)
println("Merged event file: "  * merged_evtfile * " successfully written!")
return 1
end


__precompile__()

module DiffEqBase

using RecipesBase, SimpleTraits, RecursiveArrayTools, Compat
using Ranges # For plot recipes with units
import Base: length, ndims, size, getindex, setindex!, endof, show, print,
             next, start, done, eltype, eachindex, similar

import Base: resize!, deleteat!

import RecursiveArrayTools: recursivecopy!

# Problems
@compat abstract type DEProblem end
@compat abstract type DEElement end
@compat abstract type DESensitivity end

export DEProblem, DEElement, DESensitivity

@compat abstract type AbstractSteadyStateProblem{uType,isinplace} <: DEProblem end

export AbstractSteadyStateProblem

@compat abstract type AbstractODEProblem{uType,tType,isinplace} <: DEProblem end
@compat abstract type AbstractDiscreteProblem{uType,tType,isinplace} <:
                      AbstractODEProblem{uType,tType,isinplace} end

export AbstractODEProblem, AbstractDiscreteProblem

@compat abstract type AbstractRODEProblem{uType,tType,isinplace,ND} <: DEProblem end

export AbstractRODEProblem

@compat abstract type AbstractSDEProblem{uType,tType,isinplace,ND} <:
                      AbstractRODEProblem{uType,tType,isinplace,ND} end

export AbstractSDEProblem

@compat abstract type AbstractDAEProblem{uType,duType,tType,isinplace} <: DEProblem end

export AbstractDAEProblem

@compat abstract type AbstractDDEProblem{uType,tType,lType,isinplace} <: DEProblem end
@compat abstract type AbstractConstantLagDDEProblem{uType,tType,lType,isinplace} <:
                      AbstractDDEProblem{uType,tType,lType,isinplace} end

export AbstractDDEProblem,
       AbstractConstantLagDDEProblem

@compat abstract type AbstractSecondOrderODEProblem{uType,tType,isinplace} <: AbstractODEProblem{uType,tType,isinplace} end
@compat abstract type AbstractSecondOrderSDEProblem{uType,tType,isinplace} <: AbstractSDEProblem{uType,tType,isinplace} end

export AbstractSecondOrderODEProblem, AbstractSecondOrderSDEProblem

# Algorithms
@compat abstract type DEAlgorithm end
@compat abstract type AbstractSteadyStateAlgorithm <: DEAlgorithm end
@compat abstract type AbstractODEAlgorithm <: DEAlgorithm end
@compat abstract type AbstractSplitODEAlgorithm <: DEAlgorithm end
@compat abstract type AbstractPartitionedODEAlgorithm <: DEAlgorithm end
@compat abstract type AbstractSecondOrderODEAlgorithm <: DEAlgorithm end
@compat abstract type AbstractSplitDAEAlgorithm <: DEAlgorithm end
@compat abstract type AbstractPartitionedDAEAlgorithm <: DEAlgorithm end
@compat abstract type AbstractSplitConstrainedODEAlgorithm <: DEAlgorithm end
@compat abstract type AbstractPartitionedConstrainedODEAlgorithm <: DEAlgorithm end
@compat abstract type AbstractRODEAlgorithm <: DEAlgorithm end
@compat abstract type AbstractSDEAlgorithm <: DEAlgorithm end
@compat abstract type AbstractDAEAlgorithm <: DEAlgorithm end
@compat abstract type AbstractDDEAlgorithm <: DEAlgorithm end

export DEAlgorithm, AbstractSteadyStateAlgorithm, AbstractODEAlgorithm,
       AbstractSplitODEAlgorithm,
       AbstractPartitionedODEAlgorithm, AbstractSecondOrderODEAlgorithm,
       AbstractRODEAlgorithm, AbstractSDEAlgorithm,
       AbstractDAEAlgorithm, AbstractDDEAlgorithm

# Monte Carlo Simulations
@compat abstract type AbstractMonteCarloProblem <: DEProblem end
@compat abstract type AbstractMonteCarloEstimator <: DEProblem end

export AbstractMonteCarloProblem, MonteCarloProblem
export MonteCarloSolution, MonteCarloTestSolution
export AbstractMonteCarloEstimator

@compat abstract type DEOptions end
@compat abstract type DECache end
@compat abstract type DECallback end
@compat abstract type DEDataArray{T} <: AbstractVector{T} end

export DEOptions, DECache, DECallback, DEDataArray

# Integrators
@compat abstract type DEIntegrator end
@compat abstract type AbstractSteadyStateIntegrator <: DEIntegrator end
@compat abstract type AbstractODEIntegrator <: DEIntegrator end
@compat abstract type AbstractSplitODEIntegrator <: DEIntegrator end
@compat abstract type AbstractPartitionedODEIntegrator <: DEIntegrator end
@compat abstract type AbstractSecondOrderODEIntegrator <: DEIntegrator end
@compat abstract type AbstractRODEIntegrator <: DEIntegrator end
@compat abstract type AbstractSDEIntegrator <: DEIntegrator end
@compat abstract type AbstractDDEIntegrator <: DEIntegrator end
@compat abstract type AbstractDAEIntegrator <: DEIntegrator end

export DEIntegrator, AbstractODEIntegrator, AbstractSplitODEIntegrator,
       AbstractPartitionedODEIntegrator, AbstractSecondOrderODEIntegrator,
       AbstractRODEIntegrator, AbstractSDEIntegrator,
       AbstractDDEIntegrator, AbstractDAEIntegrator

# Solutions
@compat abstract type DESolution end
@compat abstract type AbstractSteadyStateSolution <: DESolution end

export DESolution, AbstractSteadyStateSolution

@compat abstract type AbstractODESolution <: DESolution end

export AbstractODESolution

# Needed for plot recipes
@compat abstract type AbstractDDESolution <: AbstractODESolution end
@compat abstract type  AbstractRODESolution <: AbstractODESolution end
@compat abstract type  AbstractDAESolution <: AbstractODESolution end
@compat abstract type AbstractSensitivitySolution end
@compat abstract type AbstractMonteCarloSolution <: DESolution end

export AbstractRODESolution,
       AbstractDAESolution,
       AbstractDDESolution,
       AbstractSensitivitySolution, AbstractMonteCarloSolution

# Misc
@compat abstract type Tableau end
@compat abstract type ODERKTableau <: Tableau end
@compat abstract type DECostFunction end

export Tableau, ODERKTableau, DECostFunction

@compat abstract type AbstractParameterizedFunction{isinplace} <: Function end
@compat abstract type ConstructedParameterizedFunction{isinplace} <: AbstractParameterizedFunction{isinplace} end
export AbstractParameterizedFunction, ConstructedParameterizedFunction

@compat abstract type AbstractNoiseProcess{isinplace} end
export AbstractNoiseProcess

include("utils.jl")
include("extended_functions.jl")
include("solutions/steady_state_solutions.jl")
include("solutions/ode_solutions.jl")
include("solutions/rode_solutions.jl")
include("solutions/dae_solutions.jl")
include("solutions/monte_solutions.jl")
include("solutions/solution_interface.jl")
include("tableaus.jl")
include("problems/discrete_problems.jl")
include("problems/steady_state_problems.jl")
include("problems/ode_problems.jl")
include("problems/constrained_ode_problems.jl")
include("problems/rode_problems.jl")
include("problems/sde_problems.jl")
include("problems/dae_problems.jl")
include("problems/dde_problems.jl")
include("problems/monte_problems.jl")
include("problems/problem_traits.jl")
include("callbacks.jl")
include("integrator_interface.jl")
include("parameters_interface.jl")
include("constructed_parameterized_functions.jl")
include("linear_nonlinear.jl")
include("data_array.jl")

type ConvergenceSetup{P,C}
    probs::P
    convergence_axis::C
end

function solve end
function solve! end
function init end
function step!(d::DEIntegrator) error("Integrator stepping is not implemented") end

export DEParameters, Mesh, ExplicitRKTableau, ImplicitRKTableau

export isinplace, noise_class

export solve, solve!, init, step!

export tuples, intervals

export resize!,deleteat!,addat!,full_cache,user_cache,u_cache,du_cache,
       resize_non_user_cache!,deleteat_non_user_cache!,addat_non_user_cache!,
       terminate!,
       add_tstop!,add_saveat!,set_abstol!,
       set_reltol!,get_du,get_dt,get_proposed_dt,set_proposed_dt!,u_modified!,
       savevalues!

export numargs, @def, @muladd

export construct_correlated_noisefunc

export HasJac, HastGrad, HasParamFuncs, HasParamDeriv, HasParamJac,
       HasInvJac,HasInvW, HasInvW_t, HasHes, HasInvHes, HasSyms

export has_jac, has_invjac, has_invW, has_invW_t, has_hes, has_invhes,
       has_tgrad, has_paramfuncs, has_paramderiv, has_paramjac,
       has_syms, has_analytic

export DiscreteProblem

export SteadyStateProblem, SteadyStateSolution
export ODEProblem, ODESolution
export SplitODEProblem, PartitionedODEProblem
export RODEProblem, RODESolution, SDEProblem
export SplitSDEProblem, PartitionedSDEProblem, SecondOrderSDEProblem
export DAEProblem, DAESolution
export SplitDAEProblem, PartitionedDAEProblem, ConstrainedODEProblem,
       SplitConstrainedODEProblem, PartitionedConstrainedODEProblem
export ConstantLagDDEProblem, DDEProblem

export ParameterizedFunction, DAEParameterizedFunction, DDEParameterizedFunction,
       ProbParameterizedFunction, OutputParameterizedFunction

export calculate_monte_errors

export build_solution, calculate_solution_errors!

export ConvergenceSetup

export ContinuousCallback, DiscreteCallback, CallbackSet

export initialize!

export copy_non_array_fields

export param_values, num_params, problem_new_parameters

export white_noise_func_wrapper, white_noise_func_wrapper!

export is_diagonal_noise, is_sparse_noise

export LinSolveFactorize, DEFAULT_LINSOLVE

export isautodifferentiable

end # module


"""
    flux_first_order!(
        bl::BalanceLaw,
        flux::Grad,
        state::Vars,
        aux::Vars,
        t::Real
    )

Computes (and assembles) flux terms `F¹(Y)` in:

```
∂Y
-- + ∇ • F¹(Y) + ∇ • F²(Y,G) = S(Y, G),     G = ∇Y
∂t
```

Computes and assembles non-diffusive
fluxes in the model equations.

For this fallback to work, several methods must be defined:
 - [`prognostic_vars`](@ref)
 - [`eq_tends`](@ref)
 - [`get_prog_state`](@ref)
optionally,
 - [`precompute`](@ref)
and individual [`flux`](@ref) kernels that
are defined for each type that `eq_tends` returns.
"""
@inline function flux_first_order!(
    bl::BalanceLaw,
    flux,
    state,
    aux,
    t,
    direction,
)

    tend = Flux{FirstOrder}()
    _args = (; state, aux, t, direction)
    args = merge(_args, (precomputed = precompute(bl, _args, tend),))

    map(prognostic_vars(bl)) do prog
        var, name = get_prog_state(flux, prog)
        val = Σfluxes(prog, eq_tends(prog, bl, tend), bl, args)
        setproperty!(var, name, val)
    end
    nothing
end

"""
    flux_second_order!(
        bl::BalanceLaw,
        flux::Grad,
        state::Vars,
        diffusive::Vars,
        hyperdiffusive::Vars,
        aux::Vars,
        t::Real
    )

Computes (and assembles) flux terms `F²(Y, G)` in:

```
∂Y
-- + ∇ • F¹(Y) + ∇ • F²(Y,G) = S(Y, G),     G = ∇Y
∂t
```

Diffusive fluxes in BalanceLaw. Viscosity, diffusivity are calculated
in the turbulence subcomponent and accessed within the diffusive flux
function. Contributions from subcomponents are then assembled (pointwise).

For this fallback to work, several methods must be defined:
 - [`prognostic_vars`](@ref)
 - [`eq_tends`](@ref)
 - [`get_prog_state`](@ref)
optionally,
 - [`precompute`](@ref)
and individual [`flux`](@ref) kernels that
are defined for each type that `eq_tends` returns.
"""
@inline function flux_second_order!(
    bl::BalanceLaw,
    flux,
    state,
    diffusive,
    hyperdiffusive,
    aux,
    t,
)
    tend = Flux{SecondOrder}()
    _args = (; state, aux, t, diffusive, hyperdiffusive)
    args = merge(_args, (precomputed = precompute(bl, _args, tend),))

    map(prognostic_vars(bl)) do prog
        var, name = get_prog_state(flux, prog)
        val = Σfluxes(prog, eq_tends(prog, bl, tend), bl, args)
        setproperty!(var, name, val)
    end
    nothing
end

"""
    source!(
        bl::BalanceLaw,
        source::Vars,
        state::Vars,
        diffusive::Vars,
        aux::Vars,
        t::Real,
        direction::Direction,
    )

Computes (and assembles) source terms `S(Y)` in:

```
∂Y
-- + ∇ • F¹(Y) + ∇ • F²(Y,G) = S(Y, G),     G = ∇Y
∂t
```

For this fallback to work, several methods must be defined:
 - [`prognostic_vars`](@ref)
 - [`eq_tends`](@ref)
 - [`get_prog_state`](@ref)
optionally,
 - [`precompute`](@ref)
and individual [`source`](@ref) kernels that
are defined for each type that `eq_tends` returns.
"""
function source!(bl::BalanceLaw, source, state, diffusive, aux, t, direction)
    tend = Source()
    _args = (; state, aux, t, direction, diffusive)
    args = merge(_args, (precomputed = precompute(bl, _args, tend),))

    map(prognostic_vars(bl)) do prog
        var, name = get_prog_state(source, prog)
        val = Σsources(prog, eq_tends(prog, bl, tend), bl, args)
        setproperty!(var, name, val)
    end
    nothing
end


module ResNet
export ResNetModel, stages_channels

using Pickle
using Downloads: download
using Flux

include("blocks.jl")
include("model.jl")
include("load_utils.jl")

# using Images
# function main()
#     device = cpu
#     model = from_pretrained(18)
#     model = model |> testmode! |> device
#     @info "Model loaded."

#     images = [
#         raw"C:\Users\tonys\Downloads\elephant2-r.jpg",
#         raw"C:\Users\tonys\Downloads\spaceshuttle-r.jpg",
#     ]
#     μ = reshape([0.485, 0.456, 0.406], (3, 1, 1))
#     σ = reshape([0.229, 0.224, 0.225], (3, 1, 1))
#     for image in images
#         x = Images.load(image) |> channelview .|> Float32
#         @. x = (x - μ) / σ
#         x = Flux.unsqueeze(permutedims(x, (3, 2, 1)), 4)

#         @info "Image $image ($(size(x))):"
#         o = x |> device |> model |> softmax |> cpu
#         o = sortperm(o[:, 1])
#         @info "Top 5 classes: $(o[end:-1:end - 5] .- 1)"
#     end
# end
# main()

end


# Conversions
# -----------

# convert(C, c) might be called as convert(RGB, c) or convert(RGB{Float32}, c)
# This is handled in ColorTypes, which calls functions
#     _convert(::Type{Cdest}, ::Type{Odest}, ::Type{Osrc}, c)
#     _convert(::Type{Cdest}, ::Type{Odest}, ::Type{Osrc}, c, alpha)
# Here are the argument types:
# - Cdest may be any concrete Color{T,N} type. For parametric Color types
#   it _always_ has the desired element type (e.g., Float32), so it's
#   safe to dispatch on Cdest{T}.
# - Odest and Osrc are Color subtypes, i.e., things like RGB
#   or HSV. They have no element type.
# - c is the Colorant object you wish to convert.
# - alpha, if present, is a user-supplied alpha value (to be used in
#   place of any default alpha or alpha present in c).
#
# The motivation for this arrangement is that Julia doesn't (yet) support
# "triangular dispatch", e.g.,
#     convert{T,C}(::Type{C{T}}, c)
# The various arguments of _convert therefore "peel off" element types
# (or guarantee them) so that comparisons may be made via
# dispatch. The alternative design is
#    for C in parametric_colors
#       @eval convert{T}(::Type{$C{T}}, c) = ...
#       @eval convert(   ::Type{$C},    c) = convert($C{eltype(c)}, c)
#       ...
#    end
# but this requires a fair amount of codegen (especially for all
# the various alpha variants) and can break if not all C support
# the same eltypes.
#
# Note that ColorTypes handles the cases where Odest == Osrc, or they
# are both subtypes of AbstractRGB.  Therefore, here we only have to
# deal with conversions between different spaces.

function ColorTypes._convert{Cdest<:TransparentColor,Odest,Osrc}(::Type{Cdest}, ::Type{Odest}, ::Type{Osrc}, p, alpha)
    # Convert the base color
    c = cnvt(color_type(Cdest), color(p))
    # Append the alpha
    ColorTypes._convert(Cdest, Odest, Odest, c, alpha)
end
function ColorTypes._convert{Cdest<:TransparentColor,Odest,Osrc}(::Type{Cdest}, ::Type{Odest}, ::Type{Osrc}, p)
    c = cnvt(color_type(Cdest), color(p))
    ColorTypes._convert(Cdest, Odest, Odest, c, alpha(p))
end

ColorTypes._convert{Cdest<:Color,Odest,Osrc}(::Type{Cdest}, ::Type{Odest}, ::Type{Osrc}, c) = cnvt(Cdest, c)

# Fallback to catch undefined operations
cnvt{C<:Color}(::Type{C}, c::TransparentColor) = cnvt(C, color(c))
cnvt{C}(::Type{C}, c) = convert(C, c)

# Conversions from grayscale
# --------------------------
cnvt{C<:Color3}(::Type{C}, g::AbstractGray)  = cnvt(C, convert(RGB{eltype(C)}, g))

macro mul3x3(T, M, c1, c2, c3)
    esc(quote
        @inbounds ret = $T($M[1,1]*$c1 + $M[1,2]*$c2 + $M[1,3]*$c3,
                           $M[2,1]*$c1 + $M[2,2]*$c2 + $M[2,3]*$c3,
                           $M[3,1]*$c1 + $M[3,2]*$c2 + $M[3,3]*$c3)
        ret
        end)
end

# Everything to RGB
# -----------------

correct_gamut{CV<:AbstractRGB}(c::CV) = CV(clamp01(red(c)), clamp01(green(c)), clamp01(blue(c)))
clamp01{T<:Fractional}(v::T) = ifelse(v < zero(T), zero(T), ifelse(v > one(T), one(T), v))

function srgb_compand(v)
    v <= 0.0031308 ? 12.92v : 1.055v^(1/2.4) - 0.055
end

cnvt{CV<:AbstractRGB}(::Type{CV}, c::AbstractRGB) = CV(red(c), green(c), blue(c))

function cnvt{CV<:AbstractRGB}(::Type{CV}, c::HSV)
    h = c.h / 60
    i = floor(Int, h)
    f = h - i
    if i & 1 == 0
        f = 1 - f
    end
    m = c.v * (1 - c.s)
    n = c.v * (1 - c.s * f)
    if i == 6 || i == 0; CV(c.v, n, m)
    elseif i == 1;       CV(n, c.v, m)
    elseif i == 2;       CV(m, c.v, n)
    elseif i == 3;       CV(m, n, c.v)
    elseif i == 4;       CV(n, m, c.v)
    else;                CV(c.v, m, n)
    end
end

function qtrans(u, v, hue)
    if     hue > 360; hue -= 360
    elseif hue < 0;   hue += 360
    end

    if     hue < 60;  u + (v - u) * hue / 60
    elseif hue < 180; v
    elseif hue < 240; u + (v - u) * (240 - hue) / 60
    else;             u
    end
end

function cnvt{CV<:AbstractRGB}(::Type{CV}, c::HSL)
    v = c.l <= 0.5 ? c.l * (1 + c.s) : c.l + c.s - (c.l * c.s)
    u = 2 * c.l - v

    if c.s == 0; CV(c.l, c.l, c.l)
    else;        CV(qtrans(u, v, c.h + 120),
                    qtrans(u, v, c.h),
                    qtrans(u, v, c.h - 120))
    end
end

function cnvt{CV<:AbstractRGB}(::Type{CV}, c::HSI)
    h, s, i = c.h, c.s, c.i
    while(h > 360) h -= 360 end
    while(h < 0) h += 360 end
    is = i*s
    if h < 120
        cosr = cosd(h) / cosd(60-h)
        CV(i+is*cosr, i+is*(1-cosr), i-is)
    elseif h < 240
        cosr = cosd(h-120) / cosd(180-h)
        CV(i-is, i+is*cosr, i+is*(1-cosr))
    else
        cosr = cosd(h-240) / cosd(300-h)
        CV(i+is*(1-cosr), i-is, i+is*cosr)
    end
end

function cnvt{CV<:AbstractRGB}(::Type{CV}, c::XYZ)
    r =  3.2404542*c.x - 1.5371385*c.y - 0.4985314*c.z
    g = -0.9692660*c.x + 1.8760108*c.y + 0.0415560*c.z
    b =  0.0556434*c.x - 0.2040259*c.y + 1.0572252*c.z
    CV(clamp01(srgb_compand(r)),
       clamp01(srgb_compand(g)),
       clamp01(srgb_compand(b)))
end

function cnvt{CV<:AbstractRGB}(::Type{CV}, c::YIQ)
    cc = correct_gamut(c)
    CV(clamp01(cc.y+0.9563*cc.i+0.6210*cc.q),
       clamp01(cc.y-0.2721*cc.i-0.6474*cc.q),
       clamp01(cc.y-1.1070*cc.i+1.7046*cc.q))
end

function cnvt{CV<:AbstractRGB}(::Type{CV}, c::YCbCr)
    cc = correct_gamut(c)
    ny = cc.y - 16
    ncb = cc.cb - 128
    ncr = cc.cr - 128
    CV(clamp01(0.004567ny - 1.39135e-7ncb + 0.0062586ncr),
       clamp01(0.004567ny - 0.00153646ncb - 0.0031884ncr),
       clamp01(0.004567ny + 0.00791058ncb - 2.79201e-7ncr))
end

cnvt{CV<:AbstractRGB}(::Type{CV}, c::LCHab)  = cnvt(CV, convert(Lab{eltype(c)}, c))
cnvt{CV<:AbstractRGB}(::Type{CV}, c::LCHuv)  = cnvt(CV, convert(Luv{eltype(c)}, c))
cnvt{CV<:AbstractRGB}(::Type{CV}, c::Color3)    = cnvt(CV, convert(XYZ{eltype(c)}, c))

cnvt{CV<:AbstractRGB{UFixed8}}(::Type{CV}, c::RGB24) = CV(UFixed8(c.color&0x00ff0000>>>16,0), UFixed8(c.color&0x0000ff00>>>8,0), UFixed8(c.color&0x000000ff,0))
cnvt{CV<:AbstractRGB}(::Type{CV}, c::RGB24) = CV((c.color&0x00ff0000>>>16)/255, ((c.color&0x0000ff00)>>>8)/255, (c.color&0x000000ff)/255)

function cnvt{CV<:AbstractRGB}(::Type{CV}, c::AbstractGray)
    g = convert(eltype(CV), gray(c))
    CV(g, g, g)
end

# Everything to HSV
# -----------------

function cnvt{T}(::Type{HSV{T}}, c::AbstractRGB)
    c_min = Float64(min(red(c), green(c), blue(c)))
    c_max = Float64(max(red(c), green(c), blue(c)))
    if c_min == c_max
        return HSV{T}(zero(T), zero(T), c_max)
    end

    if c_min == red(c)
        f = Float64(green(c)) - Float64(blue(c))
        i = 3
    elseif c_min == green(c)
        f = Float64(blue(c)) - Float64(red(c))
        i = 5
    else
        f = Float64(red(c)) - Float64(green(c))
        i = 1
    end

    HSV{T}(60 * (i - f / (c_max - c_min)),
        (c_max - c_min) / c_max,
        c_max)
end

cnvt{T}(::Type{HSV{T}}, c::Color3) = cnvt(HSV{T}, convert(RGB{T}, c))

# Everything to HSL
# -----------------

function cnvt{T}(::Type{HSL{T}}, c::AbstractRGB)
    r, g, b = T(red(c)), T(green(c)), T(blue(c))
    c_min = min(r, g, b)
    c_max = max(r, g, b)
    l = (c_max + c_min) / 2

    if c_max == c_min
        return HSL(zero(T), zero(T), l)
    end

    if l < 0.5; s = (c_max - c_min) / (c_max + c_min)
    else;       s = (c_max - c_min) / (convert(T, 2) - c_max - c_min)
    end

    if c_max == red(c)
        h = (g - b) / (c_max - c_min)
    elseif c_max == green(c)
        h = convert(T, 2) + (b - r) / (c_max - c_min)
    else
        h = convert(T, 4) + (r - g) / (c_max - c_min)
    end

    h *= 60
    if h < 0
        h += 360
    elseif h > 360
        h -= 360
    end

    HSL{T}(h,s,l)
end

cnvt{T}(::Type{HSL{T}}, c::Color3) = cnvt(HSL{T}, convert(RGB{T}, c))

# Everything to HSI
# -----------------

function cnvt{T}(::Type{HSI{T}}, c::AbstractRGB)
    rgb = correct_gamut(c)
    r, g, b = red(rgb), green(rgb), blue(rgb)
    isum = r+g+b
    i = isum/3
    m = min(r, g, b)
    s = i > 0 ? 1-m/i : 1-one(i)/one(i) # the latter is a type-stable 0
    val = (r-(g+b)/2)/sqrt(r^2+g^2+b^2-r*g-r*b-g*b)
    val = clamp(val, -one(val), one(val))
    h = acosd(val)
    if b > g
        h = 360-h
    end
    HSI{T}(h, s, i)
end

cnvt{T}(::Type{HSI{T}}, c::Color3) = cnvt(HSI{T}, convert(RGB{T}, c))

# Everything to XYZ
# -----------------

function invert_rgb_compand(v)
    v <= 0.04045 ? v/12.92 : ((v+0.055) /1.055)^2.4
end

function cnvt{T}(::Type{XYZ{T}}, c::AbstractRGB)
    r, g, b = invert_rgb_compand(red(c)), invert_rgb_compand(green(c)), invert_rgb_compand(blue(c))
    XYZ{T}(0.4124564*r + 0.3575761*g + 0.1804375*b,
           0.2126729*r + 0.7151522*g + 0.0721750*b,
           0.0193339*r + 0.1191920*g + 0.9503041*b)
end

cnvt{T}(::Type{XYZ{T}}, c::HSV) = cnvt(XYZ{T}, convert(RGB{T}, c))
cnvt{T}(::Type{XYZ{T}}, c::HSL) = cnvt(XYZ{T}, convert(RGB{T}, c))
cnvt{T}(::Type{XYZ{T}}, c::HSI) = cnvt(XYZ{T}, convert(RGB{T}, c))

function cnvt{T}(::Type{XYZ{T}}, c::xyY)
    X = c.Y*c.x/c.y
    Z = c.Y*(1-c.x-c.y)/c.y
    XYZ{T}(X, c.Y, Z)
end

const xyz_epsilon = 216 / 24389
const xyz_kappa   = 24389 / 27

convert(::Type{XYZ}, c, wp::XYZ) = convert(XYZ{eltype(wp)}, c, wp)
convert{T}(::Type{XYZ{T}}, c, wp::XYZ) = cnvt(XYZ{T}, c, wp)
function cnvt{T}(::Type{XYZ{T}}, c::Lab, wp::XYZ)
    fy = (c.l + 16) / 116
    fx = c.a / 500 + fy
    fz = fy - c.b / 200

    fx3 = fx^3
    fz3 = fz^3

    x = fx3 > xyz_epsilon ? fx3 : (116fx - 16) / xyz_kappa
    y = c.l > xyz_kappa * xyz_epsilon ? ((c. l+ 16) / 116)^3 : c.l / xyz_kappa
    z = fz3 > xyz_epsilon ? fz3 : (116fz - 16) / xyz_kappa

    XYZ{T}(x*wp.x, y*wp.y, z*wp.z)
end

cnvt{T}(::Type{XYZ{T}}, c::Lab)   = convert(XYZ{T}, c, WP_DEFAULT)
cnvt{T}(::Type{XYZ{T}}, c::LCHab) = cnvt(XYZ{T}, convert(Lab{T}, c))
cnvt{T}(::Type{XYZ{T}}, c::DIN99) = cnvt(XYZ{T}, convert(Lab{T}, c))
cnvt{T}(::Type{XYZ{T}}, c::DIN99o) = cnvt(XYZ{T}, convert(Lab{T}, c))

cnvt{T<:UFixed}(::Type{XYZ{T}}, c::LCHab) = cnvt(XYZ{T}, convert(Lab{eltype(c)}, c))
cnvt{T<:UFixed}(::Type{XYZ{T}}, c::DIN99) = cnvt(XYZ{T}, convert(Lab{eltype(c)}, c))
cnvt{T<:UFixed}(::Type{XYZ{T}}, c::DIN99o) = cnvt(XYZ{T}, convert(Lab{eltype(c)}, c))

function xyz_to_uv(c::XYZ)
    d = c.x + 15c.y + 3c.z
    d==0 && return (d, d)
    u = 4c.x / d
    v = 9c.y / d
    return (u, v)
end

function cnvt{T}(::Type{XYZ{T}}, c::Luv, wp::XYZ = WP_DEFAULT)
    (u_wp, v_wp) = xyz_to_uv(wp)

    a = (52 * (c.l==0 ? zero(T) : c.l / (c.u + 13 * c.l * u_wp)) - 1) / 3
    y = c.l > xyz_kappa * xyz_epsilon ? wp.y * ((c.l + 16) / 116)^3 : wp.y * c.l / xyz_kappa
    b = -5y
    d = y * (39 * (c.l==0 ? zero(T) : c.l / (c.v + 13 * c.l * v_wp)) - 5)
    x = d==b ? zero(T) : (d - b) / (a + 1/3)
    z = a * x + b + zero(T)

    XYZ{T}(x, y, z)
end

cnvt{T}(::Type{XYZ{T}}, c::LCHuv) = cnvt(XYZ{T}, convert(Luv{T}, c))

function cnvt{T}(::Type{XYZ{T}}, c::DIN99d)

    # Go back to C-h space
    # FIXME: Clean this up (why is there no atan2d?)
    h = rad2deg(atan2(c.b,c.a)) + 50
    while h > 360; h -= 360; end
    while h < 0;   h += 360; end

    C = sqrt(c.a^2 + c.b^2)

    # Intermediate terms
    G = (exp(C/22.5)-1)/0.06
    f = G*sind(h - 50)
    ee = G*cosd(h - 50)

    l = (exp(c.l/325.22)-1)/0.0036
    # a = ee*cosd(50) - f/1.14*sind(50)
    a = ee*0.6427876096865393 - f/1.14*0.766044443118978
    # b = ee*sind(50) - f/1.14*cosd(50)
    b = ee*0.766044443118978 - f/1.14*0.6427876096865393

    adj = convert(XYZ, Lab(l, a, b))

    XYZ{T}((adj.x + 0.12*adj.z)/1.12, adj.y, adj.z)

end

function cnvt{T}(::Type{XYZ{T}}, c::LMS)
    @mul3x3 XYZ{T} CAT02_INV c.l c.m c.s
end

cnvt{T}(::Type{XYZ{T}}, c::YIQ) = cnvt(XYZ{T}, convert(RGB{T}, c))
cnvt{T}(::Type{XYZ{T}}, c::YCbCr) = cnvt(XYZ{T}, convert(RGB{T}, c))

cnvt{T}(::Type{XYZ{T}}, c::RGB24) = cnvt(XYZ{T}, convert(RGB{T}, c))

# Everything to xyY
# -----------------

function cnvt{T}(::Type{xyY{T}}, c::XYZ)

    x = c.x/(c.x + c.y + c.z)
    y = c.y/(c.x + c.y + c.z)

    xyY{T}(x, y, convert(typeof(x), c.y))

end

cnvt{T}(::Type{xyY{T}}, c::Color3) = cnvt(xyY{T}, convert(XYZ{T}, c))

# Everything to Lab
# -----------------

cnvt{T}(::Type{Lab{T}}, c::AbstractRGB) = convert(Lab{T}, convert(XYZ{T}, c))
cnvt{T}(::Type{Lab{T}}, c::HSV) = cnvt(Lab{T}, convert(RGB{T}, c))
cnvt{T}(::Type{Lab{T}}, c::HSL) = cnvt(Lab{T}, convert(RGB{T}, c))

convert{T}(::Type{Lab{T}}, c, wp::XYZ) = cnvt(Lab{T}, c, wp)
convert(::Type{Lab}, c, wp::XYZ) = cnvt(Lab{eltype(wp)}, c, wp)

function fxyz2lab(v)
    v > xyz_epsilon ? cbrt(v) : (xyz_kappa * v + 16) / 116
end
function cnvt{T}(::Type{Lab{T}}, c::XYZ, wp::XYZ)
    fx, fy, fz = fxyz2lab(c.x / wp.x), fxyz2lab(c.y / wp.y), fxyz2lab(c.z / wp.z)
    Lab{T}(116fy - 16, 500(fx - fy), 200(fy - fz))
end

cnvt{T}(::Type{Lab{T}}, c::XYZ{T}) = cnvt(Lab{T}, c, WP_DEFAULT)

function cnvt{T}(::Type{Lab{T}}, c::LCHab)
    hr = deg2rad(c.h)
    Lab{T}(c.l, c.c * cos(hr), c.c * sin(hr))
end

function cnvt{T}(::Type{Lab{T}}, c::DIN99)

    # We assume the adjustment parameters are always 1; the standard recommends
    # that they not be changed from these values.
    kch = 1
    ke = 1

    # Calculate Chroma (C99) in the DIN99 space
    cc = sqrt(c.a^2 + c.b^2)

    # NOTE: This is calculated in degrees, against the standard, to save
    # computation steps later.
    if (c.a > 0 && c.b >= 0)
        h = atand(c.b/c.a)
    elseif (c.a == 0 && c.b > 0)
        h = 90
    elseif (c.a < 0)
        h = 180+atand(c.b/c.a)
    elseif (c.a == 0 && c.b < 0)
        h = 270
    elseif (c.a > 0 && c.b <= 0)
        h = 360 + atand(c.b/c.a)
    else
        h = 0
    end

    # Temporary variable for chroma
    g = (e^(0.045*cc*kch*ke)-1)/0.045

    # Temporary redness
    ee = g*cosd(h)

    # Temporary yellowness
    f = g*sind(h)

    # CIELAB a*b*
    # ciea = ee*cosd(16) - (f/0.7)*sind(16)
    ciea = ee*0.9612616959383189 - (f/0.7)*0.27563735581699916
    # cieb = ee*sind(16) + (f/0.7)*cosd(16)
    cieb = ee*0.27563735581699916 + (f/0.7)*0.9612616959383189

    # CIELAB L*
    ciel = (e^(c.l*ke/105.51)-1)/0.0158

    Lab{T}(ciel, ciea, cieb)
end

function cnvt{T}(::Type{Lab{T}}, c::DIN99o)

    # We assume the adjustment parameters are always 1; the standard recommends
    # that they not be changed from these values.
    kch = 1
    ke = 1

    # Calculate Chroma (C99) in the DIN99o space
    co = sqrt(c.a^2 + c.b^2)

    # hue angle h99o
    h = atan2(c.b, c.a)

    # revert rotation by 26°
    ho= rad2deg(h)-26

    # revert logarithmic chroma compression
    g = (e^(co*kch*ke/23.0)-1)/0.075

    # Temporary redness
    eo = g*cosd(ho)

    # Temporary yellowness
    fo = g*sind(ho)

    # CIELAB a*b* (revert b* axis compression)
    # ciea = eo*cosd(26) - (fo/0.83)*sind(26)
    ciea = eo*0.898794046299167 - (fo/0.83)*0.4383711467890774
    # cieb = eo*sind(26) + (fo/0.83)*cosd(26)
    cieb = eo*0.4383711467890774 + (fo/0.83)*0.898794046299167

    # CIELAB L* (revert logarithmic lightness compression)
    ciel = (e^(c.l*ke/303.67)-1)/0.0039

    Lab{T}(ciel, ciea, cieb)
end

cnvt{T}(::Type{Lab{T}}, c::Color3) = cnvt(Lab{T}, convert(XYZ{T}, c))

# Everything to Luv
# -----------------

cnvt{T}(::Type{Luv{T}}, c::AbstractRGB) = cnvt(Luv{T}, convert(XYZ{T}, c))
cnvt{T}(::Type{Luv{T}}, c::HSV) = cnvt(Luv{T}, convert(RGB{T}, c))
cnvt{T}(::Type{Luv{T}}, c::HSL) = cnvt(Luv{T}, convert(RGB{T}, c))

convert{T}(::Type{Luv{T}}, c, wp::XYZ) = cnvt(Luv{T}, c, wp)
convert(::Type{Luv}, c, wp::XYZ) = cnvt(Luv{eltype(wp)}, c, wp)

function cnvt{T}(::Type{Luv{T}}, c::XYZ, wp::XYZ = WP_DEFAULT)
    (u_wp, v_wp) = xyz_to_uv(wp)
    (u_, v_) = xyz_to_uv(c)

    y = c.y / wp.y

    l = y > xyz_epsilon ? 116 * cbrt(y) - 16 : xyz_kappa * y
    u = 13 * l * (u_ - u_wp) + zero(T)
    v = 13 * l * (v_ - v_wp) + zero(T)

    Luv{T}(l, u, v)
end

function cnvt{T}(::Type{Luv{T}}, c::LCHuv)
    hr = deg2rad(c.h)
    Luv{T}(c.l, c.c * cos(hr), c.c * sin(hr))
end

cnvt{T}(::Type{Luv{T}}, c::Color3) = cnvt(Luv{T}, convert(XYZ{T}, c))

# Everything to LCHuv
# -------------------

function cnvt{T}(::Type{LCHuv{T}}, c::Luv)
    h = rad2deg(atan2(c.v, c.u))
    while h > 360; h -= 360; end
    while h < 0;   h += 360; end
    LCHuv{T}(c.l, sqrt(c.u^2 + c.v^2), h)
end

cnvt{T}(::Type{LCHuv{T}}, c::Color3) = cnvt(LCHuv{T}, convert(Luv{T}, c))

# Everything to LCHab
# -------------------

function cnvt{T}(::Type{LCHab{T}}, c::Lab)
    h = rad2deg(atan2(c.b, c.a))
    while h > 360; h -= 360; end
    while h < 0;   h += 360; end
    LCHab{T}(c.l, sqrt(c.a^2 + c.b^2), h)
end

cnvt{T}(::Type{LCHab{T}}, c::Color3) = cnvt(LCHab{T}, convert(Lab{T}, c))

# Everything to DIN99
# -------------------

function cnvt{T}(::Type{DIN99{T}}, c::Lab)

    # We assume the adjustment parameters are always 1; the standard recommends
    # that they not be changed from these values.
    kch = 1
    ke = 1

    # Calculate DIN99 L
    l99 = (1/ke)*105.51*log(1+0.0158*c.l)

    # Temporary value for redness and yellowness
    # ee = c.a*cosd(16) + c.b*sind(16)
    ee = c.a*0.9612616959383189 + c.b*0.27563735581699916
    # f = -0.7*c.a*sind(16) + 0.7*c.b*cosd(16)
    f = -0.7*c.a*0.27563735581699916 + 0.7*c.b*0.9612616959383189

    # Temporary value for chroma
    g = sqrt(ee^2 + f^2)

    # Hue angle
    # Calculated in degrees, against the specification.
    if (ee > 0 && f >= 0)
        h = atand(f/ee)
    elseif (ee == 0 && f > 0)
        h = 90
    elseif (ee < 0)
        h = 180+atand(f/ee)
    elseif (ee == 0 && f < 0)
        h = 270
    elseif (ee > 0 && f <= 0)
        h = 360 + atand(f/ee)
    else
        h = 0
    end

    # DIN99 chroma
    cc = log(1+0.045*g)/(0.045*kch*ke)

    # DIN99 chromaticities
    a99, b99 = cc*cosd(h), cc*sind(h)

    DIN99{T}(l99, a99, b99)

end

cnvt{T}(::Type{DIN99{T}}, c::Color3) = cnvt(DIN99{T}, convert(Lab{T}, c))

# Everything to DIN99d
# --------------------

function cnvt{T}(::Type{DIN99d{T}}, c::XYZ{T})

    # Apply tristimulus-space correction term
    adj_c = XYZ(1.12*c.x - 0.12*c.z, c.y, c.z)

    # Apply L*a*b*-space correction
    lab = convert(Lab, adj_c)
    adj_L = 325.22*log(1+0.0036*lab.l)

    # Calculate intermediate parameters
    # ee = lab.a*cosd(50) + lab.b*sind(50)
    ee = lab.a*0.6427876096865393 + lab.b*0.766044443118978
    # f = 1.14*(lab.b*cosd(50) - lab.a*sind(50))
    f = 1.14*(lab.b*0.6427876096865393 - lab.a*0.766044443118978)
    G = sqrt(ee^2+f^2)

    # Calculate hue/chroma
    C = 22.5*log(1+0.06*G)
    # FIXME: Clean this up (why is there no atan2d?)
    h = rad2deg(atan2(f,ee)) + 50
    while h > 360; h -= 360; end
    while h < 0;   h += 360; end

    DIN99d{T}(adj_L, C*cosd(h), C*sind(h))

end

cnvt{T}(::Type{DIN99d{T}}, c::Color3) = cnvt(DIN99d{T}, convert(XYZ{T}, c))

# Everything to DIN99o
# -------------------

function cnvt{T}(::Type{DIN99o{T}}, c::Lab)

    # We assume the adjustment parameters are always 1; the standard recommends
    # that they not be changed from these values.
    kch = 1
    ke = 1

    # Calculate DIN99o L (logarithmic compression)
    l99 = 303.67/ke*log(1+0.0039*c.l)

    # Temporary value for redness and yellowness
    # including rotation by 26°
    # eo = c.a*cosd(26) + c.b*sind(26)
    eo = c.a*0.898794046299167 + c.b*0.4383711467890774
    # compression along the yellowness (blue-yellow) axis
    # fo = 0.83 * (c.b*cosd(26) - c.a*sind(26))
    fo = 0.83 * (c.b*0.898794046299167 - c.a*0.4383711467890774)

    # Temporary value for chroma
    go = sqrt(eo^2 + fo^2)
    ho = atan2(fo,eo)
    # rotation of the color space by 26°
    h  = rad2deg(ho) + 26

    # DIN99o chroma (logarithmic compression)
    cc = 23.0*log(1+0.075*go)/(kch*ke)

    # DIN99o chromaticities
    a99, b99 = cc*cosd(h), cc*sind(h)

    DIN99o{T}(l99, a99, b99)

end

cnvt{T}(::Type{DIN99o{T}}, c::Color3) = cnvt(DIN99o{T}, convert(Lab{T}, c))

# Everything to LMS
# -----------------

# Chromatic adaptation from CIECAM97s
const CAT97s = [ 0.8562  0.3372 -0.1934
                -0.8360  1.8327  0.0033
                 0.0357 -0.0469  1.0112 ]

const CAT97s_INV = inv(CAT97s)

# Chromatic adaptation from CIECAM02
const CAT02 = [ 0.7328 0.4296 -0.1624
               -0.7036 1.6975  0.0061
                0.0030 0.0136  0.9834 ]

const CAT02_INV = inv(CAT02)

function cnvt{T}(::Type{LMS{T}}, c::XYZ)
    @mul3x3 LMS{T} CAT02 c.x c.y c.z
end

cnvt{T}(::Type{LMS{T}}, c::Color3) = cnvt(LMS{T}, convert(XYZ{T}, c))

# Everything to YIQ
# -----------------

correct_gamut{T}(c::YIQ{T}) = YIQ{T}(clamp(c.y, zero(T), one(T)),
                                     clamp(c.i, convert(T,-0.5957), convert(T,0.5957)),
                                     clamp(c.q, convert(T,-0.5226), convert(T,0.5226)))

function cnvt{T}(::Type{YIQ{T}}, c::AbstractRGB)
    rgb = correct_gamut(c)
    YIQ{T}(0.299*red(rgb)+0.587*green(rgb)+0.114*blue(rgb),
           0.595716*red(rgb)-0.274453*green(rgb)-0.321263*blue(rgb),
           0.211456*red(rgb)-0.522591*green(rgb)+0.311135*blue(rgb))
end

cnvt{T}(::Type{YIQ{T}}, c::Color3) = cnvt(YIQ{T}, convert(RGB{T}, c))

# Everything to YCbCr
# -------------------

correct_gamut{T}(c::YCbCr{T}) = YCbCr{T}(clamp(c.y, convert(T,16), convert(T,235)),
                                         clamp(c.cb, convert(T,16), convert(T,240)),
                                         clamp(c.cr, convert(T,16), convert(T,240)))

function cnvt{T}(::Type{YCbCr{T}}, c::AbstractRGB)
    rgb = correct_gamut(c)
    YCbCr{T}(16+65.481*red(rgb)+128.553*green(rgb)+24.966*blue(rgb),
             128-37.797*red(rgb)-74.203*green(rgb)+112*blue(rgb),
             128+112*red(rgb)-93.786*green(rgb)-18.214*blue(rgb))
end

cnvt{T}(::Type{YCbCr{T}}, c::Color3) = cnvt(YCbCr{T}, convert(RGB{T}, c))

# Everything to RGB24
# -------------------

convert(::Type{RGB24}, c::RGB24) = c
convert(::Type{RGB24}, c::AbstractRGB{UFixed8}) = RGB24(red(c), green(c), blue(c))
convert(::Type{RGB24}, c::AbstractRGB) = RGB24((round(UInt8, 255*red(c))%UInt32)<<16 +
                                               (round(UInt8, 255*green(c))%UInt32)<<8 +
                                                round(UInt8, 255*blue(c))%UInt32)

convert(::Type{RGB24}, c::Color) = convert(RGB24, convert(RGB{UFixed8}, c))

# To ARGB32
# ----------------

convert(::Type{ARGB32}, c::ARGB32) = c
convert{CV<:AbstractRGB{UFixed8}}(::Type{ARGB32}, c::TransparentColor{CV}) =
    ARGB32(red(c), green(c), blue(c), alpha(c))
convert(::Type{ARGB32}, c::TransparentColor) =
    ARGB32(convert(RGB24, c).color | (round(UInt8, 255*alpha(c))%UInt32)<<24)
convert(::Type{ARGB32}, c::Color) = ARGB32(convert(RGB24, c).color | 0xff000000)
convert(::Type{ARGB32}, c::Color, alpha) = ARGB32(convert(RGB24, c).color | round(UInt32, 255*alpha)<<24)

# To Gray
# -------

# Rec 601 luma conversion
if VERSION < v"0.4.0-dev+1827"
    const unsafe_trunc = itrunc
else
    const unsafe_trunc = Base.unsafe_trunc
end

convert{T<:UFixed}(::Type{Gray{T}}, x::AbstractRGB{T}) = (TU = FixedPointNumbers.rawtype(T); Gray{T}(T(round(TU, min(typemax(TU), 0.299f0*reinterpret(x.r) + 0.587f0*reinterpret(x.g) + 0.114f0*reinterpret(x.b))), 0)))
convert{T}(::Type{Gray{T}}, x::AbstractRGB) = convert(Gray{T}, 0.299f0*x.r + 0.587f0*x.g + 0.114f0*x.b)


# FIXME: these tests regularly triggers illegal memory accesses
#        after having moved to distributed test execution,
#        regardless of the memory pool or system.

false && @testset "threaded execution" begin
    function kernel(a, tid, id)
        a[1] = tid
        a[2] = id
        return
    end

    test_lock = ReentrantLock()
    Threads.@threads for id in 1:10
        da = CuArray{Int}(undef, 2)
        tid = Threads.threadid()
        @cuda kernel(da, tid, id)

        a = Array(da)
        lock(test_lock) do
            @test a == [tid, id]
        end
    end
end

false && @testset "threaded arrays" begin
  test_lock = ReentrantLock()
  Threads.@threads for i in 1:Threads.nthreads()*100
    # uses libraries (rand, gemm) to test library handles
    # allocates and uses unsafe_free to cover the allocator
    da = CUDA.rand(64, 64)
    db = CUDA.rand(64, 64)
    yield()
    dc = da * db
    yield()

    # @testset is not thread safe
    a = Array(da)
    b = Array(db)
    c = Array(dc)
    lock(test_lock) do
      @test c ≈ a * b
    end

    yield()
    CUDA.unsafe_free!(da)
    CUDA.unsafe_free!(db)
  end
end


#* Wrapper of GameInterface
# redirects AlphaZero.MCTS calls to MuNetwork
#TODO Uptade description
"""
    MuGameEnvWrapper

Wraper aroud GameEnv that redirects GI calls
- `game::GI.AbstractGameEnv`  GameEnv that is wrapped
- `dynamics_oracle`           g(sᵏ⁻¹,aᵏ) -> (rᵏ,sᵏ) that returns reward and next state
- `curstate`                  current (hidden) state of game
- `isrootstate::Bool`         whether or not curstate is the root state of MCTS tree
- `white_playing::Bool`       whether or not white is currently playing
- `lastwr::Float64`           last white reward obtained from dynamics_oracle
"""
mutable struct MuGameEnvWrapper{State, R, RRef<:Base.RefValue} <: GI.AbstractGameEnv
  game :: GI.AbstractGameEnv
  recurrent_inference :: R # f(g(s,a)) -> p, v, s⁺¹, r
  curstate :: State
  isrootstate :: Bool
  white_playing :: Bool # MuWrapper changes player each round
  lastwr :: Float32
  result_ref :: RRef
end

GI.game_terminated(game::MuGameEnvWrapper) = false
# GI.game_terminated(game::MuGameEnvWrapper) = GI.game_terminated(GI.init(GI.spec(game.game),game.curstate))

GI.current_state(game::MuGameEnvWrapper) = game.curstate

GI.available_actions(game::MuGameEnvWrapper) = game.isrootstate ? GI.available_actions(game.game) : GI.actions(GI.spec(game.game))

GI.white_playing(game::MuGameEnvWrapper) = game.white_playing

function GI.play!(game::MuGameEnvWrapper, action)
  # dynamics returns reward from the perspective of last player (rᵂ , sᴮ) <- g(sᵂ, aᵂ)
  # (game.lastwr, game.curstate) = game.dynamics_oracle((game.curstate, action))
  game.result_ref[] = game.recurrent_inference((game.curstate, action))
  (game.curstate, game.lastwr) = game.result_ref[][3:4]
  if GI.two_players(GI.spec(game.game))
    game.white_playing || (game.lastwr = -game.lastwr) # then it is changed if black was playing and saved as lastwr
    game.white_playing = !game.white_playing # player change
  end
  game.isrootstate = false
end

GI.white_reward(game::MuGameEnvWrapper) = game.lastwr #vulnerable when call white_reward() before play!()

function GI.clone(game::MuGameEnvWrapper)
  MuGameEnvWrapper(
    GI.clone(game.game),
    game.recurrent_inference,
    game.curstate,
    game.isrootstate,
    game.white_playing,
    game.lastwr,
    game.result_ref)
end

# cache oracle (nn) results, so it can be quickly obtained without re-running network
struct CachedOracle{O, KeyType, ValueType}
  oracle :: O
  lookuptable :: Dict{KeyType,ValueType}
end

# oracle, typeof(State), typeof(Action), typeof(Reward) - used with dynamics
function CachedOracle(oracle, ValType, S, A=Int, R=Float32)
  return CachedOracle(
    oracle,
    Dict{Tuple{S,A}, ValType}()) # (sᵏ⁻¹,aᵏ) => (rᵏ,sᵏ)
end

function (oracle::CachedOracle)(arg)
  if haskey(oracle.lookuptable, arg)
    answer = oracle.lookuptable[arg]
  else
    answer = oracle.oracle(arg)
    oracle.lookuptable[arg] = answer
  end
  return answer
end


function shufflevector_instrs(W, T, I, two_operands)
    typ = LLVM_TYPES[T]
    vtyp1 = "<$W x $typ>"
    M = length(I)
    vtyp3 = "<$M x i32>"
    vtypr = "<$M x $typ>"
    mask = '<' * join(map(x->string("i32 ", x), I), ", ") * '>'
    v2 = two_operands ? "%1" : "undef"
    M, """
        %res = shufflevector $vtyp1 %0, $vtyp1 $v2, $vtyp3 $mask
        ret $vtypr %res
    """
end
@generated function shufflevector(v1::Vec{W,T}, v2::Vec{W,T}, ::Val{I}) where {W,T,I}
    M, instrs = shufflevector_instrs(W, T, I, true)
    quote
        $(Expr(:meta, :inline))
        Vec(llvmcall($instrs, _Vec{$M,$T}, Tuple{_Vec{$W,$T}, _Vec{$W,$T}}, data(v1), data(v2)))
    end
end
@generated function shufflevector(v1::Vec{W,T}, ::Val{I}) where {W,T,I}
    M, instrs = shufflevector_instrs(W, T, I, false)
    quote
        $(Expr(:meta, :inline))
        Vec(llvmcall($instrs, _Vec{$M,$T}, Tuple{_Vec{$W,$T}}, data(v1)))
    end
end
@generated function vresize(::Union{StaticInt{W},Val{W}}, v::Vec{L,T}) where {W,L,T}
    typ = LLVM_TYPES[T]
    mask = '<' * join(map(x->string("i32 ", x ≥ L ? L : x), 0:W-1), ", ") * '>'
    instrs = """
        %res = shufflevector <$L x $typ> %0, <$L x $typ> undef, <$W x i32> $mask
        ret <$W x $typ> %res
    """
    quote
        $(Expr(:meta, :inline))
        Vec(llvmcall($instrs, _Vec{$W,$T}, Tuple{_Vec{$L,$T}}, data(v)))
    end
end
@generated function vresize(::Union{StaticInt{W},Val{W}}, v::T) where {W,T<:NativeTypes}
    typ = LLVM_TYPES[T]
    vtyp = vtype(W, typ)
    instrs = """
        %ie = insertelement $vtyp undef, $typ %0, i32 0
        ret $vtyp %ie
    """
    quote
        $(Expr(:meta, :inline))
        Vec(llvmcall($instrs, _Vec{$W,$T}, Tuple{$T}, v))
    end
end

@generated function shufflevector(i::MM{W,X}, ::Val{I}) where {W,X,I}
    allincr = true
    L = length(I)
    for l ∈ 2:L
        allincr &= (I[l] == I[l-1] + 1)
    end
    allincr || return Expr(:block, Expr(:meta,:inline), :(shufflevector(Vec(i), Val{$I}())))
    Expr(:block, Expr(:meta,:inline), :(MM{$L,$X}( extractelement(i, $(first(I))) )))
end


# functions for phenotype

"""
    assign_phenotype!(group::PTGroup, gen=nothing; male=false, female=true, year=nothing, repeated=true, limit=nothing, idlist=1:group.pop.maxAnimal)

Put a phenotype to animals in generation `gen` in `group`, born in `year` (`nothing` to ignore the birth year).
If `gen` is `nothing` or a negative integer, this function gives all animals in this group.
By default, only females have phenotypes (`male=false` and `female=true`).
Also, an animal can have multiple (repeated records) due to `repeated=true`.
To limit the number of repeated reacord, use `limit` (default = `nothing`, no limitation).
Given `idlist`, only the animals in this list will have the phenotype; by default, all animals will have the phenotype.
The list has to have been sorted.
"""
function assign_phenotype!(group::PTGroup; gen::Union{Nothing,Int}=nothing, male::Bool=false, female::Bool=true, year::Union{Nothing,Int}=nothing,
   repeated::Bool=true, verbose::Bool=false, limit::Union{Nothing,Int}=nothing, idlist::Union{UnitRange{Int},Vector{Int}}=1:group.pop.maxAnimal)
   pop = group.pop
   par = group.pop.par
   if isnothing(gen)
      gencode = -1
   else
      gencode = gen
   end
   if gencode<0
      seqlist = collect(1:group.n)
      mingen = minimum(group.generation)
      maxgen = maximum(group.generation)
      cglist = Dict{Int,Int}()
      for i in mingen:maxgen
         pop.maxCG = pop.maxCG + 1
         cglist[i] = pop.maxCG
      end
   else
      seqlist = (1:group.n)[group.generation .== gencode]
      if length(seqlist)<1
         error("no such generation in this group")
      end
      pop.maxCG = pop.maxCG + 1
      cglist = Dict{Int,Int}(gen => pop.maxCG)
   end

   if !isnothing(year)
      yearlist = pop.df[group.id[seqlist],:year]
      seqlist = seqlist[yearlist .== year]
   end
   if verbose
      if length(seqlist)<1
         @warn "no animals will be phenotyped this time."
      end
   end

   _assign_phenotype!(group, seqlist, cglist, male, female, repeated, limit, idlist=idlist)

   return nothing
end

function assign_phenotype!(groups::Vector{PTGroup}; gen::Union{Nothing,Int}=nothing, male::Bool=false, female::Bool=true, year::Union{Nothing,Int}=nothing,
   repeated::Bool=true, verbose::Bool=false, limit::Union{Nothing,Int}=nothing, idlist::Union{UnitRange{Int},Vector{Int}}=1:groups[1].pop.maxAnimal)
   for group in groups
      assign_phenotype!(group, gen=gen, male=male, female=female, year=year, repeated=repeated, verbose=verbose, limit=limit, idlist=idlist)
   end
   return nothing
end

function _assign_phenotype!(group::PTGroup, seqlist::Vector{Int}, cglist::Dict{Int,Int}, male::Bool, female::Bool,
   repeated::Bool, limit::Union{Nothing,Int}; idlist::Union{UnitRange{Int},Vector{Int}}=1:group.pop.maxAnimal)
   pop = group.pop
   par = group.pop.par

   # some CG would not be assigned if repeated=false
   var_a = get_var_poly(par)
   var_g = get_var_qtl(par)
   var_pe = get_var_pe(par)
   var_e = get_var_error(par)
   sd_pe = sqrt(var_pe)
   sd_e = sqrt(var_e)
   @inbounds for seq in seqlist
      id = group.id[seq]
      if !(male && pop.df[id,:male]) && !(female && !pop.df[id,:male]) && group.pop.df[id,:alive]
         # out of condition
         continue
      end
      if length(searchsorted(idlist,id))<1
         # not on the list
         continue
      end
      gen = group.generation[seq]
      cg = cglist[gen]
      animal = pop.animal[id]
      nrec = length(animal.y)
      if isnothing(limit)
         nlim = nrec+1
      else
         nlim = limit
      end
      if (nrec==0 || (nrec>=1 && repeated)) && nrec<=nlim
         tbv = pop.df[id,:tbv]
         if nrec==0
            this_pe = sd_pe*randn()
         else
            this_pe = animal.pe[1]
         end
         this_e = sd_e*randn()
         this_y = par.mu + tbv + this_pe + this_e
         push!(animal.cg, cg)
         push!(animal.y, this_y)
         push!(animal.pe, this_pe)
         push!(animal.e, this_e)
         s = pop.df[id,:sire]
         d = pop.df[id,:dam]
         if s>0
            pop.df[s,:nrecprog] = pop.df[s,:nrecprog] + 1
         end
         if d>0
            pop.df[d,:nrecprog] = pop.df[d,:nrecprog] + 1
         end
         pop.df[id,:nrec] = length(animal.y)
         pop.df[id,:firsty] = animal.y[1]
         pop.df[id,:lasty] = animal.y[end]
         pop.df[id,:avgy] = mean(animal.y)
      end
   end

   return nothing
end


mutable struct Region
    style::Int32
    triclinic::Int32
    interior::Int32                     # 1 for interior, 0 for exterior
    scaleflag::Int32                    # 1 for lattice, 0 for box
    bboxflag::Int32                # 1 if bounding box is computable
    varshape::Int32                # 1 if region shape changes over time
    dynamic::Int32                 # 1 if position/orient changes over time
    moveflag::Int32
    rotateflag::Int32    # 1 if position/orientation changes
    openflag::Int32                # 1 if any face is open

    r::Vector{Float64}                   # distance between particle & surf, r > 0.0
    delx::Vector{Float64}
    dely::Vector{Float64}
    delz::Vector{Float64}    # vector from surface pt to particle
    radius::Vector{Float64}              # curvature of region at contact point
    iwall::Vector{Int32}                # unique id of wall for storing shear history
    openfaces::Vector{Int32};#[6];           # flags for which faces are open
    tri::Vector{Int32}#[12*3];    # 3 corner pts of 12 triangles (2 per face)
    theta::Float64 # orientation
    rprev::Float64 # speed of time-dependent radius, if applicable

    face::Array{Float64,2}#[6*3];   # unit normals of 6 box faces
    corners::Array{Float64,2}#[8*3];                     # 8 corner points
    faces::Array{Float64,2};#[6*4*3];   # 4 corner pts of 6 prism faces
    boxhi::Vector{Float64}#[3];   # orthogonal box global bounds
    boxlo::Vector{Float64}#[3];   # orthogonal box global bounds
    boxtilt::Vector{Float64}#[3]; # 3 tilt factors
    clo::Vector{Float64}#[3]; # opposite corners of prism
    chi::Vector{Float64}#[3]; # opposite corners of prism
    scale::Vector{Float64}#[3];
    extent_lo::Vector{Float64}#[3];
    extent_hi::Vector{Float64}#[3];
    a::Vector{Float64}#[3];  # edge vectors of region
    b::Vector{Float64}#[3];  # edge vectors of region
    c::Vector{Float64}#[3];  # edge vectors of region
    h::Vector{Float64}#[6];
    h_inv::Vector{Float64}#[6];
    dx::Vector{Float64}#[3];    # current displacement
    v::Vector{Float64}#[3];                 # translational velocity
    rpoint::Vector{Float64}#[3];            # current origin of rotation axis
    omega::Vector{Float64}#[3];             # angular velocity
    xcenter::Vector{Float64}#[3];    # translated/rotated center of cylinder/sphere (only used if varshape)
    prev::Vector{Float64}#[5];       # stores displacement (X3), angle and if

    Region() = new()
end

function initregion(boxhi, boxtilt=[0.0; 0.0; 0.0])

    reg = Region();

    reg.boxlo = [0.0; 0.0; 0.0];
    reg.boxhi = boxhi;
    reg.boxtilt = boxtilt;

    if (reg.boxtilt[1] == 0.0 && reg.boxtilt[2] == 0.0 && reg.boxtilt[3] == 0.0)
        reg.triclinic = 0;
    else
        reg.triclinic = 1;
    end

    return reg
end

function printout(reg::Region)
    display("boxlo: "); display(reg.boxlo);
    display("boxhi: "); display(reg.boxhi);
    display("boxtilt: "); display(reg.boxtilt);
    display("clo: "); display(reg.clo);
    display("chi: "); display(reg.chi);
    display("extent_lo: "); display(reg.extent_lo);
    display("extent_hi: "); display(reg.extent_hi);
    display("a: "); display(reg.a);
    display("b: "); display(reg.b);
    display("c: "); display(reg.c);
    display("scale: "); display(reg.scale);
    display("h: "); display(reg.h);
    display("h_inv: "); display(reg.h_inv);
    display("dx: "); display(reg.dx);
    display("v: "); display(reg.v);
    display("rpoint: "); display(reg.rpoint);
    display("omega: "); display(reg.omega);
    display("xcenter: "); display(reg.xcenter);
    display("prev: "); display(reg.prev);
end


function plotdist(gisregion::String, minturbine=1000, maxpopdens=150; args...)
    filename = "regionalwindGIS_$(gisregion)_minturbine=$(minturbine)_maxpopdens=$(maxpopdens).csv"
    df = DataFrame(CSV.File(in_datafolder("output", filename)))
    plotdist(df; title=gisregion, args...)
end

function plotdist(gisregions::Vector{String}, minturbine=1000, maxpopdens=150; args...)
    dfs = [DataFrame(CSV.File(in_datafolder("output", "regionalwindGIS_$(gisregion)_minturbine=$(minturbine)_maxpopdens=$(maxpopdens).csv")))
            for gisregion in gisregions]
    for (i, gisregion) in enumerate(gisregions)
        dfs[i][:,:gisregion] .= i
    end
    df = vcat(dfs...)
    plotdist(df; title=join(gisregions, ", "), args...)
end

function plotdist(df::DataFrame; mincapac=0, area_mult=1.0, scatterplot=0, bins=100,
                    variable=:exploit_tot, alpha=1, markersize=2, line=:mean,
                    scale=1, xmax=30, title="", legend=true)
    df = df[df[!,:capac].>=mincapac, :]
    plotly()
    blank = "q"
    title = replace(title, "GADM" => "")
    title = replace(title, r"\d" => "")
    if scatterplot == 0
        p = histogram(df[!,variable], bins=range(0, xmax+0.5, length=bins),
            xlims=(0,Inf), ylims=(0,Inf), title=title,
            xlabel="Exploited area per municipality/county [%]",
            ylabel="Number of municipalities/counties
$blank",
            tickfont=14*scale, guidefont=14*scale, left_margin=30px,
            size=scale.*(800,550), legend=false)  # title=variable
        line_x = line == :mean ? mean(df[!,variable]) : 100*area_mult
        line != :none && plot!(line_x*[1, 1], collect(ylims(p)), line=(3, :dash))
        display(p)
        return df
    end
    # Filter out NaNs so scatter plots don't get messed up.
    # https://github.com/JuliaPlots/Plots.jl/issues/3258
    df = df[.!(isnan.(df[!,:capac]) .| isnan.(df[!,:class]) .| isnan.(df[!,variable])), :]
    if scatterplot == 1
        p = scatter(df[!,variable], df[!,:capac],
            xlabel="Exploited area per municipality/county [%]",
            ylabel="MW", title=title,
            size=scale.*(800,550), legend=false, colorbar=true,
            markersize=scale*df[!,:class].^(1+markersize/10),
            marker_z=df[!,:masked], color=:watermelon, alpha=alpha,
            hover=df[!,:region].*"
".*string.(df[!,:capac]).*" MW".*
            "
exploited = ".*string.(round.(df[!,variable],digits=1)).*"%".*
            "
class = ".*string.(df[!,:class]).*"
masked = ".*string.(df[!,:masked]))
        display(p)
    elseif scatterplot == 2
        p = scatter(df[!,variable], df[!,:class],
            xlabel="Exploited area per municipality/county [%]", xlims=(0, xmax+0.5),
            ylabel="Mean wind class",
            size=scale.*(800,550), legend=false, colorbar=true,
            markersize=scale*df[!,:capac].^(markersize/7),
            markerstrokecolor=RGBA(0,0,0,.2), markerstrokewidth=1,
            tickfont=14*scale, guidefont=14*scale, left_margin=30px,
            marker_z=df[!,:masked], color=:plasma, alpha=alpha,
            hover=df[!,:region].*"
".*string.(df[!,:capac]).*" MW".*
            "
exploited = ".*string.(round.(df[!,variable],digits=1)).*"%".*
            "
class = ".*string.(df[!,:class]).*"
masked = ".*string.(df[!,:masked]))
        display(p)
    elseif scatterplot == 3
        p = scatter(df[!,variable], df[!,:class],
            xlabel="Exploited area per municipality/county [%]", xlims=(0, xmax+0.5),
            ylabel="Mean wind class", title=title,
            size=scale.*(800,550), legend=false, colorbar=true,
            markersize=markersize*scale,
            markerstrokecolor=RGBA(0,0,0,.2), markerstrokewidth=0,
            tickfont=14*scale, guidefont=14*scale, left_margin=30px,
            color=RGB(0,0,0), alpha=alpha,
            hover=df[!,:region].*"
".*string.(df[!,:capac]).*" MW".*
            "
exploited = ".*string.(round.(df[!,variable],digits=1)).*"%".*
            "
class = ".*string.(df[!,:class]).*"
masked = ".*string.(df[!,:masked]))
        display(p)
    elseif scatterplot == 4
        cc = [RGB(0,0,1), RGB(0,0,0), RGB(0,1,0), RGB(1,0,0)]
        p = scatter(df[!,variable], df[!,:windspeed],
            xlabel="Exploited area per municipality/county [%]",
            ylabel="Mean wind speed [m/s]", xlims=(0, xmax+0.5), ylims=(3.5, 11), title=title,
            size=scale.*(950,550), label="", colorbar=true,
            markersize=markersize*scale,
            markerstrokecolor=RGBA(0,0,0,.2), markerstrokewidth=0,
            tickfont=14*scale, guidefont=14*scale, left_margin=30px,
            color=cc[df[!,:gisregion]], alpha=alpha,
            hover=df[!,:region].*"
".*string.(df[!,:capac]).*" MW".*
            "
exploited = ".*string.(round.(df[!,variable],digits=1)).*"%".*
            "
class = ".*string.(df[!,:class]).*"
masked = ".*string.(df[!,:masked]))
        qq = [-1 -1 -1 -1]
        scatter!(qq,qq,color=cc[[1 2 3 4]],alpha=alpha+0.1,legend=:outertopright,
            markerstrokewidth=0, legendfont=12*scale, label=permutedims(split(title, ", ")))
        display(p)
    elseif scatterplot == 5
        cc = [RGB(0,0,1), RGB(0,0,0), RGB(0,1,0), RGB(1,0,0)]
        p = scatter(df[!,variable], df[!,:windspeed],
            xlabel="Exploited area per municipality/county [%]",
            ylabel="Mean wind speed [m/s]", xlims=(0, xmax+0.5), ylims=(3.5, 11), title=title,
            size=scale.*(800+150*legend,550), label="", colorbar=false,
            markersize=scale*df[!,:capac].^(markersize/7),
            markerstrokecolor=RGBA(0,0,0,.2), markerstrokewidth=0,
            tickfont=14*scale, guidefont=14*scale, left_margin=30px,
            color=cc[df[!,:gisregion]], alpha=alpha,
            hover=df[!,:region].*"
".*string.(df[!,:capac]).*" MW".*
            "
exploited = ".*string.(round.(df[!,variable],digits=1)).*"%".*
            "
class = ".*string.(df[!,:class]).*"
masked = ".*string.(df[!,:masked]))
        qq = [-1 -1 -1 -1]
        legend && scatter!(qq,qq,color=cc[[1 2 3 4]],alpha=alpha+0.1,legend=:outertopright,
            markerstrokewidth=0, legendfont=12*scale, label=permutedims(split(title, ", ")))
        display(p)
    elseif scatterplot == 6
        cc = [RGB(0,0,1), RGB(0,0,0), RGB(0,1,0), RGB(1,0,0)]
        p = scatter(df[!,variable], max.(-1, log10.(df[!,:popdens])),
            xlabel="Exploited area per municipality/county [%]",
            ylabel="Population density [log10 persons/km2]", xlims=(0, xmax+0.5), ylims=(-1.1, Inf), title=title,
            size=scale.*(950,550), label="", colorbar=false,
            markersize=scale*df[!,:capac].^(markersize/7),
            markerstrokecolor=RGBA(0,0,0,.2), markerstrokewidth=0,
            tickfont=14*scale, guidefont=14*scale, left_margin=30px,
            color=cc[df[!,:gisregion]], alpha=alpha,
            hover=df[!,:region].*"
".*string.(df[!,:capac]).*" MW".*
            "
exploited = ".*string.(round.(df[!,variable],digits=1)).*"%".*
            "
class = ".*string.(df[!,:class]).*"
masked = ".*string.(df[!,:masked]))
        qq = [-1 -1 -1 -1]
        scatter!(qq,qq,color=cc[[1 2 3 4]],alpha=alpha+0.1,legend=:outertopright,
            markerstrokewidth=0, legendfont=12*scale, label=permutedims(split(title, ", ")))
        display(p)
    end
    return df
end

# function scatterplots_model(gisregion, type=[:total]; showlines=false)
#     dfg = DataFrame(CSV.File(in_datafolder("output", "regionalwindGIS_$gisregion.csv")))
#     dfm = DataFrame(CSV.File(in_datafolder("output", "windresults_$gisregion.csv")))
#     dfm[!,:mcap] = vec(sum(Array(dfm[:, [:cap1, :cap2, :cap3, :cap4, :cap5]]), dims=2))
#     dfm[!,:moffcap] = vec(sum(Array(dfm[:, [:offcap1, :offcap2, :offcap3, :offcap4, :offcap5]]), dims=2))
#     dfm[!,:melec] = vec(sum(Array(dfm[:, [:elec1, :elec2, :elec3, :elec4, :elec5]]), dims=2))
#     dfm[!,:moffelec] = vec(sum(Array(dfm[:, [:offelec1, :offelec2, :offelec3, :offelec4, :offelec5]]), dims=2))
#     dfm[:mclass] = (1*dfm[:cap1] + 2*dfm[:cap2] + 3*dfm[:cap3] + 4*dfm[:cap4] + 5*dfm[:cap5]) ./ dfm[:mcap]
#     dfm[:mclass] = replace(round.(dfm[:mclass], digits=2), NaN => missing)
#     dfm[:moffclass] = (1*dfm[:offcap1] + 2*dfm[:offcap2] + 3*dfm[:offcap3] + 4*dfm[:offcap4] + 5*dfm[:offcap5]) ./ dfm[:moffcap]
#     dfm[:moffclass] = replace(round.(dfm[:moffclass], digits=2), NaN => missing)

#     distancevars = matread(in_datafolder("output", "distances_$gisregion.mat"))
#     connectedregions = findall(distancevars["connected"] .| distancevars["connectedoffshore"])
#     connected = [c for c in connectedregions if c[1] < c[2]]

#     df = innerjoin(dfg, dfm, on=:region)
#     select!(df, [:region, :capac, :offcapac, :elec2018, :class, :mcap, :moffcap,
#                     :melec, :moffelec, :mclass, :moffclass, :exploit_tot, :masked])
#     plotly()
#     println(type)
#     if :onshore in type
#         s = scatter(df[:capac], df[:mcap], markersize=df[:mclass]*2, title="Onshore capacity",
#             hover=df[:region].*" class ".*string.(df[:mclass]), legend=false, colorbar=true,
#             xlabel="GW (real)", ylabel="GW (model)", size=(800,550))
#         display(s)
#     elseif :offshore in type
#         s = scatter(df[:offcapac], df[:moffcap], markersize=df[:moffclass]*2, title="Offshore capacity",
#             hover=df[:region].*" class ".*string.(df[:moffclass]), legend=false, colorbar=true,
#             xlabel="GW (real)", ylabel="GW (model)", size=(800,550))
#         display(s)
#     elseif :total in type
#         x = df[:capac] + df[:offcapac]
#         y = df[:mcap] + df[:moffcap]
#         s = plot()
#         xx = [x[c[d]] for d in 1:2, c in connected]
#         yy = [y[c[d]] for d in 1:2, c in connected]
#         showlines && plot!(xx, yy, c=WindSites.RGB(.85,.85,.85), legend=:none)
#         scatter!(x, y, markersize=df[:class]*2, title="Total capacity", c=1,
#             hover=df[:region].*"
real onshore = ".*string.(df[:capac]).*" MW".*
#             "
real offshore = ".*string.(df[:offcapac]).*" MW".*
#             "
exploited onshore = ".*string.(df[:exploit_tot]).*"%".*
#             "
class = ".*string.(df[:class]).*
#             "
m_onshore = ".*string.(df[:mcap]).*" GW
m_offshore = ".*string.(df[:moffcap]).*
#             " GW
m_class = ".*string.(df[:mclass]).*
#             "
m_offclass = ".*string.(df[:moffclass]).*"
masked onshore = ".*string.(df[:masked]),
#             legend=false, colorbar=true, marker_z=df[:masked], color=:watermelon,
#             xlabel="GW (real)", ylabel="GW (model)", size=(800,550))
#         display(s)
#     elseif :elec in type
#         s = scatter(df[:elec2018], df[:melec], markersize=df[:mclass], xlabel="GWh (real)", ylabel="GWh (model)")
#         display(s)
#     end
#     df
#  end


__precompile__()

module POMDPToolbox

using POMDPs

import POMDPs: Updater, update, initialize_belief, pdf, mode, updater
import POMDPs: Simulator, simulate
import POMDPs: action, value, solve
import POMDPs: actions, action_index, state_index, obs_index, iterator, sampletype, states, n_actions, n_states, observations, n_observations, discount, isterminal
import POMDPs: generate_sr, initial_state
import POMDPs: implemented
import Base: rand, rand!, mean, ==

using ProgressMeter
using StatsBase
using DataFrames

# export commons
export
    update,
    initialize_belief,
    create_belief,
    pdf,
    updater,
    simulate,
    action,
    solve

# only include the things that are working
# export things immediately above the file they are contained in

# beliefs
export
    VoidUpdater
include("beliefs/void.jl")

export
    DiscreteBelief,
    DiscreteUpdater,
    uniform_belief,
    product
include("beliefs/discrete.jl")

export
    PreviousObservationUpdater,
    FastPreviousObservationUpdater,
    PrimedPreviousObservationUpdater
include("beliefs/previous_observation.jl")

export
    KMarkovUpdater

include("beliefs/k_previous_observations.jl")

export
    Particle,
    ParticleBelief,
    ParticleDistribution,
    SIRParticleUpdater,
    mode
include("beliefs/particle.jl")

# convenience
include("convenience/implementations.jl")

# policies
export
    AlphaVectorPolicy,
    belief_vector,
    unnormalized_util
include("policies/alpha_vector.jl")

export
    VectorPolicy,
    VectorSolver,
    ValuePolicy
include("policies/vector.jl")

export
    RandomPolicy,
    RandomSolver
include("policies/random.jl")

export
    StochasticPolicy,
    UniformRandomPolicy,
    CategoricalTabularPolicy,
    EpsGreedyPolicy
include("policies/stochastic.jl")

export
    FunctionPolicy,
    FunctionSolver
include("policies/function.jl")

export
    PolicyWrapper,
    payload
include("policies/utility_wrapper.jl")

# simulators
export RolloutSimulator
include("simulators/rollout.jl")

export
    SimHistory,
    POMDPHistory,
    MDPHistory,
    AbstractPOMDPHistory,
    AbstractMDPHistory,
    HistoryIterator,
    eachstep,
    state_hist,
    action_hist,
    observation_hist,
    belief_hist,
    reward_hist,
    info_hist,
    ainfo_hist,
    uinfo_hist,
    exception,
    backtrace,
    undiscounted_reward,
    discounted_reward,
    n_steps,
    step_tuple
include("simulators/history.jl")

export sim
include("simulators/sim.jl")

export HistoryRecorder
include("simulators/history_recorder.jl")

export
    StepSimulator,
    stepthrough
include("simulators/stepthrough.jl")

export
    Sim,
    run,
    run_parallel,
    problem
include("simulators/parallel.jl")

# model tools
export
    generate_sri,
    generate_sori,
    action_info,
    solve_info,
    update_info
include("model/info.jl")

export uniform_state_distribution
include("model/initial.jl")

export
    ordered_states,
    ordered_actions,
    ordered_observations
include("model/ordered_spaces.jl")

export GenerativeBeliefMDP
include("model/generative_belief_mdp.jl")

export FullyObservablePOMDP
include("model/fully_observable_pomdp.jl")

export UnderlyingMDP
include("model/underlying_mdp.jl")

# tools for distributions
include("distributions/distributions_jl.jl")

export obs_weight
include("model/obs_weight.jl")

export
    weighted_iterator
include("distributions/weighted_iteration.jl")

export
    SparseCat
include("distributions/sparse_cat.jl")

export
    BoolDistribution
include("distributions/bool.jl")

# testing
export test_solver
include("testing/solver.jl")

export
    probability_check,
    obs_prob_consistency_check,
    trans_prob_consistency_check
include("testing/model.jl")

end # module


import PeaceFounder

using DemeNet: DemeSpec, Deme, Signer, Certificate, Profile
using PeaceVote: KeyChain, record, braid!, sync!, Vote, Proposal, load, proposals, attest, voters, AbstractChain
using Recruiters: register

### CleanUp ###

for dir in [homedir() * "/.demenet/"]
    isdir(dir) && rm(dir,recursive=true)
end

### Setting up the system ###
module Setup
include("setup.jl") # defines deme and systemconfig
include("serve.jl")
end

### Maintainer generates invites ###

invites = []

module Maintainer

import PeaceFounder

using DemeNet: Signer, Deme, DemeSpec, AbstractInitializer, init, config
using Recruiters: addtooken, ticket
import ..Setup

demespec = DemeSpec(Setup.uuid)
deme = Deme(demespec)
initializer = AbstractInitializer(deme)
pfconfig = config(initializer)

maintainer = Signer(deme,"maintainer")

import ..invites

tookens = [121233,234324,123133]

for tooken in tookens
    addtooken(pfconfig,deme,tooken,maintainer)  ### Perhaps I could just extend that with recruiters!
    invite = ticket(pfconfig,deme,tooken)
    ### One can send it, for example, over email
    push!(invites,invite)
end

end

### Registration with recruiters

for i in 1:3
    profile = Profile(Dict("uuid"=>i))
    invite = invites[i]
    register(invite,profile,account="account$i")
end

demespec = DemeSpec(Setup.uuid)
deme = Deme(demespec)
braidchain = AbstractChain(deme)

# Braiding

@sync for i in 1:3
    @async begin
        account = "account$i"
        keychain = KeyChain(deme,account)
        braid!(braidchain,keychain)
    end
end

# Proposing

keychain = KeyChain(deme,"account2")
proposal = Proposal("Found peace for a change?",["yes","no","maybe"])
cert = Certificate(proposal,keychain.member)
record(braidchain,cert)

sleep(1)
sync!(braidchain)

loadedledger = load(braidchain)
messages = attest(loadedledger,braidchain.deme.notary)
index = proposals(messages)[1]
proposal = messages[index]

# Now we can vote

for i in 1:3
    account = "account$i"
    keychain = KeyChain(deme,account)

    option = Vote(index,rand(1:length(proposal.document.options)))
    cert = Certificate(option,braidchain,keychain)
    record(braidchain,cert)
end

# Now let's count
sleep(1)
sync!(braidchain)

@show tally = count(index,braidchain)


candelete(m::MosekModel,ref::MOI.VariableIndex) = isvalid(m,ref) && m.x_numxc[ref2id(ref)] == 0
isvalid(m::MosekModel, ref::MOI.VariableIndex) = allocated(m.x_block,ref2id(ref))

MOI.addvariables!(m::MosekModel, N :: I) where { I <: Integer } = MOI.addvariables!(m,UInt(N))
function MOI.addvariables!(m::MosekModel, N :: UInt)
    ids = [ allocatevariable(m,1) for i in 1:N ]

    m.publicnumvar += N

    idxs = Vector{Int}(undef,N)
    for i in 1:Int(N)
        getindexes(m.x_block,ids[i],idxs,i)
    end

    bnd = zeros(Float64,N)
    putvarboundlist(m.task,
                    convert(Vector{Int32}, idxs),
                    fill(MSK_BK_FR,N),
                    bnd,bnd)

    # if DEBUG
    if DEBUG
        for i in idxs
            putvarname(m.task,Int32(i),"x$i")
        end
    end

    [ id2vref(id) for id in ids]
end

function MOI.addvariable!(m::MosekModel)
    N = 1
    id = allocatevariable(m,1)
    m.publicnumvar += N
    bnd = Vector{Float64}(undef,N)
    subj = convert(Vector{Int32}, getindexes(m.x_block, id))
    putvarboundlist(m.task,
                    subj,
                    fill(MSK_BK_FR,N),
                    bnd,bnd)
    # if DEBUG
    if DEBUG
        for i in subj
            putvarname(m.task,Int32(i),"x$i")
        end
    end

    id2vref(id)
end

function Base.delete!(m::MosekModel, refs::Vector{MOI.VariableIndex})
    ids = Int[ ref2id(ref) for ref in refs ]

    if ! all(id -> m.x_numxc[id] == 0, ids)
        error("Cannot delete a variable while a bound constraint is defined on it")
    elseif ! all(ref -> candelete(m,ref),refs)
        throw(CannotDelete())
    else
        sizes = Int[blocksize(m.x_block,id) for id in ids]
        N = sum(sizes)
        m.publicnumvar -= length(refs)
        indexes = Array{Int}(undef,N)
        offset = 1
        for i in 1:length(ids)
            getindexes(m.x_block,ids[i],indexes,offset)
            offset += sizes[i]
        end

        # clear all non-zeros in columns
        putacollist(m.task,
                    indexes,
                    zeros(Int64,N),
                    zeros(Int64,N),
                    Int32[],
                    Float64[])
        putclist(m.task,indexes,zeros(Int64,N))
        # clear bounds
        bnd = zeros(Float64,N)
        putvarboundlist(m.task,
                        indexes,
                        fill(MSK_BK_FX,N),
                        bnd,bnd)

        if DEBUG
            for i in ids
                putvarname(m.task,Int32(ids),"deleted$i")
            end
        end

        for i in 1:length(ids)
            deleteblock(m.x_block,ids[i])
        end
    end
end

function Base.delete!(m::MosekModel, ref::MOI.VariableIndex)
    if m.x_numxc[ref2id(ref)] != 0
        error("Cannot delete a variable while a bound constraint is defined on it")
    elseif ! candelete(m,ref)
        throw(CannotDelete())
    else
        id = ref2id(ref)

        m.publicnumvar -= 1

        indexes = convert(Array{Int32,1},getindexes(m.x_block,id))
        N = blocksize(m.x_block,id)

        # clear all non-zeros in columns
        for i in indexes
            putcj(m.task,i,0.0)
        end
        putacollist(m.task,
                    indexes,
                    zeros(Int64,N),
                    zeros(Int64,N),
                    Int32[],
                    Float64[])
        # clear bounds
        bnd = zeros(Float64,N)
        putvarboundlist(m.task,
                        indexes,
                        fill(MSK_BK_FX,N),
                        bnd,bnd)
        if DEBUG
            for i in indexes
                putvarname(m.task,Int32(i),"deleted$i")
            end
        end

        deleteblock(m.x_block,id)
    end
end

###############################################################################
## ATTRIBUTES


@inline function initialize_states!(
    baroclinic::HBModel{C},
    barotropic::SWModel{C},
    model_bc,
    model_bt,
    state_bc,
    state_bt,
) where {C <: Coupled}
    model_bc.state_auxiliary.ΔGᵘ .= -0

    return nothing
end

@inline function tendency_from_slow_to_fast!(
    baroclinic::HBModel{C},
    barotropic::SWModel{C},
    model_bc,
    model_bt,
    state_bc,
    state_bt,
    forcing_tendency,
) where {C <: Coupled}
    FT = eltype(state_bc)
    info = basic_grid_info(model_bc)
    Nqh, Nqk = info.Nqh, info.Nqk
    nelemv, nelemh = info.nvertelem, info.nhorzelem
    nrealelemh = info.nhorzrealelem

    #### integrate the tendency
    model_int = model_bc.modeldata.integral_model
    integral = model_int.balance_law
    update_auxiliary_state!(model_int, integral, forcing_tendency, 0)

    ### properly shape MPIStateArrays
    num_aux_int = number_states(integral, Auxiliary())
    data_int = model_int.state_auxiliary.data
    data_int = reshape(data_int, Nqh, Nqk, num_aux_int, nelemv, nelemh)

    num_aux_bt = number_states(barotropic, Auxiliary())
    data_bt = model_bt.state_auxiliary.data
    data_bt = reshape(data_bt, Nqh, num_aux_bt, nelemh)

    num_aux_bc = number_states(baroclinic, Auxiliary())
    data_bc = model_bc.state_auxiliary.data
    data_bc = reshape(data_bc, Nqh, Nqk, num_aux_bc, nelemv, nelemh)

    ### get vars indices
    index_∫du = varsindex(vars_state(integral, Auxiliary(), FT), :(∫x))
    index_Gᵁ = varsindex(vars_state(barotropic, Auxiliary(), FT), :Gᵁ)
    index_ΔGᵘ = varsindex(vars_state(baroclinic, Auxiliary(), FT), :ΔGᵘ)

    ### get top value (=integral over full depth) of ∫du
    ∫du = @view data_int[:, end, index_∫du, end, 1:nrealelemh]

    ### copy into Gᵁ of barotropic model
    Gᵁ = @view data_bt[:, index_Gᵁ, 1:nrealelemh]
    Gᵁ .= ∫du

    ### get top value (=integral over full depth) of ∫du
    ∫du = @view data_int[:, end:end, index_∫du, end:end, 1:nrealelemh]

    ### save vertically averaged tendency to remove from 3D tendency
    ### need to reshape for the broadcast
    ΔGᵘ = @view data_bc[:, :, index_ΔGᵘ, :, 1:nrealelemh]
    ΔGᵘ .-= ∫du / baroclinic.problem.H

    return nothing
end

@inline function cummulate_fast_solution!(
    baroclinic::HBModel{C},
    barotropic::SWModel{C},
    model_bt,
    state_bt,
    fast_time,
    fast_dt,
    substep,
) where {C <: Coupled}
    return nothing
end

@inline function reconcile_from_fast_to_slow!(
    baroclinic::HBModel{C},
    barotropic::SWModel{C},
    model_bc,
    model_bt,
    state_bc,
    state_bt,
) where {C <: Coupled}
    FT = eltype(state_bc)
    info = basic_grid_info(model_bc)
    Nqh, Nqk = info.Nqh, info.Nqk
    nelemv, nelemh = info.nvertelem, info.nhorzelem
    nrealelemh = info.nhorzrealelem

    ### integrate the horizontal velocity
    model_int = model_bc.modeldata.integral_model
    integral = model_int.balance_law
    update_auxiliary_state!(model_int, integral, state_bc, 0)

    ### properly shape MPIStateArrays
    num_aux_int = number_states(integral, Auxiliary())
    data_int = model_int.state_auxiliary.data
    data_int = reshape(data_int, Nqh, Nqk, num_aux_int, nelemv, nelemh)

    num_aux_bt = number_states(barotropic, Auxiliary())
    data_bt_aux = model_bt.state_auxiliary.data
    data_bt_aux = reshape(data_bt_aux, Nqh, num_aux_bt, nelemh)

    num_state_bt = number_states(barotropic, Prognostic())
    data_bt_state = state_bt.data
    data_bt_state = reshape(data_bt_state, Nqh, num_state_bt, nelemh)

    num_state_bc = number_states(baroclinic, Prognostic())
    data_bc_state = state_bc.data
    data_bc_state =
        reshape(data_bc_state, Nqh, Nqk, num_state_bc, nelemv, nelemh)

    ### get vars indices
    index_∫u = varsindex(vars_state(integral, Auxiliary(), FT), :(∫x))
    index_Δu = varsindex(vars_state(barotropic, Auxiliary(), FT), :Δu)
    index_U = varsindex(vars_state(barotropic, Prognostic(), FT), :U)
    index_u = varsindex(vars_state(baroclinic, Prognostic(), FT), :u)
    index_η_3D = varsindex(vars_state(baroclinic, Prognostic(), FT), :η)
    index_η_2D = varsindex(vars_state(barotropic, Prognostic(), FT), :η)

    ### get top value (=integral over full depth)
    ∫u = @view data_int[:, end, index_∫u, end, 1:nrealelemh]

    ### Δu is a place holder for 1/H * (Ū - ∫u)
    Δu = @view data_bt_aux[:, index_Δu, 1:nrealelemh]
    U = @view data_bt_state[:, index_U, 1:nrealelemh]
    Δu .= 1 / baroclinic.problem.H * (U - ∫u)

    ### copy the 2D contribution down the 3D solution
    ### need to reshape for the broadcast
    data_bt_aux = reshape(data_bt_aux, Nqh, 1, num_aux_bt, 1, nelemh)
    Δu = @view data_bt_aux[:, :, index_Δu, :, 1:nrealelemh]
    u = @view data_bc_state[:, :, index_u, :, 1:nrealelemh]
    u .+= Δu

    ### copy η from barotropic mode to baroclinic mode
    ### need to reshape for the broadcast
    data_bt_state = reshape(data_bt_state, Nqh, 1, num_state_bt, 1, nelemh)
    η_2D = @view data_bt_state[:, :, index_η_2D, :, 1:nrealelemh]
    η_3D = @view data_bc_state[:, :, index_η_3D, :, 1:nrealelemh]
    η_3D .= η_2D

    return nothing
end


function getsizes(genomes, regions, chrpadding, regionpadding)
    sizes = []
    for chrs in genomes
        m = -chrpadding
        for chr in chrs
            m += chrpadding - regionpadding
            R = get(regions, chr, [eachindex(chr.sequence)])
            for r in R
                m += length(r) + regionpadding
            end
        end
        push!(sizes, m)
    end
    sizes
end

function getpoint()
    lpoint = rpoint + Point((xmax-rightmargin-leftmargin) * relsize(chrpadding), 0)
end

"""
    compareregions(chrs; kwargs...)

Draw a genomic comparison plot for the genomes in `chrs`.
Some useful supported kwargs are:
* outfile           Path to the output. Supported file formats are PNG, SVG, PDF, and EPS.
* drawingsize       Size of the figure, can be a `Tuple{Int,Int}`, or a `String` such as "1000x1000".
* regions           A `Dict` with `GenomicAnnotations.Record`s as keys, and an `Array`s containing `UnitRange`s with chromosome positions that should be included in the plot. Defaults to `eachindex(chr)`.
* decorations       A `Dict` with `GenomicAnnotations.Record`s as keys, and `Dict`s as values. The nested `Dict`s have `UnitRange`s as keys (representing chromosome positions) and `NamedTuple`s as values. The `NamedTuple`s can have the fields `color`, `thickness`, and `strand (:+, :-, or :X)`.
* matches           A table (e.g. `Matrix` or `DataFrame`) containing BLAST hits, with columns corresponding to the output from BLAST with the parameter "-outfmt 6".
* matchpadding      Padding in pixels between the chromosome and the bands representing matches.
* drawgenes         Bool specifying whether or not to draw CDSs. Can also be a `Function` that takesa `Gene` as input and returns a `Bool`.
* colorfunction     Function that takes a `Gene` as input, and gives a color as output. Defaults to `g -> "lightgrey"`
* textfunction      Function that takes a `Gene` as input, and gives a `String` as output. The returned string is shown above the given gene.
* genethickness     Size of the gene.
"""
compareregions(chrs::AbstractVector{C}; kwargs...) where C <: GenomicAnnotations.Record = compareregions([vcat(c...) for c in chrs]; kwargs...)
function compareregions(genomes::AbstractVector;
        outfile = "regions.png",
        drawingsize = (1200, 1000),
        chrpadding = 25,
        regionpadding = 25,
        margins = (10, 10, 50, 10),
        names::AbstractArray = [],
        namesfontsize = 18,
        regions::AbstractDict = Dict(),
        centers::AbstractDict = Dict(),
        decorations::AbstractDict = Dict(),
        matchpadding = 0,
        colorfunction = g -> "lightgrey",
        textfunction = nothing,
        genetextoffset = 10,
        drawgenes = false,
        genethickness = 10,
        matches = [])
    xmax, ymax = drawingdimensions(drawingsize)
    drawing = Drawing(xmax, ymax, outfile)
    leftmargin, rightmargin, topmargin, bottommargin = margins
    genomesizes = getsizes(genomes, regions, chrpadding, regionpadding)
    maxgenomesize = maximum(genomesizes)
    if length(names) != length(genomes)
        names = [first(chrs).name for chrs in genomes]
    end
    relsize(s::Number) = s / maxgenomesize
    relsize(r) = length(r) / maxgenomesize
    function getchr(chrname)
        for chrs in genomes
            for chr in chrs
                if chrname == chr.name
                    return chr
                end
            end
        end
    end
    function cumpos(chrs, chrname, pos)
        res = -chrpadding
        for chr in chrs
            res += chrpadding - regionpadding
            R = get(regions, chr, [eachindex(chr.sequence)])
            for region in R
                if chr == chrname && pos in region
                    res += regionpadding + pos - first(region)
                    return res
                else
                    res += regionpadding + length(region)
                end
            end
        end
        res
    end
    getpoint(chrname::AbstractString, pos) = getpoint(getchr(chrname), pos)
    function getpoint(chr, pos)
        i = findfirst(chrs -> chr in chrs, genomes)
        y = topmargin + (i-.5) * (ymax - topmargin - bottommargin) / length(genomes)
        j = findfirst(==(chr), genomes[i])
        R = get(regions, chr, [eachindex(chr.sequence)])
        r = findfirst(r -> pos in r, R)
        isnothing(r) && return nothing
        relpos = cumpos(genomes[i], chr, pos)
        lpoint = between(Point(leftmargin, y), Point(xmax - rightmargin, y), (1-relsize(genomesizes[i])) / 2)
        rpoint = between(Point(leftmargin, y), Point(xmax - rightmargin, y), 1 - ((1-relsize(genomesizes[i])) / 2))
        between(lpoint, rpoint, relpos / genomesizes[i])
    end
    function getpoints(chr, p1, p2)
        i = findfirst(chrs -> chr in chrs, genomes)
        y = topmargin + (i-.5) * (ymax - topmargin - bottommargin) / length(genomes)
        j = findfirst(==(chr), genomes[i])
        p = p1 < p2 ? (p1:p2) : (p1:-1:p2)
        R = get(regions, chr, [eachindex(chr.sequence)])
        R = filter(r -> intersect(r, p) > 1, R)
    end
    ## draw matches (before decorations, so that the matches are on the lowest layer)
    mpoint = Point(0, matchpadding)
    for (i, chrs) in enumerate(genomes[2:end])
        chrsnames = [chr.name for chr in chrs]
        prechrs = genomes[i]
        prechrsnames = [chr.name for chr in prechrs]
        df = filter(r -> r[1] in prechrsnames && r[2] in chrsnames, matches)
        for r in eachrow(df)
            if r[9] <= r[10]
                setcolor(0.5,0,0,0.3)
            else
                setcolor(0,0,0.5,0.3)
            end
            points = [getpoint(r[1], r[7]),
                    getpoint(r[1], r[8]),
                    getpoint(r[2], r[10]),
                    getpoint(r[2], r[9])]
            if all(!isnothing, points)
                poly(points .+ [mpoint, mpoint, -mpoint, -mpoint], :fill)
            end
        end
    end
    setcolor("black")
    ## draw genome lines and decorations
    for (i, chrs) in enumerate(genomes)
        y = topmargin + (i-.5) * (ymax - topmargin - bottommargin) / length(genomes)
        genomerelsize = relsize(genomesizes[i])
        lpoint = between(Point(leftmargin, y), Point(xmax - rightmargin, y), (1-genomerelsize) / 2)
        ## write name
        fontsize(namesfontsize)
        text(names[i], Point(10, y), halign = :left, valign = :middle)
        for chr in chrs
            R = get(regions, chr, [eachindex(chr.sequence)])
            dec = get(decorations, chr, [])
            for (i, region) in enumerate(R)
                rpoint = lpoint + Point((xmax-rightmargin-leftmargin) * relsize(region), 0)
                line(lpoint, rpoint, :stroke)
                ## Draw decorations
                for (dregion, d) in dec
                    dregion = intersect(region, dregion)
                    if length(dregion) > 0
                        gsave()
                            setcolor(get(d, :color, "gray"))
                            thickness = get(d, :thickness, 5)
                            dlpoint = between(lpoint, rpoint, first(dregion) / length(region))
                            drpoint = between(lpoint, rpoint, last(dregion) / length(region))
                            strand = get(d, :strand, :X)
                            if strand == :+
                                box(dlpoint, drpoint - Point(0, thickness), :fill)
                            elseif strand == :-
                                box(dlpoint, drpoint + Point(0, thickness), :fill)
                            else
                                setline(thickness)
                                line(dlpoint, drpoint, :stroke)
                            end
                        grestore()
                    end
                end
                ## Draw genes if applicable
                if drawgenes != false
                    p = (;arrowwidth = genethickness)
                    drawfunction(gene) = drawgenes isa Bool ? feature(gene) == :CDS : drawgenes(gene)
                    for gene in @genes(chr, drawfunction(gene), !isempty(intersect(locus(gene).position, region)))
                        start = getpoint(chr, locus(gene).position.start)
                        stop = getpoint(chr, locus(gene).position.stop)
                        if locus(gene).strand == '+'
                            if isnothing(start) && isnothing(stop)
                                partialarrow_mid(p, lpoint, rpoint, colorfunction(gene))
                            elseif isnothing(start)
                                partialarrow_stop(p, lpoint, stop, colorfunction(gene))
                            elseif isnothing(stop)
                                partialarrow_start(p, rpoint, start, colorfunction(gene))
                            else
                                genearrow(p, start, stop, colorfunction(gene))
                            end
                        elseif locus(gene).strand == '-'
                            if isnothing(start) && isnothing(stop)
                                partialarrow_mid(p, lpoint, rpoint, colorfunction(gene))
                            elseif isnothing(start)
                                partialarrow_start(p, lpoint, stop, colorfunction(gene))
                            elseif isnothing(stop)
                                partialarrow_stop(p, rpoint, start, colorfunction(gene))
                            else
                                genearrow(p, stop, start, colorfunction(gene))
                            end
                        end
                        ## Draw gene text
                        if textfunction isa Function
                            t = textfunction(gene)
                            if !isnothing(t) && !isempty(t)
                                isnothing(start) && (start = lpoint)
                                isnothing(stop) && (stop = rpoint)
                                text(t, midpoint(start, stop) - Point(0, genetextoffset), halign = :center, valign = :bottom)
                            end
                        end
                    end
                end
                ## Update the left point for the next chr
                lpoint = rpoint + Point((xmax-rightmargin-leftmargin) * relsize(chrpadding), 0)
            end
        end
    end
    finish()
end#