text:
"""
vaporpressure(surface_pressure::ClimGrid, specific_humidity::ClimGrid)

Returns the vapor pressure (vp) (Pa) based on the surface pressure (sp) (Pa) and the specific humidity (q).

``vp = \\frac{q * sp}{q+0.622}``

"""
function vaporpressure(specific_humidity::ClimGrid, surface_pressure::ClimGrid)
    @argcheck surface_pressure[9] == "ps"
    @argcheck specific_humidity[9] == "huss"
    @argcheck surface_pressure[2] == "Pa"

    # Calculate vapor pressure
    vp_arraytmp = (specific_humidity.data .* surface_pressure.data) ./ (specific_humidity.data .+ 0.622)
    vp_array = buildarrayinterface(vp_arraytmp, surface_pressure)

    # Build dictionary for the variable vp
    vp_dict = surface_pressure.varattribs
    vp_dict["standard_name"] = "water_vapor_pressure"
    vp_dict["units"] = "Pa"
    vp_dict["history"] = "Water vapor pressure calculated by a function of surface_pressure and specific humidity"

    # Build ClimGrid object
    return ClimGrid(vp_array, longrid=surface_pressure.longrid, latgrid=surface_pressure.latgrid, msk=surface_pressure.msk, grid_mapping=surface_pressure.grid_mapping, dimension_dict=surface_pressure.dimension_dict, timeattrib=surface_pressure.timeattrib, model=surface_pressure.model, frequency=surface_pressure.frequency, experiment=surface_pressure.experiment, run=surface_pressure.run, project=surface_pressure.project, institute=surface_pressure.institute, filename=surface_pressure.filename, dataunits="Pa", latunits=surface_pressure.latunits, lonunits=surface_pressure.lonunits, variable="vp", typeofvar="vp", typeofcal=surface_pressure.typeofcal, varattribs=vp_dict, globalattribs=surface_pressure.globalattribs)
end

"""
vaporpressure(specific_humidity::ClimGrid, sealevel_pressure::ClimGrid, orography::ClimGrid, daily_temperature::ClimGrid)

Returns the vapor pressure (vp) (Pa) estimated with the specific humidity (q), the sea level pressure (psl) (Pa), the orography (orog) (m) and the daily mean temperature (tas) (K). An approximation of the surface pressure is first computed by using the sea level pressure, orography and the daily mean temperature (see [`approx_surfacepressure`](@ref)). Then, vapor pressure is calculated by:

``vp = \\frac{q * sp}{q+0.622}``

"""
function vaporpressure(specific_humidity::ClimGrid, sealevel_pressure::ClimGrid, orography::ClimGrid, daily_temperature::ClimGrid)

    @argcheck specific_humidity[9] == "huss"
    @argcheck sealevel_pressure[9] == "psl"
    @argcheck orography[9] == "orog"
    @argcheck daily_temperature[9] == "tas"
    @argcheck sealevel_pressure[2] == "Pa"
    @argcheck orography[2] == "m"
    @argcheck in(daily_temperature[2], ["Celsius", "K", "°C"])

    # Convert to Kelvin if necessery
    if daily_temperature[2] == "Celsius" || daily_temperature[2] == "°C"
        daily_temperature = daily_temperature + 273.15
    end

    # Calculate the estimated surface pressure
    surface_pressure = approx_surfacepressure(sealevel_pressure, orography, daily_temperature)

    # Calculate vapor pressure
    vapor_pressure = vaporpressure(specific_humidity, surface_pressure)

    # Return ClimGrid type containing the vapor pressure
    return vapor_pressure
end

"""
approx_surfacepressure(sealevel_pressure::ClimGrid, orography::ClimGrid, daily_temperature::ClimGrid)

Returns the approximated surface pressure (*sp*) (Pa) using sea level pressure (*psl*) (Pa), orography (*orog*) (m), and daily mean temperature (*tas*) (K).

``sp = psl * 10^{x}``

where ``x = \\frac{-orog}{18400 * tas / 273.15} ``

"""
function approx_surfacepressure(sealevel_pressure::ClimGrid, orography::ClimGrid, daily_temperature::ClimGrid)
    @argcheck sealevel_pressure[9] == "psl"
    @argcheck orography[9] == "orog"
    @argcheck daily_temperature[9] == "tas"

    # Calculate the estimated surface pressure
    exponent = (-1.0 .* orography.data) ./ (18400.0 .* daily_temperature.data ./ 273.15)
    ps_arraytmp = sealevel_pressure.data .* (10.0.^exponent)
    # ps_arraytmp = [ps_arraytmp][1]Pa
    ps_array = buildarrayinterface(ps_arraytmp, sealevel_pressure)

    # Build dictionary for the variable vp
    ps_dict = sealevel_pressure.varattribs
    ps_dict["standard_name"] = "surface_pressure"
    ps_dict["units"] = "Pa"
    ps_dict["history"] = "Surface pressure estimated with the sealevel pressure, the orography and the daily temperature"

    # Build ClimGrid object
    return ClimGrid(ps_array, longrid=sealevel_pressure.longrid, latgrid=sealevel_pressure.latgrid, msk=sealevel_pressure.msk, grid_mapping=sealevel_pressure.grid_mapping, dimension_dict=sealevel_pressure.dimension_dict, timeattrib=sealevel_pressure.timeattrib, model=sealevel_pressure.model, frequency=sealevel_pressure.frequency, experiment=sealevel_pressure.experiment, run=sealevel_pressure.run, project=sealevel_pressure.project, institute=sealevel_pressure.institute, filename=sealevel_pressure.filename, dataunits="Pa", latunits=sealevel_pressure.latunits, lonunits=sealevel_pressure.lonunits, variable="ps", typeofvar="ps", typeofcal=sealevel_pressure.typeofcal, varattribs=ps_dict, globalattribs=sealevel_pressure.globalattribs)
end

"""
wbgt(diurnal_temperature::ClimGrid, vapor_pressure::ClimGrid)

Returns the simplified wet-bulb global temperature (*wbgt*) (Celsius) calculated using the vapor pressure (Pa) of the day and the estimated mean diurnal temperature (Celsius; temperature between 7:00 (7am) and 17:00 (5pm)).

``wbgt = 0.567 * Tday + 0.00393 * vp + 3.94``

"""
function wbgt(mean_temperature::ClimGrid, vapor_pressure::ClimGrid)

    @argcheck istemperature(mean_temperature)#[2], ["Celsius", "K", "°C"])
    @argcheck vapor_pressure[9] == "vp"
    @argcheck istemperature_units(mean_temperature)#[2], ["Celsius", "K", "°C"])
    @argcheck vapor_pressure[2] == "Pa"

    # Convert to Celsius if necessery
    if mean_temperature[2] == "K"
        mean_temperature = mean_temperature - 273.15
    end

    # Calculate the wbgt
    wbgt_arraytmp = (0.567 .* mean_temperature.data) + (0.00393 .* vapor_pressure.data) .+ 3.94
    # wbgt_arraytmp = [wbgt_arraytmp][1]°C
    wbgt_array = buildarrayinterface(wbgt_arraytmp, mean_temperature)

    # Build dictionary for the variable wbgt
    wbgt_dict = mean_temperature.varattribs
    wbgt_dict["standard_name"] = "simplified_wetbulb_globe_temperature"
    wbgt_dict["units"] = "Celsius"
    wbgt_dict["history"] = "Wet-bulb globe temperature estimated with the vapor pressure and the mean temperature"

    # Build ClimGrid object
    return ClimGrid(wbgt_array, longrid=mean_temperature.longrid, latgrid=mean_temperature.latgrid, msk=mean_temperature.msk, grid_mapping=mean_temperature.grid_mapping, dimension_dict=mean_temperature.dimension_dict, timeattrib=mean_temperature.timeattrib, model=mean_temperature.model, frequency=mean_temperature.frequency, experiment=mean_temperature.experiment, run=mean_temperature.run, project=mean_temperature.project, institute=mean_temperature.institute, filename=mean_temperature.filename, dataunits="Celsius", latunits=mean_temperature.latunits, lonunits=mean_temperature.lonunits, variable="wbgt", typeofvar="wbgt", typeofcal=mean_temperature.typeofcal, varattribs=wbgt_dict, globalattribs=mean_temperature.globalattribs)
end

"""
diurnaltemperature(temperatureminimum::ClimGrid, temperaturemaximum::ClimGrid, α::Float64)

Returns an estimation of the diurnal temperature (temperature between 7:00 (7am) and 17:00 (5pm)). The estimation is a linear combination of the daily minimum temperature (temperatureminimum) and daily maximum temperature (temperaturemaximum). The value of α has to be estimated seperatly from observations and depends on the location. The daily max and min must be in the same unit and in Celsius or Kelvin The diurnal temperature returned is in the same units as the daily minimum temperature and daily maximum temperature.

``Tdiu = α * Tmin + (1 - α) * Tmax``
"""
function diurnaltemperature(temperatureminimum::ClimGrid, temperaturemaximum::ClimGrid, α::Float64)
    @argcheck temperatureminimum[9] == "tasmin"
    @argcheck temperaturemaximum[9] == "tasmax"
    @argcheck istemperature_units(temperatureminimum)#[2], ["Celsius", "K", "°C"])
    @argcheck istemperature_units(temperaturemaximum)#[2], ["Celsius", "K", "°C"])
    @argcheck temperatureminimum[2] == temperaturemaximum[2]

    # Calculate the diurnal temperature
    tdiu = α .*temperatureminimum.data + (1-α) .* temperaturemaximum.data
    # un = get_units(temperatureminimum)
    # tdiu = [tdiu][1]un
    tdiu_array = buildarrayinterface(tdiu, temperatureminimum)

    # Build dictionary for the variable wbgt
    tdiu_dict = temperatureminimum.varattribs
    tdiu_dict["standard_name"] = "diurnal_temperature"
    tdiu_dict["units"] = temperatureminimum[2]
    tdiu_dict["history"] = "Diurnal temperature (between 7:00 and 17:00) estimated using a linear combination of the daily minimum temperature and the daily maximum temperature"

    # Build ClimGrid object
    return ClimGrid(tdiu_array, longrid=temperatureminimum.longrid, latgrid=temperatureminimum.latgrid, msk=temperatureminimum.msk, grid_mapping=temperatureminimum.grid_mapping, dimension_dict=temperatureminimum.dimension_dict, timeattrib=temperatureminimum.timeattrib, model=temperatureminimum.model, frequency=temperatureminimum.frequency, experiment=temperatureminimum.experiment, run=temperatureminimum.run, project=temperatureminimum.project, institute=temperatureminimum.institute, filename=temperatureminimum.filename, dataunits=temperatureminimum.dataunits, latunits=temperatureminimum.latunits, lonunits=temperatureminimum.lonunits, variable="tdiu", typeofvar="tdiu", typeofcal=temperatureminimum.typeofcal, varattribs=tdiu_dict, globalattribs=temperatureminimum.globalattribs)
end

"""
meantemperature(temperatureminimum::ClimGrid, temperaturemaximum::ClimGrid)

Returns the daily mean temperature calculated from the maximum and minimum temperature. Daily maximum and minimum temperature must be in the same units. The mean temperature returned is in the same units as the daily minimum temperature and daily maximum temperature.

``Tmean = \\frac{Tmax + Tmin}{2}``
"""
function meantemperature(temperatureminimum::ClimGrid, temperaturemaximum::ClimGrid)
    @argcheck temperatureminimum[9] == "tasmin"
    @argcheck temperaturemaximum[9] == "tasmax"
    @argcheck istemperature_units(temperatureminimum)#[2], ["Celsius", "K", "°C"])
    @argcheck istemperature_units(temperaturemaximum)#[2], ["Celsius", "K", "°C"])
    @argcheck temperatureminimum[2] == temperaturemaximum[2]

    # Calculate the diurnal temperature
    # un = unit(temperatureminimum.data[1])
    tmean = (temperatureminimum.data .+ temperaturemaximum.data) ./ 2
    # tmean = [tmean][1]un
    tmean_array = buildarrayinterface(tmean, temperatureminimum)

    # Build dictionary for the variable wbgt
    tmean_dict = temperatureminimum.varattribs
    tmean_dict["standard_name"] = "air_temperature"
    tmean_dict["long_name"] = "mean_daily_temperature"
    tmean_dict["units"] = temperatureminimum[2]
    tmean_dict["history"] = "Mean temperature calculated from the daily minimum and maximum temperature"

    # Build ClimGrid object
    return ClimGrid(tmean_array, longrid=temperatureminimum.longrid, latgrid=temperatureminimum.latgrid, msk=temperatureminimum.msk, grid_mapping=temperatureminimum.grid_mapping, dimension_dict=temperatureminimum.dimension_dict, timeattrib=temperatureminimum.timeattrib, model=temperatureminimum.model, frequency=temperatureminimum.frequency, experiment=temperatureminimum.experiment, run=temperatureminimum.run, project=temperatureminimum.project, institute=temperatureminimum.institute, filename=temperatureminimum.filename, dataunits=temperatureminimum.dataunits, latunits=temperatureminimum.latunits, lonunits=temperatureminimum.lonunits, variable="tmean", typeofvar="tmean", typeofcal=temperatureminimum.typeofcal, varattribs=tmean_dict, globalattribs=temperatureminimum.globalattribs)
end

"""
drought_dc(pr::ClimGrid, tas::Climgrid)

Returns the drought index. The index is defined as a function of daily temperature and daily precipitation. This indice correspond to the DC indice in Wang et al. 2015.

Reference: Wang et al. 2015. Updated source code for calculating fire danger indices in the Canadian Forest Fire Weather Index System. Information Report NOR-X-424. Canadian Forst Service. 36pp.
"""
function drought_dc(pr::ClimGrid, tas::ClimGrid)

    @argcheck ClimateTools.isprecipitation(pr)
    @argcheck ClimateTools.istemperature(tas)
    @argcheck ClimateTools.istemperature_units(tas)
    @argcheck size(pr[1]) == size(tas[1])

    # Convert to Celsius if necessery
    if tas[2] == "K"
        tas = tas - 273.15
    end

    # Check for precip units (needs mm)
    if pr[2] != "mm"
        error("Precipitation needs to be in mm")
    end

    # Time vector
    timevec = get_timevec(pr)

    # Allocated output array
    dataout  = fill(NaN, (size(pr.data, 1), size(pr.data, 2), size(pr.data,3)))

    # Reshape. Allows multi-threading on grid points.
    prin = reshape(pr[1].data, (size(pr[1].data, 1)*size(pr[1].data, 2), size(pr[1].data, 3)))
    tasin = reshape(tas[1].data, (size(tas[1].data, 1)*size(tas[1].data, 2), size(tas[1].data, 3)))
    dataoutin = reshape(dataout, (size(dataout, 1)*size(dataout, 2), size(dataout, 3)))

    # Looping over grid points using multiple-dispatch calls to qqmap
    Threads.@threads for k = 1:size(prin, 1)

        prvec = prin[k,:]
        tasvec = tasin[k,:]

        dataoutin[k, :] = drought_dc(prvec, tasvec, timevec)

    end

    # Build metadata
    dc_dict = pr.varattribs
    dc_dict["standard_name"] = "drought_code"
    dc_dict["units"] = ""
    dc_dict["history"] = "Drought code inspired by Wang et al. 2015. Updated source code for calculating fire danger indices in the Canadian Forest Fire Weather Index System. Information Report NOR-X-424. Canadian Forst Service. 36pp."

    # Build AxisArray
    axisarray = buildarrayinterface(dataout, pr)

    # Build ClimGrid
    return ClimGrid(axisarray, longrid=pr.longrid, latgrid=pr.latgrid, msk=pr.msk, grid_mapping=pr.grid_mapping, dimension_dict=pr.dimension_dict, timeattrib=pr.timeattrib, model=pr.model, frequency=pr.frequency, experiment=pr.experiment, run=pr.run, project=pr.project, institute=pr.institute, filename=pr.filename, dataunits="", latunits=pr.latunits, lonunits=pr.lonunits, variable="dc", typeofvar="dc", typeofcal=pr.typeofcal, varattribs=dc_dict, globalattribs=pr.globalattribs)

