"""
    $SIGNATURES

Return `(::Bool, ::OKVarlist, ::NotOkayVarList)` on whether all other variables (besides `loovar::Symbol`)
attached to factor `fct::Symbol` are all initialized -- i.e. `fct` is usable.

Notes:
- Special carve out for multihypo cases, see issue 427, where at least one hypothesis should be available, but not all required at first.

Development Notes
* TODO get faster version of isInitialized for database version

Related

doautoinit!, initManual!, isInitialized, isMultihypo
"""
function factorCanInitFromOtherVars(dfg::AbstractDFG,
                                    fct::Symbol,
                                    loovar::Symbol;
                                    solveKey::Symbol=:default)
  #
  # all variables attached to this factor
  varsyms = DFG.getNeighbors(dfg, fct)

  # which element is being solved for
  sfidx = (1:length(varsyms))[varsyms .== loovar][1]
  # list of factors to use in init operation
  fctlist = Symbol[]
  # list fo variables that cannot be used
  faillist = Symbol[]
  isinit = Bool[]
  for vsym in varsyms
    # check each variable one by one
    xi = DFG.getVariable(dfg, vsym)
    isi = isInitialized(xi, solveKey)
    push!(isinit, isi)
    if !isi
      push!(faillist, vsym)
    end
  end

  ## determine if this factor can be used
  # priors and general n-ary cases
  canuse = length(varsyms)==1 || (length(faillist)==1 && loovar in faillist)
  ## special multihypo case (at least one hypothesis is available or initializing first hypo)
  fctnode = getFactor(dfg, fct)
  # @show canuse, isMultihypo(fctnode), isinit
  if !canuse && isMultihypo(fctnode)
    # multihypo=[1;0.5;0.5] : sfidx=1, isinit=[0,1,0] -- true
    # multihypo=[1;0.5;0.5] : sfidx=1, isinit=[0,0,1] -- true
    # multihypo=[1;0.5;0.5] : sfidx=2|3, isinit=[1,0,0] -- true
    mhp = getMultihypoDistribution(fctnode).p
    allmhp,certainidx,uncertnidx = getHypothesesVectors(mhp)
    if isLeastOneHypoAvailable(sfidx, certainidx, uncertnidx, isinit)
      # special case works
      @info "allowing init from incomplete set of previously initialized hypotheses, fct=$fct"
      canuse = true
    end
  end

  # should add the factor for use?
  if canuse
    push!(fctlist, fct)
  end

  # return if can use, the factor in an array, and the non-initialized variables attached to the factor
  return (canuse, fctlist, faillist)::Tuple{Bool, Vector{Symbol}, Vector{Symbol}}
end

"""
    $(SIGNATURES)

EXPERIMENTAL: initialize target variable `xi` based on connected factors in the
factor graph `fgl`.  Possibly called from `addFactor!`, or `doCliqAutoInitUp!` (?).

Notes:
- Special carve out for multihypo cases, see issue 427.

Development Notes:
> Target factor is first (singletons) or second (dim 2 pairwise) variable vertex in `xi`.
* TODO use DFG properly with local operations and DB update at end.
* TODO get faster version of `isInitialized` for database version.
* TODO: Persist this back if we want to here.
"""
function doautoinit!( dfg::AbstractDFG,
                      xi::DFGVariable;
                      solveKey::Symbol=:default,
                      singles::Bool=true,
                      N::Int=getSolverParams(dfg).N, #maximum([length(getPoints(getBelief(xi, solveKey))); getSolverParams(dfg).N]),
                      logger=ConsoleLogger() )
  #
  didinit = false
  # don't initialize a variable more than once
  if !isInitialized(xi, solveKey)
    with_logger(logger) do
      @info "try doautoinit! of $(xi.label)"
    end
    # get factors attached to this variable xi
    vsym = xi.label
    neinodes = DFG.getNeighbors(dfg, vsym)
    # proceed if has more than one neighbor OR even if single factor
    if (singles || length(neinodes) > 1)
      # Which of the factors can be used for initialization
      useinitfct = Symbol[]
      # Consider factors connected to $vsym...
      for xifct in neinodes
        canuse, usefct, notusevars = factorCanInitFromOtherVars(dfg, xifct, vsym, solveKey=solveKey)
        if canuse
          union!(useinitfct, usefct)
        end
      end
      with_logger(logger) do
        @info "init with useinitfct $useinitfct"
      end
      # println("Consider all singleton (unary) factors to $vsym...")
      # calculate the predicted belief over $vsym
      if length(useinitfct) > 0
        with_logger(logger) do
          @info "do init of $vsym"
        end
        # FIXME ensure a product of only partial densities and returned pts are put to proper dimensions
        fcts = map(fx->getFactor(dfg,fx), useinitfct)
        bel,ipc = propagateBelief(dfg, getVariable(dfg,vsym), fcts, solveKey=solveKey, logger=logger, N=N)
        # while the propagate step might allow large point counts, the graph should stay restricted to N
        bel_ = Npts(bel) == getSolverParams(dfg).N ? bel : resample(bel, getSolverParams(dfg).N)
        # @info "MANIFOLD IS" bel.manifold isPartial(bel) string(bel._partial) string(getPoints(bel, false)[1])
        setValKDE!(xi, bel_, true, ipc, solveKey=solveKey) # getPoints(bel, false)
        # Update the estimates (longer DFG function used so cloud is also updated)
        setVariablePosteriorEstimates!(dfg, xi.label, solveKey)
        # Update the data in the event that it's not local
        # TODO perhaps usecopy=false
        updateVariableSolverData!(dfg, xi, solveKey, true; warn_if_absent=false)
        # deepcopy graphinit value, see IIF #612
        updateVariableSolverData!(dfg, xi.label, getSolverData(xi, solveKey), :graphinit, true, Symbol[]; warn_if_absent=false)
        didinit = true
      end
    end
  end
  return didinit
end

function doautoinit!( dfg::AbstractDFG,
                      Xi::Vector{<:DFGVariable};
                      solveKey::Symbol=:default,
                      singles::Bool=true,
                      N::Int=getSolverParams(dfg).N,
                      logger=ConsoleLogger()  )
  #
  #
  # Mighty inefficient function, since we only need very select fields nearby from a few neighboring nodes
  # do double depth search for variable nodes

  didinit = true

  # loop over all requested variables that must be initialized
  for xi in Xi
    didinit &= doautoinit!(dfg, xi, solveKey=solveKey, singles=singles, N=N, logger=logger)
  end
  return didinit
end

function doautoinit!( dfg::AbstractDFG,
                      xsyms::Vector{Symbol};
                      solveKey::Symbol=:default,
                      singles::Bool=true,
                      N::Int=getSolverParams(dfg).N,
                      logger=ConsoleLogger()  )
  #
  verts = getVariable.(dfg, xsyms)
  return doautoinit!(dfg, verts, solveKey=solveKey, singles=singles, N=N, logger=logger)
end
function doautoinit!( dfg::AbstractDFG,
                      xsym::Symbol;
                      solveKey::Symbol=:default,
                      singles::Bool=true,
                      N::Int=getSolverParams(dfg).N,
                      logger=ConsoleLogger()  )
  #
  return doautoinit!(dfg, [getVariable(dfg, xsym);], solveKey=solveKey, singles=singles, N=N, logger=logger)
end

"""
    $(TYPEDSIGNATURES)

Method to manually initialize a variable using a set of points.

Notes
- Disable automated graphinit on `addFactor!(fg, ...; graphinit=false)
  - any un-initialized variables will automatically be initialized by `solveTree!`

Example:

```julia
# some variable is added to fg
addVariable!(fg, :somepoint3, ContinuousEuclid{2})

# data is organized as (row,col) == (dimension, samples)
pts = randn(2,100)
initManual!(fg, :somepoint3, pts)

# manifold management should be done automatically.
# note upgrades are coming to consolidate with Manifolds.jl, see RoME #244

## it is also possible to initManual! by using existing factors, e.g.
initManual!(fg, :x3, [:x2x3f1])
```

DevNotes
- TODO better document graphinit and treeinit.
"""
function initManual!( variable::DFGVariable,
                      ptsArr::ManifoldKernelDensity,
                      solveKey::Symbol=:default;
                      dontmargin::Bool=false,
                      N::Int=length(getPoints(ptsArr)) )
  #
  @debug "initManual! $(getLabel(variable))"
  if !(solveKey in listSolveKeys(variable))
    @debug "$(getLabel(variable)) needs new VND solveKey=$(solveKey)"
    varType = getVariableType(variable)
    setDefaultNodeData!(variable, 0, N, getDimension(varType), solveKey=solveKey,
    initialized=false, varType=varType, dontmargin=dontmargin)
  end
  setValKDE!(variable, ptsArr, true, solveKey=solveKey)
  return nothing
end
function initManual!( dfg::AbstractDFG,
                      label::Symbol,
                      belief::ManifoldKernelDensity,
                      solveKey::Symbol=:default;
                      dontmargin::Bool=false,
                      N::Int=getSolverParams(dfg).N  )
  #
  variable = getVariable(dfg, label)
  initManual!(variable, belief, solveKey, dontmargin=dontmargin, N=N)
  return nothing
end
function initManual!( dfg::AbstractDFG,
                      label::Symbol,
                      usefcts::Vector{Symbol},
                      solveKey::Symbol=:default;
                      dontmargin::Bool=false,
                      N::Int=getSolverParams(dfg).N )
  #
  pts = predictbelief(dfg, label, usefcts, solveKey=solveKey)[1]
  vert = getVariable(dfg, label)
  Xpre = manikde!(getManifold(getVariableType(vert)), pts )
  initManual!(vert, Xpre, solveKey, dontmargin=dontmargin, N=N )
  # setValKDE!(vert, Xpre, true, solveKey=solveKey)
  # return nothing
end

function initManual!( dfg::AbstractDFG,
                      sym::Symbol,
                      pts::AbstractVector{P},
                      solveKey::Symbol=:default;
                      bw=nothing ) where {P}
  #
  var = getVariable(dfg, sym)
  M = getManifold(var)
  pp = manikde!(M, pts, bw=bw)
  initManual!(var,pp, solveKey)
end

const initVariableManual! = initManual!

"""
    $SIGNATURES

Set solveKey values of `dest::AbstractDFG` according to `initKey::Symbol=:graphinit` values.

Notes
- Some flexibility for using two DFGs and different key values, see Examples and code for details.
- Can also be specific with `varList::Vector{Symbol}`.
- Returns `dest` graph.
- Uses the supersolve mechanism.

Examples
```julia
resetInitialValues!(fg)
resetInitialValues!(fg1,fg2)  # into 1 from 2
resetInitialValues!(fg1,fg1,:myotherinit)  # use different init value into solveKey :default
resetInitialValues!(fg1,fg1,:graphinit, :mysolver) # not into solveKey=:default but :mysolver
resetInitialValues!(fg1, varList=[:x1;:l3])  # Specific variables only

# Into `fgNew` object, leaving `fg` untouched
fgNew = deepcopy(fg)
resetInitialValues!(fgNew,fg)
```

Related

initManual!, graphinit (keyword)
"""
function resetInitialValues!(dest::AbstractDFG,
                            src::AbstractDFG=dest,
                            initKey::Symbol=:graphinit,
                            solveKey::Symbol=:default;
                            varList::AbstractVector{Symbol}=ls(dest))
  #
  for vs in varList
    vnd = getSolverData(getVariable(src, vs), initKey)
    # guess we definitely want to use copy to preserve the initKey memory
    updateVariableSolverData!(dest,vs,vnd,solveKey,true; warn_if_absent=false)
  end
  return dest
end
const resetInitValues! = resetInitialValues!

"""
    $SIGNATURES

Ensure that no variables set as `solvable=1` are floating free without any connected `solvable=1` factors.  If any found, then set those 'free' variable's `solvable=solvableFallback` (default `0`).

Related

[`initAll!`](@ref)
"""
function ensureSolvable!(dfg::AbstractDFG; solvableTarget::Int=1, solvableFallback::Int=0)
  # workaround in case isolated variables occur
  solvVars = ls(dfg, solvable=solvableTarget)
  varHasFact = (x->length(ls(dfg,x, solvable=solvableTarget))==0).(solvVars)
  blankVars = solvVars[findall(varHasFact)]
  if 0 < length(blankVars)
    @warn("solveTree! dissallows solvable variables without any connected solvable factors -- forcing solvable=0 on $(blankVars)")
    (x->setSolvable!(dfg, x, solvableFallback)).(blankVars)
  end
  return blankVars
end

"""
    $SIGNATURES

Perform `graphinit` over all variables with `solvable=1` (default).

Related

ensureSolvable!, (EXPERIMENTAL 'treeinit')
"""
function initAll!(dfg::AbstractDFG,
                  solveKey::Symbol=:default;
                  solvable::Int=1,
                  N::Int=getSolverParams(dfg).N )
  #
  # allvarnodes = getVariables(dfg)
  syms = intersect(getAddHistory(dfg), ls(dfg, solvable=solvable) )
  # syms = ls(dfg, solvable=solvable) # |> sortDFG

  # May have to first add the solveKey VNDs if they are not yet available
  for sym in syms
    var = getVariable(dfg, sym)
    # does SolverData exist for this solveKey?
    if !( solveKey in listSolveKeys(var) )
      varType = getVariableType(var)
      # accept complete defaults for a novel solveKey
      setDefaultNodeData!(var, 0, N, getDimension(varType), solveKey=solveKey,
                          initialized=false, varType=varType, dontmargin=false)
    end
  end

  # do the init
  repeatCount = 0
  repeatFlag = true
  while repeatFlag
    repeatFlag = false
    repeatCount += 1
    if 10 < repeatCount
      @info "not able to initialize all variables via the factor graph, abort autoinit."
      break;
    end
    for sym in syms
      var = getVariable(dfg, sym)
      # is this SolverData initialized?
      if !isInitialized(var, solveKey)
        @info "$(var.label) is not initialized, and will do so now..."
        doautoinit!(dfg, [var;], solveKey=solveKey, singles=true)
        !isInitialized(var, solveKey) ? (repeatFlag = true) : nothing
      end
    end
  end
  nothing
end


using HorizonSideRobots

function poisk(r)
    j = 0
    i = 1
    while !ismarker(r)
        for n in 1:i
            move!(r,HorizonSide((j + 1) % 4))
            if ismarker(r)
                break
            end
        end
        if ismarker(r)
            break
        end
        for n in 1:i
            move!(r,HorizonSide((j + 2) % 4))
            if ismarker(r)
                break
            end
        end
        i += 1
        j += 2
    end
end

poisk(r)


import .Basic: gather

# gpu gather
gather(w::CuMatrix{T}, xs::OneHotArray) where T = gather(w, onehot2indices(xs))
function gather(w::CuMatrix{T}, xs::CuArray{Int}) where T
    ys = CuArray{T}(undef, size(w, 1), size(xs)...)

    function kernel!(ys::CuDeviceArray{T}, w::CuDeviceArray{T}, xs)
        li = threadIdx().y + (blockIdx().y - 1) * blockDim().y
        i = threadIdx().x + (blockIdx().x - 1) * blockDim().x

        @inbounds if li <= length(xs)
            ind = Tuple(CartesianIndices(xs)[li])
            ys[i, ind...] = w[i, xs[li]]
        end

        return
    end

    max_threads = 256
    threads_x = min(max_threads, size(ys,1))
    threads_y = min(max_threads ÷ threads_x, length(xs))
    threads = (threads_x, threads_y)
    blocks = ceil.(Int, (size(ys,1), length(xs)) ./ threads)

    CuArrays.@cuda blocks=blocks threads=threads kernel!(ys, w, xs)
    return ys
end

function gather(W::CuArray{T}, xs::CuArray{<:Tuple}) where T
    ys = CuArray{T}(undef, size(W, 1), size(xs)...)

    function kernel!(ys::CuDeviceArray{T}, w::CuDeviceArray{T}, xs)
        li = threadIdx().y + (blockIdx().y - 1) * blockDim().y
        i = threadIdx().x + (blockIdx().x - 1) * blockDim().x

        @inbounds if li <= length(xs)
            ind = Tuple(CartesianIndices(xs)[li])
            ys[i, ind...] = w[i, xs[li]...]
        end

        return
    end

    max_threads = 256
    threads_x = min(max_threads, size(ys,1))
    threads_y = min(max_threads ÷ threads_x, length(xs))
    threads = (threads_x, threads_y)
    blocks = ceil.(Int, (size(ys,1), length(xs)) ./ threads)

    CuArrays.@cuda blocks=blocks threads=threads kernel!(ys, W, xs)
    return ys
end


struct EdgeIter{G<:AGraphOrDiGraph}
    g::G
end

#TODO implement iterate protocolo for real
iterate(it::EdgeIter{<:AGraphOrDiGraph}, i=start(it)) = done(it, i) ? nothing : next(it, i)

@inline function start(it::EdgeIter{G}) where G<:AGraph
    s = _start(it)
    while !_done(it, s)
        e, t = _next(it, s)
        if src(e) <= dst(e)
            return (false, e, t)
        end
        s = t
    end
    (true, edge(it.g, 1, 2), s)
end

@inline function next(it::EdgeIter{G}, st) where G<:AGraph
    _, e, s = st
    while !_done(it, s)
        w, t = _next(it, s)
        if src(w) <= dst(w)
            return e, (false, w, t)
        end
        s = t
    end
    e, (true, e, s)
end

@inline done(it::EdgeIter{G}, s) where {G<:AGraph} = s[1]

@inline start(it::EdgeIter{G}) where {G<:ADiGraph} = _start(it)
@inline next(it::EdgeIter{G}, s) where {G<:ADiGraph} = _next(it, s)
@inline done(it::EdgeIter{G}, s) where {G<:ADiGraph} = _done(it, s)

@inline function _start(it::EdgeIter)
    i = 1
    xs_state = 1
    while i <= nv(it.g)
        eit = out_edges(it.g, i)
        xs_state = Base.start(eit)
        !Base.done(eit, xs_state) && break
        i += 1
    end
    return i, xs_state
end

@inline function _next(it::EdgeIter, state)
    i, xs_state = state
    e, xs_state = Base.next(out_edges(it.g, i), xs_state)
    while Base.done(out_edges(it.g, i), xs_state)
        i += 1
        i > nv(it.g) && break
        xs_state = Base.start(out_edges(it.g, i))
    end
    return e, (i, xs_state)
end

@inline _done(it::EdgeIter, state) = state[1] > nv(it.g)

eltype(it::EdgeIter) = edgetype(it.g)
length(it::EdgeIter) = ne(it.g)
IteratorSize(::Type{EdgeIter}) = HasLength()

"""
    edges(g)

Returns an iterator to the edges of a graph `g`.
The returned iterator is invalidated by changes to `g`.
"""
edges(g::AGraphOrDiGraph) = EdgeIter(g)

## Simpler but slower with julia 0.5. Try again with future versions
# edges(g::ADiGraph) = nv(g) == 0 ? #julia issue #18852
#                 (edge(g, u, v) for u=1:1 for v=1:0) :
#                 (e for u=1:nv(g) for e in out_edges(g, u))
#
# edges(g::AGraph) = nv(g) == 0 ? #julia issue #18852
#                 (edge(g, u, v) for u=1:1 for v=1:0) :
#                 (e for u=1:nv(g) for e in out_edges(g, u) if src(e) <= dst(e))
#
# edges(g::ADiGraph, vs::AbstractVector) = length(vs) == 0 ? #julia issue #18852
#                 (edge(g, u, v) for u=1:1 for v=1:0) :
#                 (e for u in vs for e in out_edges(g, u) if dst(e) ∈ vs)
#
# edges(g::AGraph, vs::AbstractVector) = length(vs) == 0 ? #julia issue #18852
#                 (edge(g, u, v) for u=1:1 for v=1:0) :
#                 (e for u in vs for e in out_edges(g, u) if src(e) <= dst(e) && dst(e) ∈ vs)


# This file is a part of Julia. License is MIT: http://julialang.org/license

"""
    ConjArray(array)

A lazy-view wrapper of an `AbstractArray`, taking the elementwise complex conjugate. This
type is usually constructed (and unwrapped) via the [`conj`](@ref) function (or related
[`ctranspose`](@ref)), but currently this is the default behavior for `RowVector` only. For
other arrays, the `ConjArray` constructor can be used directly.

# Examples

```jldoctest
julia> [1+im, 1-im]'
1×2 RowVector{Complex{Int64},ConjArray{Complex{Int64},1,Array{Complex{Int64},1}}}:
 1-1im  1+1im

julia> ConjArray([1+im 0; 0 1-im])
2×2 ConjArray{Complex{Int64},2,Array{Complex{Int64},2}}:
 1-1im  0+0im
 0+0im  1+1im
```
"""
immutable ConjArray{T, N, A <: AbstractArray} <: AbstractArray{T, N}
    parent::A
end

@inline ConjArray{T,N}(a::AbstractArray{T,N}) = ConjArray{conj_type(T), N, typeof(a)}(a)

ConjVector{T, V <: AbstractVector} = ConjArray{T, 1, V}
@inline ConjVector{T}(v::AbstractVector{T}) = ConjArray{conj_type(T), 1, typeof(v)}(v)

ConjMatrix{T, M <: AbstractMatrix} = ConjArray{T, 2, M}
@inline ConjMatrix{T}(m::AbstractMatrix{T}) = ConjArray{conj_type(T), 2, typeof(m)}(m)

# This type can cause the element type to change under conjugation - e.g. an array of complex arrays.
@inline conj_type(x) = conj_type(typeof(x))
@inline conj_type{T}(::Type{T}) = promote_op(conj, T)

@inline parent(c::ConjArray) = c.parent
@inline parent_type(c::ConjArray) = parent_type(typeof(c))
@inline parent_type{T,N,A}(::Type{ConjArray{T,N,A}}) = A

@inline size(a::ConjArray) = size(a.parent)
IndexStyle{CA <: ConjArray}(::CA) = IndexStyle(parent_type(CA))
IndexStyle{CA <: ConjArray}(::Type{CA}) = IndexStyle(parent_type(CA))

@propagate_inbounds getindex{T,N}(a::ConjArray{T,N}, i::Int) = conj(getindex(a.parent, i))
@propagate_inbounds getindex{T,N}(a::ConjArray{T,N}, i::Vararg{Int,N}) = conj(getindex(a.parent, i...))
@propagate_inbounds setindex!{T,N}(a::ConjArray{T,N}, v, i::Int) = setindex!(a.parent, conj(v), i)
@propagate_inbounds setindex!{T,N}(a::ConjArray{T,N}, v, i::Vararg{Int,N}) = setindex!(a.parent, conj(v), i...)

@inline similar{T,N}(a::ConjArray, ::Type{T}, dims::Dims{N}) = similar(parent(a), T, dims)

# Currently, this is default behavior for RowVector only
@inline conj(a::ConjArray) = parent(a)

# Helper functions, currently used by RowVector
@inline _conj(a::AbstractArray) = ConjArray(a)
@inline _conj{T<:Real}(a::AbstractArray{T}) = a
@inline _conj(a::ConjArray) = parent(a)
@inline _conj{T<:Real}(a::ConjArray{T}) = parent(a)


"""
    deleteAnomalies!(df::DataFrame, ColOverflow::String, ColPct::String, threshold::Float64)

Deletes rows with precipitations under a certain threshold but with overflow.

Data provided must not contain any missing values for overflows' duration or occurrence.

# Arguments

- `df::DataFrame`: The dataframe containing the data.
- `ColOverflow::String`: The name of the column of `df` containing the binary vector of overflows.
- `ColPct::String`: The name of the column of `df` that allows to know if it has rained or not during the time division used.
- `threshold::Float64`: The precipitation threshold under which an anomalie is considered.
"""
function deleteAnomalies!(df::DataFrame, ColOverflow::String, ColPct::String, threshold::Float64)
    filter!([Symbol(ColOverflow), Symbol(ColPct)] => (x, y) -> (x == 1 && y > threshold) || (x == 0), df)
end


module VarianceComponentTest

import Distributions

export
       vctestnullsim,
       vctest

include("vctestnullsim.jl")
include("vctest.jl")

end


"""
    DiGraph{V,E}()
    DiGraph{V}()
    DiGraph()

Construct a static directed graph with node type `V` and edge type `E`. `DiGraph()` constructs a static directed graph with integer nodes and edges.

# Example

```jldoctest
julia> using EvolvingGraphs

julia> g = DiGraph()
DiGraph 0 nodes, 0 edges

julia> add_edge!(g, 1, 2)
Node(1)->Node(2)

julia> add_edge!(g, 2, 3)
Node(2)->Node(3)

julia> adjacency_matrix(g)
3×3 Array{Float64,2}:
 0.0  1.0  0.0
 0.0  0.0  1.0
 0.0  0.0  0.0

julia> add_node!(g, 4)
Node(4)

julia> nodes(g)
4-element Array{EvolvingGraphs.Node{Int64},1}:
 Node(1)
 Node(2)
 Node(3)
 Node(4)
```
"""
mutable struct DiGraph{V<:AbstractNode, E<:AbstractEdge, IncList, VK} <: AbstractStaticGraph{V, E}
    nodes::Vector{V}
    edges::Vector{E}
    forward_ilist::IncList # forward incidence list
    backward_ilist::IncList # backward incidence list
    indexof::Dict{VK,Int}
end
DiGraph{V, E}() where V where E = DiGraph(V[], E[], [], [], Dict{eltype(V), Int}())
DiGraph{V}() where V = DiGraph{V, Edge{V}}()
DiGraph() = DiGraph{Node{Int}, Edge{Node{Int}}}()
is_directed(g::DiGraph) = true

nodes(g::DiGraph) = g.nodes
num_nodes(g::DiGraph) = length(g.nodes)
edges(g::DiGraph) = g.edges
num_edges(g::DiGraph) = length(g.edges)

function add_bunch_of_edges!(g::DiGraph, ebunch)
    for e in ebunch
        if length(e) == 2
            i,j = e
            add_node!(g,i)
            add_node!(g,j)
            add_edge!(g,i,j)
        else
            error("Each edge in ebunch must have 2 elemnts: source and target")
        end
    end
    g
end

"""
    out_edges(g, v)

Return the outward edges of node `v` in a static graph `g`, where `v` can be a node or a node key.

# Example

```jldoctest
julia> using EvolvingGraphs

julia> g = DiGraph()
DiGraph 0 nodes, 0 edges

julia> add_bunch_of_edges!(g, [(1,2), (2,3), (3,4)])
DiGraph 4 nodes, 3 edges

julia> out_edges(g, 1)
1-element Array{EvolvingGraphs.Edge{EvolvingGraphs.Node{Int64}},1}:
 Node(1)->Node(2)
```
"""
out_edges{V}(g::DiGraph{V}, v::V) = g.forward_ilist[node_index(v)]
out_edges{V}(g::DiGraph{V}, key::eltype(V)) = (v = Node(g.indexof[key],key); out_edges(g, v))

"""
    out_degree(g, v)

Return the number of outward edges of node `v` in static graph `g`, where `v` can be a node of a node key.
"""
out_degree{V}(g::DiGraph{V}, v::V) = length(out_edges(g, v))
out_degree{V}(g::DiGraph{V}, key::eltype(V)) = (v = Node(g.indexof[key],key); out_degree(g, v))

"""
    in_edges(g, v)

Return the inward edges of node `v` in static graph `g`.

# Example

```jldoctest
julia> using EvolvingGraphs

julia> g = DiGraph()
DiGraph 0 nodes, 0 edges

julia> add_bunch_of_edges!(g, [(2,1), (3,1), (4,1)])
DiGraph 4 nodes, 3 edges

julia> in_edges(g,1)
3-element Array{EvolvingGraphs.Edge{EvolvingGraphs.Node{Int64}},1}:
 Node(2)->Node(1)
 Node(3)->Node(1)
 Node(4)->Node(1)

julia> in_degree(g,1)
3
```
"""
in_edges{V}(g::DiGraph{V}, v::V) = g.backward_ilist[node_index(v)]
in_edges{V}(g::DiGraph{V}, key::eltype(V)) = (v = Node(g.indexof[key],key); in_edges(g, v))