end

"""
drought_dc(prvec::Array{N,1}, tasvec::Array{N,1}, timevec)

Returns the drought index. The index is defined as a function of daily temperature and daily precipitation. This indice correspond to the DC indice in Wang et al. 2015.

Reference: Wang et al. 2015. Updated source code for calculating fire danger indices in the Canadian Forest Fire Weather Index System. Information Report NOR-X-424. Canadian Forst Service. 36pp.
"""
function drought_dc(prvec::Array{N,1} where N, tasvec::Array{N,1} where N, timevec)

    dc0 = 15.0 # initial drought code value for each year

    years    = Dates.year.(timevec)
    numYears = unique(years)

    # Output vector
    dc = fill(NaN, size(prvec))

    for i in 1:length(numYears)
        idx = searchsortedfirst(years, numYears[i]):searchsortedlast(years, numYears[i])

        # Find index where tas is > 6°C for more than 3 days
        idx_tas = tasvec[idx] .>= 6.0

        nanfrac = sum(idx_tas)#/length(idx_tas)

        if nanfrac >= 3 # need a start and end to the season

            cum_ = cumsum(idx_tas)
            cum_2 = (cum_[1+3:end] - cum_[1:end-3]) .== 3

            if sum(cum_2) >= 2

                idx_start = findfirst(cum_2)
                idx_end = findlast(cum_2)

                for iday = idx_start:idx_end#length(dates_tuple)

                    pr_day = prvec[iday]
                    tas_day = tasvec[iday]

                    mth = Dates.month(timevec[iday])

                    dc[iday] = drought_dc(pr_day, tas_day, dc0, mth)

                    dc0 = dc[iday]

                end
            end
        end

    end

    return dc

end

function drought_dc(pr_day::Real, tas_day::Real, dc0::Real, mth::Int)

    # Months constant
    fl = [-1.6, -1.6, -1.6, 0.9, 3.8, 5.8, 6.4, 5.0, 2.4, 0.4, -1.6, -1.6]
    dc_single = 0.0 # initialize

    if tas_day < -2.8
        tas_day = -2.8
    end

    pe = (0.36*(tas_day + 2.8) + fl[mth]) / 2

    if pe <= 0.0
        pe = 0.0
    end

    if pr_day > 2.8
        rw = 0.83*pr_day - 1.27
        smi = 800.0*exp(-dc0/400.0)
        if smi == 0.0
            smi = 1.0
        end
        dr = dc0 - 400.0*log(1.0+((3.937*rw)/smi))

        if dr > 0.0
            dc_single = dr + pe
        end

    elseif pr_day <= 2.8
        dc_single = dc0 + pe
    end

    return dc_single

end

function istemperature_units(C::ClimGrid)
    return in(C[2], ["K", "Kelvin", "Celsius", "°C", "C"])
end

function istemperature(C::ClimGrid)
    return in(C[9], ["tasmax", "tas", "tasmin", "tmean", "tmoy"])
end

function isprecipitation(C::ClimGrid)
    return in(C[9], ["pr", "precip", "precipitation", "precipitation_flux"])
end

function isprecipitation_units(C::ClimGrid)
    return in(C[2], ["mm", "kg m-2 s-1", "kg m-2"])
end


text:
function changeIntegrand(config, integrand)
    # update to change an integrand to its neighbors.
    # The degrees of freedom could be increase, decrease or remain the same.

    curr = config.curr
    new = rand(config.rng, config.neighbor[curr]) # jump to a randomly picked neighboring integrand

    currdof, newdof = config.dof[curr], config.dof[new]

    currAbsWeight = config.absWeight

    # propose probability caused by the selection of neighbors
    prop = length(config.neighbor[curr]) / length(config.neighbor[new])

    # create/remove variables if there are more/less degrees of freedom
    for vi = 1:length(config.var)
        offset = config.var[vi].offset
        if (currdof[vi] < newdof[vi]) # more degrees of freedom
            for pos = currdof[vi]+1:newdof[vi]
                prop *= create!(config.var[vi], pos + offset, config)
            end
        elseif (currdof[vi] > newdof[vi]) # less degrees of freedom
            for pos = newdof[vi]+1:currdof[vi]
                prop *= remove!(config.var[vi], pos + offset, config)
            end
        end
    end

    # sampler may want to reject, then prop has already been set to zero
    if prop <= eps(0.0)
        return
    end

    config.curr = new
    newAbsWeight = (new == config.norm ? 1.0 : abs(integrand(config)))
    R = prop * newAbsWeight * config.reweight[new] / currAbsWeight / config.reweight[curr]

    config.propose[1, curr, new] += 1.0
    if rand(config.rng) < R  # accept the change
        config.accept[1, curr, new] += 1.0
        config.absWeight = newAbsWeight
    else # reject the change
        config.curr = curr # reset the current diagram index
        config.absWeight = currAbsWeight

        ############ Redo changes to config.var #############
        for vi = 1:length(config.var)
            offset = config.var[vi].offset
            if (currdof[vi] < newdof[vi]) # more degrees of freedom
                for pos = currdof[vi]+1:newdof[vi]
                    createRollback!(config.var[vi], pos + offset, config)
                end
            elseif (currdof[vi] > newdof[vi]) # less degrees of freedom
                for pos = newdof[vi]+1:currdof[vi]
                    removeRollback!(config.var[vi], pos + offset, config)
                end
            end
        end
        ####################################################

    end
end

function changeVariable(config, integrand)
    # update to change the variables of the current diagrams
    (config.curr == config.norm) && return

    curr = config.curr
    currdof = config.dof[curr]
    vi = rand(config.rng, 1:length(currdof)) # update the variable type of the index vi
    var = config.var[vi]
    (currdof[vi] <= 0) && return # return if the var has zero degree of freedom
    idx = var.offset + rand(config.rng, 1:currdof[vi]) # randomly choose one var to update

    # oldvar = copy(var[idx])
    currAbsWeight = config.absWeight

    prop = shift!(var, idx, config)

    # sampler may want to reject, then prop has already been set to zero
    if prop <= eps(0.0)
        return
    end

    newAbsWeight = abs(integrand(config))
    currAbsWeight = config.absWeight
    R = prop * newAbsWeight / currAbsWeight

    # curr == 2 && println("propose, $curr: old: $oldvar --> new: $(var[idx]), with R $newAbsWeight / $currAbsWeight * $prop = $R")
    config.propose[2, curr, vi] += 1.0
    if rand(config.rng) < R
        # curr == 2 && println("accept, $curr")
        config.accept[2, curr, vi] += 1.0
        config.absWeight = newAbsWeight
    else
        # var[idx] = oldvar
        config.absWeight = currAbsWeight
        shiftRollback!(var, idx, config)
    end
end

function swapVariable(config, integrand)
    # update to change the variables of the current diagrams
    (config.curr == config.norm) && return

    curr = config.curr
    currdof = config.dof[curr]
    vi = rand(config.rng, 1:length(currdof)) # update the variable type of the index vi
    var = config.var[vi]
    (currdof[vi] <= 0) && return # return if the var has zero degree of freedom
    idx1 = var.offset + rand(config.rng, 1:currdof[vi]) # randomly choose one var to update
    idx2 = var.offset + rand(config.rng, 1:currdof[vi]) # randomly choose one var to update
    (idx1 == idx2) && return

    # oldvar = copy(var[idx])
    currAbsWeight = config.absWeight

    prop = swap!(var, idx1, idx2, config)

    # sampler may want to reject, then prop has already been set to zero
    if prop <= eps(0.0)
        return
    end

    newAbsWeight = abs(integrand(config))
    currAbsWeight = config.absWeight
    R = prop * newAbsWeight / currAbsWeight

    # curr == 2 && println("propose, $curr: old: $oldvar --> new: $(var[idx]), with R $newAbsWeight / $currAbsWeight * $prop = $R")
    config.propose[2, curr, vi] += 1.0
    if rand(config.rng) < R
        # curr == 2 && println("accept, $curr")
        config.accept[2, curr, vi] += 1.0
        config.absWeight = newAbsWeight
    else
        # var[idx] = oldvar
        config.absWeight = currAbsWeight
        swapRollback!(var, idx1, idx2, config)
    end
end

ext: jl


text:
module DiscretePopulationGrowth

using UnicodePlots

"Next time step of lohistic model"
next(xt, b) = b * (1 - xt)xt

function sim(x0, b, n)
  x = x0
  xs = [x]
  for i = 1:n
    x = next(x, b)
    push!(xs, x)
  end
  xs
end

function test()
  lineplot(sim(0.1, 2.2, 500))
end

end # module


text:
__precompile__()

module SpinMonteCarlo

using Random
using Printf
using Markdown
using Statistics
using LinearAlgebra

using DataStructures

export Parameter
export @gen_convert_parameter, convert_parameter
export Model, Ising, XY, Potts, Clock
export QuantumLocalZ2Model, QuantumXXZ
export local_update!, SW_update!, Wolff_update!, loop_update!
export default_estimator, simple_estimator, improved_estimator
export dimer_lattice, chain_lattice, square_lattice, triangular_lattice, cubic_lattice, fully_connected_lattice
export Lattice, dim, size, numsites, numbonds, neighbors, source, target, sitetype, bondtype
export sitecoordinate, bonddirection, lattice_sitecoordinate, lattice_bonddirection
export UnionFind, addnode!, unify!, clusterize!, clusterid
export gen_snapshot!, gensave_snapshot!, load_snapshot
export runMC, print_result

abstract type Model end

@doc doc"""
Input parameter of simulation
"""
const Parameter = Dict{String, Any}
const Measurement = Dict{String, Any}

include("union_find.jl")
include("lattice.jl")
include("gen_lattice.jl")
include("model.jl")
include("qmodel.jl")
include("parameter.jl")
include("local_update.jl")
include("SW.jl")
include("loop.jl")
include("Wolff.jl")
include("simple_estimator.jl")
include("improved_estimator.jl")
include("snapshot.jl")
include("observables/MCObservables.jl")
include("runMC.jl")
include("print.jl")
end # of module


text:
struct x1{T<:AbstractFloat, S<:Int}
    allo::T
    bonjour::T
    salut::S
end

function f1(x::B) where {T<:AbstractFloat, S<:Int, B<:Vector{x1{T, S}}}
    a = x[1]
    a.allo + a.bonjour
end

a1 = x1(1.0, 2.0, 3)
@time r1 = f1([a1])
typeof(r1)

a1 = x1{Float16, Int}(1.0, 2.0, 3)
@time r1 = f1([a1])
typeof(r1)


text:
using SQLite

"""
    select_columns(db, table)
Return an array with names of columns in the given table
"""
function select_columns(db::SQLite.DB, table::AbstractString)
    cols_query = SQLite.columns(db, table)
    cols_query[:name]
end

"""
    select_all_tables_mysql(con)

Return an array of all tables in a given MySQL database
"""
function select_all_tables(db::SQLite.DB)
    tables_query = DataFrame(SQLite.Query(db, "SELECT name FROM sqlite_master WHERE type='table'"))
    tables_query[1]
end

"""
    query(db, query_code)

Execute a SQLite command
"""
function db_query(db::SQLite.DB, query_code)
    sel = DataFrame(SQLite.Query(db, query_code))
    return sel
end

"""
    insert_row!(db, tablename, values)
Insert a row of values into the specified table for a given a SQLite database handle

### Arguments:

* `db::MySQLDB`: Database object (connection and map)
* `data_values::Dict{String, Any}`: Array of (string) values
* `verbose`: Print debugging info
"""
function insert_row!(db::SQLite.DB, tablename, data_values::Dict{Symbol, T},
    verbose = false) where T

    cols_string, vals_string = assemble_cols_and_vals(data_values)
    lastid = -1
    try
        q = SQLite.execute!(db, "INSERT INTO `$tablename` ($cols_string) values $vals_string")
    catch e
        if verbose
            Base.showerror(stdout, e)
            println("\n")
            @warn "Warning! Could not insert values $vals_string into table $tablename"
        end
        return -1
    end

    lastid_query = DataFrame(SQLite.Query(db, "SELECT last_insert_rowid()"))
    lastid = lastid_query[1][1]

    if ismissing(lastid)
        @warn "Could not insert values $vals_string into table $tablename"
    end
    if verbose
        println("Row successfully inserted into table: $tablename")
    end

    return lastid
end

"""
    disable_foreign_checks(con::SQLite.DB)
Disables foreign checks for SQLite database
"""
function disable_foreign_checks(conn::SQLite.DB)
    SQLite.execute!(conn, "PRAGMA foreign_keys = OFF")

end

"""
    enable_foreign_checks(con::SQLite.DB)
Enables foreign checks for SQLite database
"""
function enable_foreign_checks(conn::SQLite.DB)
    SQLite.execute!(conn, "PRAGMA foreign_keys = ON")

end


text:
"""
...
# Arguments
- `snp::TouchstonerSnP`: Provided Touchstone file
- `parameter_type::Str`: Network parameter you want to plot. Possible options:
    * `S` scattering parameters
    * `Y` admittance parameters
    * `Z` impedance parameters
    * `H` hybrid-h parameters (two-port network only)
    * `G` hybrid-g parameters (two-port network only)
- `img_path` : Path to save the image to
- `ports::Arr` : Ports to plot. For example, in order to plot S11 and S12 you the provided parameter
                    should he [[1, 1], [1, 2]]
- `noise_data::Bool` : If set to `true` returns results for Noise Data. Else,
    Network Data are assumed
...
"""
function plot_snp(snp::TouchstoneSnP, parameter_type::String, img_path::String, ports::Set = [], noise_data=false)
    # todo, implement option for alternative plotting options (e.g. Smith chart)
    # for now, just the magnitudes will be plotted

    frequencies = sort(collect(keys(snp.network_data)))
    parameters = getindex.(Ref(snp.network_data), frequencies)
    # Convert to appropriate parameters

    # this is the most inefficient way possible to achieve this but in most cases, there are 4 ports at most
    if snp.matrix_format != "FULL"
        ports = Set(sort.(ports))
    end

    tplot = plot(palette = :darktest, xlabel = "Frequency ["*snp.frequency_unit*"]", ylabel = "|Magnitude| [dB]")

    for p in ports
        labelp = parameter_type*string(p[1])*string(p[2])

        param_magnitudes = []

        for freq in frequencies
            ri_param = getindex(convert_param_type(snp, freq, parameter_type, noise_data), p[1], p[2])
            push!(param_magnitudes, 20log10(sqrt(real(ri_param)^2+imag(ri_param)^2)))
        end
        plot!(tplot, frequencies, param_magnitudes, label=labelp, lw = 2)
    end
    savefig(tplot, img_path)
end


text:
type BasicDiscMuvParameterNState{NI<:Integer, NR<:Real} <: ParameterNState{Continuous, Multivariate}
  value::Matrix{NI}
  loglikelihood::Vector{NR}
  logprior::Vector{NR}
  logtarget::Vector{NR}
  diagnosticvalues::Matrix
  size::Integer
  n::Integer
  diagnostickeys::Vector{Symbol}
  copy::Function

  function BasicDiscMuvParameterNState(
    size::Integer,
    n::Integer,
    monitor::Vector{Bool}=[true; fill(false, 3)],
    diagnostickeys::Vector{Symbol}=Symbol[],
    ::Type{NI}=Int64,
    ::Type{NR}=Float64,
    diagnosticvalues::Matrix=Array(Any, length(diagnostickeys), isempty(diagnostickeys) ? 0 : n)
  )
    instance = new()

    fnames = fieldnames(BasicDiscMuvParameterNState)
    setfield!(instance, fnames[1], Array(NI, (monitor[1] == false ? (zero(Integer), zero(Integer)) : (size, n))...))
    for i in 2:4
      l = (monitor[i] == false ? zero(Integer) : n)
      setfield!(instance, fnames[i], Array(NR, l))
    end

    instance.diagnosticvalues = diagnosticvalues

    instance.size = size
    instance.n = n
    instance.diagnostickeys = diagnostickeys

    instance.copy = eval(codegen(:copy, instance, monitor))

    instance
  end
end

BasicDiscMuvParameterNState{NI<:Integer, NR<:Real}(
  size::Integer,
  n::Integer,
  monitor::Vector{Bool}=[true; fill(false, 3)],
  diagnostickeys::Vector{Symbol}=Symbol[],
  ::Type{NI}=Int64,
  ::Type{NR}=Float64,
  diagnosticvalues::Matrix=Array(Any, length(diagnostickeys), isempty(diagnostickeys) ? 0 : n)
) =
  BasicDiscMuvParameterNState{NI, NR}(size, n, monitor, diagnostickeys, NI, NR, diagnosticvalues)

function BasicDiscMuvParameterNState{NI<:Integer, NR<:Real}(
  size::Integer,
  n::Integer,
  monitor::Vector{Symbol},
  diagnostickeys::Vector{Symbol}=Symbol[],
  ::Type{NI}=Int64,
  ::Type{NR}=Float64,
  diagnosticvalues::Matrix=Array(Any, length(diagnostickeys), isempty(diagnostickeys) ? 0 : n)
)
  fnames = fieldnames(BasicDiscMuvParameterNState)
  BasicDiscMuvParameterNState(
    size, n, [fnames[i] in monitor ? true : false for i in 1:4], diagnostickeys, NI, NR, diagnosticvalues
  )
end

typealias DiscMuvMarkovChain BasicDiscMuvParameterNState

codegen(f::Symbol, nstate::BasicDiscMuvParameterNState, monitor::Vector{Bool}) = codegen(Val{f}, nstate, monitor)

function codegen(::Type{Val{:copy}}, nstate::BasicDiscMuvParameterNState, monitor::Vector{Bool})
  body = []
  fnames = fieldnames(BasicDiscMuvParameterNState)
  local f::Symbol # f must be local to avoid compiler errors. Alternatively, this variable declaration can be omitted

  if monitor[1]
    f = fnames[1]
    push!(
      body,
      :(getfield(_nstate, $(QuoteNode(f)))[1+(_i-1)*_state.size:_i*_state.size] = getfield(_state, $(QuoteNode(f))))
    )
  end

  for j in 2:4
    if monitor[j]
      f = fnames[j]
      push!(body, :(getfield(_nstate, $(QuoteNode(f)))[_i] = getfield(_state, $(QuoteNode(f)))))
    end
  end

  if !isempty(nstate.diagnosticvalues)
    push!(body, :(_nstate.diagnosticvalues[:, _i] = _state.diagnosticvalues))
  end

  @gensym _copy

  quote
    function $_copy(_nstate::BasicDiscMuvParameterNState, _state::BasicDiscMuvParameterState, _i::Integer)
      $(body...)
    end
  end
end

Base.eltype{NI<:Integer, NR<:Real}(::Type{BasicDiscMuvParameterNState{NI, NR}}) = (NI, NR)
Base.eltype{NI<:Integer, NR<:Real}(::BasicDiscMuvParameterNState{NI, NR}) = (NI, NR)

Base.(:(==)){S<:BasicDiscMuvParameterNState}(z::S, w::S) =
  reduce(&, [getfield(z, n) == getfield(w, n) for n in fieldnames(S)[1:8]])

Base.isequal{S<:BasicDiscMuvParameterNState}(z::S, w::S) =
  reduce(&, [isequal(getfield(z, n), getfield(w, n)) for n in fieldnames(S)[1:8]])

function Base.show{NI<:Integer, NR<:Real}(io::IO, nstate::BasicDiscMuvParameterNState{NI, NR})
  fnames = fieldnames(BasicDiscMuvParameterNState)
  fbool = map(n -> !isempty(getfield(nstate, n)), fnames[1:4])
  indentation = "  "

  println(io, "BasicDiscMuvParameterNState:")

  println(io, indentation*"eltype: $(eltype(nstate))")
  println(io, indentation*"state size = $(nstate.size)")
  println(io, indentation*"number of states = $(nstate.n)")

  print(io, indentation*"monitored components:")
  if !any(fbool)
    println(io, " none")
  else
    print(io, "\n")
    for i in 1:4
      if fbool[i]
        println(io, string(indentation^2, fnames[i]))
      end
    end
  end

  print(io, indentation*"diagnostics:")
  if isempty(nstate.diagnostickeys)
    print(io, " none")
  else
    for k in nstate.diagnostickeys
      print(io, "\n")
      print(io, string(indentation^2, k))
    end
  end
end


text:
if basename(pwd()) == "aoc"
    cd("2016/11")
end

max_stars_repo_name: stellartux/advent-of-code

max_issues_repo_name: stellartux/advent-of-code

max_forks_repo_name: stellartux/advent-of-code


text:
# Generic fallbacks
# TODO overload for Graph and Network types
edges(g::ADiGraph) = (e for u=1:nv(g) for e in out_edges(g, u))

edges(g::AGraph) =  (e for u=1:nv(g) for e in out_edges(g, u) if src(e) <= dst(e))

edges(g::ADiGraph, vs::AbstractVector) = (e for u in vs for e in out_edges(g, u) if dst(e) ∈ vs)

edges(g::AGraph, vs::AbstractVector) = (e for u in vs for e in out_edges(g, u) if src(e) <= dst(e) && dst(e) ∈ vs)


text:
# https://adventofcode.com/2021/day/8
using AdventOfCode
using Combinatorics
input = split.(readlines("2021/data/day_8.txt"), " | ") .|> (x->split.(x," "))
nseg = Dict((0:9) .=> (6,2,5,5,4,5,6,3,7,6))

defsegs = ("abcefg","cf","acdeg","acdfg","bcdf","abdfg","abdefg","acf","abcdefg","abcdfg")
defsegdict=Dict(Set.(collect.(defsegs)).=> 0:9)

input = (patterns=first.(input), outputs = last.(input), nseg=nseg, defsegdict=defsegdict)
function iteratedict(v::AbstractVector)
    (Dict(v.=> permv) for permv in permutations(v))
end
vec2nseg(v,input)=getindex.(Ref(input.nseg),v)
translateset(transdict, set)=getindex.(Ref(transdict),set)|>Set

function part_1(input)
    outputnsegs=(length.(x) for x in input.outputs) # vector whose elements [w, x, y, z] are 4 segment numbers of each output
    sum(sum(Base.Fix2(in, getindex.(Ref(input.nseg),(1,4,7,8))) ,x) for x in outputnsegs)
end

function part_2(input)
    sum(begin
        matchdict=iteratedict(collect("abcdefg"))|>collect # all possible translations
        translator=filter(dict->translateset.(Ref(dict),
            Set.([collect.(pattern);collect.(output)])) .|>
            Base.Fix2(in, keys(input.defsegdict)) |> all,matchdict) |>first # filter those making sense
        translateset.(Ref(translator), Set.(collect.(output))) .|>
            Base.Fix1(getindex,input.defsegdict) |> join |> parse # translate output
    end
        for (pattern,output) in zip(input.patterns, input.outputs))
end


text:
# an example on mixed integer programming
#
#   maximize x + 2 y + 5 z
#
#   s.t.  x + y + z <= 10
#         x + 2 y + z <= 15
#
#         x is continuous: 0 <= x <= 5
#         y is integer: 0 <= y <= 10
#         z is binary

@testset "MIP 01" begin
    env = CPLEX.Env()

    model = CPLEX.Model(env, "mip_01")
    CPLEX.set_sense!(model, :Max)
    CPLEX.set_param!(env, "CPX_PARAM_MIPDISPLAY",1)
    CPLEX.set_param!(env, "CPX_PARAM_MIPINTERVAL",1)

    CPLEX.add_var!(model, 1., 0., 5.)  # x
    CPLEX.add_var!(model, 2., 0, 10)   # y
    CPLEX.add_var!(model, 5., 0, 1)    # z
    CPLEX.set_vartype!(model, ['C', 'I', 'B'])

    CPLEX.add_constr!(model, ones(3), '<', 10.)
    CPLEX.add_constr!(model, [1., 2., 1.], '<', 15.)

    CPLEX.optimize!(model)

    sol = CPLEX.get_solution(model)
    @test sol[1] ≈ 4
    @test sol[2] ≈ 5
    @test sol[3] ≈ 1
    @test CPLEX.get_objval(model) ≈ 19
end


text:
# src/crypto.jl
# Cryptocurrency Wallet Extras

export AbstractPublicKey,
       keypairs,
       privatekey,
       AbstractDeterministicWallet,
       rootkey,
       mnemonic,
       seed,
       next_keypair!,
       next_address!

"""```
AbstractPublicKey <: AbstractAddress
```
Public Keys for Wallets are Addresses.
"""
abstract type AbstractPublicKey <: AbstractAddress end

"""```
keypairs(wallet::AbstractWallet)
```
Return public-private key pairs that identify the given `wallet`.
"""
function keypairs(wallet::AbstractWallet)
    missing_api("keypairs", wallet)
end

"""```
privatekey(wallet::AbstractWallet, key::AbstractPublicKey)
```
Return the private key corresponding to the `publickey` in `wallet`.
"""
function privatekey(wallet::AbstractWallet, publickey::AbstractPublicKey)
    missing_api("privatekey", wallet, publickey)
end

"""```
AbstractDeterministicWallet <: AbstractWallet
```
Wallets where addresses can be computed deterministically from a seed.
"""
abstract type AbstractDeterministicWallet{K,N,P,A<:AbstractPublicKey} <: AbstractWallet{K,N,P,A} end

"""```
rootkey(wallet::AbstractDeterministicWallet)
```
The root private key for determining wallet addresses.
"""
function rootkey(wallet::AbstractDeterministicWallet)
    missing_api("rootkey", wallet)
end

"""```
mnemonic(wallet::AbstractDeterministicWallet)
```
Human-readable mnemonic representing the root private key.
"""
function mnemonic(wallet::AbstractDeterministicWallet)
    missing_api("mnemonic", wallet)
end

"""```
seed(wallet::AbstractDeterministicWallet)
```
Human-readable seed representing the root private key.
"""
function seed(wallet::AbstractDeterministicWallet)
    return mnemonic(wallet)
end

"""```
next_keypair!(wallet::AbstractDeterministicWallet)
```
Produce the next key pair in the deterministic wallet.
"""
function next_keypair!(wallet::AbstractDeterministicWallet)
    missing_api("next_keypair!", wallet)
end

"""```
next_address!(wallet::AbstractDeterministicWallet)::AbstractPublicKey
```
Produce the next address in the deterministic wallet. This should be the first
value in the pair produced from `next_keypair!(wallet)`.
"""
function next_address!(wallet::AbstractDeterministicWallet)::AbstractPublicKey
    return first(next_keypair!(wallet))
end


text:
using Documenter
using RecoverPose

makedocs(
    sitename="RecoverPose.jl",
    authors="Anton Smirnov",
    repo="https://github.com/pxl-th/RecoverPose.jl/blob/{commit}{path}#L{line}",
    modules=[RecoverPose],
    pages=[
        "Home" => "index.md",
        "API Reference" => [
            "Five Point" => "five-point.md",
            "P3P" => "p3p.md",
            "Triangulation" => "triangulation.md",
            "RANSAC" => "ransac.md"]
    ],
    format=Documenter.HTML(prettyurls=get(ENV, "CI", nothing) == "true"))
deploydocs(;repo="github.com/pxl-th/RecoverPose.jl.git")


text:
# This file was generated by the Julia Swagger Code Generator
# Do not modify this file directly. Modify the swagger specification instead.

mutable struct AvailableProvidersListCountry <: SwaggerModel
    countryName::Any # spec type: Union{ Nothing, String } # spec name: countryName
    providers::Any # spec type: Union{ Nothing, Vector{String} } # spec name: providers
    states::Any # spec type: Union{ Nothing, Vector{AvailableProvidersListState} } # spec name: states

    function AvailableProvidersListCountry(;countryName=nothing, providers=nothing, states=nothing)
        o = new()
        validate_property(AvailableProvidersListCountry, Symbol("countryName"), countryName)
        setfield!(o, Symbol("countryName"), countryName)
        validate_property(AvailableProvidersListCountry, Symbol("providers"), providers)
        setfield!(o, Symbol("providers"), providers)
        validate_property(AvailableProvidersListCountry, Symbol("states"), states)
        setfield!(o, Symbol("states"), states)
        o
    end