"""
    in_degree(g, v)

Return the number of inward edges of node `v` in static graph `g`.
"""
in_degree{V}(g::DiGraph{V}, v::V) = length(in_edges(g, v))
in_degree{V}(g::DiGraph{V}, key::eltype(V)) = (v = Node(g.indexof[key],key); in_degree(g, v))

function add_node!{K,E}(g::DiGraph{Node{K},E}, key::K; return_graph::Bool = false)
    index = get(g.indexof, key, 0)
    if index != 0
        v = Node(index,key)
        return return_graph? (g, v) : v
    end

    v = Node{K}(g, key)
    push!(g.nodes, v)
    push!(g.forward_ilist, E[])
    push!(g.backward_ilist, E[])
    g.indexof[key] = node_index(v)
    return_graph ? (g,v) : v
end

function add_edge!{V, E}(g::DiGraph{V ,E}, u::V, v::V, e::E)
    ui = node_index(u)
    vi = node_index(v)
    if !(e in g.forward_ilist[ui])
        push!(g.forward_ilist[ui], e)
    end
    if !(e in g.backward_ilist[vi])
        push!(g.backward_ilist[vi], e)
    end
    if !(e in g.edges)
        push!(g.edges, e)
    end
    e
end

function add_edge!{V,E}(g::DiGraph{V,E}, u::V, v::V)
    e = E(u, v)
    add_edge!(g, u, v, e)
end

function add_edge!{K}(g::DiGraph{Node{K}}, u_key::K, v_key::K)
    g, u = add_node!(g, u_key, return_graph = true)
    g, v = add_node!(g, v_key, return_graph = true)

    add_edge!(g, u, v)
end

"""
    aggregate_graph(g)

Convert an evolving graph `g` to a static graph by aggregate the edges to the same timestamp.

# Example

```jldoctest
julia> using EvolvingGraphs

julia> eg = EvolvingGraph()
Directed EvolvingGraph 0 nodes, 0 static edges, 0 timestamps

julia> add_bunch_of_edges!(eg, [(1,2,3), (2,3,4), (1,2,1)])
Directed EvolvingGraph 3 nodes, 3 static edges, 3 timestamps

julia> aggregate_graph(eg)
DiGraph 3 nodes, 2 edges
```
"""
function aggregate_graph{V}(eg::AbstractEvolvingGraph{V})
    g = DiGraph{V,Edge{V}}()
    for e in edges(eg)
        add_edge!(g, node_key(source(e)), node_key(target(e)))
    end
    g
end

find_node{V}(g::DiGraph{V}, v::V) = v in g.nodes ? v : false
find_node(g::DiGraph, key) = get(g.indexof, key, false)

function adjacency_matrix(g::DiGraph)
    n = num_nodes(g)
    es = edges(g)
    A = zeros(Float64, n, n)
    for e in es
        i = node_index(source(e))
        j = node_index(target(e))
        A[i,j] = 1.
    end
    return A
end

function sparse_adjacency_matrix(g::DiGraph)
    es = edges(g)
    n = num_nodes(g)
    is = Int[]
    js = Int[]

    for e in es
        i = node_index(source(e))
        j = node_index(target(e))
        push!(is, i)
        push!(js, j)
    end
    vs = ones(Float64, length(is))
    return sparse(is, js, vs, n, n)
end


using DataFrames, DataFramesMeta, Statistics
using PyPlot
using Colors

# Functions/Helpers to process dataframe files (outputs of simulations)

# Retain results for only one combination of
# stochasticy and changeprobability values
function df_trim(df, svalue, changeprobabilityvalue)
    dftrimmed = []
    if in(:sigma, names(df))
        dftrimmed = @where(df, :changeprobability .== changeprobabilityvalue,
                            :sigma .== svalue)
    elseif in(:stochasticity, names(df))
        dftrimmed = @where(df, :changeprobability .== changeprobabilityvalue,
                            :stochasticity .== svalue)
    end
    dftrimmed
end
# Merge seeds
function df_mergeseeds_errors(df)
    if in(:sigma, names(df))
        df = by(df, [:learner, :sigma, :changeprobability],
                allmerrors = :merrors => x -> [collect(skipmissing(reduce(vcat, x)))])
    elseif in(:stochasticity, names(df))
        df = by(df, [:learner, :stochasticity, :changeprobability],
                allmerrors = :terrors => x -> [collect(skipmissing(reduce(vcat, x)))])
    end
    df
end
# Get the mean error across all time steps and all seeds
function df_mergeseeds_meanerrors(df)
    if in(:sigma, names(df))
        df = by(df, [:learner, :sigma, :changeprobability],
                meanerrors = :merrors => x -> mean(collect(skipmissing(reduce(vcat, x)))))
    elseif in(:stochasticity, names(df))
        df = by(df, [:learner, :stochasticity, :changeprobability],
                meanerrors = :terrors => x -> mean(collect(skipmissing(reduce(vcat, x)))))
    end
    df
end
# Get the mean error across time steps for a single seed
function df_meanerrors_perseed(df)
    if in(:sigma, names(df))
        df = by(df, [:learner, :sigma, :changeprobability, :seed],
                meanerrors_seed = :merrors => x -> mean(collect(skipmissing(reduce(vcat, x)))))
    elseif in(:stochasticity, names(df))
        df = by(df, [:learner, :stochasticity, :changeprobability, :seed],
                meanerrors_seed = :terrors => x -> mean(collect(skipmissing(reduce(vcat, x)))))
    end
    df
end
# Get the mean error across time steps for each seed
function df_meanerrors_acrosseeds(df)
    df = df_meanerrors_perseed(df)
    if in(:sigma, names(df))
        df = by(df, [:learner, :sigma, :changeprobability],
                allmeanerrors_seed = :meanerrors_seed => x -> [collect(skipmissing(reduce(vcat, x)))])
    elseif in(:stochasticity, names(df))
        df = by(df, [:learner, :stochasticity, :changeprobability],
                allmeanerrors_seed = :meanerrors_seed => x -> [collect(skipmissing(reduce(vcat, x)))])
    end
    df
end
# Get the mean, std, se across seeds (Call df_meanerrors_perseed(df) first)
function df_stats_acrossseeds(df_meanerrors_seed) #
    if in(:sigma, names(df_meanerrors_seed))
        df_meanerrors_seed = by(df_meanerrors_seed, [:learner, :sigma, :changeprobability],
                meanerrors_seed = :meanerrors_seed => x -> mean(collect(skipmissing(reduce(vcat, x)))),
                stderrors_seed = :meanerrors_seed => x -> std(collect(skipmissing(reduce(vcat, x)))),
                nseed = :meanerrors_seed => x -> length(collect(skipmissing(reduce(vcat, x)))),
                seerrors_seed = :meanerrors_seed => x -> std(collect(skipmissing(reduce(vcat, x)))) / sqrt(length(collect(skipmissing(reduce(vcat, x)))))
                )
    elseif in(:stochasticity, names(df_meanerrors_seed))
        df_meanerrors_seed = by(df_meanerrors_seed, [:learner, :stochasticity, :changeprobability],
                meanerrors_seed = :meanerrors_seed => x -> mean(collect(skipmissing(reduce(vcat, x)))),
                stderrors_seed = :meanerrors_seed => x -> std(collect(skipmissing(reduce(vcat, x)))),
                nseed = :meanerrors_seed => x -> length(collect(skipmissing(reduce(vcat, x)))),
                seerrors_seed = :meanerrors_seed => x -> std(collect(skipmissing(reduce(vcat, x)))) / sqrt(length(collect(skipmissing(reduce(vcat, x)))))
                )
    end
    df_meanerrors_seed
end
function formatparamlist(paramlist)
    tuple([isa(x, AbstractFloat) ? round(x, digits=3) : x for x in paramlist]...)
end
# Keep K number of steps after a change point
function keep_Ksteps(dataafterswitches, Ksteps)
    Nsteps = [size(x, 1) for x in dataafterswitches]
    toosmall = findall(Nsteps .<= Ksteps)
    deleteat!(dataafterswitches, toosmall)
    dataafterswitches = [dataafterswitches[i][1:Ksteps]
                    for i in 1:length(dataafterswitches)]
end
function df_getlearnerinfo(df)
    unique(df[:learner])[1], unique(df[:param])[1]
end
function calc_stats(dataarray) # dataarray : timepoints x instances
    m = vec(mean(dataarray, dims=2))
    s = vec(std(dataarray, dims=2))
    serror = s ./ sqrt(size(dataarray, 2))
    m, s, serror
end
nanmean(x) = mean(filter(!isnan,x))
nanmean(x,dims) = mapslices(nanmean, x, dims=dims)
nanstd(x) = std(filter(!isnan,x))
nanstd(x,dims) = mapslices(nanstd, x, dims=dims)
nanlength(x) = length(filter(!isnan,x))
nanlength(x,dims) = mapslices(nanlength, x, dims=dims)
function nanserror(x, dims)
    std = nanstd(x, dims)
    n = nanlength(x, dims)
    std ./ sqrt.(n)
end
function calc_stats_nan(dataarray) # dataarray : timepoints x instances
    m = vec(nanmean(dataarray, 2))
    s = vec(nanstd(dataarray, 2))
    serror = vec(nanserror(dataarray, 2))
    m, s, serror
end
# Get the mean error after change points (in time)
# For Figure 2 and Figure 5
function df_meanerrorsafterswitches(df)
    df2 = df_geterrorsafterswitches(df)
    erallswitches = vcat(df2[:errorafterswitches]...)
    erallswitches = hcat(erallswitches...)
    merafterswitches, stderafterswitches, stderrorerafterswitches = calc_stats(erallswitches)
end
# Get the error after change points (in time) for each row (enviroment instance)
# in the dataframe
function df_geterrorsafterswitches(df; Ksteps = 50)
    if in(:merrors, names(df))
    df2 = @byrow! df begin
        @newcol errorafterswitches::Array{Array{Array{Float64,1} ,1}, 1}
        :errorafterswitches = geterrorsafterswitches(:merrors, :switches,
                                Int(1. / :changeprobability))
        end
    elseif in(:terrors, names(df))
    df2 = @byrow! df begin
        @newcol errorafterswitches::Array{Array{Array{Float64,1} ,1}, 1}
        :errorafterswitches = geterrorsafterswitches(:terrors, :switches,
                            Int(1. / :changeprobability))
        end
    end
end
# Get the errors in time (whole simulation) and align them with respect to
# the change points
function geterrorsafterswitches(errors, switches, Ksteps)
    switchindices = find_switchpoints(errors, switches)
    #[a[b[i]:b[i+1]] for i in 1:length(b)-1]
    erafterswitches = [errors[switchindices[i]:switchindices[i+1]]
                        for i in 1:length(switchindices)-1]
    erafterswitches = keep_Ksteps(erafterswitches, Ksteps)
end
# Find the time steps of change points
function find_switchpoints(errors, switches)
    switchindices = findall(switches)
    switchindices = vcat(switchindices, length(errors))
end
# ------------------------------------------------------------------------------
# Function to plot (or create csv data in order to plot them)
# the lower left inset plots of Figure 5 (task example)
function plottask_M(dflearner, stochasticity, changeprobability, seed;
                    savepath = homedir())
    states = @where(dflearner,
        :stochasticity .== stochasticity,
        :changeprobability .== changeprobability,
        :seed .== seed
        )[:states]

    switches = @where(dflearner,
            :stochasticity .== stochasticity,
            :changeprobability .== changeprobability,
            :seed .== seed
            )[:switches]

    fig = figure(); ax = gca()
    data = states[1][1:100]
    switchdata = switches[1][1:100]
    iswitch = findall(switchdata)
    ax.plot(1:length(data), data)

    if !isempty(switchdata)
        for i in 1:length(iswitch)
            ax.axvline(iswitch[i], color="red", linestyle="--", linewidth=0.5)
        end
    end
    ax.grid(true)
    xlabel("Time steps")
    ylabel("Observed state")
    PyPlot.savefig(joinpath(savepath, "states.png"))

    dfresults = DataFrame()
    dfresults[:x] = collect(1:length(states[1]))
    dfresults[:states] = states[1]
    dfresults[:switches] = Int.(switches[1])

    CSV.write(joinpath(savepath, "task_multi_s"* string(stochasticity) * "_Pc" *string(changeprobability)*".csv"), dfresults,
        delim = " ");
end
# Function to plot (or create csv data in order to plot them)
# the lower left inset plot of Figure 2 (task example)
function plottask_G(dflearner, sigma, changeprobability, seed;
                        savepath = homedir())

    learnertype = GLeakyIntegrator
    parameterset = (0.9,)

    callback = runG!(learnertype, parameterset,
            sigma = sigma,
            changeprobability = changeprobability,
            nsteps = 10^3,
            iseedenvpolicy = seed,
            iseedlearner = seed)

    true_mean = callback.mu_n_history[2:end]
    # true_mean = callback.mu_n_history[2:300]
    samples = callback.state_history[2:end]
    #samples = callback.state_history[2:300]
    switches = Int.(callback.switchflag)
    #switches = Int.(callback.switchflag[1:299])
    fig = figure(); ax = gca()
    ax.plot(true_mean)
    ax.scatter(1:length(samples), samples, color = "r")
    #iswitch = findall(callback.switchflag[1:299])
    iswitch = findall(callback.switchflag)
    if !isempty(switches)
        for i in 1:length(iswitch)
            ax.axvline(iswitch[i], color="red", linestyle="--", linewidth=0.5)
        end
    end
    PyPlot.savefig(joinpath(savepath, "gaussian_states.png"))

    dfresults = DataFrame()
    dfresults[:x] = collect(1:length(samples))
    dfresults[:truemean] = true_mean
    dfresults[:samples] = samples
    dfresults[:switches] = switches

    CSV.write(joinpath(savepath, "task_gaussian_s"* string(sigma) * "_Pc" *string(changeprobability)*".csv"), dfresults,
        delim = " ");