end # type AvailableProvidersListCountry

const _property_map_AvailableProvidersListCountry = Dict{Symbol,Symbol}(Symbol("countryName")=>Symbol("countryName"), Symbol("providers")=>Symbol("providers"), Symbol("states")=>Symbol("states"))
const _property_types_AvailableProvidersListCountry = Dict{Symbol,String}(Symbol("countryName")=>"String", Symbol("providers")=>"Vector{String}", Symbol("states")=>"Vector{AvailableProvidersListState}")
Base.propertynames(::Type{ AvailableProvidersListCountry }) = collect(keys(_property_map_AvailableProvidersListCountry))
Swagger.property_type(::Type{ AvailableProvidersListCountry }, name::Symbol) = Union{Nothing,eval(Base.Meta.parse(_property_types_AvailableProvidersListCountry[name]))}
Swagger.field_name(::Type{ AvailableProvidersListCountry }, property_name::Symbol) =  _property_map_AvailableProvidersListCountry[property_name]

function check_required(o::AvailableProvidersListCountry)
    true
end

function validate_property(::Type{ AvailableProvidersListCountry }, name::Symbol, val)
end


text:
module TerminalPager

using REPL
using REPL.LineEdit

using Crayons

import Base: convert, string

# The performance of TerminalPager.jl does not increase by a lot of
# optimizations that is performed by the compiler. Hence, we disable then to
# improve compile time.
if isdefined(Base, :Experimental) && isdefined(Base.Experimental, Symbol("@optlevel"))
    @eval Base.Experimental.@optlevel 1
end

################################################################################
#                             Types and structures
################################################################################

include("./types.jl")

################################################################################
#                                  Constants
################################################################################

const CSI = "\x1b["
const PKG_VERSION = v"0.2.1"

# Crayons
const _reset_crayon = string(Crayon(reset = true))
const _ruler_crayon = string(crayon"dark_gray")

################################################################################
#                                   Includes
################################################################################

include("./command_line.jl")
include("./debug.jl")
include("./deprecations.jl")
include("./help.jl")
include("./helpers.jl")
include("./pager.jl")
include("./recipe.jl")
include("./repl.jl")
include("./rulers.jl")
include("./search.jl")
include("./screen.jl")
include("./string.jl")
include("./view.jl")

include("./input/keybindings.jl")
include("./input/input.jl")

export pager

"""
    pager(obj; kwargs...)

Call the pager to show the output of the object `obj`.

# Keywords

- `auto::Bool`: If `true`, then the pager is only shown if the output does not
    fit into the display. (**Default** = `false`)
- `change_freeze::Bool`: If `true`, then the user can change the number of
    frozen rows and columns inside the pager. (**Default** = `true`)
- `draw_ruler::Bool`: If `true`, then a vertical ruler is drawn at the pager
    startup. (**Default** = `false`)
- `freeze_columns::Int = 0`: Number of columns to be frozen at startup.
    (**Default** = 0)
- `freeze_rows::Int = 0`: Number of rows to be frozen at starupt.
    (**Default** = 0)
- `hashelp::Bool = true`: If `true`, then the user can see the pager help.
    (**Default** = `true`)
"""
function pager(obj::Any; kwargs...)
    str = sprint(show, MIME"text/plain"(), obj, context = :color => true)
    return pager(str; kwargs...)
end

pager(obj::AbstractString; kwargs...) = return _pager(obj; kwargs...)

const less = pager

function __init__()
    # TODO: Fix initialization time with PAGER_MODE=vi
    #   The code that adds a key into `_keybindings` takes a lot of time. The
    #   startup time is increased by almost 0.1s with `PAGER_MODE=vi`.

    # Modify the key bindings if the used wants `vi` mode.
    if get(ENV, "PAGER_MODE", "default") == "vi"
        _keybindings[(:eot,     false, false, false)] = :halfpagedown
        _keybindings[(:shiftin, false, false, false)] = :halfpageup
    end

    if isdefined(Base, :active_repl)
        _init_pager_repl_mode(Base.active_repl)
    else
        atreplinit() do repl
            if isinteractive() && repl isa REPL.LineEditREPL
                if !isdefined(repl, :interface)
                    repl.interface = REPL.setup_interface(repl)
                end

                _init_pager_repl_mode(repl)
            end
        end
    end

    return nothing
end

################################################################################
#                                Precompilation
################################################################################

if Base.VERSION >= v"1.4.2"
    # This try/catch is necessary in case the precompilation statements do not
    # exists. In this case, TerminalPager.jl will work correctly but without the
    # optimizations.
    try
        include("precompile/precompile_TerminalPager.jl")
        _precompile_()
    catch
    end
end

end # module


text:
# This file was generated by the Julia Swagger Code Generator
# Do not modify this file directly. Modify the swagger specification instead.

mutable struct ApplicationGatewayUrlPathMapPropertiesFormat <: SwaggerModel
    defaultBackendAddressPool::Any # spec type: Union{ Nothing, SubResource } # spec name: defaultBackendAddressPool
    defaultBackendHttpSettings::Any # spec type: Union{ Nothing, SubResource } # spec name: defaultBackendHttpSettings
    defaultRewriteRuleSet::Any # spec type: Union{ Nothing, SubResource } # spec name: defaultRewriteRuleSet
    defaultRedirectConfiguration::Any # spec type: Union{ Nothing, SubResource } # spec name: defaultRedirectConfiguration
    pathRules::Any # spec type: Union{ Nothing, Vector{ApplicationGatewayPathRule} } # spec name: pathRules
    provisioningState::Any # spec type: Union{ Nothing, ProvisioningState } # spec name: provisioningState

    function ApplicationGatewayUrlPathMapPropertiesFormat(;defaultBackendAddressPool=nothing, defaultBackendHttpSettings=nothing, defaultRewriteRuleSet=nothing, defaultRedirectConfiguration=nothing, pathRules=nothing, provisioningState=nothing)
        o = new()
        validate_property(ApplicationGatewayUrlPathMapPropertiesFormat, Symbol("defaultBackendAddressPool"), defaultBackendAddressPool)
        setfield!(o, Symbol("defaultBackendAddressPool"), defaultBackendAddressPool)
        validate_property(ApplicationGatewayUrlPathMapPropertiesFormat, Symbol("defaultBackendHttpSettings"), defaultBackendHttpSettings)
        setfield!(o, Symbol("defaultBackendHttpSettings"), defaultBackendHttpSettings)
        validate_property(ApplicationGatewayUrlPathMapPropertiesFormat, Symbol("defaultRewriteRuleSet"), defaultRewriteRuleSet)
        setfield!(o, Symbol("defaultRewriteRuleSet"), defaultRewriteRuleSet)
        validate_property(ApplicationGatewayUrlPathMapPropertiesFormat, Symbol("defaultRedirectConfiguration"), defaultRedirectConfiguration)
        setfield!(o, Symbol("defaultRedirectConfiguration"), defaultRedirectConfiguration)
        validate_property(ApplicationGatewayUrlPathMapPropertiesFormat, Symbol("pathRules"), pathRules)
        setfield!(o, Symbol("pathRules"), pathRules)
        validate_property(ApplicationGatewayUrlPathMapPropertiesFormat, Symbol("provisioningState"), provisioningState)
        setfield!(o, Symbol("provisioningState"), provisioningState)
        o
    end
end # type ApplicationGatewayUrlPathMapPropertiesFormat

const _property_map_ApplicationGatewayUrlPathMapPropertiesFormat = Dict{Symbol,Symbol}(Symbol("defaultBackendAddressPool")=>Symbol("defaultBackendAddressPool"), Symbol("defaultBackendHttpSettings")=>Symbol("defaultBackendHttpSettings"), Symbol("defaultRewriteRuleSet")=>Symbol("defaultRewriteRuleSet"), Symbol("defaultRedirectConfiguration")=>Symbol("defaultRedirectConfiguration"), Symbol("pathRules")=>Symbol("pathRules"), Symbol("provisioningState")=>Symbol("provisioningState"))
const _property_types_ApplicationGatewayUrlPathMapPropertiesFormat = Dict{Symbol,String}(Symbol("defaultBackendAddressPool")=>"SubResource", Symbol("defaultBackendHttpSettings")=>"SubResource", Symbol("defaultRewriteRuleSet")=>"SubResource", Symbol("defaultRedirectConfiguration")=>"SubResource", Symbol("pathRules")=>"Vector{ApplicationGatewayPathRule}", Symbol("provisioningState")=>"ProvisioningState")
Base.propertynames(::Type{ ApplicationGatewayUrlPathMapPropertiesFormat }) = collect(keys(_property_map_ApplicationGatewayUrlPathMapPropertiesFormat))
Swagger.property_type(::Type{ ApplicationGatewayUrlPathMapPropertiesFormat }, name::Symbol) = Union{Nothing,eval(Base.Meta.parse(_property_types_ApplicationGatewayUrlPathMapPropertiesFormat[name]))}
Swagger.field_name(::Type{ ApplicationGatewayUrlPathMapPropertiesFormat }, property_name::Symbol) =  _property_map_ApplicationGatewayUrlPathMapPropertiesFormat[property_name]

function check_required(o::ApplicationGatewayUrlPathMapPropertiesFormat)
    true
end

function validate_property(::Type{ ApplicationGatewayUrlPathMapPropertiesFormat }, name::Symbol, val)
end


text:
"""
    abstract type DiscreteModel{Dc,Dp} <: GridapType

Abstract type holding information about a physical grid,
the underlying grid topology, and a labeling of
the grid faces. This is the information that typically provides a mesh
generator, and it is what one needs to perform a simulation.

The `DiscreteModel` interface is defined by overloading the methods:

- [`get_grid(model::DiscreteModel)`](@ref)
- [`get_grid_topology(model::DiscreteModel)`](@ref)
- [`get_face_labeling(g::DiscreteModel)`](@ref)

The interface is tested with this function:

- [`test_discrete_model`](@ref)

"""
abstract type DiscreteModel{Dc,Dp} <: GridapType end

"""
    get_grid(model::DiscreteModel)
"""
function get_grid(model::DiscreteModel)
  @abstractmethod
end

"""
    get_grid_topology(model::DiscreteModel)
"""
function get_grid_topology(model::DiscreteModel)
  @abstractmethod
end

"""
    get_face_labeling(g::DiscreteModel)
"""
function get_face_labeling(g::DiscreteModel)
  @abstractmethod
end

# Testers

"""
    test_discrete_model(model::DiscreteModel)
"""
function test_discrete_model(model::DiscreteModel{Dc,Dp}) where {Dc,Dp}
  D = Dc
  grid = get_grid(model)
  test_grid(grid)
  topo = get_grid_topology(model)
  test_grid_topology(topo)
  @test num_cell_dims(topo) == Dc
  @test num_point_dims(topo) == Dp
  labels = get_face_labeling(model)
  @test num_faces(labels) == num_faces(topo)
  for d in 0:D
    @test num_faces(labels,d) == num_faces(topo,d)
  end
end

# Delegators to the underlying grid

get_cell_nodes(g::DiscreteModel) = get_cell_nodes(get_grid(g))

get_node_coordinates(g::DiscreteModel) = get_node_coordinates(get_grid(g))

get_cell_type(g::DiscreteModel) = get_cell_type(get_grid(g))

get_reffes(g::DiscreteModel) = get_reffes(get_grid(g))

# Default API

"""
    num_dims(model::DiscreteModel)
"""
num_dims(model::DiscreteModel) = num_dims(get_grid_topology(model))

"""
    num_cell_dims(model::DiscreteModel)
"""
num_cell_dims(model::DiscreteModel) = num_cell_dims(get_grid_topology(model))

"""
    num_point_dims(model::DiscreteModel)
"""
num_point_dims(model::DiscreteModel) = num_point_dims(get_grid_topology(model))

"""
    num_faces(g::DiscreteModel,d::Integer)
    num_faces(g::DiscreteModel)
"""
num_faces(g::DiscreteModel,d::Integer) = num_faces(get_grid_topology(g),d)
num_faces(g::DiscreteModel) = num_faces(get_grid_topology(g))

"""
    num_cells(g::DiscreteModel)
"""
num_cells(g::DiscreteModel) = num_cells(get_grid_topology(g))

"""
    num_facets(g::DiscreteModel)
"""
num_facets(g::DiscreteModel) = num_facets(get_grid_topology(g))

"""
    num_edges(g::DiscreteModel)
"""
num_edges(g::DiscreteModel) = num_edges(get_grid_topology(g))

"""
    num_vertices(g::DiscreteModel)
"""
num_vertices(g::DiscreteModel) = num_vertices(get_grid_topology(g))

"""
    num_nodes(g::DiscreteModel)
"""
num_nodes(g::DiscreteModel) = num_nodes(get_grid(g))

"""
    get_polytopes(model::DiscreteModel)
"""
function get_polytopes(model::DiscreteModel)
  topo = get_grid_topology(model)
  get_polytopes(topo)
end

"""
    get_face_nodes(g::DiscreteModel,d::Integer)
"""
function get_face_nodes(g::DiscreteModel,d::Integer)
  compute_face_nodes(g,d)
end

"""
    get_face_nodes(g::DiscreteModel)
"""
function get_face_nodes(g::DiscreteModel)
  compute_face_nodes(g)
end

"""
    compute_face_nodes(model::DiscreteModel,d::Integer)
"""
function compute_face_nodes(model::DiscreteModel,d::Integer)

  if d == num_cell_dims(model)
    return get_cell_nodes(model)
  end

  topo = get_grid_topology(model)
  D = num_cell_dims(topo)
  cell_to_nodes = Table(get_cell_nodes(model))
  cell_to_faces = Table(get_faces(topo,D,d))
  cell_to_ctype = get_cell_type(model)
  reffes = get_reffes(model)
  ctype_to_lface_to_lnodes = map( (reffe)-> get_face_nodes(reffe,d) , reffes )
  nfaces = num_faces(topo,d)

  face_to_nodes = generate_face_to_vertices(
    cell_to_nodes,
    cell_to_faces,
    cell_to_ctype,
    ctype_to_lface_to_lnodes,
    nfaces)

  face_to_nodes

end

"""
    compute_face_nodes(model::DiscreteModel)
"""
function compute_face_nodes(model::DiscreteModel)
  D = num_cell_dims(model)
  data = [compute_face_nodes(model,d) for d in 0:D]
  append_tables_globally(data...)
end