end
# ------------------------------------------------------------------------------
# Helper functions to explore learners
function explorelearner(df, learnername, stochasticity, stay, seed)
    dflearner = @where(df,
        :learner .== learnername,
        :stochasticity .== stochasticity,
        :changeprobability .== changeprobability,
        :seed .== seed
        )
end
function explorelearner_terrors(df, learnername, stochasticity, changeprobability)
    terrors = @where(df,
        :learner .== learnername,
        :stochasticity .== stochasticity,
        :changeprobability .== changeprobability,
        )[:terrors]
    terrors = reduce(vcat, terrors);
end
function explorelearner_merrors(df, learnername, sigma, changeprobability)
    merrors = @where(df,
        :learner .== learnername,
        :sigma .== sigma,
        :changeprobability .== changeprobability,
        )[:merrors]
    merrors = reduce(vcat, merrors);
end
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# --------- Plot within julia: not used eventually for the paper ---------------
function plot_shadedline(m, s, labelstring, color; titlestring = "")
    fig = figure(); ax = gca()
    plot_shadedline_core(m, s, labelstring, color, ax)
    title(titlestring)
end
function plot_shadedlines(means, stderrors, labels, ax;
                            xlabelstring = "Time steps after switch",
                            ylabelstring = "",
                            titlestring = "",
                            xdata = [],
                            ylims = [0., 1.])
    clist = [cmap(i) for i in range(0, 1, length = length(labels)+1)]
    for i in 1:length(labels)
        plot_shadedline_core(means[i], stderrors[i], labels[i], clist[i], ax,
                            xdata = xdata)
    end
    xlabel(xlabelstring)
    ylabel(ylabelstring)
    ax.set_ylim(ylims[1], ylims[2])
    title(titlestring)
    ax.legend()

end
function plot_shadedline_core(m, s, labelstring, color, ax; xdata = [])
    if isempty(xdata)
        ax.plot(1:length(m), m, label = labelstring, color = color)
        ax.fill_between(1:length(m), m + s, m - s, facecolor = color, alpha=0.5)
    else
        ax.plot(xdata, m, label = labelstring, color = color)
        ax.fill_between(xdata, m + s, m - s, facecolor = color, alpha=0.5)
    end
    ax.grid(true)
end
function plot_subplots_shadedlines(means, stderrors, labels, i; titlestring = "")
    subplot(1,5,i)
    ax = gca()
    plot_shadedlines(means, stderrors, labels, ax, titlestring = titlestring)
end


function MidpointIntegrator(arg0::jdouble)
    return MidpointIntegrator((jdouble,), arg0)
end

function get_a(obj::MidpointIntegrator)
    return jcall(obj, "getA", Vector{Vector{jdouble}}, ())
end

function get_b(obj::MidpointIntegrator)
    return jcall(obj, "getB", Vector{jdouble}, ())
end

function get_c(obj::MidpointIntegrator)
    return jcall(obj, "getC", Vector{jdouble}, ())
end


# This file is a part of TextSearch.jl

export compute_bow_corpus, compute_bow, bow, dvec
const BOW = DVEC{UInt64,Int32}

"""
    compute_bow(tokenlist::AbstractVector{S}, bow::BOW=BOW()) where {S<:Symbol}
    compute_bow(tok::Tokenizer, text::Symbol, bow::BOW=BOW())
    compute_bow(tok::Tokenizer, text::AbstractVector, bow::BOW=BOW())

Creates a bag of words from the given text (a string or a list of strings).
If bow is given then updates the bag with the text.
When `config` is given, the text is parsed according to it.
"""
function compute_bow(tokenlist::AbstractVector, bow::BOW=BOW())
    for sym in tokenlist
        bow[sym] = get(bow, sym, 0) + 1
    end

    bow
end

function compute_bow(tok::Tokenizer, text::AbstractString, bow::BOW=BOW())
    compute_bow(tokenize(tok, text), bow)
end

"""
    compute_bow(messages::AbstractVector, bow::BOW=BOW())

Computes a bag of words from messages
"""
function compute_bow(tok::Tokenizer, messages::AbstractVector, bow::BOW=BOW())
    for text in messages
        empty!(tok)
        tokenize(tok, text)
        compute_bow(tok.tokens, bow)
    end

    bow
end

"""
    compute_bow_corpus(tok::Tokenizer, corpus::AbstractVector)

Computes a list of bag of words from a corpus
"""
function compute_bow_corpus(tok::Tokenizer, corpus::AbstractVector, bow::BOW=BOW())
    X = Vector{BOW}(undef, length(corpus))
    for i in eachindex(corpus)
        empty!(tok)
        empty!(bow)
        compute_bow(tok, corpus[i], bow)
        X[i] = copy(bow)
    end

    X
end

"""
    bow(model::Tokenizer, x::AbstractSparseVector)
    bow(model::Tokenizer, x::DVEC{Ti,Tv}) where {Ti<:Integer,Tv<:Number}

Creates a bag of words using the sparse vector `x` and the text model `model`
"""
function bow(m::Tokenizer, x::AbstractSparseVector)
    DVEC{String,eltype{x.nzval}}(decode(m, t) => v for (t, v) in zip(x.nzind, x.nzval))
end

function bow(m::Tokenizer, x::DVEC{Ti,Tv}) where {Ti<:Integer,Tv<:Number}
    DVEC{String,Tv}(decode(m, t) => v for (t, v) in x)
end

"""
    dvec(m::Tokenizer, x::DVEC{Symbol,Tv}, Ti=Int) where {Tv<:Number}

Creates a DVEC sparse vector from a bag of words sparse vector (i.e., with type DVEC{Symbol,Tv}),
using the inverse map `m`
"""
function dvec(m::Tokenizer, x::DVEC{String,Tv}, Ti=Int) where {Tv<:Number}
    DVEC{Ti,Tv}(encode(m, t) => v for (t, v) in x)
end


using HorizonSideRobots

function alldraw(r)
    y = 0
    z = 0
    y, z = goinSWcorner(r)
    draw(r)
    vozvrat(r,y,z)
end

function obhod(r,side)
    x = 0
    sign = true
    while isborder(r,HorizonSide(side)) && !isborder(r,HorizonSide((side + 3) % 4))
        move!(r,HorizonSide((side + 3) % 4))
        x += 1
    end
    if isborder(r,HorizonSide(side)) && isborder(r,HorizonSide((side + 3) % 4))
        sign = false
    end
    if  !isborder(r,HorizonSide(side))
        move!(r,HorizonSide(side))
    end
    while !isborder(r,HorizonSide(side)) && isborder(r,HorizonSide((side + 1) % 4))
        move!(r,HorizonSide(side))
    end
    for i = 1:x
        move!(r,HorizonSide((side+1) % 4))
    end
    return sign
end

function obhod2(r,side,y)
    x = 0
    sign = true
    while isborder(r,HorizonSide(side)) && !isborder(r,HorizonSide((side + 3) % 4))
        move!(r,HorizonSide((side + 3) % 4))
        x += 1
    end
    if isborder(r,HorizonSide(side)) && isborder(r,HorizonSide((side + 3) % 4))
        sign = false
    end
    if  !isborder(r,HorizonSide(side))
        move!(r,HorizonSide(side))
        y+=1
    end
    while !isborder(r,HorizonSide(side)) && isborder(r,HorizonSide((side + 1) % 4))
        move!(r,HorizonSide(side))
        y+= 1
    end
    for i = 1:x
        move!(r,HorizonSide((side+1) % 4))
    end
    return sign,y
end

function obhod3(r,side,y)
    x = 0
    sign = true
    while isborder(r,HorizonSide(side)) && !isborder(r,HorizonSide((side + 3) % 4))
        move!(r,HorizonSide((side + 3) % 4))
        x += 1
    end
    if isborder(r,HorizonSide(side)) && isborder(r,HorizonSide((side + 3) % 4))
        sign = false
    end
    if  !isborder(r,HorizonSide(side))
        move!(r,HorizonSide(side))
        y-=1
    end
    while !isborder(r,HorizonSide(side)) && isborder(r,HorizonSide((side + 1) % 4))
        move!(r,HorizonSide(side))
        y-= 1
    end
    for i = 1:x
        move!(r,HorizonSide((side+1) % 4))
    end
    return sign,y
end

function goinSWcorner(r)
    sign = true
    y = 0
    z = 0
    while !isborder(r,Sud) || sign
        if !isborder(r,Sud)
            move!(r,Sud)
            y += 1
        end
        if isborder(r,Sud)
            sign,y =  obhod2(r,2,y)
        end
    end
    while !isborder(r,West)
        move!(r,West)
        z += 1
    end
    return y,z
end

function draw(r)
    sign2 = true
    while !isborder(r,Nord) || sign2
        sign1 = true
        while !isborder(r,Ost) || sign1
            putmarker!(r)
            if !isborder(r,Ost)
                move!(r,Ost)
            else
                sign1 = obhod(r,3)
            end
        end
        sign1 = true
        while !isborder(r,West) || sign1
            if !isborder(r,West)
                move!(r,West)
            else
                sign1 = obhod(r,1)
            end
        end
        if !isborder(r,Nord)
            move!(r,Nord)
        else
            sign2 = obhod(r,0)
        end
    end
end

function vozvrat(r,y,z)
    sign = true
    while !isborder(r,Sud)
        move!(r,Sud)
    end
    while !isborder(r,West)
        move!(r,West)
    end
    for i = 1:z
        move!(r,Ost)
    end
    while y > 0
        if !isborder(r,Nord)
            move!(r,Nord)
            y -= 1
        else
            sign, y = obhod3(r,0,y)
        end
    end
end

alldraw(r)


#!/usr/bin/env julia

#=
-------------------------
Log related functionality
-------------------------
=#
struct RheoLogItem
   action      # Nothing, or NamedTuple with fields:
               #     type::Symbol, funct::Symbol, params::NamedTuple, keywords::NamedTuple
   info        # NamedTuple for process output, comments, etc.
end

const RheoLog = Vector{RheoLogItem}

# simply pass text as comment
function RheoLogItem(s::String)
   return(RheoLogItem(Nothing,(comment=s,)))
end

# store info data in log from arbitrary keyword arguments
function RheoLogItem(;kwargs...)
   return(RheoLogItem(Nothing, kwargs.data))
end

#  There is a bug in julia that prevents the proper use of these functions.
#

# function Base.show(io::IO, rli::RheoLogItem)
#     println()
#     print("action = "); println(rli.action)
#     print("info   = "); println(rli.info)
# end
# function Base.display(io::IO, rl::RheoLog)
#     println("Hello!")
#      for rli in rl
#          print(rli)
#      end
# end

#=
-------------------------------
Time data related functionality
-------------------------------
=#
"""
    RheoTimeData(;σ::Vector{T1}, ϵ::Vector{T2}, t::Vector{T3}) where {T1<:Real, T2<:Real, T3<:Real}

`RheoTimeData` struct contains stress, strain and time data.

If preferred, an instance can be generated manually by just providing the three data
vectors in the right order, sampling type will be checked automatically.

# Fields

- `σ`: stress
- `ϵ`: strain
- `t`: time
- `log`: a log of struct's events, e.g. preprocessing
"""
struct RheoTimeData

    σ::Vector{RheoFloat}
    ϵ::Vector{RheoFloat}
    t::Vector{RheoFloat}

    log::Union{RheoLog,Nothing}

end

function RheoTimeData(;ϵ::Vector{T1} = RheoFloat[], σ::Vector{T2} = RheoFloat[], t::Vector{T3} = RheoFloat[], comment="Created from generic constructor", savelog = true, log = savelog ? RheoLogItem(comment) : nothing)  where {T1<:Real, T2<:Real, T3<:Real}
    typecheck = check_time_data_consistency(t,ϵ,σ)
    RheoTimeData(convert(Vector{RheoFloat},σ), convert(Vector{RheoFloat},ϵ), convert(Vector{RheoFloat},t),
                    log == nothing ? nothing : [ RheoLogItem(log.action,merge(log.info, (type=typecheck,)))]     )
end

@enum TimeDataType invalid_time_data=-1 time_only=0 strain_only=1 stress_only=2 strain_and_stress=3

function check_time_data_consistency(t,e,s)
    @assert (length(t)>0)  "Time data empty"

    sdef=(s != RheoFloat[])
    edef=(e != RheoFloat[])
    if (sdef && edef)
        @assert (length(s)==length(t)) && (length(e)==length(t)) "Time data length inconsistent"
        return strain_and_stress
    end

    if (sdef && (!edef))
        @assert (length(s)==length(t)) "Time data length inconsistent"
        return stress_only
    end

    if (edef && (!sdef))
        @assert (length(e)==length(t)) "Time data length inconsistent"
        return strain_only
    end

    if ((!edef) && (!sdef))
        return time_only
    end

    return invalid_time_data

end

function RheoTimeDataType(d::RheoTimeData)
    return check_time_data_consistency(d.t,d.ϵ,d.σ)
end

"""
    hastime(d::RheoTimeData)

returns 'true' if d contains a time array
"""
function hastime(d::RheoTimeData)
    return (d.t != RheoFloat[])
end

"""
    hasstress(d::RheoTimeData)

returns 'true' if d contains stress data
"""
function hasstress(d::RheoTimeData)
    return (d.σ != RheoFloat[])
end

"""
    hasstrain(d::RheoTimeData)

returns 'true' if d contains strain data
"""
function hasstrain(d::RheoTimeData)
    return (d.ϵ != RheoFloat[])
end

@enum LoadingType strain_imposed=1 stress_imposed=2

function Base.show(io::IO, d::RheoTimeData)
    b = length(d.t) > 10
    n = b ? 10 : length(d.t)

    if d.t != RheoFloat[]
        print("t =\t")
        for i = 1:n
            print("$(d.t[i])\t")
        end
        println( b ? "..." : "")
    end
    if d.ϵ != RheoFloat[]
        print("ϵ =\t")
        for i = 1:n
            print("$(d.ϵ[i])\t")
        end
        println( b ? "..." : "")
    end
    if d.σ != RheoFloat[]
        print("σ =\t")
        for i = 1:n
            print("$(d.σ[i])\t")
        end
        println( b ? "..." : "")
    end
end

function +(d1::RheoTimeData, d2::RheoTimeData)

    type1 = RheoTimeDataType(d1)
    type2 = RheoTimeDataType(d2)
    @assert (type1!=invalid_time_data) "Addition error: first parameter invalid"
    @assert (type2!=invalid_time_data) "Addition error: second parameter invalid"
    @assert (type1==type2) "Addition error: parameters inconsistent"
    @assert (type1!=time_only) "Addition error: time only data cannot be added"
    @assert (d1.t == d2.t) "Addition error: timelines inconsistent"

    log =   if (d1.log == nothing) || (d2.log == nothing)
                nothing
            else
                [ RheoLogItem( (type = :source, funct = :+, params = (rl1 = d1.log, rl2 = d2.log), keywords = ()), ()  ) ]
            end

    if (type1==strain_only) && (type2==strain_only)
        return RheoTimeData(RheoFloat[], d1.ϵ+d2.ϵ, d1.t, log)
    end

    if (type1==stress_only) && (type2==stress_only)
        return RheoTimeData(d1.σ+d2.σ, RheoFloat[], d1.t, log)
    end

    if (type1==strain_and_stress) && (type2==strain_and_stress)
        return RheoTimeData(d1.σ+d2.σ, d1.ϵ+d2.ϵ, d1.t, log)
    end

end

function -(d1::RheoTimeData, d2::RheoTimeData)

    type1 = RheoTimeDataType(d1)
    type2 = RheoTimeDataType(d2)
    @assert (type1!=invalid_time_data) "Subtraction error: first parameter invalid"
    @assert (type2!=invalid_time_data) "Subtraction error: second parameter invalid"
    @assert (type1==type2) "Subtraction error: parameters inconsistent"
    @assert (type1!=time_only) "Subtraction error: time only data cannot be added"
    @assert (d1.t == d2.t) "Subtraction error: timelines inconsistent"

    log =   if (d1.log == nothing) || (d2.log == nothing)
                nothing
            else
                [ RheoLogItem( (type = :source, funct = :-, params = (rl1 = d1.log, rl2 = d2.log), keywords = ()), ()  ) ]
            end

    if (type1==strain_only) && (type2==strain_only)
        return RheoTimeData(RheoFloat[], d1.ϵ-d2.ϵ, d1.t, log)
    end

    if (type1==stress_only) && (type2==stress_only)
        return RheoTimeData(d1.σ-d2.σ, RheoFloat[], d1.t, log)
    end

    if (type1==strain_and_stress) && (type2==strain_and_stress)
        return RheoTimeData(d1.σ-d2.σ, d1.ϵ-d2.ϵ, d1.t, log)
    end

end

function -(d::RheoTimeData)

    type = RheoTimeDataType(d)
    @assert (type!=invalid_time_data) "unary - error: parameter invalid"
    @assert (type!=time_only) "unary - error: time only data cannot be manipulated this way"

    log = d.log == nothing ? nothing : [d.log; RheoLogItem( (type=:process, funct=:-, params=(), keywords=() ), () ) ]

    return RheoTimeData(-d.σ, -d.ϵ, d.t, log)

end

# Required to execute +/- operators for logs

function +(rl1::RheoLog, rl2::RheoLog)
    return(rheologrun(rl1) + rheologrun(rl2))
end

function -(rl1::RheoLog, rl2::RheoLog)
    return(rheologrun(rl1) - rheologrun(rl2))
end

function *(operand::Real, d::RheoTimeData)

    type = RheoTimeDataType(d)
    @assert (type!=invalid_time_data) "* error: parameter invalid"
    @assert (type!=time_only) "* error: time only data cannot be manipulated this way"

    log = d.log == nothing ? nothing : [d.log; RheoLogItem( (type=:process, funct=:*, params=(operand = operand), keywords=() ), () ) ]

    return( RheoTimeData(operand .* d.σ, operand .* d.ϵ, d.t, log) )
end

function *(d::RheoTimeData, operand::Real)
    return(operand * d)
end

function |(d1::RheoTimeData, d2::RheoTimeData)
    # get types and type-check
    type1 = RheoTimeDataType(d1)
    type2 = RheoTimeDataType(d2)

    @assert ((type1==strain_only && type2==stress_only) || (type1==stress_only && type2==strain_only)) "One RheoTimeData must be stress_only and the other must be strain_only"

    # call recursively if not expected way round
    (type1==strain_only && type2==stress_only) && return d2|d1

    # expected case, type1==stress_only, type2==strain_only
    @assert d1.t==d2.t "Data to be combined must have same time-series"

    log = ((d1.log == nothing) || (d2.log == nothing)) ? nothing : [RheoLogItem((type = :source, funct = :|, params = (rl1 = d1.log, rl2 = d2.log), keywords = ()), ())]

    return RheoTimeData(d1.σ, d2.ϵ, d1.t, log)
end
#=
------------------------------------
Frequency data related functionality
------------------------------------
=#
"""
    RheoFreqData(Gp::Vector{T1}, Gpp::Vector{T2}, ω::Vector{T3}, log::OrderedDict{Any,Any}) where {T1<:Real, T2<:Real, T3<:Real}

`RheoFreqData` contains storage modulus, loss modulus and frequency data.

If preferred, an instance can be generated manually by just providing the three data
vectors in the right order.

# Fields

- `Gp`: storage modulus
- `Gpp`: loss modulus
- `ω`: frequency
- `log`: a log of struct's events, e.g. preprocessing
"""
struct RheoFreqData
    Gp::Vector{RheoFloat}
    Gpp::Vector{RheoFloat}
    ω::Vector{RheoFloat}

    log::Union{RheoLog,Nothing}
end

function RheoFreqData(;Gp::Vector{T1} = RheoFloat[], Gpp::Vector{T2} = RheoFloat[], ω::Vector{T3} = RheoFloat[], comment="Created from generic constructor", savelog = true, log = savelog ? RheoLogItem(comment) : nothing)  where {T1<:Real, T2<:Real, T3<:Real}
    typecheck = check_freq_data_consistency(ω,Gp,Gpp)
    RheoFreqData(convert(Vector{RheoFloat},Gp), convert(Vector{RheoFloat},Gpp), convert(Vector{RheoFloat},ω),
    log == nothing ? nothing : [ RheoLogItem(log.action,merge(log.info, (type=typecheck,)))]     )
end

@enum FreqDataType invalid_freq_data=-1 freq_only=0 with_modulus=1

function check_freq_data_consistency(o,gp,gpp)
    @assert (length(o)>0)  "Freq data empty"

    gpdef=(gp != RheoFloat[])
    gppdef=(gpp != RheoFloat[])
    if (gpdef && gppdef)
        @assert (length(gp)==length(o)) && (length(gpp)==length(o)) "Data length inconsistent"
        return with_modulus
    end

    if ((!gpdef) && (!gppdef))
        return freq_only
    end

    return invalid_freq_data

end

function RheoFreqDataType(d::RheoFreqData)
    return check_freq_data_consistency(d.ω,d.Gp,d.Gpp)
end

function Base.show(io::IO, d::RheoFreqData)
    b = length(d.ω) > 10
    n = b ? 10 : length(d.ω)

    if d.ω != RheoFloat[]
        print("ω =\t")
        for i = 1:n
            print("$(d.ω[i])\t")
        end
        println( b ? "..." : "")
    end
    if d.Gp != RheoFloat[]
        print("Gp =\t")
        for i = 1:n
            print("$(d.Gp[i])\t")
        end
        println( b ? "..." : "")
    end
    if d.Gpp != RheoFloat[]
        print("Gpp =\t")
        for i = 1:n
            print("$(d.Gpp[i])\t")
        end
        println( b ? "..." : "")
    end
end

#=
---------------------------------------
Log-based data generation functionality
---------------------------------------
=#
function rheologrun(rli::RheoLogItem, d=nothing)

      if typeof(rli.action) <: NamedTuple && :type in keys(rli.action)
         type=rli.action.type
         if type==:source && d==nothing
            return(eval(rli.action.funct)(rli.action.params...;rli.action.keywords...))
         elseif type==:process && d!=nothing
            return(eval(rli.action.funct)(d,rli.action.params...;rli.action.keywords...))
         elseif type==:analysis && d!=nothing
            return(eval(rli.action.funct)(d,rli.action.params...;rli.action.keywords...))
         end
      end
   println(rli.info)
end

"""
    rheologrun(log::RheoLog, d::Union{RheoTimeData,RheoFreqData,Nothing} = nothing)

execute all actions from the log. It applies them to the data `d` provided, or use the log's first action to reload/recreate it otherwise.
"""
function rheologrun(arli::RheoLog, d::Union{RheoTimeData,RheoFreqData,Nothing} = nothing)

  # check first item is a source item
  if d == nothing
        @assert typeof(arli[1].action) <: NamedTuple && :type in keys(arli[1].action) && arli[1].action.type == :source
                "Source missing in RheoLog"
        end

  for rli in arli
      if typeof(rli.action) <: NamedTuple && :type in keys(rli.action)
          type=rli.action.type
          if type==:analysis && d!=nothing
              rheologrun(rli, d)
          else  d=rheologrun(rli, d)
          end
      else
          println(rli.info)
      end
  end
  return(d)
end

"""
    showlog(d::Union{RheoTimeData,RheoFreqData})

shows the record of operations on a rheological data.
"""
function showlog(d::Union{RheoTimeData,RheoFreqData})
    if d.log != nothing
        for idx in 1:length(d.log)
            println(idx)
            print("     action = "); println(d.log[idx].action)
            print("     info   = "); println(d.log[idx].info)
        end
    else
        println("No log data available")
    end
end

#=
---------------------------
Model related functionality
---------------------------
=#
"""
    RheoModelClass(name::String, params::Vector{Symbol}, _G::FunctionWrapper{RheoFloat,Tuple{RheoFloat,Vector{RheoFloat}}}, _Ga::FunctionWrapper{Vector{RheoFloat},Tuple{Vector{RheoFloat},Vector{RheoFloat}}}, _J::FunctionWrapper{RheoFloat,Tuple{RheoFloat,Vector{RheoFloat}}}, _Ja::FunctionWrapper{Vector{RheoFloat},Tuple{Vector{RheoFloat},Vector{RheoFloat}}}, _Gp::FunctionWrapper{RheoFloat,Tuple{RheoFloat,Vector{RheoFloat}}}, _Gpa::FunctionWrapper{Vector{RheoFloat},Tuple{Vector{RheoFloat},Vector{RheoFloat}}}, _Gpp::FunctionWrapper{RheoFloat,Tuple{RheoFloat,Vector{RheoFloat}}}, _Gppa::FunctionWrapper{Vector{RheoFloat},Tuple{Vector{RheoFloat},Vector{RheoFloat}}}, constraint::FunctionWrapper{Bool,Tuple{Vector{RheoFloat}}}, info::String, expressions::NamedTuple)

`RheoModelClass` contains a model name, it's symbolic parameters and all its moduli (both single-input and array-input versions).

It also contains information about any constraints that must be observed (e.g. the springpot coefficient being inbetween 0 and 1).

Lastly, it also contains additional info about the model which may include a text-art schematic.

Generally, users will want to use the `RheoModelClass` constructor function as shown in the 'Create Your Model' section of the documentation
rather than the default constructor.
"""
struct RheoModelClass

    name::String
    params::Vector{Symbol}

    _G::FunctionWrapper{RheoFloat,Tuple{RheoFloat,Vector{RheoFloat}}}
    _Ga::FunctionWrapper{Vector{RheoFloat},Tuple{Vector{RheoFloat},Vector{RheoFloat}}}
    _J::FunctionWrapper{RheoFloat,Tuple{RheoFloat,Vector{RheoFloat}}}
    _Ja::FunctionWrapper{Vector{RheoFloat},Tuple{Vector{RheoFloat},Vector{RheoFloat}}}
    _Gp::FunctionWrapper{RheoFloat,Tuple{RheoFloat,Vector{RheoFloat}}}
    _Gpa::FunctionWrapper{Vector{RheoFloat},Tuple{Vector{RheoFloat},Vector{RheoFloat}}}
    _Gpp::FunctionWrapper{RheoFloat,Tuple{RheoFloat,Vector{RheoFloat}}}
    _Gppa::FunctionWrapper{Vector{RheoFloat},Tuple{Vector{RheoFloat},Vector{RheoFloat}}}

    constraint::FunctionWrapper{Bool,Tuple{Vector{RheoFloat}}}

    info::String
    expressions::NamedTuple

end

function Base.show(io::IO, m::RheoModelClass)
    print(io, "\nModel name: $(m.name)")
    ps=join([string(s) for s in m.params], ", ", " and ")
    print(io, "\n\nFree parameters: $ps\n")
    print(io, m.info)
    return
end

rheoconv(t::Real) = RheoFloat(t)
rheoconv(t::Array{T,1}) where T<:Real = convert(Vector{RheoFloat},t)

invLaplace(f::Function, t::Array{RheoFloat}) = InverseLaplace.talbotarr(f, t)
invLaplace(f::Function, t::RheoFloat) = InverseLaplace.talbot(f, t)

# place holder for undefined moduli/compliance functions
const nanexp = quote NaN end