"""
    get_face_own_nodes(g::DiscreteModel,d::Integer)
"""
function get_face_own_nodes(g::DiscreteModel,d::Integer)
  compute_face_own_nodes(g,d)
end

"""
    get_face_own_nodes(g::DiscreteModel)
"""
function get_face_own_nodes(g::DiscreteModel)
  compute_face_own_nodes(g)
end

"""
    compute_face_own_nodes(model::DiscreteModel,d::Integer)
"""
function compute_face_own_nodes(model::DiscreteModel,d::Integer)

  topo = get_grid_topology(model)
  D = num_cell_dims(topo)
  cell_to_nodes = Table(get_cell_nodes(model))
  cell_to_faces = Table(get_faces(topo,D,d))
  cell_to_ctype = get_cell_type(model)
  reffes = get_reffes(model)
  ctype_to_lface_to_lnodes = map( (reffe)-> get_face_own_nodes(reffe,d) , reffes )
  nfaces = num_faces(topo,d)

  face_to_own_nodes = generate_face_to_vertices(
    cell_to_nodes,
    cell_to_faces,
    cell_to_ctype,
    ctype_to_lface_to_lnodes,
    nfaces)

  face_to_own_nodes

end

"""
    compute_face_own_nodes(model::DiscreteModel)
"""
function compute_face_own_nodes(model::DiscreteModel)
  D = num_cell_dims(model)
  data = [compute_face_own_nodes(model,d) for d in 0:D]
  append_tables_globally(data...)
end

"""
    get_vertex_node(g::DiscreteModel)
"""
function get_vertex_node(g::DiscreteModel)
  compute_vertex_node(g)
end

"""
    compute_vertex_node(g::DiscreteModel)
"""
function compute_vertex_node(g::DiscreteModel)
  d=0
  vertex_to_nodes = Table(get_face_own_nodes(g,d))
  vertex_to_nodes.data
end

"""
    get_node_face_owner(g::DiscreteModel)
"""
function get_node_face_owner(g::DiscreteModel)
  compute_node_face_owner(g)
end

"""
    compute_node_face_owner(g::DiscreteModel)
"""
function compute_node_face_owner(g::DiscreteModel)
  face_to_own_nodes = Table(get_face_own_nodes(g))
  node_to_face_owner = zeros(Int,num_nodes(g))
  _compute_node_face_owner!(node_to_face_owner,face_to_own_nodes)
  node_to_face_owner
end

function  _compute_node_face_owner!(node_to_face_owner,face_to_own_nodes)
  for face in 1:length(face_to_own_nodes)
    pini = face_to_own_nodes.ptrs[face]
    pend = face_to_own_nodes.ptrs[face+1]-1
    for p in pini:pend
      node = face_to_own_nodes.data[p]
      node_to_face_owner[node] = face
    end
  end
end

"""
    get_reffaces(::Type{ReferenceFE{d}},model::DiscreteModel) where d
"""
function get_reffaces(::Type{ReferenceFE{d}},model::DiscreteModel) where d
  reffaces,_ = compute_reffaces(ReferenceFE{d},model)
  reffaces
end

"""
    get_face_type(g::DiscreteModel,d::Integer)

Index to the vector `get_reffaces(ReferenceFE{d},g)`
"""
function get_face_type(g::DiscreteModel,d::Integer)
  _, face_to_ftype = compute_reffaces(ReferenceFE{d},g)
  face_to_ftype
end

"""
    compute_reffaces(::Type{ReferenceFE{d}}, g::DiscreteModel) where d
"""
function compute_reffaces(::Type{ReferenceFE{d}}, g::DiscreteModel) where d
  D = num_cell_dims(g)
  topo = get_grid_topology(g)
  ctype_to_reffe = get_reffes(g)
  ctype_to_lftype_to_refface = [ get_reffaces(ReferenceFE{d},reffe) for reffe in ctype_to_reffe]
  ctype_to_lface_to_lftype = [ get_face_type(reffe,d) for reffe in ctype_to_reffe]
  t = _generate_ftype_to_refface(Val{d}(),ctype_to_lftype_to_refface,ctype_to_lface_to_lftype)
  ftype_to_refface, ctype_to_lface_to_ftype = t
  cell_to_faces = Table(get_faces(topo,D,d))
  cell_to_ctype = get_cell_type(g)
  nfaces = num_faces(g,d)
  face_to_ftype = generate_face_to_face_type(
    cell_to_faces, cell_to_ctype, ctype_to_lface_to_ftype, nfaces)
  (collect1d(ftype_to_refface), face_to_ftype)
end

function compute_reffaces(::Type{ReferenceFE{D}}, g::DiscreteModel{D}) where D
  (get_reffes(g), get_cell_type(g))
end

"""
    get_reffaces(model::DiscreteModel)
"""
function get_reffaces(model::DiscreteModel)
  reffaces, _ , _ = compute_reffaces(model)
  reffaces
end

"""
    get_face_type(model::DiscreteModel)
"""
function get_face_type(model::DiscreteModel)
  _, face_to_ftype, _ = compute_reffaces(model)
  face_to_ftype
end

"""
    get_reffaces_offsets(model::DiscreteModel)
"""
function get_reffaces_offsets(model::DiscreteModel)
  _, _, offsets = compute_reffaces(model)
  offsets
end

"""
    compute_reffaces(g::DiscreteModel)
"""
function compute_reffaces(g::DiscreteModel)
  D = num_cell_dims(g)
  d_to_refdfaces = Vector{LagrangianRefFE}[]
  d_to_dface_to_ftype = Vector{Int8}[]
  for d in 0:D
    push!(d_to_refdfaces,get_reffaces(ReferenceFE{d},g))
    push!(d_to_dface_to_ftype,get_face_type(g,d))
  end
  d_to_offset = zeros(Int,D+1)
  for d in 1:D
    d_to_offset[d+1] = d_to_offset[d] + length(d_to_refdfaces[d])
    d_to_dface_to_ftype[d+1] .+= d_to_offset[d+1]
  end
  (vcat(d_to_refdfaces...), vcat(d_to_dface_to_ftype...), d_to_offset)
end

"""
    Grid(::Type{ReferenceFE{d}},model::DiscreteModel) where d
"""
function Grid(::Type{ReferenceFE{d}},model::DiscreteModel) where d
  node_coordinates = collect1d(get_node_coordinates(model))
  cell_to_nodes = Table(get_face_nodes(model,d))
  cell_to_type = collect1d(get_face_type(model,d))
  reffes = get_reffaces(ReferenceFE{d},model)
  UnstructuredGrid(node_coordinates, cell_to_nodes, reffes, cell_to_type)
end

function Grid(::Type{ReferenceFE{d}},model::DiscreteModel{d}) where d
  get_grid(model)
end

"""
    Triangulation(::Type{ReferenceFE{d}},model::DiscreteModel) where d
    Triangulation(model::DiscreteModel)
"""
function Triangulation(::Type{ReferenceFE{d}},model::DiscreteModel) where d
  Grid(ReferenceFE{d},model)
end

function Triangulation(model::DiscreteModel)
  get_triangulation(model)
end

"""
"""
function Triangulation(model::DiscreteModel,cell_to_oldcell::AbstractVector{<:Integer})
  oldtrian = Triangulation(model)
  RestrictedTriangulation(oldtrian,cell_to_oldcell)
end

function Triangulation(model::DiscreteModel,cell_to_mask::AbstractVector{Bool})
  oldtrian = Triangulation(model)
  RestrictedTriangulation(oldtrian,cell_to_mask)
end

"""
    get_triangulation(model::DiscreteModel)
"""
function get_triangulation(model::DiscreteModel)
  get_grid(model)
end

"""
    simplexify(model::DiscreteModel)
"""
function simplexify(model::DiscreteModel)
  simplexify(UnstructuredDiscreteModel(model))
end

"""
"""
function DiscreteModel(model::DiscreteModel,cell_to_oldcell::AbstractVector{<:Integer})
  RestrictedDiscreteModel(model,cell_to_oldcell)
end

function DiscreteModel(model::DiscreteModel,cell_to_mask::AbstractVector{Bool})
  RestrictedDiscreteModel(model,cell_to_mask)
end

function DiscreteModel(model::DiscreteModel,labels::FaceLabeling,tags)
  cell_to_mask = get_face_mask(labels,tags,num_cell_dims(model))
  DiscreteModel(model,cell_to_mask)
end

function DiscreteModel(model::DiscreteModel,tags)
  labels = get_face_labeling(model)
  DiscreteModel(model,labels,tags)
end

# IO

function to_dict(model::DiscreteModel)
  umodel = UnstructuredDiscreteModel(model)
  to_dict(umodel)
end

function from_dict(::Type{DiscreteModel},dict::Dict{Symbol,Any})
  from_dict(UnstructuredDiscreteModel,dict)
end

"""
    DiscreteModelFromFile(filename::AbstractString)
"""
function DiscreteModelFromFile(filename::AbstractString)
  base, extension = splitext(filename)
  s = Symbol(extension[2:end])
  DiscreteModelFromFile(filename,Val(s))
end

function DiscreteModelFromFile(filename::AbstractString,::Any)
  @notimplemented
end

function DiscreteModelFromFile(filename::AbstractString,::Val{:json})
  model = from_json_file(DiscreteModel,filename)
  model
end

# GenericDiscreteModel

struct GenericDiscreteModel{Dc,Dp} <: DiscreteModel{Dc,Dp}
  grid::Grid
  grid_topology::GridTopology
  labels::FaceLabeling
  function GenericDiscreteModel(grid::Grid{Dc,Dp},grid_topology::GridTopology,labels::FaceLabeling) where {Dc,Dp}
    new{Dc,Dp}(grid,grid_topology,labels)
  end
end

"""
"""
function DiscreteModel(grid::Grid,grid_topology::GridTopology,labels::FaceLabeling)
  GenericDiscreteModel(grid,grid_topology,labels)
end

get_grid(model::GenericDiscreteModel) = model.grid

get_grid_topology(model::GenericDiscreteModel) = model.grid_topology

get_face_labeling(model::GenericDiscreteModel) = model.labels

function DiscreteModel(::Type{<:Polytope{D}},model::DiscreteModel{D}) where D
  model
end

"""
"""
function DiscreteModel(::Type{<:Polytope{D}},model::DiscreteModel) where D
  grid = Grid(ReferenceFE{D},model)
  topo = GridTopology(Polytope{D},model)
  labeling = FaceLabeling(get_face_labeling(model),D)
  DiscreteModel(grid,topo,labeling)
end

"""
"""
function GridTopology(::Type{<:Polytope{D}},model::DiscreteModel) where D
  GridTopology(Polytope{D},get_grid_topology(model))
end


text:
using PrintfFunctions
using Test

@testset "PrintfFunctions.jl" begin
    # Write your tests here.
end


text:
# This file contains functions to install solc.js with NodeJS.

"""
    function getOSname()
This function returns the OS name for the user.
"""
function getOSname()

    if Sys.iswindows()
        OS = "win32"
    elseif Sys.islinux()
        OS = "linux"
    elseif Sys.isapple()
        OS = "macosx"
    else
        println("OS not recognized")
    end

    return OS
end

"""
    function getInstallableSolcVersions()

Return a list of all solc versions that can be installed by Solc.jl.

It is the equivalent au get_installable_solc_version() in py-solc-x.
"""
function getInstallableSolcVersions()

    OS = getOSname()
    BINARY_DOWNLOAD_BASE = string("https://solc-bin.ethereum.org/",OS,"-amd64/list.json")
    r = HTTP.request("GET", BINARY_DOWNLOAD_BASE)
    str = (String(r.body))

    listVersions = sort(JSON.Parser.parse(str)["releases"], rev = true)

    return listVersions
end

"""
    function installSolc()
Download and install a precompiled version of solc.
"""
function installSolc(version::String)

    data = getInstallableSolcVersions()

    if version == "latest"
        version = collect(keys(data))[1]
    end

    OS = getOSname()

    filename = data[version]

    url = string("https://solc-bin.ethereum.org/",OS,"-amd64/",filename)

    downloadSolc(url)

    installSolcUnix(filename)

end

"""
    function downloadSolc()
This function download the solidity compiler version.
"""
function downloadSolc(url::String)

    r = HTTP.request("GET", url)
    HTTP.download(url, "solcv/")
end

"""
    function installSolcUnix(filename)
Installator for Unix system.
"""
function installSolcUnix(filename::String)

    run(`chmod a+x $(string("solcv/",filename))`)

    run(`./$(string("solcv/",filename))`);

end


text:
"""
    complement(g)

Produces the [graph complement](https://en.wikipedia.org/wiki/Complement_graph)
of a graph.
"""
function complement(g::Graph)
    gnv = nv(g)
    h = Graph(gnv)
    for i=1:gnv
        for j=i+1:gnv
            if !has_edge(g, i, j)
                add_edge!(h,i,j)
            end
        end
    end
    return h
end

function complement(g::DiGraph)
    gnv = nv(g)
    h = DiGraph(gnv)
    for i=1:gnv
        for j=1:gnv
            if i != j && !has_edge(g,i,j)
                add_edge!(h,i,j)
            end
        end
    end
    return h
end

"""
    reverse(g::DiGraph)

Produces a graph where all edges are reversed from the
original.
"""
function reverse(g::DiGraph)
    gnv = nv(g)
    gne = ne(g)
    h = DiGraph(gnv)
    h.fadjlist = deepcopy(g.badjlist)
    h.badjlist = deepcopy(g.fadjlist)
    h.ne = gne
    h.vertices = g.vertices

    return h
end

"""
    reverse!(g::DiGraph)

In-place reverse (modifies the original graph).
"""
function reverse!(g::DiGraph)
    g.fadjlist, g.badjlist = g.badjlist, g.fadjlist
    return g
end

doc"""
    blkdiag(g, h)

Produces a graph with $|V(g)| + |V(h)|$ vertices and $|E(g)| + |E(h)|$
edges.

Put simply, the vertices and edges from graph `h` are appended to graph `g`.
"""
function blkdiag{T<:SimpleGraph}(g::T, h::T)
    gnv = nv(g)
    r = T(gnv + nv(h))
    for e in edges(g)
        add_edge!(r, e)
    end
    for e in edges(h)
        add_edge!(r, gnv+src(e), gnv+dst(e))
    end
    return r
end

"""
    intersect(g, h)

Produces a graph with edges that are only in both graph `g` and graph `h`.

Note that this function may produce a graph with 0-degree vertices.
"""
function intersect{T<:SimpleGraph}(g::T, h::T)
    gnv = nv(g)
    hnv = nv(h)

    r = T(min(gnv, hnv))
    for e in intersect(edges(g),edges(h))
        add_edge!(r,e)
    end
    return r
end