function RheoModelClass(;name::String,
                         p::Array{Symbol},
                         G::Expr = nanexp,
                         J::Expr = nanexp,
                         Gp::Expr = nanexp,
                         Gpp::Expr = nanexp,
                         constraint::Expr = quote true end,
                         info="", #name * ": model with parameters " * string(join(string.(p), ", "), "."),
                         # flags to avoid bugs related to the FunctionWrappers and MittLeff
                         Ga_safe::Bool = true,
                         Ja_safe::Bool = true
                         )

    # Expression to unpack parameter array into suitably names variables in the moduli expressions
    unpack_expr = Meta.parse(string(join(string.(p), ","), ",=params"))
    expressions = (G=G,J=J,Gp=Gp,Gpp=Gpp,constraint=constraint, Ga_safe=Ga_safe, Ja_safe=Ja_safe)

    @eval return(RheoModelClass($name, $p,
        ((t,params) -> begin $unpack_expr; $G; end)                 |> FunctionWrapper{RheoFloat,Tuple{RheoFloat,Vector{RheoFloat}}},
        ((ta,params) -> begin $unpack_expr; [$G for t in ta]; end)  |> FunctionWrapper{Vector{RheoFloat},Tuple{Vector{RheoFloat},Vector{RheoFloat}}},
        ((t,params) -> begin $unpack_expr; $J; end)                 |> FunctionWrapper{RheoFloat,Tuple{RheoFloat,Vector{RheoFloat}}},
        ((ta,params) -> begin $unpack_expr; [$J for t in ta]; end)  |> FunctionWrapper{Vector{RheoFloat},Tuple{Vector{RheoFloat},Vector{RheoFloat}}},
        ((ω,params) -> begin $unpack_expr; $Gp; end)                |> FunctionWrapper{RheoFloat,Tuple{RheoFloat,Vector{RheoFloat}}},
        ((ωa,params) -> begin $unpack_expr; [$Gp for ω in ωa]; end) |> FunctionWrapper{Vector{RheoFloat},Tuple{Vector{RheoFloat},Vector{RheoFloat}}},
        ((ω,params) -> begin $unpack_expr; $Gpp; end)               |> FunctionWrapper{RheoFloat,Tuple{RheoFloat,Vector{RheoFloat}}},
        ((ωa,params) -> begin $unpack_expr; [$Gpp for ω in ωa]; end) |> FunctionWrapper{Vector{RheoFloat},Tuple{Vector{RheoFloat},Vector{RheoFloat}}},
        (params -> begin $unpack_expr; $constraint; end)            |> FunctionWrapper{Bool,Tuple{Vector{RheoFloat}}},
        $info, $expressions) )
end

# Cool replacement function inspired from
# https://stackoverflow.com/questions/29778698/julia-lang-metaprogramming-turn-expression-into-function-with-expression-depend
# This probably could go in its own module as this is very useful.
expr_replace!(ex, s, v) = ex
expr_replace!(ex::Symbol, s, v) = s == ex ? v : ex

function expr_replace!(ex::Expr, s, v)
    for i=1:length(ex.args)
        ex.args[i] = expr_replace!(ex.args[i], s, v)
    end
    return ex
end

expr_replace(ex, s, v) = expr_replace!(copy(ex), s, v)

function expr_replace(ex, nt)
    e = copy(ex)
    k=keys(nt)
    v=values(nt)
    for i in 1:length(nt)
        expr_replace!(e, k[i], v[i])
    end
    return e
end

function modulusexists(modulus_single_input)
   # 5.0 is not meaningful, just an input to check that the function does not return NaN
    # at a non-zero value, which is the default function output for moduli that have
    # not been defined.
    return !isnan(modulus_single_input(5.0))
end

#
#  Function used to check and convert any named tupple of parameters into an array ordered as expected by the moduli functions of the RheoModelClass
#

function check_and_reorder_parameters(nt::NamedTuple, params_order::Vector{Symbol}; err_string::String="calling function")
    # check that every parameter in m exists in the named tuple nt
    @assert all(i->i in keys(nt),params_order) "Missing parameter(s) in " * err_string
    # check that no extra parameters have been provided
    @assert length(params_order) == length(nt) "Mismatch number of model parameters and parameters provided in " * err_string

    p = map(i->RheoFloat(nt[i]), params_order)
end

"""
    freeze_params(m::RheoModelClass, nt0::NamedTuple)

Return a new `RheoModelClass` with some of the parameters frozen to specific values

# Arguments

- `m`: original `RheoModelClass`
- `nt0`: named tuple with values for each parameter to freeze

# Example
```@example
julia> SLS2_mod = freeze_params( SLS2, (G₀=2,η₂=3.5))
[...]

julia> SLS2.G(1,[2,1,2,3,3.5])
3.8796492

julia> SLS2_mod.G(1,[1,2,3])
3.8796492
```

"""
function freeze_params(m::RheoModelClass, nt0::NamedTuple)

    # check that every parameter in m exists in the named tuple nt
    @assert all( i-> i in m.params,keys(nt0)) "A parameter to freeze is not involved in the model"
    # convert values format for consistency
    nt=NamedTuple{keys(nt0)}([RheoFloat(i) for i in nt0])

    # create array of remaining variables
    p = filter(s -> !(s in keys(nt)),m.params)

    name="$(m.name) with set parameters: $nt"
    info = m.info

    # Expression to unpack parameter array into suitably names variables in the moduli expressions
    unpack_expr = Meta.parse(string(join(string.(p), ","), ",=params"))

    # This section creates moduli expressions with material parameters
    # replaced by specific values.

    G = expr_replace(m.expressions.G, nt)
    J = expr_replace(m.expressions.J, nt)
    Gp = expr_replace(m.expressions.Gp, nt)
    Gpp = expr_replace(m.expressions.Gpp, nt)
    constraint = expr_replace(m.expressions.constraint, nt)

    expressions=NamedTuple{(:G, :J, :Gp, :Gpp, :constraint, :Ga_safe, :Ja_safe)}( ( G, J, Gp, Gpp, constraint, m.expressions.Ga_safe, m.expressions.Ja_safe ) )

    @eval return( RheoModelClass($name, $p,
        ((t,params) -> begin $unpack_expr; $G; end) |> FunctionWrapper{RheoFloat,Tuple{RheoFloat,Vector{RheoFloat}}},
        ((ta,params) -> begin $unpack_expr; [$G for t in ta]; end) |> FunctionWrapper{Vector{RheoFloat},Tuple{Vector{RheoFloat},Vector{RheoFloat}}},
        ((t,params) -> begin $unpack_expr; $J; end) |> FunctionWrapper{RheoFloat,Tuple{RheoFloat,Vector{RheoFloat}}},
        ((ta,params) -> begin $unpack_expr; [$J for t in ta]; end) |> FunctionWrapper{Vector{RheoFloat},Tuple{Vector{RheoFloat},Vector{RheoFloat}}},
        ((ω,params) -> begin $unpack_expr; $Gp; end) |> FunctionWrapper{RheoFloat,Tuple{RheoFloat,Vector{RheoFloat}}},
        ((ωa,params) -> begin $unpack_expr; [$Gp for ω in ωa]; end) |> FunctionWrapper{Vector{RheoFloat},Tuple{Vector{RheoFloat},Vector{RheoFloat}}},
        ((ω,params) -> begin $unpack_expr; $Gpp; end) |> FunctionWrapper{RheoFloat,Tuple{RheoFloat,Vector{RheoFloat}}},
        ((ωa,params) -> begin $unpack_expr; [$Gpp for ω in ωa]; end) |> FunctionWrapper{Vector{RheoFloat},Tuple{Vector{RheoFloat},Vector{RheoFloat}}},
        (params -> begin $unpack_expr; $constraint; end) |> FunctionWrapper{Bool,Tuple{Vector{RheoFloat}}},
        $info, $expressions)   )
end

function freeze_params(m::RheoModelClass; kwargs...)
    return(freeze_params(m,kwargs.data))
end

"""
    RheoModel(_G::FunctionWrapper{RheoFloat,Tuple{RheoFloat}}, _Ga::FunctionWrapper{Vector{RheoFloat},Tuple{Vector{RheoFloat}}}, _J::FunctionWrapper{RheoFloat,Tuple{RheoFloat}}, _Ja::FunctionWrapper{Vector{RheoFloat},Tuple{Vector{RheoFloat}}}, _Gp::FunctionWrapper{RheoFloat,Tuple{RheoFloat}}, _Gpa::FunctionWrapper{Vector{RheoFloat},Tuple{Vector{RheoFloat}}}, _Gpp::FunctionWrapper{RheoFloat,Tuple{RheoFloat}}, _Gppa::FunctionWrapper{Vector{RheoFloat},Tuple{Vector{RheoFloat}}}, expressions::NamedTuple)

`RheoModel` contains all known moduli of a particular model, as for a `RheoModelClass` model name. However, a `RheoModel`
has all its parameters fixed to known values.

Generally, users will begin with a defined `RheoModelClass` (e.g. SLS) and then specialise the parameters,
rather than calling the default constructor explicitly.
"""
struct RheoModel

    _G::FunctionWrapper{RheoFloat,Tuple{RheoFloat}}
    _Ga::FunctionWrapper{Vector{RheoFloat},Tuple{Vector{RheoFloat}}}
    _J::FunctionWrapper{RheoFloat,Tuple{RheoFloat}}
    _Ja::FunctionWrapper{Vector{RheoFloat},Tuple{Vector{RheoFloat}}}
    _Gp::FunctionWrapper{RheoFloat,Tuple{RheoFloat}}
    _Gpa::FunctionWrapper{Vector{RheoFloat},Tuple{Vector{RheoFloat}}}
    _Gpp::FunctionWrapper{RheoFloat,Tuple{RheoFloat}}
    _Gppa::FunctionWrapper{Vector{RheoFloat},Tuple{Vector{RheoFloat}}}

    expressions::NamedTuple
    params::NamedTuple
    info::String    # do we need this?
    # log::OrderedDict{Any,Any}

end

function RheoModel(m::RheoModelClass; kwargs...)
    return(RheoModel(m,kwargs.data))
end

function RheoModel(m::RheoModelClass, nt0::NamedTuple)

    # check all parameters are provided and create a well ordered named tuple
    p = check_and_reorder_parameters(nt0, m.params, err_string = "model definition")
    nt=NamedTuple{Tuple(m.params)}(p)

    # string printed
    info = string("\nModel: $(m.name)\n\nParameter values: $nt \n",m.info)

    # This attaches expressions to the models with parameters substituted by values.
    # Future development: this could be disabled with a key word if processing time is an issue
    G = expr_replace(m.expressions.G, nt)
    J = expr_replace(m.expressions.J, nt)
    Gp = expr_replace(m.expressions.Gp, nt)
    Gpp = expr_replace(m.expressions.Gpp, nt)

    expressions=NamedTuple{(:G, :J, :Gp, :Gpp, :Ga_safe, :Ja_safe)}( ( G, J, Gp, Gpp, m.expressions.Ga_safe, m.expressions.Ja_safe ) )

    @eval return( RheoModel(
    (t -> begin $G; end) |> FunctionWrapper{RheoFloat,Tuple{RheoFloat}},
    (ta -> begin [$G for t in ta]; end) |> FunctionWrapper{Vector{RheoFloat},Tuple{Vector{RheoFloat}}},
    (t -> begin $J; end) |> FunctionWrapper{RheoFloat,Tuple{RheoFloat}},
    (ta -> begin [$J for t in ta]; end) |> FunctionWrapper{Vector{RheoFloat},Tuple{Vector{RheoFloat}}},
    (ω -> begin $Gp; end) |> FunctionWrapper{RheoFloat,Tuple{RheoFloat}},
    (ωa -> begin [$Gp for ω in ωa]; end) |> FunctionWrapper{Vector{RheoFloat},Tuple{Vector{RheoFloat}}},
    (ω -> begin $Gpp; end) |> FunctionWrapper{RheoFloat,Tuple{RheoFloat}},
    (ωa -> begin [$Gpp for ω in ωa]; end) |> FunctionWrapper{Vector{RheoFloat},Tuple{Vector{RheoFloat}}},
    $expressions, $nt, $info) )
end

function Base.show(io::IO, m::RheoModel)
    print(io,m.info)
end

#
# These are internal functions to access the array versions of the relaxation and creep moduli
# They are used in processing.jl as well as in the user level moduli functions
#

function _Ga(m::RheoModelClass)
    if m.expressions.Ga_safe
        m._Ga
    else
        (ta,p) -> [m._G(t,p) for t in ta]
    end
end

function _Ja(m::RheoModelClass)
    if m.expressions.Ja_safe
        m._Ja
    else
        (ta,p) -> [m._J(t,p) for t in ta]
    end
end

function _Ga(m::RheoModel)
    if m.expressions.Ga_safe
        m._Ga
    else
        ta -> [m._G(t) for t in ta]
    end
end

function _Ja(m::RheoModel)
    if m.expressions.Ja_safe
        m._Ja
    else
        ta -> [m._J(t) for t in ta]
    end
end

#
# These are functions enabling users to access the different moduli functions for each model
#

"""
    relaxmod(m[, t, params])

provide access to the relaxation modulus (G) of a given model m. It can be used with a broad range of inputs.

* When a time value is provided (or an array of time values), the function returns the corresponding value(s) of the relaxation modulus.

    relaxmod(m::RheoModel, time (single value or array))

    relaxmod(m::RheoModelClass, time (single value or array), parameters (array, named tupple or keyword parameters))

Examples:

    relaxmod(Maxwell, 1, k=1., η=1)

    relaxmod(Maxwell, [1,2,3], [1,1])

* When no time value is provided, relaxmod returns the relaxation function itself.

    relaxmod(m::RheoModel)

    relaxmod(m::RheoModelClass, parameters (array, named tupple or keyword parameters))

Examples:

    m = RheoModel(Maxwell, k=1., η=1)
    G = relaxmod(m)
    G([0,1,2])
"""
function relaxmod(m::RheoModel, t::Number)
    m._G(t)
end

function relaxmod(m::RheoModel, ta::Vector{T}) where T <: Number
    _Ga(m)(ta)
end

relaxmod(m) = x -> relaxmod(m,x)

function relaxmod(m::RheoModelClass, t::Number, params::Vector{T}) where T <: Number
    m._G(t, params)
end

function relaxmod(m::RheoModelClass, ta::Vector{T1}, params::Vector{T2}) where {T1 <: Number, T2 <: Number}
    _Ga(m)(ta, params)
end

function relaxmod(m::RheoModelClass, x, params::NamedTuple)
    # check all parameters are provided and create a well ordered named tuple
    p = check_and_reorder_parameters(params, m.params, err_string = "relaxmod")
    relaxmod(m, x, p)
end

function relaxmod(m::RheoModelClass, x; kwargs...)
    relaxmod(m, x, kwargs.data)
end

relaxmod(m::RheoModelClass, params::Vector{T}) where T <: Number =  x -> relaxmod(m, x, params)
relaxmod(m::RheoModelClass, params::NamedTuple) =  x -> relaxmod(m, x, params)
relaxmod(m::RheoModelClass; kwargs...) =  x -> relaxmod(m, x, kwargs.data)

"""
    creepcomp(m[, t, params])

provide access to the creep compliance (J) of a given model m. It can be used with a broad range of inputs.

* When a time value is provided (or an array of time values), the function returns the corresponding value(s) of the creep compliance.

    creepcomp(m::RheoModel, time (single value or array))

    creepcomp(m::RheoModelClass, time (single value or array), parameters (array, named tupple or keyword parameters))

Examples:

    creepcomp(Maxwell, 1, k=1., η=1)

    creepcomp(Maxwell, [1,2,3], [1,1])

* When no time value is provided, creepcomp returns the creep compliance function itself.

    creepcomp(m::RheoModel)

    creepcomp(m::RheoModelClass, parameters (array, named tupple or keyword parameters))

Examples:

    m = RheoModel(Maxwell, k=1., η=1)
    J = creepcomp(m)
    J([0,1,2])
"""
function creepcomp(m::RheoModel, t::Number)
    m._J(t)
end

function creepcomp(m::RheoModel, ta::Vector{T}) where T <: Number
    _Ja(m)(ta)
end

creepcomp(m) = x -> creepcomp(m,x)

function creepcomp(m::RheoModelClass, t::Number, params::Vector{T}) where T <: Number
    m._J(t, params)
end

function creepcomp(m::RheoModelClass, ta::Vector{T1}, params::Vector{T2}) where {T1 <: Number, T2 <: Number}
    _Ja(m)(ta, params)
end

function creepcomp(m::RheoModelClass, x, params::NamedTuple)
    # check all parameters are provided and create a well ordered named tuple
    p = check_and_reorder_parameters(params, m.params, err_string = "relaxmod")
    creepcomp(m, x, p)
end

function creepcomp(m::RheoModelClass, x; kwargs...)
    creepcomp(m, x, kwargs.data)
end

creepcomp(m::RheoModelClass, params::Vector{T}) where T <: Number =  x -> creepcomp(m, x, params)
creepcomp(m::RheoModelClass, params::NamedTuple) =  x -> creepcomp(m, x, params)
creepcomp(m::RheoModelClass; kwargs...) =  x -> creepcomp(m, x, kwargs.data)

"""
    storagemod(m[, ω, params])

provide access to the storage modulus (G') of a given model m.

* When a frequency value is provided (or an array of frequency values), the function returns the corresponding value(s) of the storage modulus.

    storagemod(m::RheoModel, frequency (single value or array))

    storagemod(m::RheoModelClass, frequency (single value or array), parameters (array, named tupple or keyword parameters))

Examples:

    storagemod(Maxwell, 1, k=1., η=1)

    storagemod(Maxwell, [1,2,3], [1,1])

* When no time value is provided, storagemod returns the storage modulus function itself.

    storagemod(m::RheoModel)

    storagemod(m::RheoModelClass, parameters (array, named tupple or keyword parameters))

Examples:

    m = RheoModel(Maxwell, k=1., η=1)
    Gp = storagemod(m)
    Gp([0,1,2])
"""
function storagemod(m::RheoModel, ω::Number)
    m._Gp(ω)
end

function storagemod(m::RheoModel, ωa::Vector{T}) where T <: Number
    m._Gpa(ωa)
end

storagemod(m) = x -> storagemod(m,x)

function storagemod(m::RheoModelClass, ω::Number, params::Vector{T}) where T <: Number
    m._Gp(ω, params)
end

function storagemod(m::RheoModelClass, ωa::Vector{T1}, params::Vector{T2}) where {T1 <: Number, T2 <: Number}
      m._Gpa(ωa, params)
end

function storagemod(m::RheoModelClass, x, params::NamedTuple)
    # check all parameters are provided and create a well ordered named tuple
    p = check_and_reorder_parameters(params, m.params, err_string = "relaxmod")
    storagemod(m, x, p)
end

function storagemod(m::RheoModelClass, x; kwargs...)
    storagemod(m, x, kwargs.data)
end

storagemod(m::RheoModelClass, params::Vector{T}) where T <: Number =  x -> storagemod(m, x, params)
storagemod(m::RheoModelClass, params::NamedTuple) =  x -> storagemod(m, x, params)
storagemod(m::RheoModelClass; kwargs...) =  x -> storagemod(m, x, kwargs.data)

"""
    lossmod(m[, ω, params])

provide access to the loss modulus (G'') of a given model m.

* When a frequency value is provided (or an array of frequency values), the function returns the corresponding value(s) of the loss modulus.

    lossmod(m::RheoModel, frequency (single value or array))

    lossmod(m::RheoModelClass, frequency (single value or array), parameters (array, named tupple or keyword parameters))

Examples:

    lossmod(Maxwell, 1, k=1., η=1)

    lossmod(Maxwell, [1,2,3], [1,1])

* When no time value is provided, lossmod returns the loss modulus function itself.

    lossmod(m::RheoModel)

    lossmod(m::RheoModelClass, parameters (array, named tupple or keyword parameters))

Examples:

    m = RheoModel(Maxwell, k=1., η=1)
    Gpp = lossmod(m)
    Gpp([0,1,2])
"""
function lossmod(m::RheoModel, ω::Number)
    m._Gpp(ω)
end

function lossmod(m::RheoModel, ωa::Vector{T}) where T <: Number
    m._Gppa(ωa)
end

lossmod(m) = x -> lossmod(m,x)

function lossmod(m::RheoModelClass, ω::Number, params::Vector{T}) where T <: Number
    m._Gpp(ω, params)
end

function lossmod(m::RheoModelClass, ωa::Vector{T1}, params::Vector{T2}) where {T1 <: Number, T2 <: Number}
    m._Gppa(ωa, params)
end

function lossmod(m::RheoModelClass, x, params::NamedTuple)
    # check all parameters are provided and create a well ordered named tuple
    p = check_and_reorder_parameters(params, m.params, err_string = "relaxmod")
    lossmod(m, x, p)
end

function lossmod(m::RheoModelClass, x; kwargs...)
    lossmod(m, x, kwargs.data)
end

lossmod(m::RheoModelClass, params::Vector{T}) where T <: Number =  x -> lossmod(m, x, params)
lossmod(m::RheoModelClass, params::NamedTuple) =  x -> lossmod(m, x, params)
lossmod(m::RheoModelClass; kwargs...) =  x -> lossmod(m, x, kwargs.data)

"""
    dynamicmod(m[, ω, params])

provide access to the complex dynamic modulus (G' + i G'') of a given model m.

* When a frequency value is provided (or an array of frequency values), the function returns the corresponding value(s) of the complex dynamic modulus.

    dynamicmod(m::RheoModel, frequency (single value or array))

    dynamicmod(m::RheoModelClass, frequency (single value or array), parameters (array, named tupple or keyword parameters))

Examples:

    dynamicmod(Maxwell, 1, k=1., η=1)

    dynamicmod(Maxwell, [1,2,3], [1,1])

* When no time value is provided, lossmod returns the complex dynamic modulus function itself.

    dynamicmod(m::RheoModel)

    dynamicmod(m::RheoModelClass, parameters (array, named tupple or keyword parameters))

Examples:

    m = RheoModel(Maxwell, k=1., η=1)
    Gpp = dynamicmod(m)
    Gpp([0,1,2])

Note: use abs() and angle() to get the magnitude and phase of the complex modulus.
"""
function dynamicmod(m::RheoModel, ω::Number)
    m._Gp(ω) + m._Gpp(ω) * im
end

function dynamicmod(m::RheoModel, ωa::Vector{T}) where T <: Number
    m._Gpa(ωa) + m._Gppa(ωa) * im
end

dynamicmod(m) = x -> dynamicmod(m,x)

function dynamicmod(m::RheoModelClass, ω::Number, params::Vector{T}) where T <: Number
    m._Gp(ω) + m._Gpp(ω) * im
end

function dynamicmod(m::RheoModelClass, ωa::Vector{T1}, params::Vector{T2}) where {T1 <: Number, T2 <: Number}
    m._Gpa(ωa, params) + m._Gppa(ωa, params) * im
end

function dynamicmod(m::RheoModelClass, x, params::NamedTuple)
    # check all parameters are provided and create a well ordered named tuple
    p = check_and_reorder_parameters(params, m.params, err_string = "relaxmod")
    dynamicmod(m, x, p)
end

function dynamicmod(m::RheoModelClass, x; kwargs...)
    dynamicmod(m, x, kwargs.data)
end

dynamicmod(m::RheoModelClass, params::Vector{T}) where T <: Number =  x -> dynamicmod(m, x, params)
dynamicmod(m::RheoModelClass, params::NamedTuple) =  x -> dynamicmod(m, x, params)
dynamicmod(m::RheoModelClass; kwargs...) =  x -> dynamicmod(m, x, kwargs.data)


# Set up model for testing
using ModelObjectsLH, ModelParams

import ModelParams.has_pvector

mdl = ModelParams;

mutable struct Obj1 <: ModelObject
    objId :: ObjectId
    x :: Float64
    y :: Vector{Float64}
    z :: Array{Float64,2}
    pvec :: ParamVector
end

# function Obj1(x, y, z)
#     return Obj1(x, y, z, ParamVector(ObjectId(:pv1)))
# end

function init_obj1(objId)
    # objId = ObjectId(:obj1);
    px = Param(:x, "x obj1", "x1", 11.1, 9.9, 1.1, 99.9, true);
    # Important to have vector of length 1 as test case
    valueY = fill(1.1, 1);
    py = Param(:y, "y obj1", "y1", valueY, valueY .+ 1.0,
        valueY .- 5.0, valueY .+ 5.0, true);
    valueZ = [3.3 4.4; 5.5 7.6];
    pz = Param(:z, "z obj1", "z1", valueZ, valueZ .+ 1.0,
        valueZ .- 5.0, valueZ .+ 5.0, false);
    pvector = ParamVector(objId, [px, py, pz])
    o1 = Obj1(objId, px.value, py.value, pz.value, pvector);
    ModelParams.set_own_values_from_pvec!(o1, true);
    return o1
end

mutable struct Obj3Switches <: ModelSwitches
    pvec :: ParamVector
end

function init_obj3_switches(objId :: ObjectId)
    # objId = ObjectId(:obj3);
    px = Param(:x, "x", "x", 0.5, 0.6, 0.0, 1.0, true);
    yV = [1.0, 2.0];
    py = Param(:y, "y", "y", yV, yV .+ 1.0, [0.0, 0.0], [9.0, 9.0], false);
    v = [1.0 2.0 3.0; 1.1 2.1 3.1];
    isCalM = [true false true; false true false];
    pca = CalArray(:ca, "CalArray", "CalArray", v, v .+ 0.1, v .- 2.0, v .+ 2.0,
        isCalM);
    pvec = ParamVector(objId, [px, py, pca]);
    return Obj3Switches(pvec)
end

ModelObjectsLH.get_object_id(switches :: Obj3Switches) = get_object_id(switches.pvec);

mutable struct Obj3 <: ModelObject
    objId :: ObjectId
    switches :: Obj3Switches
    x :: Float64
    y :: Vector{Float64}
    ca :: Matrix{Float64}
end

ModelParams.get_pvector(o :: Obj3) = o.switches.pvec;

function init_obj3(objId :: ObjectId)
    switches = init_obj3_switches(objId);
    ca = retrieve(switches.pvec, :ca);
    return Obj3(get_object_id(switches), switches, 0.5, [1.0, 2.0], default_value(ca));
end

mutable struct Obj2 <: ModelObject
    objId :: ObjectId
    a :: Float64
    y :: Float64
    b :: Array{Float64,2}
    bvec :: Vector{Float64}
    o3 :: Obj3
    pvec :: ParamVector
end

# function Obj2(a, y, b)
#     return Obj2(a, y, b, ParamVector(ObjectId(:pv1)))
# end

function init_obj2(objId)
    # objId = ObjectId(:obj2);
    pa = Param(:a, "a obj2", "a2", 12.1, 7.9, -1.1, 49.9, true);
    valueY = 9.4;
    py = Param(:y, "y obj2", "y2", valueY, valueY .+ 1.0,
        valueY .- 5.0, valueY .+ 5.0, true);
    valueB = 2.0 .+ [3.3 4.4; 5.5 7.6];
    pb = Param(:b, "b obj2", "b2", valueB, valueB .+ 1.0,
        valueB .- 5.0, valueB .+ 5.0, true);
    pBvec = mdl.make_test_bvector(:bvec; isCalibrated = true, increasing = :increasing);
    pvector = ParamVector(objId, [pa, py, pb, pBvec]);
    obj3 = init_obj3(make_child_id(objId, :obj3));
    o2 = Obj2(objId, pa.value, py.value, pb.value, ModelParams.value(pBvec), obj3, pvector);
    ModelParams.set_own_values_from_pvec!(o2, true);
    return o2
end

mutable struct TestModel <: ModelObject
    objId :: ObjectId
    o1 :: Obj1
    o2 :: Obj2
    a :: Float64
    y :: Float64
end

has_pvector(o :: TestModel) = false;

function init_test_model()
    objName = ObjectId(:testModel, "Test model");
    o1 = init_obj1(ModelParams.make_child_id(objName, :o1, "Child object 1"));
    o2 = init_obj2(ModelParams.make_child_id(objName, :o2, "Child object 2"));
    m = TestModel(objName, o1, o2, 9.87, 87.73);
    sync_values!(m);
    return m
end

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


# optmizie.jl - optimize ExGraph

const OPT_PHS = [:X, :Y, :Z, :V, :W,
                 :i, :j, :k, :l, :m, :n, :p, :q, :r, :s, :t]