"""
    difference(g, h)

Produces a graph with edges in graph `g` that are not in graph `h`.

Note that this function may produce a graph with 0-degree vertices.
"""
function difference{T<:SimpleGraph}(g::T, h::T)
    gnv = nv(g)
    hnv = nv(h)

    r = T(gnv)
    for e in edges(g)
        !has_edge(h, e) && add_edge!(r,e)
    end
    return r
end

"""
    symmetric_difference(g, h)

Produces a graph with edges from graph `g` that do not exist in graph `h`,
and vice versa.

Note that this function may produce a graph with 0-degree vertices.
"""
function symmetric_difference{T<:SimpleGraph}(g::T, h::T)
    gnv = nv(g)
    hnv = nv(h)

    r = T(max(gnv, hnv))
    for e in edges(g)
        !has_edge(h, e) && add_edge!(r, e)
    end
    for e in edges(h)
        !has_edge(g, e) && add_edge!(r, e)
    end
    return r
end

"""
    union(g, h)

Merges graphs `g` and `h` by taking the set union of all vertices and edges.
"""
function union{T<:SimpleGraph}(g::T, h::T)
    gnv = nv(g)
    hnv = nv(h)

    r = T(max(gnv, hnv))
    r.ne = ne(g)
    for i = 1:gnv
        r.fadjlist[i] = deepcopy(g.fadjlist[i])
        if is_directed(g)
            r.badjlist[i] = deepcopy(g.badjlist[i])
        end
    end
    for e in edges(h)
        add_edge!(r, e)
    end
    return r
end

"""
    join(g, h)

Merges graphs `g` and `h` using `blkdiag` and then adds all the edges between
 the vertices in `g` and those in `h`.
"""
function join(g::Graph, h::Graph)
    r = blkdiag(g, h)
    for i=1:nv(g)
        for j=nv(g)+1:nv(g)+nv(h)
            add_edge!(r, i, j)
        end
    end
    return r
end

"""
    crosspath(len::Integer, g::Graph)

Replicate `len` times `h` and connect each vertex with its copies in a path
"""
crosspath(len::Integer, g::Graph) = cartesian_product(PathGraph(len), g)

"""
    induced_subgraph(g, iter)

Filters graph `g` to include only the vertices present in the iterable
argument `vs`. Returns the subgraph of `g` induced by `vs`.
"""
function induced_subgraph{T<:SimpleGraph}(g::T, iter)
    n = length(iter)
    isequal(n, length(unique(iter))) || error("Vertices in subgraph list must be unique")
    isequal(n, nv(g)) && return copy(g) # if iter is not a proper subgraph

    h = T(n)
    newvid = Dict{Int, Int}()
    i=1
    for (i,v) in enumerate(iter)
        newvid[v] = i
    end

    iterset = Set(iter)
    for s in iter
        for d in out_neighbors(g, s)
            # println("s = $s, d = $d")
            if d in iterset && has_edge(g, s, d)
                newe = Edge(newvid[s], newvid[d])
                add_edge!(h, newe)
            end
        end
    end
    return h
end

# dispatch for g[[1,2,3]], g[1:3], g[Set([1,2,3])]
# these are the only allowed dispatches, everything else is slow
getindex(g::SimpleGraph, iter) = induced_subgraph(g, iter)

# The following operators allow one to use a LightGraphs.Graph as a matrix in eigensolvers for spectral ranking and partitioning.
# """Provides multiplication of a graph `g` by a vector `v` such that spectral
# graph functions in [GraphMatrices.jl](https://github.com/jpfairbanks/GraphMatrices.jl) can utilize LightGraphs natively.
# """
function *{T<:Real}(g::Graph, v::Vector{T})
    length(v) == nv(g) || error("Vector size must equal number of vertices")
    y = zeros(T, nv(g))
    for e in edges(g)
        i = src(e)
        j = dst(e)
        y[i] += v[j]
        y[j] += v[i]
    end
    return y
end

function *{T<:Real}(g::DiGraph, v::Vector{T})
    length(v) == nv(g) || error("Vector size must equal number of vertices")
    y = zeros(T, nv(g))
    for e in edges(g)
        i = src(e)
        j = dst(e)
        y[i] += v[j]
    end
    return y
end

"""sum(g,i) provides 1:indegree or 2:outdegree vectors"""
function sum(g::SimpleGraph, dim::Int)
    dim == 1 && return indegree(g, vertices(g))
    dim == 2 && return outdegree(g, vertices(g))
    error("Graphs are only two dimensional")
end

size(g::SimpleGraph) = (nv(g), nv(g))
"""size(g,i) provides 1:nv or 2:nv else 1 """
size(g::Graph,dim::Int) = (dim == 1 || dim == 2)? nv(g) : 1

"""sum(g) provides the number of edges in the graph"""
sum(g::SimpleGraph) = ne(g)

"""sparse(g) is the adjacency_matrix of g"""
sparse(g::SimpleGraph) = adjacency_matrix(g)

#arrayfunctions = (:eltype, :length, :ndims, :size, :strides, :issymmetric)
eltype(g::SimpleGraph) = Float64
length(g::SimpleGraph) = nv(g)*nv(g)
ndims(g::SimpleGraph) = 2
issymmetric(g::SimpleGraph) = !is_directed(g)

"""
    cartesian_product(g, h)

Returns the (cartesian product)[https://en.wikipedia.org/wiki/Tensor_product_of_graphs] of `g` and `h`
"""
function cartesian_product{G<:SimpleGraph}(g::G, h::G)
    z = G(nv(g)*nv(h))
    id(i, j) = (i-1)*nv(h) + j
    for (i1, i2) in edges(g)
        for j=1:nv(h)
            add_edge!(z, id(i1,j), id(i2,j))
        end
    end

    for e in edges(h)
        j1, j2 = src(e), dst(e)
        for i=1:nv(g)
            add_edge!(z, id(i,j1), id(i,j2))
        end
    end
    return z
end

"""
    tensor_product(g, h)

Returns the (tensor product)[https://en.wikipedia.org/wiki/Tensor_product_of_graphs] of `g` and `h`
"""
function tensor_product{G<:SimpleGraph}(g::G, h::G)
    z = G(nv(g)*nv(h))
    id(i, j) = (i-1)*nv(h) + j
    for (i1, i2) in edges(g)
        for (j1, j2) in edges(h)
            add_edge!(z, id(i1, j1), id(i2, j2))
        end
    end
    return z
end


text:
import PredictMD
import Test

Test.@test Base.IteratorEltype(PredictMD.SimplePipeline{String, Vector}) == Base.HasEltype()
Test.@test Base.IteratorSize(PredictMD.SimplePipeline{String, Vector}) == Base.HasShape{1}()
Test.@test Base.IndexStyle(PredictMD.SimplePipeline{String, Vector}) == Base.IndexLinear()

a = PredictMD.SimplePipeline("name", PredictMD.AbstractFittable[])
Test.@test PredictMD.ispipeline(a)
Test.@test PredictMD.isflat(a)
Test.@test a isa PredictMD.SimplePipeline
Test.@test isempty(a)
Test.@test length(a) == 0
Test.@test ndims(a) == 1
Test.@test length(size(a)) == 1
Test.@test size(a) == (0,)
Test.@test size(a)[1] == 0
PredictMD.get_underlying(a)
PredictMD.get_history(a)
empty!(a)

Test.@test !PredictMD.ispipeline(Foo_Fittable(0))

b = Foo_Fittable(11) |> Bar_Fittable(22)
c = Foo_Fittable(11) |> Bar_Fittable(22) |> Baz_Fittable(30)
d = Foo_Fittable(11) |> Bar_Fittable(22) |> Baz_Fittable(30) |> Foo_Fittable(40) |> Bar_Fittable(50) |> Baz_Fittable(60)
e = PredictMD.SimplePipeline("name", PredictMD.AbstractFittable[Baz_Fittable(11)])

Test.@test PredictMD.ispipeline(b)
Test.@test PredictMD.ispipeline(c)
Test.@test PredictMD.ispipeline(d)
Test.@test PredictMD.ispipeline(e)

Test.@test PredictMD.isflat(b)
Test.@test PredictMD.isflat(c)
Test.@test PredictMD.isflat(d)
Test.@test PredictMD.isflat(e)

Test.@test b isa PredictMD.SimplePipeline
Test.@test c isa PredictMD.SimplePipeline
Test.@test d isa PredictMD.SimplePipeline
Test.@test e isa PredictMD.SimplePipeline

Test.@test !isempty(b)
Test.@test !isempty(c)
Test.@test !isempty(d)
Test.@test !isempty(e)

Test.@test length(b) == 2
Test.@test ndims(b) == 1
Test.@test length(size(b)) == 1
Test.@test size(b) == (2,)
Test.@test size(b)[1] == 2

Test.@test length(c) == 3
Test.@test ndims(c) == 1
Test.@test length(size(c)) == 1
Test.@test size(c) == (3,)
Test.@test size(c)[1] == 3

Test.@test length(d) == 6
Test.@test ndims(d) == 1
Test.@test length(size(d)) == 1
Test.@test size(d) == (6,)
Test.@test size(d)[1] == 6

Test.@test length(e) == 1
Test.@test ndims(e) == 1
Test.@test length(size(e)) == 1
Test.@test size(e) == (1,)
Test.@test size(e)[1] == 1

Test.@test b[1] == Foo_Fittable(11)
Test.@test b[2] == Bar_Fittable(22)

Test.@test c[1] == Foo_Fittable(11)
Test.@test c[2] == Bar_Fittable(22)
Test.@test c[3] == Baz_Fittable(30)
Test.@test c[1:3] == [Foo_Fittable(11), Bar_Fittable(22), Baz_Fittable(30)]
Test.@test view(c, 2:3)[1] == Bar_Fittable(22)
Test.@test view(c, 2:3)[2] == Baz_Fittable(30)

for x in c
    Test.@test x isa PredictMD.AbstractFittable
end

c[2] = Foo_Fittable(22)
Test.@test c[1] == Foo_Fittable(11)
Test.@test c[2] == Foo_Fittable(22)
Test.@test c[3] == Baz_Fittable(30)

Test.@test e[1] == Baz_Fittable(11)
for x in e
    Test.@test x == Baz_Fittable(11)
end
Test.@test isassigned(e, 1)

Test.@test isa(Foo_Fittable(11) |> Bar_Fittable(22), PredictMD.SimplePipeline)
Test.@test isa(PredictMD.SimplePipeline("", [Foo_Fittable(11)]) |> Bar_Fittable(22), PredictMD.SimplePipeline)
Test.@test isa(Foo_Fittable(11) |> PredictMD.SimplePipeline("", [Bar_Fittable(22)]), PredictMD.SimplePipeline)
Test.@test isa(PredictMD.SimplePipeline("", [Foo_Fittable(11)]) |> PredictMD.SimplePipeline("", [Bar_Fittable(22)]), PredictMD.SimplePipeline)

f = Foo_Fittable(1) |> Foo_Fittable(2) |> Foo_Fittable(3) |> Foo_Fittable(4) |> Foo_Fittable(5) |> Foo_Fittable(6) |> Foo_Fittable(7)
Test.@test PredictMD.ispipeline(f)
Test.@test PredictMD.isflat(f)
for x in f
    Test.@test x isa Foo_Fittable
end
for i = 1:length(f)
    Test.@test f[i] isa Foo_Fittable
    Test.@test f[i] == Foo_Fittable(i)
    Test.@test f[i].x == i
end
for i = 1:size(f,1)
    Test.@test f[i] isa Foo_Fittable
    Test.@test f[i] == Foo_Fittable(i)
    Test.@test f[i].x == i
end
for i = 1:size(f)[1]
    Test.@test f[i] isa Foo_Fittable
    Test.@test f[i] == Foo_Fittable(i)
    Test.@test f[i].x == i
end
Test.@test ndims(f) == 1
Test.@test length(f) == 7
Test.@test size(f) == (7,)
Test.@test size(f)[1] == 7
Test.@test length(size(f)) == 1

g = Foo_Fittable(1) |> Bar_Fittable(2) |> Baz_Fittable(3) |> Foo_Fittable(4) |> Bar_Fittable(5) |> Baz_Fittable(6) |> Foo_Fittable(7)
Test.@test PredictMD.ispipeline(g)
Test.@test PredictMD.isflat(g)
for x in g
    Test.@test x isa PredictMD.AbstractFittable
end
for i = 1:length(g)
    Test.@test g[i] isa PredictMD.AbstractFittable
    Test.@test g[i].x == i
end
for i = 1:size(g,1)
    Test.@test g[i] isa PredictMD.AbstractFittable
    Test.@test g[i].x == i
end
for i = 1:size(g)[1]
    Test.@test g[i] isa PredictMD.AbstractFittable
    Test.@test g[i].x == i
end
Test.@test ndims(g) == 1
Test.@test length(g) == 7
Test.@test size(g) ==(7,)
Test.@test size(g)[1] == 7
Test.@test length(size(g)) == 1

h = Foo_Fittable(11) |> Bar_Fittable(22)
i = PredictMD.SimplePipeline([Foo_Fittable(11)]) |> Bar_Fittable(22)
j = Foo_Fittable(11) |> PredictMD.SimplePipeline([Bar_Fittable(22)])
k = PredictMD.SimplePipeline([Foo_Fittable(11)]) |> PredictMD.SimplePipeline([Bar_Fittable(22)])

Test.@test PredictMD.ispipeline(h)
Test.@test PredictMD.ispipeline(i)
Test.@test PredictMD.ispipeline(j)
Test.@test PredictMD.ispipeline(k)

Test.@test PredictMD.isflat(h)
Test.@test PredictMD.isflat(i)
Test.@test PredictMD.isflat(j)
Test.@test PredictMD.isflat(k)

Test.@test h[1] == Foo_Fittable(11)
Test.@test i[1] == Foo_Fittable(11)
Test.@test j[1] == Foo_Fittable(11)
Test.@test k[1] == Foo_Fittable(11)

Test.@test h[2] == Bar_Fittable(22)
Test.@test i[2] == Bar_Fittable(22)
Test.@test j[2] == Bar_Fittable(22)
Test.@test k[2] == Bar_Fittable(22)

Test.@test h[1].x == 11
Test.@test i[1].x == 11
Test.@test j[1].x == 11
Test.@test k[1].x == 11

Test.@test h[2].x == 22
Test.@test i[2].x == 22
Test.@test j[2].x == 22
Test.@test k[2].x == 22

aa = PredictMD.SimplePipeline([Foo_Fittable(10), Foo_Fittable(20), Foo_Fittable(30)]; name = "aa")
bb = PredictMD.SimplePipeline([Foo_Fittable(40), Foo_Fittable(50), Foo_Fittable(60)]; name = "bb")
cc = PredictMD.SimplePipeline([Foo_Fittable(70), Foo_Fittable(80), Foo_Fittable(90)]; name = "cc")