const OPT_VEC_RULES = [
    :(Z = X * transpose(Y)) => :(Z = X * Y'),
    :(Z = X * Y') => :(Z = X * Y'),
    :(Z = transpose(X) * Y) => :(Z = X' * Y),
    :(Z = X' * Y) => :(Z = X' * Y),
]

function remove_unused(g::AbstractExGraph, outvars::Vector{Symbol})
    deps = collect_deps(g, outvars)
    push!(deps, outvars...)
    gr = reset_tape(g)
    for nd in g.tape
        if in(varname(nd), deps)
            push!(gr, nd)
        end
    end
    return gr
end

remove_unused(g::AbstractExGraph, outvar::Symbol) = remove_unused(g, [outvar])
remove_unused(g::AbstractExGraph) = remove_unused(g, [varname(g[end])])

"""
Removes unused variables from multiline expressions, e.g. in:

    x = u * v
    y = x + 1
    z = 2x

`y` isn't used to compute output variable `z`, so it's removed:

    x = u * v
    z = 2x

"""
function remove_unused(ex::Expr; output_vars=nothing)
    ex.head == :block || return ex  # nothing to remove
    g = ExGraph(ex; fuse=false)
    output_vars = output_vars != nothing ? output_vars : [varname(g[end])]
    deps = collect_deps(g, output_vars)
    for vname in output_vars
        push!(deps, vname)
    end
    res = quote end
    for subex in ex.args
        if subex.head == :(=)
            vname = split_indexed(subex.args[1])[1]
            if in(vname, deps)
                push!(res.args, subex)
            end
        else
            push!(res.args, subex)
        end
    end
    return res
end

function reset_tape(g::G) where {G <: AbstractExGraph}
    # new_g = deepcopy(g)
    new_g = G()
    new_g.tape = []
    new_g.idx = Dict()
    new_g.ctx = g.ctx
    return new_g
end

function tryoptimize(ex::Expr)
    for (pat, subs_ex) in OPT_VEC_RULES
        new_ex = tryrewrite(ex, pat, subs_ex; phs=OPT_PHS, allow_ex=false)
        if new_ex != nothing
            genname_patterns = unique(findex(:(genname__(_n)), new_ex))
            if !isempty(genname_patterns)
                new_names = gennames(length(genname_patterns))
                st = Dict(zip(genname_patterns, new_names))
                return subs(new_ex, st)
            else
                return new_ex
            end
        end
    end
    return nothing
end

function expand_fixed_sequences(g::ExGraph, nd::ExNode)
    ex = to_expr(nd)
    changed = false
    for dep in dependencies(nd)
        expanded = subs(ex, Dict(dep => getexpr(g[dep])))
        new_ex = tryoptimize(expanded)
        if new_ex != nothing
            ex = new_ex
            changed = true
        end
    end
    return changed ? copy(nd; category=:opaque, var=ex.args[1], ex=ex.args[2]) : nd
end

"""
Look at each node's dependencies and, if there are known pattern sequences,
rewrite them in a more optimal way.
"""
function expand_fixed_sequences(g::ExGraph)
    new_g = reset_tape(g)
    for nd in g.tape
        if isa(nd, ExNode{:input})
            push!(new_g, nd)
        else
            new_nd = expand_fixed_sequences(g, nd)
            push!(new_g, new_nd)
        end
    end
    new_g = remove_unused(new_g,  varname(new_g[end]))
    # new_g = fuse_assigned(new_g)
    return new_g
end

# common subexpression elimination

safe_size(a::AbstractArray) = size(a)
safe_size(a) = nothing

function eliminate_common(g::AbstractExGraph)
    g = reindex_from_beginning(g)
    new_g = reset_tape(g)
    existing = Dict()             # index of existing expressions
    st = Dict{Symbol,Symbol}()    # later var -> previous var with the same expression
    for nd in g.tape
        new_full_ex = subs(to_expr_kw(nd), st)
        vname, vidxs = split_indexed(new_full_ex.args[1])
        key = string((vidxs, new_full_ex.args[2]))
        if haskey(existing, key) &&
            (g[vname] |> getvalue |> safe_size) == (g[existing[key]] |> getvalue |> safe_size)
            st[vname] = existing[key]
        else
            # C = getcategory(nd)
            new_nd = copy(nd)
            setexpr_kw!(new_nd, new_full_ex.args[2])
            push!(new_g, new_nd)
            existing[key] = vname
        end
    end
    rename!(new_g, st)
    return new_g
end

# fuse broadcasting

function is_bcast_vec(nd::ExNode{:opaque})
    # convert to expression, re-parse and check every node
    return all(is_bcast_vec(nd) for nd in ExGraph(to_expr(nd)).tape)
end

const OLD_BCAST_OPS = Set([:.+, :.-, :.*, :./, :.^])

is_bcast_vec(nd::ExNode{:call}) = getexpr(nd).args[1] in OLD_BCAST_OPS
is_bcast_vec(nd::ExNode{:bcast}) = true
is_bcast_vec(nd::ExNode) = false

function fuse_broadcasting_node(g::ExGraph, new_g::ExGraph, nd::ExNode)
    dep_nds = [new_g[dep] for dep in dependencies(nd)]
    if any(is_bcast_vec(dep_nd) for dep_nd in dep_nds)
        # at least one dependency is broadcasting
        # transform node to :opaque and expand dep expression
        st = Dict{Any, Any}()
        for dep_nd in dep_nds
            if is_bcast_vec(dep_nd)
                # if dependency is broadcasting, replace its name with its expression
                dep_ex = getexpr(dep_nd)
                # idx_st = Dict(zip(varidxs(dep_nd),
                #                   get_indices_in_expr(getexpr(nd), varname(dep_nd))))
                # new_dep_ex = subs(dep_ex, idx_st)
                new_dep_ex = dep_ex
                st[getvar(dep_nd)] = new_dep_ex
            end
        end
        new_ex = subs(getexpr(nd), st)
        return copy(nd; ex=new_ex, category=:opaque)
    else
        return nd
    end
end

function fuse_broadcasting(g::ExGraph)
    new_g = reset_tape(g)
    for nd in g.tape
        if is_bcast_vec(nd)
            push!(new_g, fuse_broadcasting_node(g, new_g, nd))
        else
            push!(new_g, nd)
        end
    end
    return remove_unused(new_g, varname(g[end]))
end


# RELOG: Reverse Logistics Optimization
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
# Released under the modified BSD license. See COPYING.md for more details.

using DataFrames
using CSV

function products_report(solution; marginal_costs = true)::DataFrame
    df = DataFrame()
    df."product name" = String[]
    df."location name" = String[]
    df."latitude (deg)" = Float64[]
    df."longitude (deg)" = Float64[]
    df."year" = Int[]
    df."amount (tonne)" = Float64[]
    df."marginal cost (\$/tonne)" = Float64[]
    T = length(solution["Energy"]["Plants (GJ)"])
    for (prod_name, prod_dict) in solution["Products"]
        for (location_name, location_dict) in prod_dict
            for year = 1:T
                marginal_cost = location_dict["Marginal cost (\$/tonne)"][year]
                latitude = round(location_dict["Latitude (deg)"], digits = 6)
                longitude = round(location_dict["Longitude (deg)"], digits = 6)
                amount = location_dict["Amount (tonne)"][year]
                push!(
                    df,
                    [
                        prod_name,
                        location_name,
                        latitude,
                        longitude,
                        year,
                        amount,
                        marginal_cost,
                    ],
                )
            end
        end
    end
    return df
end

write_products_report(solution, filename) = CSV.write(filename, products_report(solution))


module Day6

using Advent: save_input, eachlinegroup

# use codeunits. and BitSet for a slight performance gain over Vector{Char} and Set

function solve_a(filename)
    sum(eachlinegroup(filename)) do group
        codeunits.(group) |> Iterators.flatten |> BitSet |> length
    end
end

function solve_b(filename)
    sum(eachlinegroup(filename)) do group
        reduce(intersect, BitSet.(codeunits.(group))) |> length
    end
end

solve(filename = save_input(2020, 6)) = solve_a(filename), solve_b(filename)

end


using GridUtilities
using Test
##using TestSetExtensions

#@test isempty(detect_ambiguities(ViscousFlow))
include("histories.jl")

#@testset ExtendedTestSet "All tests" begin
#    @includetests ARGS
#end

#if isempty(ARGS)
#    include("../docs/make.jl")
#end


#=
CGP.jl is a library for using
[Cartesian Genetic Programming](http://www.cartesiangp.co.uk/) in
Julia. It is being developed at the University of Montana in Missoula,
MT for use in simulating the evolution of technology, though there is
nothing specific to that application in the library so it is (will be)
perfectly suitable for other applications as well.
=#

using Distributed
using DataFrames
#using DataFramesMeta
using StatsBase
using Combinatorics
using Printf
using Dates
using CSV
using Statistics
#using Revise
using Random

module CGP
using Distributed
using DataFrames
using StatsBase
using Combinatorics
using Printf
using Dates
using CSV
using Statistics
using Random
#const MyInt = UInt8       # Type of bit string integers used in bit functions
const MyInt = UInt16     # Type of bit string integers used in bit functions
#const MyInt = UInt32     # Type of bit string integers used in bit functions
#const MyInt = UInt64     # Type of bit string integers used in bit functions
#const MyInt = UInt128     # Type of bit string integers used in bit functions
#const MyInt = BigInt      # Type of bit string integers used in bit functions
const MyFunc = UInt128  # Type of concatenated output representation of functions
const maxints_for_degen = 20
include("aliases.jl")
include("Contexts.jl")
include("Parameters.jl")
#include("SetParams.jl")
include("Node.jl")
include("Chromosome.jl")
include("LinChromosome.jl")
include("Goals.jl")
include("Evolve.jl")
#include("Chrome_result.jl")
include("Execute.jl")
include("Func.jl")
include("Entropy.jl")
#include("Avg_mut_robustness.jl")
include("InfTheory.jl")
include("Indiv_evolution.jl")
include("Genotype_phenotype.jl")  # computations of evolvability and robustness are not ccorrect
#include("Env_evolution.jl")
include("Propsel.jl")
#include("Robust_evolve.jl")
#include("Inf_alleles.jl")
include("Analyze.jl")
include("RecordOutputs.jl")
include("Evolvability.jl")
#include("Build_chromosome.jl")
#include("epistasis.jl")
include("Complexity.jl")
include("Degeneracy.jl")
include("Evo_dict.jl")
include("Composition.jl")
#include("random_walk.jl")
end
using Main.CGP
MyInt=Main.CGP.MyInt


using FactCheck
using FunctionalCollections

typealias PS PersistentSet

facts("Persistent Sets") do

    context("construction") do
        s = PS(1, 1, 2, 3, 3)
        @fact length(s) --> 3
        @fact length(PS{AbstractString}()) --> 0
        @fact typeof(PS{Integer}([1,2,3])) --> PS{Integer}
        @fact typeof(PS(Integer[1,2,3])) --> PS{Integer}
    end

    context("isequal") do
        @fact PS(1, 2, 3) --> PS(1, 2, 3)
        @fact PS(1, 2, 3) --> PS(3, 2, 1)
        @fact PS{AbstractString}() --> PS{Int}()
    end

    context("conj") do
        @fact conj(PS(1, 2, 3), 4) --> PS(1, 2, 3, 4)
        @fact conj(PS(1, 2, 3), 1) --> PS(1, 2, 3)
        @fact conj(PS(1, 2, 3), 4) --> PS(4, 3, 2, 1)
    end

    context("disj") do
        @fact disj(PS(1, 2, 3), 3) --> PS(1, 2)
        @fact disj(PS(1, 2), 3) --> PS(1, 2)
        @fact disj(PS{Int}(), 1234) --> PS{Int}()
    end

    context("in") do
        @fact "foo" in PS("foo", "bar") --> true
        @fact "baz" in PS("foo", "bar") --> false
    end

    context("filter") do
        @fact filter(iseven, PS(1, 2, 3, 4)) --> PS(2, 4)
    end

    context("setdiff, -") do
        @fact setdiff(PS(1, 2, 3), PS(1, 2)) --> PS(3)
        @fact setdiff(PS(1, 2), PS(1, 2, 3)) --> PS{Int}()
        @fact setdiff(PS(1, 2, 3), Set([1, 2])) --> PS(3)

        @fact PS(1, 2, 3) - PS(1, 2) --> PS(3)
    end

    context("isempty") do
        @fact PS{Int}() --> isempty
        @fact PS(1) --> not(isempty)
    end

    context("union") do
        @fact union(PS(1, 2, 3), PS(4, 5)) --> PS(1, 2, 3, 4, 5)
        @fact union(PS(1, 2, 3), PS(1, 2, 3, 4)) --> PS(1, 2, 3, 4)
        @fact union(PS(1), PS(2), PS(3)) --> PS(1, 2, 3)
    end

    context("isless, <=") do
        @fact PS(1)    <= PS(1, 2) --> true
        @fact PS(1, 2) <= PS(1, 2) --> true

        @fact PS(1, 2, 3) <= PS(1, 2) --> false
        @fact PS(1, 2) <= PS(1, 2, 3) --> true

        @fact isless(PS(1, 2), PS(1, 2))    --> false
        @fact isless(PS(1, 2), PS(1, 2, 3)) --> true
    end

    context("iteration") do
        @fact length([el for el in PS(1, 2, 3, 4)]) --> 4
    end

end


module RenewalProcesses

include("calculus.jl")
include("probabilities.jl")

end # module


"An abstract type for the problems from [NSDE.jl](https://github.com/antonuccig/NSDE.jl)."
abstract type AbstractNSDEProblem end

"An abstract type for the solvers from [NSDE.jl](https://github.com/antonuccig/NSDE.jl)."
abstract type AbstractNSDESolver end

"An abstract type for the solutions from [NSDE.jl](https://github.com/antonuccig/NSDE.jl)."
abstract type AbstractNSDESolution end

"An abstract type for initial value problems."
abstract type AbstractInitialValueProblem <: AbstractNSDEProblem end

"An abstract type for the solvers of [`AbstractInitialValueProblem`](@ref)s."
abstract type AbstractInitialValueSolver <: AbstractNSDESolver end

"An abstract type for the solution of an [`AbstractInitialValueProblem`](@ref) obtained with an [`AbstractInitialValueSolver`](@ref)."
abstract type AbstractInitialValueSolution <: AbstractNSDESolution end

"An abstract type for the right-hand side of an [`AbstractInitialValueProblem`](@ref)."
abstract type AbstractRightHandSideFunction end