Test.@test PredictMD.ispipeline(aa)
Test.@test PredictMD.ispipeline(bb)
Test.@test PredictMD.ispipeline(cc)

Test.@test PredictMD.isflat(aa)
Test.@test PredictMD.isflat(bb)
Test.@test PredictMD.isflat(cc)

dd = PredictMD.SimplePipeline([Foo_Fittable(100), Foo_Fittable(110), Foo_Fittable(120)]; name = "dd")
ee = PredictMD.SimplePipeline([Foo_Fittable(130), Foo_Fittable(140), Foo_Fittable(150)]; name = "ee")

Test.@test PredictMD.ispipeline(dd)
Test.@test PredictMD.ispipeline(ee)

Test.@test PredictMD.isflat(dd)
Test.@test PredictMD.isflat(ee)

ff = PredictMD.SimplePipeline([Foo_Fittable(160), Foo_Fittable(170), Foo_Fittable(180)]; name = "ff")
gg = PredictMD.SimplePipeline([Foo_Fittable(190), Foo_Fittable(200), Foo_Fittable(210)]; name = "gg")

Test.@test PredictMD.ispipeline(ff)
Test.@test PredictMD.ispipeline(gg)

Test.@test PredictMD.isflat(ff)
Test.@test PredictMD.isflat(gg)

hh = PredictMD.SimplePipeline([aa, bb, cc])
ii = PredictMD.SimplePipeline([dd, ee])
jj = PredictMD.SimplePipeline([ff, gg])

Test.@test PredictMD.ispipeline(hh)
Test.@test PredictMD.ispipeline(ii)
Test.@test PredictMD.ispipeline(jj)

Test.@test !PredictMD.isflat(hh)
Test.@test !PredictMD.isflat(ii)
Test.@test !PredictMD.isflat(jj)

kk = PredictMD.SimplePipeline([hh])
ll = PredictMD.SimplePipeline([ii, jj]) |> Foo_Fittable(220)

Test.@test PredictMD.ispipeline(kk)
Test.@test PredictMD.ispipeline(ll)
Test.@test !PredictMD.ispipeline(Foo_Fittable(220))

Test.@test !PredictMD.isflat(kk)
Test.@test !PredictMD.isflat(ll)

mm = PredictMD.SimplePipeline([kk, ll])
Test.@test PredictMD.ispipeline(mm)
Test.@test !PredictMD.isflat(mm)

mm_flattened = PredictMD.flatten(mm)
Test.@test PredictMD.ispipeline(mm_flattened)
Test.@test PredictMD.isflat(mm_flattened)
Test.@test mm_flattened isa PredictMD.SimplePipeline
for x in mm_flattened
    Test.@test x isa Foo_Fittable
end
for i = 1:length(mm_flattened)
    Test.@test mm_flattened[i] isa Foo_Fittable
    Test.@test mm_flattened[i] == Foo_Fittable(i*10)
    Test.@test mm_flattened[i].x == i*10
end
for i = 1:size(mm_flattened,1)
    Test.@test mm_flattened[i] isa Foo_Fittable
    Test.@test mm_flattened[i] == Foo_Fittable(i*10)
    Test.@test mm_flattened[i].x == i*10
end
for i = 1:size(mm_flattened)[1]
    Test.@test mm_flattened[i] isa Foo_Fittable
    Test.@test mm_flattened[i] == Foo_Fittable(i*10)
    Test.@test mm_flattened[i].x == i*10
end
Test.@test length(mm_flattened) == 22
Test.@test ndims(mm_flattened) == 1
Test.@test length(size(mm_flattened)) == 1
Test.@test size(mm_flattened) == (22,)
Test.@test size(mm_flattened)[1] == 22
Test.@test mm_flattened[1] == Foo_Fittable(10)
Test.@test mm_flattened[2] == Foo_Fittable(20)
Test.@test mm_flattened[3] == Foo_Fittable(30)
Test.@test mm_flattened[4] == Foo_Fittable(40)
Test.@test mm_flattened[5] == Foo_Fittable(50)
Test.@test mm_flattened[6] == Foo_Fittable(60)
Test.@test mm_flattened[7] == Foo_Fittable(70)
Test.@test mm_flattened[8] == Foo_Fittable(80)
Test.@test mm_flattened[9] == Foo_Fittable(90)
Test.@test mm_flattened[10] == Foo_Fittable(100)
Test.@test mm_flattened[11] == Foo_Fittable(110)
Test.@test mm_flattened[12] == Foo_Fittable(120)
Test.@test mm_flattened[13] == Foo_Fittable(130)
Test.@test mm_flattened[14] == Foo_Fittable(140)
Test.@test mm_flattened[15] == Foo_Fittable(150)
Test.@test mm_flattened[16] == Foo_Fittable(160)
Test.@test mm_flattened[17] == Foo_Fittable(170)
Test.@test mm_flattened[18] == Foo_Fittable(180)
Test.@test mm_flattened[19] == Foo_Fittable(190)
Test.@test mm_flattened[20] == Foo_Fittable(200)
Test.@test mm_flattened[21] == Foo_Fittable(210)
Test.@test mm_flattened[22] == Foo_Fittable(220)

mm_flat_on_creation_in_one_step_from_some_primitives = aa |> bb |> cc |> dd |> ee |> ff |> gg |> Foo_Fittable(220)

Test.@test PredictMD.ispipeline(mm_flat_on_creation_in_one_step_from_some_primitives)
Test.@test PredictMD.isflat(mm_flat_on_creation_in_one_step_from_some_primitives)
Test.@test mm_flat_on_creation_in_one_step_from_some_primitives isa PredictMD.SimplePipeline
for x in mm_flat_on_creation_in_one_step_from_some_primitives
    Test.@test x isa Foo_Fittable
end
for i = 1:length(mm_flat_on_creation_in_one_step_from_some_primitives)
    Test.@test mm_flat_on_creation_in_one_step_from_some_primitives[i] isa Foo_Fittable
    Test.@test mm_flat_on_creation_in_one_step_from_some_primitives[i] == Foo_Fittable(i*10)
    Test.@test mm_flat_on_creation_in_one_step_from_some_primitives[i].x == i*10
end
for i = 1:size(mm_flat_on_creation_in_one_step_from_some_primitives,1)
    Test.@test mm_flat_on_creation_in_one_step_from_some_primitives[i] isa Foo_Fittable
    Test.@test mm_flat_on_creation_in_one_step_from_some_primitives[i] == Foo_Fittable(i*10)
    Test.@test mm_flat_on_creation_in_one_step_from_some_primitives[i].x == i*10
end
for i = 1:size(mm_flat_on_creation_in_one_step_from_some_primitives)[1]
    Test.@test mm_flat_on_creation_in_one_step_from_some_primitives[i] isa Foo_Fittable
    Test.@test mm_flat_on_creation_in_one_step_from_some_primitives[i] == Foo_Fittable(i*10)
    Test.@test mm_flat_on_creation_in_one_step_from_some_primitives[i].x == i*10
end
Test.@test length(mm_flat_on_creation_in_one_step_from_some_primitives) == 22
Test.@test ndims(mm_flat_on_creation_in_one_step_from_some_primitives) == 1
Test.@test length(size(mm_flat_on_creation_in_one_step_from_some_primitives)) == 1
Test.@test size(mm_flat_on_creation_in_one_step_from_some_primitives) == (22,)
Test.@test size(mm_flat_on_creation_in_one_step_from_some_primitives)[1] == 22
Test.@test mm_flat_on_creation_in_one_step_from_some_primitives[1] == Foo_Fittable(10)
Test.@test mm_flat_on_creation_in_one_step_from_some_primitives[2] == Foo_Fittable(20)
Test.@test mm_flat_on_creation_in_one_step_from_some_primitives[3] == Foo_Fittable(30)
Test.@test mm_flat_on_creation_in_one_step_from_some_primitives[4] == Foo_Fittable(40)
Test.@test mm_flat_on_creation_in_one_step_from_some_primitives[5] == Foo_Fittable(50)
Test.@test mm_flat_on_creation_in_one_step_from_some_primitives[6] == Foo_Fittable(60)
Test.@test mm_flat_on_creation_in_one_step_from_some_primitives[7] == Foo_Fittable(70)
Test.@test mm_flat_on_creation_in_one_step_from_some_primitives[8] == Foo_Fittable(80)
Test.@test mm_flat_on_creation_in_one_step_from_some_primitives[9] == Foo_Fittable(90)
Test.@test mm_flat_on_creation_in_one_step_from_some_primitives[10] == Foo_Fittable(100)
Test.@test mm_flat_on_creation_in_one_step_from_some_primitives[11] == Foo_Fittable(110)
Test.@test mm_flat_on_creation_in_one_step_from_some_primitives[12] == Foo_Fittable(120)
Test.@test mm_flat_on_creation_in_one_step_from_some_primitives[13] == Foo_Fittable(130)
Test.@test mm_flat_on_creation_in_one_step_from_some_primitives[14] == Foo_Fittable(140)
Test.@test mm_flat_on_creation_in_one_step_from_some_primitives[15] == Foo_Fittable(150)
Test.@test mm_flat_on_creation_in_one_step_from_some_primitives[16] == Foo_Fittable(160)
Test.@test mm_flat_on_creation_in_one_step_from_some_primitives[17] == Foo_Fittable(170)
Test.@test mm_flat_on_creation_in_one_step_from_some_primitives[18] == Foo_Fittable(180)
Test.@test mm_flat_on_creation_in_one_step_from_some_primitives[19] == Foo_Fittable(190)
Test.@test mm_flat_on_creation_in_one_step_from_some_primitives[20] == Foo_Fittable(200)
Test.@test mm_flat_on_creation_in_one_step_from_some_primitives[21] == Foo_Fittable(210)
Test.@test mm_flat_on_creation_in_one_step_from_some_primitives[22] == Foo_Fittable(220)

mm_flat_on_creation_in_one_step_from_all_primitives = Foo_Fittable(10) |> Foo_Fittable(20) |> Foo_Fittable(30) |> Foo_Fittable(40) |> Foo_Fittable(50) |> Foo_Fittable(60) |> Foo_Fittable(70) |> Foo_Fittable(80) |> Foo_Fittable(90) |> Foo_Fittable(100) |> Foo_Fittable(110) |> Foo_Fittable(120) |> Foo_Fittable(130) |> Foo_Fittable(140) |> Foo_Fittable(150) |> Foo_Fittable(160) |> Foo_Fittable(170) |> Foo_Fittable(180) |> Foo_Fittable(190) |> Foo_Fittable(200) |> Foo_Fittable(210) |> Foo_Fittable(220)

Test.@test PredictMD.ispipeline(mm_flat_on_creation_in_one_step_from_all_primitives)
Test.@test PredictMD.isflat(mm_flat_on_creation_in_one_step_from_all_primitives)
Test.@test mm_flat_on_creation_in_one_step_from_all_primitives isa PredictMD.SimplePipeline
for x in mm_flat_on_creation_in_one_step_from_all_primitives
    Test.@test x isa Foo_Fittable
end
for i = 1:length(mm_flat_on_creation_in_one_step_from_all_primitives)
    Test.@test mm_flat_on_creation_in_one_step_from_all_primitives[i] isa Foo_Fittable
    Test.@test mm_flat_on_creation_in_one_step_from_all_primitives[i] == Foo_Fittable(i*10)
    Test.@test mm_flat_on_creation_in_one_step_from_all_primitives[i].x == i*10
end
for i = 1:size(mm_flat_on_creation_in_one_step_from_all_primitives,1)
    Test.@test mm_flat_on_creation_in_one_step_from_all_primitives[i] isa Foo_Fittable
    Test.@test mm_flat_on_creation_in_one_step_from_all_primitives[i] == Foo_Fittable(i*10)
    Test.@test mm_flat_on_creation_in_one_step_from_all_primitives[i].x == i*10
end
for i = 1:size(mm_flat_on_creation_in_one_step_from_all_primitives)[1]
    Test.@test mm_flat_on_creation_in_one_step_from_all_primitives[i] isa Foo_Fittable
    Test.@test mm_flat_on_creation_in_one_step_from_all_primitives[i] == Foo_Fittable(i*10)
    Test.@test mm_flat_on_creation_in_one_step_from_all_primitives[i].x == i*10
end
Test.@test length(mm_flat_on_creation_in_one_step_from_all_primitives) == 22
Test.@test ndims(mm_flat_on_creation_in_one_step_from_all_primitives) == 1
Test.@test length(size(mm_flat_on_creation_in_one_step_from_all_primitives)) == 1
Test.@test size(mm_flat_on_creation_in_one_step_from_all_primitives) == (22,)
Test.@test size(mm_flat_on_creation_in_one_step_from_all_primitives)[1] == 22
Test.@test mm_flat_on_creation_in_one_step_from_all_primitives[1] == Foo_Fittable(10)
Test.@test mm_flat_on_creation_in_one_step_from_all_primitives[2] == Foo_Fittable(20)
Test.@test mm_flat_on_creation_in_one_step_from_all_primitives[3] == Foo_Fittable(30)
Test.@test mm_flat_on_creation_in_one_step_from_all_primitives[4] == Foo_Fittable(40)
Test.@test mm_flat_on_creation_in_one_step_from_all_primitives[5] == Foo_Fittable(50)
Test.@test mm_flat_on_creation_in_one_step_from_all_primitives[6] == Foo_Fittable(60)
Test.@test mm_flat_on_creation_in_one_step_from_all_primitives[7] == Foo_Fittable(70)
Test.@test mm_flat_on_creation_in_one_step_from_all_primitives[8] == Foo_Fittable(80)
Test.@test mm_flat_on_creation_in_one_step_from_all_primitives[9] == Foo_Fittable(90)
Test.@test mm_flat_on_creation_in_one_step_from_all_primitives[10] == Foo_Fittable(100)
Test.@test mm_flat_on_creation_in_one_step_from_all_primitives[11] == Foo_Fittable(110)
Test.@test mm_flat_on_creation_in_one_step_from_all_primitives[12] == Foo_Fittable(120)
Test.@test mm_flat_on_creation_in_one_step_from_all_primitives[13] == Foo_Fittable(130)
Test.@test mm_flat_on_creation_in_one_step_from_all_primitives[14] == Foo_Fittable(140)
Test.@test mm_flat_on_creation_in_one_step_from_all_primitives[15] == Foo_Fittable(150)
Test.@test mm_flat_on_creation_in_one_step_from_all_primitives[16] == Foo_Fittable(160)
Test.@test mm_flat_on_creation_in_one_step_from_all_primitives[17] == Foo_Fittable(170)
Test.@test mm_flat_on_creation_in_one_step_from_all_primitives[18] == Foo_Fittable(180)
Test.@test mm_flat_on_creation_in_one_step_from_all_primitives[19] == Foo_Fittable(190)
Test.@test mm_flat_on_creation_in_one_step_from_all_primitives[20] == Foo_Fittable(200)
Test.@test mm_flat_on_creation_in_one_step_from_all_primitives[21] == Foo_Fittable(210)
Test.@test mm_flat_on_creation_in_one_step_from_all_primitives[22] == Foo_Fittable(220)

hh_flat = aa |> bb |> cc
ii_flat = dd |> ee
jj_flat = ff |> gg

kk_flat = hh_flat
ll_flat = ii_flat |> jj_flat |> Foo_Fittable(220)

mm_flat_on_creation_in_multiple_steps_from_some_primitives = kk_flat |> ll_flat

Test.@test !PredictMD.ispipeline(Foo_Fittable(220))

Test.@test PredictMD.ispipeline(aa)
Test.@test PredictMD.ispipeline(bb)
Test.@test PredictMD.ispipeline(cc)
Test.@test PredictMD.ispipeline(dd)
Test.@test PredictMD.ispipeline(ee)
Test.@test PredictMD.ispipeline(ff)
Test.@test PredictMD.ispipeline(gg)
Test.@test PredictMD.ispipeline(hh_flat)
Test.@test PredictMD.ispipeline(ii_flat)
Test.@test PredictMD.ispipeline(jj_flat)
Test.@test PredictMD.ispipeline(kk_flat)
Test.@test PredictMD.ispipeline(ll_flat)
Test.@test PredictMD.ispipeline(mm_flat_on_creation_in_multiple_steps_from_some_primitives)

Test.@test PredictMD.isflat(aa)
Test.@test PredictMD.isflat(bb)
Test.@test PredictMD.isflat(cc)
Test.@test PredictMD.isflat(dd)
Test.@test PredictMD.isflat(ee)
Test.@test PredictMD.isflat(ff)
Test.@test PredictMD.isflat(gg)
Test.@test PredictMD.isflat(hh_flat)
Test.@test PredictMD.isflat(ii_flat)
Test.@test PredictMD.isflat(jj_flat)
Test.@test PredictMD.isflat(kk_flat)
Test.@test PredictMD.isflat(ll_flat)
Test.@test PredictMD.isflat(mm_flat_on_creation_in_multiple_steps_from_some_primitives)

Test.@test mm_flat_on_creation_in_multiple_steps_from_some_primitives isa PredictMD.SimplePipeline
for x in mm_flat_on_creation_in_multiple_steps_from_some_primitives
    Test.@test x isa Foo_Fittable
end
for i = 1:length(mm_flat_on_creation_in_multiple_steps_from_some_primitives)
    Test.@test mm_flat_on_creation_in_multiple_steps_from_some_primitives[i] isa Foo_Fittable
    Test.@test mm_flat_on_creation_in_multiple_steps_from_some_primitives[i] == Foo_Fittable(i*10)
    Test.@test mm_flat_on_creation_in_multiple_steps_from_some_primitives[i].x == i*10
end
for i = 1:size(mm_flat_on_creation_in_multiple_steps_from_some_primitives,1)
    Test.@test mm_flat_on_creation_in_multiple_steps_from_some_primitives[i] isa Foo_Fittable
    Test.@test mm_flat_on_creation_in_multiple_steps_from_some_primitives[i] == Foo_Fittable(i*10)
    Test.@test mm_flat_on_creation_in_multiple_steps_from_some_primitives[i].x == i*10
end
for i = 1:size(mm_flat_on_creation_in_multiple_steps_from_some_primitives)[1]
    Test.@test mm_flat_on_creation_in_multiple_steps_from_some_primitives[i] isa Foo_Fittable
    Test.@test mm_flat_on_creation_in_multiple_steps_from_some_primitives[i] == Foo_Fittable(i*10)
    Test.@test mm_flat_on_creation_in_multiple_steps_from_some_primitives[i].x == i*10
end
Test.@test length(mm_flat_on_creation_in_multiple_steps_from_some_primitives) == 22
Test.@test ndims(mm_flat_on_creation_in_multiple_steps_from_some_primitives) == 1
Test.@test length(size(mm_flat_on_creation_in_multiple_steps_from_some_primitives)) == 1
Test.@test size(mm_flat_on_creation_in_multiple_steps_from_some_primitives) == (22,)
Test.@test size(mm_flat_on_creation_in_multiple_steps_from_some_primitives)[1] == 22
Test.@test mm_flat_on_creation_in_multiple_steps_from_some_primitives[1] == Foo_Fittable(10)
Test.@test mm_flat_on_creation_in_multiple_steps_from_some_primitives[2] == Foo_Fittable(20)
Test.@test mm_flat_on_creation_in_multiple_steps_from_some_primitives[3] == Foo_Fittable(30)
Test.@test mm_flat_on_creation_in_multiple_steps_from_some_primitives[4] == Foo_Fittable(40)
Test.@test mm_flat_on_creation_in_multiple_steps_from_some_primitives[5] == Foo_Fittable(50)
Test.@test mm_flat_on_creation_in_multiple_steps_from_some_primitives[6] == Foo_Fittable(60)
Test.@test mm_flat_on_creation_in_multiple_steps_from_some_primitives[7] == Foo_Fittable(70)
Test.@test mm_flat_on_creation_in_multiple_steps_from_some_primitives[8] == Foo_Fittable(80)
Test.@test mm_flat_on_creation_in_multiple_steps_from_some_primitives[9] == Foo_Fittable(90)
Test.@test mm_flat_on_creation_in_multiple_steps_from_some_primitives[10] == Foo_Fittable(100)
Test.@test mm_flat_on_creation_in_multiple_steps_from_some_primitives[11] == Foo_Fittable(110)
Test.@test mm_flat_on_creation_in_multiple_steps_from_some_primitives[12] == Foo_Fittable(120)
Test.@test mm_flat_on_creation_in_multiple_steps_from_some_primitives[13] == Foo_Fittable(130)
Test.@test mm_flat_on_creation_in_multiple_steps_from_some_primitives[14] == Foo_Fittable(140)
Test.@test mm_flat_on_creation_in_multiple_steps_from_some_primitives[15] == Foo_Fittable(150)
Test.@test mm_flat_on_creation_in_multiple_steps_from_some_primitives[16] == Foo_Fittable(160)
Test.@test mm_flat_on_creation_in_multiple_steps_from_some_primitives[17] == Foo_Fittable(170)
Test.@test mm_flat_on_creation_in_multiple_steps_from_some_primitives[18] == Foo_Fittable(180)
Test.@test mm_flat_on_creation_in_multiple_steps_from_some_primitives[19] == Foo_Fittable(190)
Test.@test mm_flat_on_creation_in_multiple_steps_from_some_primitives[20] == Foo_Fittable(200)
Test.@test mm_flat_on_creation_in_multiple_steps_from_some_primitives[21] == Foo_Fittable(210)
Test.@test mm_flat_on_creation_in_multiple_steps_from_some_primitives[22] == Foo_Fittable(220)

aa_frombasepipe = Foo_Fittable(10) |> Foo_Fittable(20) |> Foo_Fittable(30)
bb_frombasepipe = Foo_Fittable(40) |> Foo_Fittable(50) |> Foo_Fittable(60)
cc_frombasepipe = Foo_Fittable(70) |> Foo_Fittable(80) |> Foo_Fittable(90)
dd_frombasepipe = Foo_Fittable(100) |> Foo_Fittable(110) |> Foo_Fittable(120)
ee_frombasepipe = Foo_Fittable(130) |> Foo_Fittable(140) |> Foo_Fittable(150)
ff_frombasepipe = Foo_Fittable(160) |> Foo_Fittable(170) |> Foo_Fittable(180)
gg_frombasepipe = Foo_Fittable(190) |> Foo_Fittable(200) |> Foo_Fittable(210)

hh_flat = aa_frombasepipe |> bb_frombasepipe |> cc_frombasepipe
ii_flat = dd_frombasepipe |> ee_frombasepipe
jj_flat = ff_frombasepipe |> gg_frombasepipe

kk_flat = hh_flat
ll_flat = ii_flat |> jj_flat |> Foo_Fittable(220)

mm_flat_on_creation_in_multiple_steps_from_all_primitives = kk_flat |> ll_flat

Test.@test !PredictMD.ispipeline(Foo_Fittable(220))

Test.@test PredictMD.ispipeline(aa_frombasepipe)
Test.@test PredictMD.ispipeline(bb_frombasepipe)
Test.@test PredictMD.ispipeline(cc_frombasepipe)
Test.@test PredictMD.ispipeline(dd_frombasepipe)
Test.@test PredictMD.ispipeline(ee_frombasepipe)
Test.@test PredictMD.ispipeline(ff_frombasepipe)
Test.@test PredictMD.ispipeline(gg_frombasepipe)
Test.@test PredictMD.ispipeline(hh_flat)
Test.@test PredictMD.ispipeline(ii_flat)
Test.@test PredictMD.ispipeline(jj_flat)
Test.@test PredictMD.ispipeline(kk_flat)
Test.@test PredictMD.ispipeline(ll_flat)
Test.@test PredictMD.ispipeline(mm_flat_on_creation_in_multiple_steps_from_all_primitives)

Test.@test PredictMD.isflat(aa_frombasepipe)
Test.@test PredictMD.isflat(bb_frombasepipe)
Test.@test PredictMD.isflat(cc_frombasepipe)
Test.@test PredictMD.isflat(dd_frombasepipe)
Test.@test PredictMD.isflat(ee_frombasepipe)
Test.@test PredictMD.isflat(ff_frombasepipe)
Test.@test PredictMD.isflat(gg_frombasepipe)
Test.@test PredictMD.isflat(hh_flat)
Test.@test PredictMD.isflat(ii_flat)
Test.@test PredictMD.isflat(jj_flat)
Test.@test PredictMD.isflat(kk_flat)
Test.@test PredictMD.isflat(ll_flat)
Test.@test PredictMD.isflat(mm_flat_on_creation_in_multiple_steps_from_all_primitives)

Test.@test mm_flat_on_creation_in_multiple_steps_from_all_primitives isa PredictMD.SimplePipeline
for x in mm_flat_on_creation_in_multiple_steps_from_all_primitives
    Test.@test x isa Foo_Fittable
end
for i = 1:length(mm_flat_on_creation_in_multiple_steps_from_all_primitives)
    Test.@test mm_flat_on_creation_in_multiple_steps_from_all_primitives[i] isa Foo_Fittable
    Test.@test mm_flat_on_creation_in_multiple_steps_from_all_primitives[i] == Foo_Fittable(i*10)
    Test.@test mm_flat_on_creation_in_multiple_steps_from_all_primitives[i].x == i*10
end
for i = 1:size(mm_flat_on_creation_in_multiple_steps_from_all_primitives,1)
    Test.@test mm_flat_on_creation_in_multiple_steps_from_all_primitives[i] isa Foo_Fittable
    Test.@test mm_flat_on_creation_in_multiple_steps_from_all_primitives[i] == Foo_Fittable(i*10)
    Test.@test mm_flat_on_creation_in_multiple_steps_from_all_primitives[i].x == i*10
end
for i = 1:size(mm_flat_on_creation_in_multiple_steps_from_all_primitives)[1]
    Test.@test mm_flat_on_creation_in_multiple_steps_from_all_primitives[i] isa Foo_Fittable
    Test.@test mm_flat_on_creation_in_multiple_steps_from_all_primitives[i] == Foo_Fittable(i*10)
    Test.@test mm_flat_on_creation_in_multiple_steps_from_all_primitives[i].x == i*10
end
Test.@test length(mm_flat_on_creation_in_multiple_steps_from_all_primitives) == 22
Test.@test ndims(mm_flat_on_creation_in_multiple_steps_from_all_primitives) == 1
Test.@test length(size(mm_flat_on_creation_in_multiple_steps_from_all_primitives)) == 1
Test.@test size(mm_flat_on_creation_in_multiple_steps_from_all_primitives) == (22,)
Test.@test size(mm_flat_on_creation_in_multiple_steps_from_all_primitives)[1] == 22
Test.@test mm_flat_on_creation_in_multiple_steps_from_all_primitives[1] == Foo_Fittable(10)
Test.@test mm_flat_on_creation_in_multiple_steps_from_all_primitives[2] == Foo_Fittable(20)
Test.@test mm_flat_on_creation_in_multiple_steps_from_all_primitives[3] == Foo_Fittable(30)
Test.@test mm_flat_on_creation_in_multiple_steps_from_all_primitives[4] == Foo_Fittable(40)
Test.@test mm_flat_on_creation_in_multiple_steps_from_all_primitives[5] == Foo_Fittable(50)
Test.@test mm_flat_on_creation_in_multiple_steps_from_all_primitives[6] == Foo_Fittable(60)
Test.@test mm_flat_on_creation_in_multiple_steps_from_all_primitives[7] == Foo_Fittable(70)
Test.@test mm_flat_on_creation_in_multiple_steps_from_all_primitives[8] == Foo_Fittable(80)
Test.@test mm_flat_on_creation_in_multiple_steps_from_all_primitives[9] == Foo_Fittable(90)
Test.@test mm_flat_on_creation_in_multiple_steps_from_all_primitives[10] == Foo_Fittable(100)
Test.@test mm_flat_on_creation_in_multiple_steps_from_all_primitives[11] == Foo_Fittable(110)
Test.@test mm_flat_on_creation_in_multiple_steps_from_all_primitives[12] == Foo_Fittable(120)
Test.@test mm_flat_on_creation_in_multiple_steps_from_all_primitives[13] == Foo_Fittable(130)
Test.@test mm_flat_on_creation_in_multiple_steps_from_all_primitives[14] == Foo_Fittable(140)
Test.@test mm_flat_on_creation_in_multiple_steps_from_all_primitives[15] == Foo_Fittable(150)
Test.@test mm_flat_on_creation_in_multiple_steps_from_all_primitives[16] == Foo_Fittable(160)
Test.@test mm_flat_on_creation_in_multiple_steps_from_all_primitives[17] == Foo_Fittable(170)
Test.@test mm_flat_on_creation_in_multiple_steps_from_all_primitives[18] == Foo_Fittable(180)
Test.@test mm_flat_on_creation_in_multiple_steps_from_all_primitives[19] == Foo_Fittable(190)
Test.@test mm_flat_on_creation_in_multiple_steps_from_all_primitives[20] == Foo_Fittable(200)
Test.@test mm_flat_on_creation_in_multiple_steps_from_all_primitives[21] == Foo_Fittable(210)
Test.@test mm_flat_on_creation_in_multiple_steps_from_all_primitives[22] == Foo_Fittable(220)