#############################################################
# Lidar Sensor
#TODO move to a separate repo (AutomotiveSensors.jl)

mutable struct LidarSensor
    angles::Vector{Float64}
    ranges::Vector{Float64}
    range_rates::Vector{Float64}
    max_range::Float64
    poly::ConvexPolygon
end
function LidarSensor(nbeams::Int;
    max_range::Float64=100.0,
    angle_offset::Float64=0.0,
    angle_spread::Float64=2*pi,
    )

    if nbeams > 1
        angles = collect(range(angle_offset-angle_spread/2, stop=angle_offset + angle_spread/2, length=nbeams+1))[2:end]
    else
        angles = Float64[]
        nbeams = 0
    end

    ranges = Array{Float64}(undef, nbeams)
    range_rates = Array{Float64}(undef, nbeams)
    LidarSensor(angles, ranges, range_rates, max_range, ConvexPolygon(4))
end
nbeams(lidar::LidarSensor) = length(lidar.angles)
function observe!(lidar::LidarSensor, scene::Scene, roadway::Roadway, vehicle_index::Int)
    state_ego = scene[vehicle_index].state
    egoid = scene[vehicle_index].id
    ego_vel = polar(state_ego.v, state_ego.posG.θ)

    # precompute the set of vehicles within max_range of the vehicle
    in_range_ids = Set()
    for veh in scene
        if veh.id != egoid
            distance = norm(VecE2(state_ego.posG - veh.state.posG))
            # account for the length and width of the vehicle by considering
            # the worst case where their maximum radius is aligned
            distance = distance - hypot(veh.def.length/2.,veh.def.width/2.)
            if distance < lidar.max_range
                push!(in_range_ids, veh.id)
            end
        end
    end

    # compute range and range_rate for each angle
    for (i,angle) in enumerate(lidar.angles)
        ray_angle = state_ego.posG.θ + angle
        ray_vec = polar(1.0, ray_angle)
        ray = VecSE2(state_ego.posG.x, state_ego.posG.y, ray_angle)

        range = lidar.max_range
        range_rate = 0.0
        for veh in scene
            # only consider the set of potentially in range vehicles
            if in(veh.id, in_range_ids)
                to_oriented_bounding_box!(lidar.poly, veh)

                range2 = AutomotiveDrivingModels.get_collision_time(ray, lidar.poly, 1.0)
                if !isnan(range2) && range2 < range
                    range = range2
                    relative_speed = polar(veh.state.v, veh.state.posG.θ) - ego_vel
                    range_rate = proj(relative_speed, ray_vec, Float64)
                end
            end
        end
        lidar.ranges[i] = range
        lidar.range_rates[i] = range_rate
    end

    lidar
end
# function render_lidar!(rendermodel::RenderModel, lidar::LidarSensor, posG::VecSE2{Float64};
#     color::Colorant=colorant"white",
#     line_width::Float64 = 0.05,
#     )

#     for (angle, range, range_rate) in zip(lidar.angles, lidar.ranges, lidar.range_rates)
#         ray = VecSE2(posG.x, posG.y, posG.θ + angle)
#         t = 2 / (1 + exp(-range_rate))
#         ray_color = t > 1.0 ? lerp(RGBA(1.0,1.0,1.0,0.5), RGBA(0.2,0.2,1.0,0.5), t/2) :
#                               lerp(RGBA(1.0,0.2,0.2,0.5), RGBA(1.0,1.0,1.0,0.5), t)
#         render_ray!(rendermodel::RenderModel, ray, color=ray_color, length=range, line_width=line_width)
#     end
#     rendermodel
# end

#############################################################
# Road Line Lidar Sensor

mutable struct RoadlineLidarSensor
    angles::Vector{Float64}
    ranges::Matrix{Float64} # [n_lanes by nbeams]
    max_range::Float64
    poly::ConvexPolygon
end
nbeams(lidar::RoadlineLidarSensor) = length(lidar.angles)
nlanes(lidar::RoadlineLidarSensor) = size(lidar.ranges, 1)
function RoadlineLidarSensor(nbeams::Int;
    max_range::Float64=100.0,
    max_depth::Int=2,
    angle_offset::Float64=0.0,
    )

    if nbeams > 1
        angles = collect(range(angle_offset, stop=2*pi+angle_offset, length=nbeams+1))[2:end]
    else
        angles = Float64[]
        nbeams = 0
    end
    ranges = Array{Float64}(undef, max_depth, nbeams)
    RoadlineLidarSensor(angles, ranges, max_range, ConvexPolygon(4))
end
function _update_lidar!(lidar::RoadlineLidarSensor, ray::VecSE2{Float64}, beam_index::Int, p_lo::VecE2, p_hi::VecE2)
    test_range = get_intersection_time(Projectile(ray, 1.0),
        AutomotiveDrivingModels.LineSegment(p_lo, p_hi))

    if !isnan(test_range)
        n_ranges = size(lidar.ranges, 1)
        for k in 1 : n_ranges
            if test_range < lidar.ranges[k,beam_index]
                for l in k+1 : n_ranges
                    lidar.ranges[l,beam_index] = lidar.ranges[l-1,beam_index]
                end
                lidar.ranges[k,beam_index] = test_range
                break
            end
        end
    end

    lidar
end
function _update_lidar!(lidar::RoadlineLidarSensor, ray::VecSE2{Float64}, beam_index::Int, lane::Lane, roadway::Roadway; check_right_lane::Bool=false)
    halfwidth = lane.width/2
    Δ = check_right_lane ? -π/2 : π/2
    p_lo = convert(VecE2, lane.curve[1].pos + polar(halfwidth, lane.curve[1].pos.θ + Δ))
    for j in 2:length(lane.curve)
        p_hi = convert(VecE2, lane.curve[j].pos + polar(halfwidth, lane.curve[j].pos.θ + Δ))
        _update_lidar!(lidar, ray, beam_index, p_lo, p_hi)
        p_lo = p_hi
    end
    if has_next(lane)
        lane2 = next_lane(lane, roadway)
        pt = lane2.curve[1]
        p_hi = convert(VecE2, pt.pos + polar(lane2.width/2, pt.pos.θ + Δ))
        _update_lidar!(lidar, ray, beam_index, p_lo, p_hi)
    end
    lidar
end
function _update_lidar!(lidar::RoadlineLidarSensor, ray::VecSE2{Float64}, beam_index::Int, roadway::Roadway)

    for seg in roadway.segments
        for lane in seg.lanes

            # always check the left lane marking
            _update_lidar!(lidar, ray, beam_index, lane, roadway)

            # only check the right lane marking if this is the first lane
            if lane.tag.lane == 1
                _update_lidar!(lidar, ray, beam_index, lane, roadway, check_right_lane=true)
            end
        end
    end

    lidar
end
function observe!(lidar::RoadlineLidarSensor, scene::Scene, roadway::Roadway, vehicle_index::Int)
    state_ego = scene[vehicle_index].state
    egoid = scene[vehicle_index].id
    ego_vel = polar(state_ego.v, state_ego.posG.θ)

    fill!(lidar.ranges, lidar.max_range)
    for (beam_index,angle) in enumerate(lidar.angles)
        ray_angle = state_ego.posG.θ + angle
        ray = VecSE2(state_ego.posG.x, state_ego.posG.y, ray_angle)

        _update_lidar!(lidar, ray, beam_index, roadway)
    end

    lidar
end
# function render_lidar!(rendermodel::RenderModel, lidar::RoadlineLidarSensor, posG::VecSE2{Float64};
#     color::Colorant=RGBA(1.0,1.0,1.0,0.5),
#     line_width::Float64 = 0.05,
#     depth_level::Int = 1, # if 1, render first lanes struck by beams, if 2 render 2nd ...
#     )

#     for (angle, range) in zip(lidar.angles, lidar.ranges[depth_level,:])
#         ray = VecSE2(posG.x, posG.y, posG.θ + angle)
#         render_ray!(rendermodel::RenderModel, ray, color=color, length=range, line_width=line_width)
#     end
#     rendermodel
# end

#############################################################
# Road Line Lidar Culling

struct LanePortion
    tag::LaneTag
    curveindex_lo::Int
    curveindex_hi::Int
end
mutable struct RoadwayLidarCulling
    is_leaf::Bool

    x_lo::Float64
    x_hi::Float64
    y_lo::Float64
    y_hi::Float64

    top_left::RoadwayLidarCulling
    top_right::RoadwayLidarCulling
    bot_left::RoadwayLidarCulling
    bot_right::RoadwayLidarCulling

    lane_portions::Vector{LanePortion}

    function RoadwayLidarCulling(x_lo::Float64, x_hi::Float64, y_lo::Float64, y_hi::Float64, lane_portions::Vector{LanePortion})
        retval = new()
        retval.is_leaf = true
        retval.x_lo = x_lo
        retval.x_hi = x_hi
        retval.y_lo = y_lo
        retval.y_hi = y_hi
        retval.lane_portions = lane_portions
        retval
    end
    function RoadwayLidarCulling(x_lo::Float64, x_hi::Float64, y_lo::Float64, y_hi::Float64,
                                 top_left::RoadwayLidarCulling,
                                 top_right::RoadwayLidarCulling,
                                 bot_left::RoadwayLidarCulling,
                                 bot_right::RoadwayLidarCulling,)
        retval = new()
        retval.is_leaf = false
        retval.x_lo = x_lo
        retval.x_hi = x_hi
        retval.y_lo = y_lo
        retval.y_hi = y_hi
        retval.top_left = top_left
        retval.top_right = top_right
        retval.bot_left = bot_left
        retval.bot_right = bot_right
        retval
    end
end
RoadwayLidarCulling() = RoadwayLidarCulling(typemax(Float64), typemin(Float64), typemax(Float64), typemin(Float64), LanePortion[])

function Base.get(rlc::RoadwayLidarCulling, x::Real, y::Real)
    if rlc.is_leaf
        return rlc
    else
        if y < (rlc.y_lo + rlc.y_hi)/2
            if x < (rlc.x_lo + rlc.x_hi)/2
                return get(rlc.bot_left, x, y)
            else
                return get(rlc.bot_right, x, y)
            end
        else
            if x < (rlc.x_lo + rlc.x_hi)/2
                return get(rlc.top_left, x, y)
            else
                return get(rlc.top_right, x, y)
            end
        end
    end
end
function ensure_leaf_in_rlc!(rlc::RoadwayLidarCulling, x::Real, y::Real, fidelity_x::Real, fidelity_y::Real)

    leaf = get(rlc, x, y)
    @assert(leaf.is_leaf)

    x_lo, x_hi = leaf.x_lo, leaf.x_hi
    y_lo, y_hi = leaf.y_lo, leaf.y_hi
    while (x_hi - x_lo) > fidelity_x || (y_hi - y_lo) > fidelity_y # drill down

        x_mid = (x_hi + x_lo)/2
        y_mid = (y_hi + y_lo)/2

        leaf.top_left  = RoadwayLidarCulling(x_lo, x_mid, y_mid, y_hi, LanePortion[])
        leaf.top_right = RoadwayLidarCulling(x_mid, x_hi, y_mid, y_hi, LanePortion[])
        leaf.bot_left  = RoadwayLidarCulling(x_lo, x_mid, y_lo, y_mid, LanePortion[])
        leaf.bot_right = RoadwayLidarCulling(x_mid, x_hi, y_lo, y_mid, LanePortion[])
        leaf.is_leaf = false

        leaf = get(leaf, x, y)
        x_lo, x_hi = leaf.x_lo, leaf.x_hi
        y_lo, y_hi = leaf.y_lo, leaf.y_hi
    end

    rlc
end
function get_lane_portions(roadway::Roadway, x::Real, y::Real, lane_portion_max_range::Float64)
    P = VecE2(x, y)
    Δ² = lane_portion_max_range*lane_portion_max_range

    lane_portions = LanePortion[]

    for seg in roadway.segments
        for lane in seg.lanes
            f = curvept -> normsquared(VecE2(curvept.pos - P)) ≤ Δ²
            i = findfirst(f, lane.curve)
            if i != nothing
                j = findlast(f, lane.curve)
                @assert(j != nothing)
                push!(lane_portions, LanePortion(lane.tag, i, j))
            end
        end
    end

    lane_portions
end

function RoadwayLidarCulling(
    roadway::Roadway,
    lane_portion_max_range::Float64, # lane portions will be extracted such that points within lane_portion_max_range are in the leaves
    culling_fidelity::Float64, # the culling will drill down until Δx, Δy < culling_fidelity
    )

    #=
    1 - get area bounds
    2 - ensure all points in rlc
    3 - for each leaf, construct all lane portions
    =#

    # get area bounds
    x_lo = typemax(Float64)
    x_hi = typemin(Float64)
    y_lo = typemax(Float64)
    y_hi = typemin(Float64)
    for seg in roadway.segments
        for lane in seg.lanes
            for curvept in lane.curve
                pos = curvept.pos
                x_lo = min(pos.x, x_lo)
                x_hi = max(pos.x, x_hi)
                y_lo = min(pos.y, y_lo)
                y_hi = max(pos.y, y_hi)
            end
        end
    end
    x_lo -= lane_portion_max_range
    x_hi += lane_portion_max_range
    y_lo -= lane_portion_max_range
    y_hi += lane_portion_max_range
    root = RoadwayLidarCulling(x_lo, x_hi, y_lo, y_hi, LanePortion[])

    # ensure all points are in rlc
    x = x_lo - culling_fidelity
    while x < x_hi
        x += culling_fidelity
        y = y_lo - culling_fidelity
        while y < y_hi
            y += culling_fidelity
            ensure_leaf_in_rlc!(root, x, y, culling_fidelity, culling_fidelity)
        end
    end

    nodes = RoadwayLidarCulling[root]
    while !isempty(nodes)
        node = pop!(nodes)
        if node.is_leaf
            x = (node.x_lo + node.x_hi)/2
            y = (node.y_lo + node.y_hi)/2
            append!(node.lane_portions, get_lane_portions(roadway, x, y, lane_portion_max_range))
        else
            push!(nodes, node.top_left)
            push!(nodes, node.top_right)
            push!(nodes, node.bot_left)
            push!(nodes, node.bot_right)
        end
    end

    root
end

function _update_lidar!(
    lidar::RoadlineLidarSensor,
    ray::VecSE2{Float64},
    beam_index::Int,
    roadway::Roadway,
    lane_portion::LanePortion;
    check_right_lane::Bool=false
    )

    lane = roadway[lane_portion.tag]

    halfwidth = lane.width/2
    Δ = check_right_lane ? -π/2 : π/2
    p_lo = convert(VecE2, lane.curve[lane_portion.curveindex_lo].pos +
                          polar(halfwidth, lane.curve[lane_portion.curveindex_lo].pos.θ + Δ))
    for j in lane_portion.curveindex_lo+1:lane_portion.curveindex_hi
        p_hi = convert(VecE2, lane.curve[j].pos + polar(halfwidth, lane.curve[j].pos.θ + Δ))
        _update_lidar!(lidar, ray, beam_index, p_lo, p_hi)
        p_lo = p_hi
    end
    if lane_portion.curveindex_hi == length(lane.curve) && has_next(lane)
        lane2 = next_lane(lane, roadway)
        pt = lane2.curve[1]
        p_hi = convert(VecE2, pt.pos + polar(lane2.width/2, pt.pos.θ + Δ))
        _update_lidar!(lidar, ray, beam_index, p_lo, p_hi)
    end
    lidar
end
function _update_lidar!(lidar::RoadlineLidarSensor, ray::VecSE2{Float64}, beam_index::Int, roadway::Roadway, rlc::RoadwayLidarCulling)

    leaf = get(rlc, ray.x, ray.y)
    for lane_portion in leaf.lane_portions

        # always update the left lane marking
        _update_lidar!(lidar, ray, beam_index, roadway, lane_portion)

        # only check the right lane marking if this is the first lane
        if lane_portion.tag.lane == 1
            _update_lidar!(lidar, ray, beam_index, roadway, lane_portion, check_right_lane=true)
        end
    end
    lidar
end
function observe!(lidar::RoadlineLidarSensor, scene::Scene, roadway::Roadway, vehicle_index::Int, rlc::RoadwayLidarCulling)
    state_ego = scene[vehicle_index].state
    egoid = scene[vehicle_index].id
    ego_vel = polar(state_ego.v, state_ego.posG.θ)

    fill!(lidar.ranges, lidar.max_range)
    for (beam_index,angle) in enumerate(lidar.angles)
        ray_angle = state_ego.posG.θ + angle
        ray = VecSE2(state_ego.posG.x, state_ego.posG.y, ray_angle)

        _update_lidar!(lidar, ray, beam_index, roadway, rlc)
    end

    lidar
end


    #
    # Two-photon absorption by monochromatic and equally-polarized photons, usually from the same beam.
    #
    """
    `struct  MultiPhotonDeExcitation.Channel_2pAbsorptionMonochromatic`
        ... defines a type for a two-photon absorption channel for the absorption of monochromatic light with well-defined
            multipolarities.

        + K              ::AngularJ64             ... Rank K of the channel.
        + omega          ::Float64                ... omega.
        + multipole1     ::EmMultipole            ... Multipole M1.
        + multipole2     ::EmMultipole            ... Multipole M2.
        + gauge          ::EmGauge                ... Gauge for dealing with the (coupled) radiation field.
        + Jsym           ::LevelSymmetry          ... Symmetry of the Green function channel used in the summation.
        + amplitude      ::Complex{Float64}       ... reduced two-photon absorption amplitude U^(K, 2gamma, absorption) (..)
                                                      associated with the given channel.
    """
    struct  Channel_2pAbsorptionMonochromatic
        K                ::AngularJ64
        omega            ::Float64
        multipole1       ::EmMultipole
        multipole2       ::EmMultipole
        gauge            ::EmGauge
        Jsym             ::LevelSymmetry
        amplitude        ::Complex{Float64}
    end

    """
    `struct  MultiPhotonDeExcitation.Line_2pAbsorptionMonochromatic`
        ... defines a type for a two-photon absorption line by monochromatic light that may include the definition of channels.

        + initialLevel     ::Level          ... initial-(state) level
        + finalLevel       ::Level          ... final-(state) level
        + omega            ::Float64        ... Energy of the incoming photons.
        + csLinear         ::EmProperty     ... Total cross section for linearly-polarized incident light.
        + csRightCircular  ::EmProperty     ... Total cross section for right-circularly polarized incident light.
        + csUnpolarized    ::EmProperty     ... Total cross section for unpolarized incident light.
        + channels         ::Array{MultiPhotonDeExcitation.Channel_2pAbsorptionMonochromatic,1}
                                            ... List of MultiPhotonDeExcitation.Channel_2pAbsorptionMonochromatic's of this line.
    """
    struct  Line_2pAbsorptionMonochromatic
        initialLevel       ::Level
        finalLevel         ::Level
        omega              ::Float64
        csLinear           ::EmProperty
        csRightCircular    ::EmProperty
        csUnpolarized      ::EmProperty
        channels           ::Array{MultiPhotonDeExcitation.Channel_2pAbsorptionMonochromatic,1}
    end

    # `Base.show(io::IO, line::MultiPhotonDeExcitation.Line_2pAbsorptionMonochromatic)`
    #   ... prepares a proper printout of the variable line::MultiPhotonDeExcitation.Line_2pAbsorptionMonochromatic.
    function Base.show(io::IO, line::MultiPhotonDeExcitation.Line_2pAbsorptionMonochromatic)
        println(io, "initialLevel:      $(line.initialLevel)  ")
        println(io, "finalLevel:        $(line.finalLevel)  ")
        println(io, "omega:             $(line.omega)  ")
        println(io, "csLinear:          $(line.csLinear)  ")
        println(io, "csRightCircular:   $(line.csRightCircular)  ")
        println(io, "csUnpolarized:     $(line.csUnpolarized)  ")
        println(io, "channels:          $(line.channels)  ")
    end

    """
    `MultiPhotonDeExcitation.computeAmplitudesProperties_2pAbsorptionMonochromatic(
                             line::MultiPhotonDeExcitation.Line_2pAbsorptionMonochromatic, grid::Radial.Grid,
                             settings::MultiPhotonDeExcitation.Settings)`
        ... to compute all amplitudes and properties of the given line; a line::MultiPhotonDeExcitation.Line_2pAbsorptionMonochromatic
            is returned for which the amplitudes and properties are now evaluated.
    """
    function  computeAmplitudesProperties_2pAbsorptionMonochromatic(line::MultiPhotonDeExcitation.Line_2pAbsorptionMonochromatic,
                                                                    grid::Radial.Grid, settings::MultiPhotonDeExcitation.Settings)
        newChannels = MultiPhotonDeExcitation.Channel_2pAbsorptionMonochromatic[]
        for channel in line.channels
            amplitude = MultiPhotonDeExcitation.computeReducedAmplitudeAbsorption(channel.K, line.finalLevel, channel.multipole2,
                                            channel.Jsym, channel.omega, channel.multipole1, line.initialLevel, channel.gauge, grid, settings.green)
            push!( newChannels, MultiPhotonDeExcitation.Channel_2pAbsorptionMonochromatic(channel.K, channel.omega, channel.multipole1, channel.multipole2,
                                                                                channel.gauge, channel.Jsym, amplitude) )
        end
        # Calculate the requested cross sections, etc.
        csLinear        = EmProperty(0., 0.)
        csRightCircular = EmProperty(0., 0.)
        csUnpolarized   = EmProperty(0., 0.)
        for property in settings.process.properties
            if      typeof(property) == TotalCsLinear
                totalCs_Cou = MultiPhotonDeExcitation.computeTotalCsLinear(line, EmGauge("Coulomb"), settings)
                totalCs_Bab = MultiPhotonDeExcitation.computeTotalCsLinear(line, EmGauge("Babushkin"), settings)
                csLinear    = EmProperty( totalCs_Cou,  totalCs_Bab)
            elseif  typeof(property) == TotalCsRightCircular
                totalCs_Cou = MultiPhotonDeExcitation.computeTotalCsRightCircular(line, EmGauge("Coulomb"), settings)
                totalCs_Bab = MultiPhotonDeExcitation.computeTotalCsRightCircular(line, EmGauge("Babushkin"), settings)
                csRightCircular = EmProperty( totalCs_Cou,  totalCs_Bab)
            elseif  typeof(property) == TotalCsUnpolarized
                totalCs_Cou = MultiPhotonDeExcitation.computeTotalCsUnpolarized(line, EmGauge("Coulomb"), settings)
                totalCs_Bab = MultiPhotonDeExcitation.computeTotalCsUnpolarized(line, EmGauge("Babushkin"), settings)
                csUnpolarized = EmProperty( totalCs_Cou,  totalCs_Bab)
            end
        end
        line = MultiPhotonDeExcitation.Line_2pAbsorptionMonochromatic( line.initialLevel, line.finalLevel, line.omega,
                                                                       csLinear, csRightCircular, csUnpolarized, newChannels)
        return( line )
    end

    """
    `MultiPhotonDeExcitation.computeLines(process::TwoPhotonAbsorptionMonochromatic, finalMultiplet::Multiplet,
                                          initialMultiplet::Multiplet, grid::Radial.Grid, settings::MultiPhotonDeExcitation.Settings;
                                          output=true)`
        ... to compute the multiphoton transition amplitudes and all properties as requested by the given settings. A list of
            lines::Array{MultiPhotonDeExcitation.Line_2pAbsorptionMonochromatic,1} is returned.
    """
    function  computeLines(process::TwoPhotonAbsorptionMonochromatic, finalMultiplet::Multiplet, initialMultiplet::Multiplet,
                           grid::Radial.Grid, settings::MultiPhotonDeExcitation.Settings; output=true)
        println("")
        printstyled("MultiPhotonDeExcitation.computeLines(::TwoPhotonAbsorptionMonochromatic): The computation of amplitudes starts now ... \n", color=:light_green)
        printstyled("---------------------------------------------------------------------------------------------------------------------- \n", color=:light_green)
        println("")
        #
        lines = MultiPhotonDeExcitation.determineLines_2pAbsorptionMonochromatic(finalMultiplet, initialMultiplet, settings)
        # Display all selected lines before the computations start
        if  settings.printBefore    MultiPhotonDeExcitation.displayLines_2pAbsorptionMonochromatic(lines)    end
        # Calculate all amplitudes and requested properties
        newLines = MultiPhotonDeExcitation.Line_2pAbsorptionMonochromatic[]
        for  line in lines
            newLine = MultiPhotonDeExcitation.computeAmplitudesProperties_2pAbsorptionMonochromatic(line, grid, settings)
            push!( newLines, newLine)
        end
        # Print all results to screen
        MultiPhotonDeExcitation.displayCrossSections_2pAbsorptionMonochromatic(stdout, settings.process.properties, lines)
        printSummary, iostream = Defaults.getDefaults("summary flag/stream")
        if  printSummary    MultiPhotonDeExcitation.displayCrossSections_2pAbsorptionMonochromatic(iostream, settings.process.properties, lines)   end
        #
        if    output    return( lines )
        else            return( nothing )
        end
    end

    """
    `MultiPhotonDeExcitation.computeReducedAmplitudeAbsorption(K::AngularJ64, finalLevel::Level, multipole2::EmMultipole, Jsym::LevelSymmetry,
                                                                                omega::Float64, multipole1::EmMultipole, initialLevel::Level,
                                                                 gauge::EmGauge, grid::Radial.Grid, greenChannels::Array{AtomicState.GreenChannel,1})`
        ... to compute the reduced amplitude U^{K, 2gamma emission} (K, Jf, multipole2, Jsym, omega, multipole1, Ji) by means of the
            given Green function channels. An amplitude::Complex{Float64} is returned.
    """
    function computeReducedAmplitudeAbsorption(K::AngularJ64, finalLevel::Level, multipole2::EmMultipole, Jsym::LevelSymmetry,
                                                                omega::Float64, multipole1::EmMultipole, initialLevel::Level,
                                                                gauge::EmGauge, grid::Radial.Grid, greenChannels::Array{AtomicState.GreenChannel,1})
        U = Complex(0.);    found = false
        for channel in greenChannels
            if  Jsym == channel.symmetry
                found = true
                for (i, nuLevel) in enumerate(channel.gMultiplet.levels)
                    U = U + PhotoEmission.amplitude("absorption", multipole2, gauge, omega, finalLevel, nuLevel, grid, display=false) *
                            PhotoEmission.amplitude("absorption", multipole1, gauge, omega, nuLevel, initialLevel, grid, display=false) /
                            (initialLevel.energy + omega - nuLevel.energy)
                end
            end
        end

        if    found
              U = U * AngularMomentum.Wigner_6j(initialLevel.J, finalLevel.J, K, AngularJ64(multipole2.L), AngularJ64(multipole1.L), Jsym.J)
              ##x println(" ")
        else  println("No Green function cannel found for amplitude U^{K, 2gamma absorption} (K, Jf, multipole2, Jsym = $Jsym, omega, multipole1, Ji) ")
        end

        return( U )
    end

    """
    `MultiPhotonDeExcitation.computeTotalCsLinear(line::MultiPhotonDeExcitation.Line_2pAbsorptionMonochromatic,
                                                  gauge::EmGauge, settings::MultiPhotonDeExcitation.Settings)`
        ... to compute the total cross sections for linearly-polarized incident light. A tcs::Float64 is returned.
    """
    function computeTotalCsLinear(line::MultiPhotonDeExcitation.Line_2pAbsorptionMonochromatic,
                                  gauge::EmGauge, settings::MultiPhotonDeExcitation.Settings)
        tcs = 0.;   Klist = oplus(line.finalLevel.J, line.initialLevel.J);      omega = line.omega
        symi = LevelSymmetry(line.initialLevel.J, line.initialLevel.parity);    symf = LevelSymmetry(line.finalLevel.J, line.finalLevel.parity)

        for  K in Klist
            qList = AngularMomentum.m_values(K)
            for  q in qList
                amp = ComplexF64(0.)
                for  lambda1  in [-1, 1]
                    for  lambda2  in [-1, 1]
                        for  mp1 in settings.multipoles
                            for  mp2 in settings.multipoles
                                if   mp1.electric   p1 = 1    else    p1 = 0    end
                                if   mp2.electric   p2 = 1    else    p2 = 0    end
                                symmetries  = AngularMomentum.allowedTotalSymmetries(symf, mp2, mp1, symi)
                                for Jsym in symmetries
                                    wa = (1.0im)^(mp1.L - p1 + mp2.L - p2) * (-lambda1)^p1 * (-lambda2)^p2
                                    wb = sqrt( (2*mp1.L + 1)*(2*mp2.L + 1) ) * (2*Basics.twice(K) + 1)
                                    wc = AngularMomentum.Wigner_3j(mp1.L, mp2.L, K, lambda1, lambda2, q)
                                    wd = MultiPhotonDeExcitation.getReducedAmplitudeAbsorption(K, line.finalLevel, mp2, Jsym, omega, mp1,
                                                                                line.initialLevel, gauge, line.channels)
                                    ##x println("computeTotalCsLinear: L1, L2, K, lambda1, lambda2, q = $(mp1.L), $(mp2.L), $K, $lambda1, $lambda2, $q")
                                    ##x println("computeTotalCsLinear: wa, wb, wc, wd = $wa  $wb  $wc  $wd")

                                    amp = amp + (1.0im)^(mp1.L - p1 + mp2.L - p2) * (-lambda1)^p1 * (-lambda2)^p2           *
                                                sqrt( (2*mp1.L + 1)*(2*mp2.L + 1) ) * (2*Basics.twice(K) + 1)       *
                                                AngularMomentum.Wigner_3j(mp1.L, mp2.L, K, lambda1, lambda2, q)             *
                                                MultiPhotonDeExcitation.getReducedAmplitudeAbsorption(K, line.finalLevel, mp2, Jsym, omega, mp1,
                                                                                line.initialLevel, gauge, line.channels)
                                end
                            end
                        end
                    end
                end
                tcs = tcs + abs( amp )^2
            end
        end

        ## println("computeTotalCsLinear: tcs = $tcs")
        tcs = tcs * 8*pi^5 * Defaults.getDefaults("alpha")^2 / (Basics.twice(line.initialLevel.J) + 1) / omega^2

        return( tcs )
    end

    """
    `MultiPhotonDeExcitation.computeTotalCsRightCircular(line::MultiPhotonDeExcitation.Line_2pAbsorptionMonochromatic,
                                                         gauge::EmGauge, settings::MultiPhotonDeExcitation.Settings)`
        ... to compute the total cross sections for right-cicularly polarized incident light. A tcs::Float64 is returned.
    """
    function computeTotalCsRightCircular(line::MultiPhotonDeExcitation.Line_2pAbsorptionMonochromatic,
                                         gauge::EmGauge, settings::MultiPhotonDeExcitation.Settings)
        tcs = 0.;   Klist = oplus(line.finalLevel.J, line.initialLevel.J);      omega = line.omega
        symi = LevelSymmetry(line.initialLevel.J, line.initialLevel.parity);    symf = LevelSymmetry(line.finalLevel.J, line.finalLevel.parity)

        # Need to be filled

        tcs = tcs * 8*pi^5 * Defaults.getDefaults("alpha")^2 / (Basics.twice(line.initialLevel.J) + 1) / omega^2

        return( tcs )
    end

    """
    `MultiPhotonDeExcitation.computeTotalCsUnpolarized(line::MultiPhotonDeExcitation.Line_2pAbsorptionMonochromatic,
                                                       gauge::EmGauge, settings::MultiPhotonDeExcitation.Settings)`
        ... to compute the total cross sections for linearly-polarized incident light. A tcs::Float64 is returned.
    """
    function computeTotalCsUnpolarized(line::MultiPhotonDeExcitation.Line_2pAbsorptionMonochromatic,
                                       gauge::EmGauge, settings::MultiPhotonDeExcitation.Settings)
        tcs = 0.;   Klist = oplus(line.finalLevel.J, line.initialLevel.J);      omega = line.omega
        symi = LevelSymmetry(line.initialLevel.J, line.initialLevel.parity);    symf = LevelSymmetry(line.finalLevel.J, line.finalLevel.parity)

        for  K in Klist
            qList = AngularMomentum.m_values(K)
            for  q in qList
                for  lambda1  in [-1, 1]
                    for  lambda2  in [-1, 1]
                        amp = ComplexF64(0.)
                        for  mp1 in settings.multipoles
                            for  mp2 in settings.multipoles
                                if   mp1.electric   p1 = 1    else    p1 = 0    end
                                if   mp2.electric   p2 = 1    else    p2 = 0    end
                                symmetries  = AngularMomentum.allowedTotalSymmetries(symf, mp2, mp1, symi)
                                for Jsym in symmetries
                                    amp = amp + (1.0im)^(mp1.L - p1 + mp2.L - p2) * (-lambda1)^p1 * (-lambda2)^p2 *
                                                sqrt( (2*mp1.L + 1)*(2*mp2.L + 1) ) * (2*Basics.twice(K) + 1)       *
                                                AngularMomentum.Wigner_3j(mp1.L, mp2.L, K, lambda1, lambda2, q)             *
                                                MultiPhotonDeExcitation.getReducedAmplitudeAbsorption(K, line.finalLevel, mp2, Jsym, omega, mp1,
                                                                                line.initialLevel, gauge, line.channels)
                                end
                            end
                        end
                        tcs = tcs + abs( amp )^2
                    end
                end
            end
        end

        tcs = tcs * 8*pi^5 * Defaults.getDefaults("alpha")^2 / (Basics.twice(line.initialLevel.J) + 1) / omega^2

        return( tcs )
    end

    """
    `MultiPhotonDeExcitation.getReducedAmplitudeEmission(K::AngularJ64, finalLevel::Level, multipole2::EmMultipole, Jsym::LevelSymmetry, omega::Float64,
                                                                                multipole1::EmMultipole, initialLevel::Level,
                                                         gauge::EmGauge, channels::Array{MultiPhotonDeExcitation.Channel_2pAbsorptionMonochromatic,1})`
        ... to get/return the reduced amplitude U^{K, 2gamma emission} (K, Jf, multipole2, Jsym, omega, multipole1, Ji) from the calculated list
            of channels. An amplitude::Complex{Float64} is returned.
    """
    function getReducedAmplitudeAbsorption(K::AngularJ64, finalLevel::Level, multipole2::EmMultipole, Jsym::LevelSymmetry, omega::Float64,
                                                                             multipole1::EmMultipole, initialLevel::Level,
                                           gauge::EmGauge, channels::Array{MultiPhotonDeExcitation.Channel_2pAbsorptionMonochromatic,1})
        U = Complex(0.);    found = false
        for channel in channels
            if  K == channel.K  &&  omega == channel.omega  &&   Jsym == channel.Jsym       &&  multipole1 == channel.multipole1  &&
                multipole2 == channel.multipole2            &&   (gauge == channel.gauge  ||  EmGauge("Magnetic")  == channel.gauge)
                U = channel.amplitude;    found = true
            end
        end

        if    found
              println("U^{K, 2gamma absorption} (..) = $U   ** amplitude found. ")
        else  println("U^{$K, 2gamma absorption} (Jf, mp2=$multipole2, Jsym=$Jsym, omega=$omega, mp1=$multipole1, Ji)  ** NO amplitude found.")
        end

        return( U )
    end

    """
    `MultiPhotonDeExcitation.determineChannels_2pAbsorptionMonochromatic(omega::Float64, finalLevel::Level, initialLevel::Level,
                                                                         settings::MultiPhotonDeExcitation.Settings)`
        ... to determine a list of MultiPhotonDeExcitation.Channel_2pAbsorptionMonochromatic for a transitions from the initial to
            final level and by taking into account the particular settings of for this computation;
            an Array{MultiPhotonDeExcitation.Channel_2pAbsorptionMonochromatic,1} is returned.
    """
    function determineChannels_2pAbsorptionMonochromatic(omega::Float64, finalLevel::Level, initialLevel::Level, settings::MultiPhotonDeExcitation.Settings)
        channels   = MultiPhotonDeExcitation.Channel_2pAbsorptionMonochromatic[];
        symi       = LevelSymmetry(initialLevel.J, initialLevel.parity);    symf = LevelSymmetry(finalLevel.J, finalLevel.parity)
        for  mp1 in settings.multipoles
            for  mp2 in settings.multipoles
                symmetries  = AngularMomentum.allowedTotalSymmetries(symf, mp2, mp1, symi)
                Klist       = oplus(symf.J, symi.J)
                for  symn in symmetries
                    for  gauge in settings.gauges
                        # Include further restrictions if appropriate
                        if     string(mp1)[1] == 'E' || string(mp2)[1] == 'E'  &&   gauge == Basics.UseCoulomb
                            for K in Klist  push!(channels, MultiPhotonDeExcitation.Channel_2pAbsorptionMonochromatic(K, omega, mp1, mp2, Basics.Coulomb, symn, 0.) )     end
                        elseif string(mp1)[1] == 'E' || string(mp2)[1] == 'E'  &&   gauge == Basics.UseBabushkin
                            for K in Klist  push!(channels, MultiPhotonDeExcitation.Channel_2pAbsorptionMonochromatic(K, omega, mp1, mp2, Basics.Babushkin, symn, 0.) )   end
                        elseif string(mp1)[1] == 'M' && string(mp2)[1] == 'M'
                            for K in Klist  push!(channels, MultiPhotonDeExcitation.Channel_2pAbsorptionMonochromatic(K, omega, mp1, mp2, Basics.Magnetic, symn, 0.) )    end
                        end
                    end
                end
            end
        end

        return( channels )
    end

    """
    `MultiPhotonDeExcitation.determineLines_2pAbsorptionMonochromatic(finalMultiplet::Multiplet, initialMultiplet::Multiplet,
                                                                      settings::MultiPhotonDeExcitation.Settings)`
        ... to determine a list of MultiPhotonDeExcitation.Line_2pAbsorptionMonochromatic's for transitions between the levels from the given
            initial- and final-state multiplets and by taking into account the particular selections and settings for this computation;
            an Array{MultiPhotonDeExcitation.Line_2pAbsorptionMonochromatic,1} is returned. Apart from the level specification, all physical
            properties are set to zero during this initialization process.
    """
    function  determineLines_2pAbsorptionMonochromatic(finalMultiplet::Multiplet, initialMultiplet::Multiplet,
                                                       settings::MultiPhotonDeExcitation.Settings)
        lines = MultiPhotonDeExcitation.Line_2pAbsorptionMonochromatic[]
        for  iLevel  in  initialMultiplet.levels
            for  fLevel  in  finalMultiplet.levels
                if  Basics.selectLevelPair(iLevel, fLevel, settings.lineSelection)
                    omega    = (fLevel.energy - iLevel.energy) / 2.
                    channels = MultiPhotonDeExcitation.determineChannels_2pAbsorptionMonochromatic(omega, fLevel, iLevel, settings)
                    push!( lines, MultiPhotonDeExcitation.Line_2pAbsorptionMonochromatic(iLevel, fLevel, omega,
                                                                     EmProperty(0., 0.), EmProperty(0., 0.), EmProperty(0., 0.), channels) )
                end
            end
        end
        return( lines )
    end

    """
    `MultiPhotonDeExcitation.displayLines_2pAbsorptionMonochromatic(lines::Array{MultiPhotonDeExcitation.Line_2pAbsorptionMonochromatic,1})`
        ... to display a list of lines and channels that have been selected due to the prior settings. A neat table of all selected
            transitions and energies is printed but nothing is returned otherwise.
    """
    function  displayLines_2pAbsorptionMonochromatic(lines::Array{MultiPhotonDeExcitation.Line_2pAbsorptionMonochromatic,1})
        nx = 175
        println(" ")
        println("  Selected two-photon absorption lines by monochromatic and equally-polarized photons:")
        println(" ")
        println("  ", TableStrings.hLine(nx))
        sa = "  ";   sb = "  "
        sa = sa * TableStrings.center(18, "i-level-f"; na=0);                         sb = sb * TableStrings.hBlank(18)
        sa = sa * TableStrings.center(18, "i--J^P--f"; na=3);                         sb = sb * TableStrings.hBlank(21)
        sa = sa * TableStrings.center(12, "Energy"; na=2);
        sb = sb * TableStrings.center(12, TableStrings.inUnits("energy"); na=2)
        sa = sa * TableStrings.center(12, "omega"; na=4);
        sb = sb * TableStrings.center(12, TableStrings.inUnits("energy"); na=4)
        sa = sa * TableStrings.flushleft(90, "List of multipoles & intermediate level symmetries"; na=4)
        sb = sb * TableStrings.flushleft(90, "(K-rank, multipole_1, Jsym, multipole_2, gauge), ..."; na=4)
        println(sa);    println(sb);    println("  ", TableStrings.hLine(nx))
        #
        for  line in lines
            sa  = "";    isym = LevelSymmetry( line.initialLevel.J, line.initialLevel.parity)
                         fsym = LevelSymmetry( line.finalLevel.J,   line.finalLevel.parity)
            sa = sa * TableStrings.center(18, TableStrings.levels_if(line.initialLevel.index, line.finalLevel.index); na=2)
            sa = sa * TableStrings.center(18, TableStrings.symmetries_if(isym, fsym); na=4)
            energy = line.finalLevel.energy - line.initialLevel.energy
            sa = sa * @sprintf("%.4e", Defaults.convertUnits("energy: from atomic", energy))      * "    "
            sa = sa * @sprintf("%.4e", Defaults.convertUnits("energy: from atomic", line.omega))  * "     "
            mpGaugeList = Tuple{AngularJ64, Basics.EmMultipole, LevelSymmetry, Basics.EmMultipole, Basics.EmGauge}[]
            for  channel in  line.channels
                push!( mpGaugeList, (channel.K, channel.multipole1, channel.Jsym, channel.multipole2, channel.gauge) )
            end
            ##x println("displayLines_2pAbsorptionMonochromatic: mpGaugeList = ", mpGaugeList)
            wa = TableStrings.twoPhotonGaugeTupels(105, mpGaugeList)
            if  length(wa) > 0    sb = sa * wa[1];    println( sb )    end
            for  i = 2:length(wa)
                sb = TableStrings.hBlank( length(sa) );    sb = sb * wa[i];    println( sb )
            end
        end
        println("  ", TableStrings.hLine(nx))
        #
        return( nothing )
    end

    """
    `MultiPhotonDeExcitation.displayCrossSections_2pAbsorptionMonochromatic(stream::IO,
                                                  properties::Array{MultiPhotonDeExcitation.AbstractMultiPhotonProperty,1},
                                                  lines::Array{MultiPhotonDeExcitation.Line_2pAbsorptionMonochromatic,1})`
        ... to display all results, energies, rates, etc. of the selected lines. A neat table is printed but nothing is
            returned otherwise.
    """
    function  displayCrossSections_2pAbsorptionMonochromatic(stream::IO, properties::Array{AbstractMultiPhotonProperty,1},
                                                             lines::Array{Line_2pAbsorptionMonochromatic,1})
        nx = 75
        println(stream, " ")
        println(stream, "  Two-photon absorption by monochromatic and equally-polarized photons (usually from the same beam):")
        println(stream, " ")
        println(stream, "  Cross sections are given for:")
        noCs = 0  # Number of cross sections to be printed
        for property in properties
            if      typeof(property) == TotalCsLinear
                noCs = noCs + 1;   println(stream, "    + total cross sections for linearly-polarized incident light ($noCs)")
            elseif  typeof(property) == TotalCsRightCircular
                noCs = noCs + 1;   println(stream, "    + total cross sections for right-circularly polarized incident light ($noCs)")
            elseif  typeof(property) == TotalCsUnpolarized
                noCs = noCs + 1;   println(stream, "    + total cross sections for unpolarized incident light ($noCs)")
            end
        end
        println(stream, " ")
        println(stream, "  ", TableStrings.hLine(nx + 34noCs))
        sa = "  ";   sb = "  "
        sa = sa * TableStrings.center(18, "i-level-f"; na=2);                         sb = sb * TableStrings.hBlank(20)
        sa = sa * TableStrings.center(18, "i--J^P--f"; na=4);                         sb = sb * TableStrings.hBlank(22)
        sa = sa * TableStrings.center(10, "Energy"; na=4);
        sb = sb * TableStrings.center(10, TableStrings.inUnits("energy"); na=4)
        sa = sa * TableStrings.center(10, "omega";  na=4);
        sb = sb * TableStrings.center(10, TableStrings.inUnits("energy"); na=7)
        for no = 1:noCs
            sa = sa * TableStrings.center(28, "Cou -- cross section ($no) -- Bab"; na=4);
            sb = sb * TableStrings.center(28, TableStrings.inUnits("cross section") * "          " * TableStrings.inUnits("cross section"); na=8)
        end

        println(stream, sa);    println(stream, sb);    println(stream, "  ", TableStrings.hLine(nx + 34noCs))
        #
        for  line in lines
            sa  = "";    isym = LevelSymmetry( line.initialLevel.J, line.initialLevel.parity)
                         fsym = LevelSymmetry( line.finalLevel.J,   line.finalLevel.parity)
            sa = sa * TableStrings.center(18, TableStrings.levels_if(line.initialLevel.index, line.finalLevel.index); na=4)
            sa = sa * TableStrings.center(18, TableStrings.symmetries_if(isym, fsym); na=4)
            energy = line.finalLevel.energy - line.initialLevel.energy
            sa = sa * @sprintf("%.4e", Defaults.convertUnits("energy: from atomic", energy))         * "    "
            sa = sa * @sprintf("%.4e", Defaults.convertUnits("energy: from atomic", line.omega))     * "        "
            for property in properties
                if      typeof(property) == TotalCsLinear
                    sa = sa * @sprintf("%.4e", Defaults.convertUnits("cross section: from atomic",   line.csLinear.Coulomb))          * "      "
                    sa = sa * @sprintf("%.4e", Defaults.convertUnits("cross section: from atomic",   line.csLinear.Babushkin))        * "          "
                elseif  typeof(property) == TotalCsRightCircular
                    sa = sa * @sprintf("%.4e", Defaults.convertUnits("cross section: from atomic",   line.csRightCircular.Coulomb))   * "      "
                    sa = sa * @sprintf("%.4e", Defaults.convertUnits("cross section: from atomic",   line.csRightCircular.Babushkin)) * "          "
                elseif  typeof(property) == TotalCsUnpolarized
                    sa = sa * @sprintf("%.4e", Defaults.convertUnits("cross section: from atomic",   line.csUnpolarized.Coulomb))     * "      "
                    sa = sa * @sprintf("%.4e", Defaults.convertUnits("cross section: from atomic",   line.csUnpolarized.Babushkin))   * "          "
                end
            end
            println(stream, sa )
        end
        println(stream, "  ", TableStrings.hLine(nx + 34noCs))
        #
        return( nothing )
    end


opt_success = (:NLOPT_SUCCESS, :NLOPT_STOPVAL_REACHED, :NLOPT_FTOL_REACHED, :NLOPT_XTOL_REACHED, :NLOPT_MAXEVAL_REACHED,
  :NLOPT_MAXTIME_REACHED)

opt_failure = (:NLOPT_FAILURE, :NLOPT_INVALID_ARGS, :NLOPT_OUT_OF_MEMORY, :NLOPT_ROUNDOFF_LIMITED, :NLOPT_FORCED_STOP)

# MCML decoder for non-coherent UTURUniChannelCSK
immutable UTURUniChannelCSKMCMLDecoder <: MCMLDecoder
  carrier::Carrier
  nmc::Int64
  opt::Vector{NLoptimizer} # NLopt Opt types for the two functions to be mazimized
  init::Vector{Float64}

  function UTURUniChannelCSKMCMLDecoder(c::Carrier, nmc::Int64, opt::Vector{NLoptimizer}, init::Vector{Float64})
    @assert length(opt) == 2 "2 optimizers are needed for the MCML decoder of UTURUniChannelCSK."
    @assert length(init) == 2 "The init field of the MCML decoder of UTURUniChannelCSK must be a vector of length 2."
    new(c, nmc, opt, init)
  end
end

UTURUniChannelCSKMCMLDecoder(c::Carrier, nmc::Int64, opt::NLoptimizer, init::Vector{Float64}) =
  UTURUniChannelCSKMCMLDecoder(c, nmc, [opt, opt], init)
UTURUniChannelCSKMCMLDecoder(c::Carrier, nmc::Int64, opt::Vector{NLoptimizer}, init::Float64) =
  UTURUniChannelCSKMCMLDecoder(c, nmc, opt, [init, init])
UTURUniChannelCSKMCMLDecoder(c::Carrier, nmc::Int64, opt::NLoptimizer, init::Float64) =
  UTURUniChannelCSKMCMLDecoder(c, nmc, [opt, opt], [init, init])

# Default optimizer for MCML decoder of non-coherent UTURUniChannelCSK
uturunichannelcsk_default_opt = NLoptimizer(alg=:LD_SLSQP, lb=0.0, ub=100.0, absftol=NaN, relftol=NaN, absxtol=1e-32,
  relxtol=NaN, nfeval=-1, maxtime=-1.0)

UTURUniChannelCSKMCMLDecoder(; carrier::Carrier=LogisticCarrier(5), nmc::Int64=50,
  opt::Union(NLoptimizer, Vector{NLoptimizer})=uturunichannelcsk_default_opt,
  init::Union(Float64, Vector{Float64})=1.0) =
  UTURUniChannelCSKMCMLDecoder(carrier, nmc, opt, init)

# Wrapper for MCML decoder construction of any systems that supports MCML decoding
mcml_types = (:UTURUniChannelCSK,)

function mcml_decoder(; system::Symbol=:UTURUniChannelCSK, args...)
  @assert in(system, mcml_types) "MCML decoder not defined for $system."

  if system == :UTURUniChannelCSK
    UTURUniChannelCSKMCMLDecoder(; args...)
  end
end


# ---------------------------------------------------------------------------- #
#
#   genSTLslice.jl
#
#   Generate STL slices directly from lattice frame representation
#
#   intrico
#   Summer 2018
#
#   Max Opgenoord
#
# ---------------------------------------------------------------------------- #

function genSTLslice( mesh::MeshF,  lat::Lattice, flname::String,
                      nprinter::SVector{3,Float64}, z::Float64,
                      actptsNods::Vector{Vector{SVector{3,Float64}}},
                      edgesNods::Vector{Vector{SVector{5,Int64}}};
                      n::Int64 = 8 )

    # z is defined from origin

    # open STL file
    fid = open( flname, "w" )

    # write header
    for jj in 1:80
        write(fid,' ')
    end
    write(fid,UInt32(0)) # number of faces is unknown now, so write 0

    # z location in part coordinates
    zpart = z * nprinter

    # find intersecting edges
    edgesInt = findEdgesInt( mesh, lat, nprinter, zpart )

    # generate the slices
    factot = [ 0 ]
    writeNodStrutSlice( mesh, lat, nprinter, zpart, z,
                        edgesInt, n, fid, factot,
                        actptsNods, edgesNods )

    # close STL
    close( fid )

    ## reopen to write the number of faces
    fid = open( flname, "a+" )
    seekstart(fid) # go back to top of file

    # rewrite header
    for jj in 1:80
        write(fid,' ')
    end

    # write correct number of faces
    write( fid, UInt32( factot[1] ) )

    # close STL
    close( fid )

end

function findEdgesInt( mesh::MeshF, lat::Lattice,
                       nprinter::SVector{3,Float64}, zpart::SVector{3,Float64} )

    ne = size(mesh.e,1)

    edgesInt = fill( false, ne )

    # loop over edges
    for ee in 1:ne

        if lat.ar[ee] < 1e-10
            continue
        end

        r = sqrt( lat.ar[ee] / π )

        inod1 = mesh.e[ee,1]
        inod2 = mesh.e[ee,2]
        nod1  = SVector{3,Float64}( mesh.p[inod1,:] )
        nod2  = SVector{3,Float64}( mesh.p[inod2,:] )
        ledge = norm( nod2 - nod1 )
        nvec  = SVector{3,Float64}( (nod2 - nod1) ./ ledge )

        # check if edge is intersecting or even remotely close to intersecting
        xint, d = findPlaneLineIntersec( zpart, nod1, nprinter, nvec )
        if d >= -2*r && d <= (ledge+2*r)
            # may be intersecting
            edgesInt[ee] = true
        end

    end

    return edgesInt

end

function writeNodStrutSlice( mesh::MeshF, lat::Lattice,
                             nprinter::SVector{3,Float64},
                             zpart::SVector{3,Float64}, z::Float64,
                             edgesInt::Vector{Bool},
                             n::Int64, fid::IOStream, factot::Vector{Int64},
                             actptsNods::Vector{Vector{SVector{3,Float64}}},
                             edgesNods::Vector{Vector{SVector{5,Int64}}} )

    zs = mesh.p * nprinter
    minz = minimum( zs )
    maxz = maximum( zs )

    φ = linspace( 0, 2*π, n+1 )
    xpts = Vector{ SVector{3,Float64} }( n+1 )

    edgWritten = fill( false, size(mesh.e,1) )

    # loop over nodes
    for nn in 1:mesh.n

        if !any(edgesInt[ mesh.n2e[nn] ])
            # no intersections
            continue
        end

        nedg = length(mesh.n2e[nn])

        # check if cutting through node
        intersecEdge = fill( false, nedg )
        intersecNodEdge = fill( false, length(edgesNods[nn]) )
        for kk in 1:length(edgesNods[nn])
            nod1 = mesh.p[nn,:] + actptsNods[nn][ edgesNods[nn][kk][1] ]
            nod2 = mesh.p[nn,:] + actptsNods[nn][ edgesNods[nn][kk][2] ]
            if (dot(nod1,nprinter) - z) * ( dot(nod2,nprinter) - z ) < 0.

                intersecNodEdge[kk] = true

                intersecEdge[ mesh.n2e[nn] .== edgesNods[nn][kk][3] ] = true
                intersecEdge[ mesh.n2e[nn] .== edgesNods[nn][kk][4] ] = true
            end
        end

        # write complicated node geometry
        writeNodSlice( mesh, lat, nn, edgesNods[nn], actptsNods[nn],
                       intersecEdge, intersecNodEdge, nprinter, zpart, z, n, fid, factot )

       if any(intersecEdge)
           # intersecting the node
           continue
       end

        # compute dist from node -- use pairings from edgesNods
        distNode = fill( 0., nedg )
        radmax = sqrt( maximum(lat.ar[ mesh.n2e[nn] ]) / π )
        for kk in 1:length(edgesNods[nn])

            inde1 = edgesNods[nn][kk][3]
            inde2 = edgesNods[nn][kk][4]
            inod1 = mesh.e[inde1,1] + mesh.e[inde1,2] - nn
            inod2 = mesh.e[inde2,1] + mesh.e[inde2,2] - nn
            vec1 = mesh.p[inod1,:] - mesh.p[nn,:]
            vec2 = mesh.p[inod2,:] - mesh.p[nn,:]

            arg = dot(vec1,vec2)/( norm(vec1)*norm(vec2) )
            arg = minimum( ( 1.0, arg) ) # guard for round-off errors
            arg = maximum( (-1.0, arg) ) # guard for round-off errors
            θ = acos( arg )

            # compute absolute distance between node center and intersection
            lv = radmax / tan( θ/2 ) # offset such that rods do not collide
            # l = sqrt( lv^2 + radmax^2 ) # absolute distance

            iedgloc1 = find(mesh.n2e[nn] .== edgesNods[nn][kk][3])[]
            iedgloc2 = find(mesh.n2e[nn] .== edgesNods[nn][kk][4])[]
            distNode[ iedgloc1 ] = max( distNode[ iedgloc1 ], lv )
            distNode[ iedgloc2 ] = max( distNode[ iedgloc2 ], lv )

        end

        # write slices for struts that do not collide with other struts
        for eloc in 1:nedg
            ee = mesh.n2e[nn][eloc]

            if intersecEdge[eloc] || edgWritten[ee]
                continue
            end

            rad = sqrt( lat.ar[ee] / π )

            inod1 = mesh.e[ee,1]
            inod2 = mesh.e[ee,2]
            nod1  = SVector{3,Float64}( mesh.p[nn,:] )
            nod2  = SVector{3,Float64}( mesh.p[inod1+inod2-nn,:] )
            ledge = norm( nod2 - nod1 )
            nvec  = SVector{3,Float64}( (nod2 - nod1) ./ ledge )

            nperp = cross( nvec, nprinter )
            nperp = nperp/norm(nperp)

            nzero = SVector{3}( nprinter[3], nprinter[1], nprinter[2] )
            if (abs( abs(dot(nprinter,nvec)) - 1.) ) < 1e-10
                nzero = cross(nzero,nprinter)
                nperp = cross(nprinter,nzero)
                nperp = nperp/norm(nperp)
            else
                nzero = cross(nvec,nperp)
            end
            nzero = nzero/norm(nzero)

            d = fill(0., 3)
            intsec0, d[1] = findPlaneLineIntersec( zpart, nod1, nprinter, nvec )

            # first line
            currpt = nod1 + nzero*rad
            intsec1, d[2] = findPlaneLineIntersec( zpart, currpt, nprinter, nvec )

            # second line
            currpt = nod1 - nzero*rad
            intsec2, d[3] = findPlaneLineIntersec( zpart, currpt, nprinter, nvec )

            if all( (d .< distNode[eloc]) .| (d .> 0.5*ledge) )
                # No intersections with first half of edge
                continue
            end

            edgWritten[ee] = true

            # generate points for STL
            vec1 = - rad * nperp
            vec2 = intsec1 - intsec0
            for jj in 1:n+1
                xpts[jj] = intsec0 + vec1 * cos(φ[jj]) + vec2 * sin(φ[jj])
            end

            writeSTLcontour( xpts, intsec0, nprinter, n, fid )
            factot[1] += n

        end

    end

end

function findPlaneLineIntersec( planept::SVector{3,Float64},
                                linept::SVector{3,Float64},
                                nPlane::SVector{3,Float64},
                                nLine::SVector{3,Float64} )

    d    = dot( planept-linept, nPlane ) / dot( nLine, nPlane )
    xloc = linept + d * nLine

    return xloc, d

end

function writeSTLcontour( xpts::Vector{SVector{3,Float64}},
                          intersec0::SVector{3,Float64},
                          nprinter::SVector{3,Float64},
                          n::Int64, fid::IOStream )

      for jj in 1:n

          xface = SVector{3,SVector{3,Float64}}( xpts[jj], xpts[jj+1], intersec0 )

          sterno.writeFacetSTLB( xface, nprinter, fid )

      end

end

function writeNodSlice( mesh::MeshF, lat::Lattice, nn::Int64,
                        edgesNods::Vector{SVector{5,Int64}},
                        actptsNods::Vector{SVector{3,Float64}},
                        intersecEdge::Vector{Bool}, intersecNodEdge::Vector{Bool},
                        nprinter::SVector{3,Float64},
                        zpart::SVector{3,Float64}, z::Float64,
                        n::Int64, fid::IOStream, factot::Vector{Int64} )

    nodvec = SVector{3}( mesh.p[nn,:] )

    Δn = ( z - dot(nodvec,nprinter) ) * nprinter

    Δnodvec = nodvec + Δn

    nint = count( intersecEdge )

    xpts   = Vector{ SVector{3,Float64} }( n+1 )
    xptscl = Vector{ SVector{3,Float64} }( 3 )
    xptsfl = Vector{ SVector{3,Float64} }( 4 )

    # loop over intersecting edges
    for kk in 1:length( mesh.n2e[nn] )

        e0 = mesh.n2e[nn][kk]

        if !intersecEdge[kk]
            continue
        end

        # find the other two neighboring edges
        e1 = 0
        e2 = 0
        enew = 0
        ihalf1 = 0
        ihalf2 = 0
        ct = 1
        for el in edgesNods
            if !intersecNodEdge[ct]
                ct += 1
                continue
            end
            if el[3] == e0
                enew = el[4]
            elseif el[4] == e0
                enew = el[3]
            else
                ct += 1
                continue
            end
            if e1 > 0
                e2 = enew
                ihalf2  = el[5]
            else
                e1 = enew
                ihalf1  = el[5]
            end
            ct += 1
        end

        # find which edge is active on which side by comparing ncut vectors
        nod0 = mesh.e[ e0, 1 ] + mesh.e[ e0, 2 ] - nn
        nod1 = mesh.e[ e1, 1 ] + mesh.e[ e1, 2 ] - nn
        nod2 = mesh.e[ e2, 1 ] + mesh.e[ e2, 2 ] - nn

        nvec0 = SVector{3}( mesh.p[nod0,:] - mesh.p[nn,:]); nvec0 = nvec0/norm(nvec0)
        nvec1 = SVector{3}( mesh.p[nod1,:] - mesh.p[nn,:]); nvec1 = nvec1/norm(nvec1)
        nvec2 = SVector{3}( mesh.p[nod2,:] - mesh.p[nn,:]); nvec2 = nvec2/norm(nvec2)

        ncut1 = nvec0 - nvec1; ncut1 = ncut1/norm(ncut1)
        ncut2 = nvec0 - nvec2; ncut2 = ncut2/norm(ncut2)
        nline1 = cross(ncut1,nprinter); nline1 = nline1/norm(nline1)
        nline2 = cross(ncut2,nprinter); nline2 = nline2/norm(nline2)

        # find correct orientation to make sure that nline is aligned with the halfvector of the edge (as projected from the edge)
        if dot( nline1 - dot(nline1,nvec1)*nvec1, actptsNods[ihalf1] ) < 0.
            nline1 = -nline1
        end
        if dot( nline2 - dot(nline2,nvec2)*nvec2, actptsNods[ihalf2] ) < 0.
            nline2 = -nline2
        end

        rad  = sqrt( lat.ar[ e0 ] / π )
        arg1 = dot(nvec0,nvec1)
        # arg = minimum( ( 1.0, arg) ) # guard for round-off errors
        # arg = maximum( (-1.0, arg) ) # guard for round-off errors
        θ1  = acos( arg1 )
        lv1 = rad / tan( θ1/2 ) # offset such that rods do not collide
        ℓ1  = sqrt( lv1^2 + rad^2 ) # absolute distance
        arg2 = dot(nvec0,nvec2)
        # arg = minimum( ( 1.0, arg) ) # guard for round-off errors
        # arg = maximum( (-1.0, arg) ) # guard for round-off errors
        θ2  = acos( arg2 )
        lv2 = rad / tan( θ2/2 ) # offset such that rods do not collide
        ℓ2  = sqrt( lv2^2 + rad^2 ) # absolute distance

        planeoff = dot( nprinter, Δn )
        cent1 = planeoff * cross( ncut1, cross(nprinter, ncut1 ) ) / norm( cross(ncut1, nprinter) )^2 # point of plane-plane intersection
        intersec1 = findIntersecEdge( nvec0, nline1, cent1, rad, ℓ1) + nodvec

        cent2 = planeoff * cross( ncut2, cross(nprinter, ncut2 ) ) / norm( cross(ncut2, nprinter) )^2 # point of plane-plane intersection
        intersec2 = findIntersecEdge( nvec0, nline2, cent2, rad, ℓ2) + nodvec

        # --- save this somewhere TODO for more efficient algorithm

        # --- write contour
        ledge = norm( mesh.p[nod0,:] - nodvec )
        if abs( dot(nvec0,nprinter) ) < 1e-10

            if dot( cross( intersec1 - nodvec, intersec2 - nodvec ), nprinter ) < 0.
                temp1 = intersec1
                intersec1 = intersec2
                intersec2 = temp1
            end
            # flat edge
            xptsfl[1] = intersec1
            xptsfl[2] = intersec1 + nvec0 * ( 0.5*ledge - dot(intersec1 - Δnodvec,nvec0) )
            xptsfl[3] = intersec2 + nvec0 * ( 0.5*ledge - dot(intersec2 - Δnodvec,nvec0) )
            xptsfl[4] = intersec2

            writeSTLcontour( xptsfl, Δnodvec, nprinter, 3, fid )
            factot[1] += 3

        else

            nperp = cross( nvec0, nprinter )
            nperp = nperp ./ norm(nperp)

            nzero = SVector{3}( nprinter[3], nprinter[1], nprinter[2] )
            if abs( abs(dot(nprinter,nvec0)) - 1.) < 1e-10
                nzero = cross(nzero,nprinter)
                nperp = cross(nprinter,nzero)
                nperp = nperp/norm(nperp)
            else
                nzero = cross(nvec0,nperp)
            end
            nzero = nzero/norm(nzero)

            intsec0, d0 = findPlaneLineIntersec( zpart, nodvec, nprinter, nvec0 )

            # first line
            currpt = nodvec + nzero*rad
            intsec1, = findPlaneLineIntersec( zpart, currpt, nprinter, nvec0 )

            # second line
            currpt = nodvec - nzero*rad
            intsec2, = findPlaneLineIntersec( zpart, currpt, nprinter, nvec0 )

            # find angle limits due to intersection
            vec1 = -rad * nperp
            vec2 =  intsec1 - intsec0
            if dot( cross(vec1,vec2), nprinter ) < 0.
                vec2 = -vec2
            end

            normvec1 = norm(vec1)
            normvec2 = norm(vec2)

            Δvec = intersec1 - intsec0
            φ1 = atan2( dot(vec2,Δvec)/normvec2^2, dot(vec1,Δvec)/normvec1^2 )

            Δvec = intersec2 - intsec0
            φ2 = atan2( dot(vec2,Δvec)/normvec2^2, dot(vec1,Δvec)/normvec1^2 )

            φmin = min(φ1,φ2)
            φmax = max(φ1,φ2)
            if (φmax - φmin) > π
                φ1 = φmax
                φmax = φmin
                φmin = φ1 - 2*π
            end

            φ = linspace( φmin, φmax, n+1 )
            for jj in 1:n+1
                xpts[jj] = intsec0 + vec1 * cos(φ[jj]) + vec2 * sin(φ[jj])
            end

            writeSTLcontour( xpts, intsec0, nprinter, n, fid )
            factot[1] += n

            # close the contour
            xptscl[1] = xpts[1]
            xptscl[3] = xpts[end]
            xptscl[2] = intsec0

            if d0 > 0.
                writeSTLcontour( xptscl, Δnodvec, nprinter, 2, fid )
                factot[1] += 2
            end

        end

    end

end

function findIntersecEdge( nvec1::SVector{3,Float64}, nline::SVector{3,Float64},
                           Δn::SVector{3,Float64}, r::Float64, ℓ::Float64 )

    # This uses the bisection method, so temporary.

    b1 = 0.
    b2 = 2*ℓ

    b = 0.
    rcomp = 0.
    while (b2 - b1)/b2 > 1e-5

        b = (b2 + b1)/2
        tempvec = b*nline + Δn
        rcomp = norm( tempvec - dot(tempvec,nvec1)*nvec1 )

        if rcomp > r
            b2 = b
        elseif rcomp < r
            b1 = b
        else
            break
        end

    end

    return (b*nline + Δn)

end


function diffuse(	particle_type_ss::String,
					R_ss::Array{Float64, 2},
					X_ss::Array{Float64, 1},
					Y_ss::Array{Float64, 1},
					Z_ss::Array{Float64, 1},
					Q0_ss::Array{Float64, 1},
					Q1_ss::Array{Float64, 1},
					Q2_ss::Array{Float64, 1},
					Q3_ss::Array{Float64, 1},
					A11_ss::Array{Float64, 1},
					A12_ss::Array{Float64, 1},
					A13_ss::Array{Float64, 1},
					A21_ss::Array{Float64, 1},
					A22_ss::Array{Float64, 1},
					A23_ss::Array{Float64, 1},
					A31_ss::Array{Float64, 1},
					A32_ss::Array{Float64, 1},
					A33_ss::Array{Float64, 1},
					cell_lists_ss::Array{Array{Int64, 1}, 3},
					particle_type_dm::String,
					R_dm::Array{Float64, 2},
					X_dm::Array{Float64, 1},
					Y_dm::Array{Float64, 1},
					Z_dm::Array{Float64, 1},
					Q0_dm::Array{Float64, 1},
					Q1_dm::Array{Float64, 1},
					Q2_dm::Array{Float64, 1},
					Q3_dm::Array{Float64, 1},
					A11_dm::Array{Float64, 1},
					A12_dm::Array{Float64, 1},
					A13_dm::Array{Float64, 1},
					A21_dm::Array{Float64, 1},
					A22_dm::Array{Float64, 1},
					A23_dm::Array{Float64, 1},
					A31_dm::Array{Float64, 1},
					A32_dm::Array{Float64, 1},
					A33_dm::Array{Float64, 1},
					cell_lists_dm::Array{Array{Int64, 1}, 3},
					Lx::Float64,
					Ly::Float64,
					Lz::Float64,
					D0::Float64,
					D1::Float64,
					deltat_coarse::Float64,
					number_of_time_points_coarse::Int64,
					number_of_time_points_fine_per_coarse::Int64,
					number_of_diffusers::Int64,
					boundary_condition::String)

	# Standard deviations of Gaussian jumps.
	deltat_fine::Float64 = deltat_coarse / convert(Float64, number_of_time_points_fine_per_coarse)
	sigma0::Float64 = sqrt(2.0 * D0 * deltat_fine)
	sigma1::Float64 = sqrt(2.0 * D1 * deltat_fine)
	sigma::Float64 = 0.0

	# Number of cells (number of cells must be the same for both cell lists)
	(number_of_cells_x::Int64, number_of_cells_y::Int64, number_of_cells_z::Int64) = size(cell_lists_ss)

	current_particle_in_cell::Int64 = 0
	is_proposed_position_ok::Bool = true
	is_in_phase0::Bool = true

	x::Float64 = 0.0
	y::Float64 = 0.0
	z::Float64 = 0.0
	x_abs::Float64 = 0.0
	y_abs::Float64 = 0.0
	z_abs::Float64 = 0.0
	x_star::Float64 = 0.0
	y_star::Float64 = 0.0
	z_star::Float64 = 0.0
	deltax::Float64 = 0.0
	deltay::Float64 = 0.0
	deltaz::Float64 = 0.0
	vx::Float64 = 0.0
	vy::Float64 = 0.0
	vz::Float64 = 0.0
	vx_star::Float64 = 0.0
	vy_star::Float64 = 0.0
	vz_star::Float64 = 0.0
	w1::Float64 = 0.0
	w1_star::Float64 = 0.0
	w2::Float64 = 0.0
	w2_star::Float64 = 0.0
	w3::Float64 = 0.0
	w3_star::Float64 = 0.0
	w1_intersection::Float64 = 0.0
	w2_intersection::Float64 = 0.0
	alpha::Float64 = 0.0
	current_cell_x::Int64 = 0
	current_cell_y::Int64 = 0
	current_cell_z::Int64 = 0
	number_of_particles_current_cell::Int64 = 0
	trajectory_x::Array{Float64} = zeros(number_of_time_points_coarse)
	trajectory_y::Array{Float64} = zeros(number_of_time_points_coarse)
	trajectory_z::Array{Float64} = zeros(number_of_time_points_coarse)
	ssd::Array{Float64} = zeros(number_of_time_points_coarse)
	ssd_x::Array{Float64} = zeros(number_of_time_points_coarse)
	ssd_y::Array{Float64} = zeros(number_of_time_points_coarse)
	ssd_z::Array{Float64} = zeros(number_of_time_points_coarse)
	D_empirical::Float64 = 0.0

	t_start_diffusion::Float64 = convert(Float64, time_ns()) / 1e9
	t_elapsed_diffusion::Float64 = 0.0

	chunk::Int64 = 0
	for current_diffuser = 1:number_of_diffusers
		if mod(current_diffuser, 10) == 0
			println(current_diffuser)
		end

		# Pick random, valid initial position.
		is_proposed_position_ok = false
		while !is_proposed_position_ok
			x = Lx * rand()
			y = Ly * rand()
			z = Lz * rand()

			if particle_type_ss == "ellipse"
				is_proposed_position_ok = true # By definition, any point in the simulation domain is outside of the ellipses w.p. 1.
			else
				current_cell_x = convert(Int64, ceil(x / Lx * convert(Float64, number_of_cells_x)))
				current_cell_y = convert(Int64, ceil(y / Ly * convert(Float64, number_of_cells_y)))
				current_cell_z = convert(Int64, ceil(z / Lz * convert(Float64, number_of_cells_z)))
				number_of_particles_current_cell = length(cell_lists_ss[current_cell_x, current_cell_y, current_cell_z])
				current_cell_list_ss = cell_lists_ss[current_cell_x, current_cell_y, current_cell_z]

				is_proposed_position_ok = true
				current_particle_in_cell = 0
				while current_particle_in_cell < number_of_particles_current_cell && is_proposed_position_ok
					current_particle_in_cell += 1
					vx = signed_distance_mod(x, X_ss[current_cell_list_ss[current_particle_in_cell]], Lx)
					vy = signed_distance_mod(y, Y_ss[current_cell_list_ss[current_particle_in_cell]], Ly)
					vz = signed_distance_mod(z, Z_ss[current_cell_list_ss[current_particle_in_cell]], Lz)
					if particle_type_ss == "sphere"
						if vx^2 + vy^2 + vz^2 <= R_ss[current_cell_list_ss[current_particle_in_cell], 1]^2
							is_proposed_position_ok = false
						end
					else
						(vx, vy, vz) = (A11_ss[current_cell_list_ss[current_particle_in_cell]] * vx + A12_ss[current_cell_list_ss[current_particle_in_cell]] * vy + A13_ss[current_cell_list_ss[current_particle_in_cell]] * vz,
										A21_ss[current_cell_list_ss[current_particle_in_cell]] * vx + A22_ss[current_cell_list_ss[current_particle_in_cell]] * vy + A23_ss[current_cell_list_ss[current_particle_in_cell]] * vz,
										A31_ss[current_cell_list_ss[current_particle_in_cell]] * vx + A32_ss[current_cell_list_ss[current_particle_in_cell]] * vy + A33_ss[current_cell_list_ss[current_particle_in_cell]] * vz)
						if particle_type_ss == "ellipsoid"
							if  (vx/R_ss[current_cell_list_ss[current_particle_in_cell], 1])^2 +
								(vy/R_ss[current_cell_list_ss[current_particle_in_cell], 2])^2 +
								(vz/R_ss[current_cell_list_ss[current_particle_in_cell], 3])^2 <= 1.0
								is_proposed_position_ok = false
							end
						elseif particle_type_ss == "cuboid"
							if abs(vx) <= R_ss[current_cell_list_ss[current_particle_in_cell], 1] && abs(vy) <= R_ss[current_cell_list_ss[current_particle_in_cell], 2] && abs(vz) <= R_ss[current_cell_list_ss[current_particle_in_cell], 3]
								is_proposed_position_ok = false
							end
						elseif particle_type_ss == "superellipsoid"
							if  (abs(vx)/R_ss[current_cell_list_ss[current_particle_in_cell], 1])^R_ss[current_cell_list_ss[current_particle_in_cell], 4] +
								(abs(vy)/R_ss[current_cell_list_ss[current_particle_in_cell], 2])^R_ss[current_cell_list_ss[current_particle_in_cell], 4] +
								(abs(vz)/R_ss[current_cell_list_ss[current_particle_in_cell], 3])^R_ss[current_cell_list_ss[current_particle_in_cell], 4] <= 1.0
								is_proposed_position_ok = false
							end
						end
					end
				end
			end
		end

		x_abs = x
		y_abs = y
		z_abs = z

		trajectory_x[1] = x_abs
		trajectory_y[1] = y_abs
		trajectory_z[1] = z_abs

		# Starting diffusion.
		for current_time_coarse = 2:number_of_time_points_coarse
			for current_time_fine = 1:number_of_time_points_fine_per_coarse
				# Diffusion map.
				if particle_type_ss == "ellipse"
					is_in_phase0 = true # By definition, w.p. 1.
				else
					current_cell_x = convert(Int64, ceil(x / Lx * convert(Float64, number_of_cells_x)))
					current_cell_y = convert(Int64, ceil(y / Ly * convert(Float64, number_of_cells_y)))
					current_cell_z = convert(Int64, ceil(z / Lz * convert(Float64, number_of_cells_z)))
					number_of_particles_current_cell = length(cell_lists_dm[current_cell_x, current_cell_y, current_cell_z])
					current_cell_list_dm = cell_lists_dm[current_cell_x, current_cell_y, current_cell_z]

					is_in_phase0 = true
					current_particle_in_cell = 0

					while current_particle_in_cell < number_of_particles_current_cell && is_in_phase0
						current_particle_in_cell += 1
						vx = signed_distance_mod(x, X_dm[current_cell_list_dm[current_particle_in_cell]], Lx)
						vy = signed_distance_mod(y, Y_dm[current_cell_list_dm[current_particle_in_cell]], Ly)
						vz = signed_distance_mod(z, Z_dm[current_cell_list_dm[current_particle_in_cell]], Lz)

						if particle_type_dm == "sphere"
							if vx^2 + vy^2 + vz^2 <= R_dm[current_cell_list_dm[current_particle_in_cell], 1]^2
								is_in_phase0 = false
							end
						else
							(vx, vy, vz) = (A11_dm[current_cell_list_dm[current_particle_in_cell]] * vx + A12_dm[current_cell_list_dm[current_particle_in_cell]] * vy + A13_dm[current_cell_list_dm[current_particle_in_cell]] * vz,
											A21_dm[current_cell_list_dm[current_particle_in_cell]] * vx + A22_dm[current_cell_list_dm[current_particle_in_cell]] * vy + A23_dm[current_cell_list_dm[current_particle_in_cell]] * vz,
											A31_dm[current_cell_list_dm[current_particle_in_cell]] * vx + A32_dm[current_cell_list_dm[current_particle_in_cell]] * vy + A33_dm[current_cell_list_dm[current_particle_in_cell]] * vz)

							if particle_type_dm == "ellipsoid"
								if (vx/R_dm[current_cell_list_dm[current_particle_in_cell], 1])^2 +
									(vy/R_dm[current_cell_list_dm[current_particle_in_cell], 2])^2 +
									(vz/R_dm[current_cell_list_dm[current_particle_in_cell], 3])^2 <= 1.0
									is_in_phase0 = false
								end
							elseif particle_type_dm == "cuboid"
								if abs(vx) <= R_dm[current_cell_list_dm[current_particle_in_cell], 1] && abs(vy) <= R_dm[current_cell_list_dm[current_particle_in_cell], 2] && abs(vz) <= R_dm[current_cell_list_dm[current_particle_in_cell], 3]
									is_in_phase0 = false
								end
							elseif particle_type_dm == "superellipsoid"
								if (abs(vx)/R_dm[current_cell_list_dm[current_particle_in_cell], 1])^R_dm[current_cell_list_dm[current_particle_in_cell], 4] +
									(abs(vy)/R_dm[current_cell_list_dm[current_particle_in_cell], 2])^R_dm[current_cell_list_dm[current_particle_in_cell], 4] +
									(abs(vz)/R_dm[current_cell_list_dm[current_particle_in_cell], 3])^R_dm[current_cell_list_dm[current_particle_in_cell], 4] <= 1.0
									is_in_phase0 = false
								end
							end
						end
					end
				end
				if is_in_phase0
					sigma = sigma0
				else
					sigma = sigma1
				end

				# Solid structure.
				current_cell_x = convert(Int64, ceil(x / Lx * convert(Float64, number_of_cells_x)))
				current_cell_y = convert(Int64, ceil(y / Ly * convert(Float64, number_of_cells_y)))
				current_cell_z = convert(Int64, ceil(z / Lz * convert(Float64, number_of_cells_z)))
				number_of_particles_current_cell = length(cell_lists_ss[current_cell_x, current_cell_y, current_cell_z])
				current_cell_list_ss = cell_lists_ss[current_cell_x, current_cell_y, current_cell_z]

				is_proposed_position_ok = false
				while !is_proposed_position_ok
					deltax = sigma * randn()
					deltay = sigma * randn()
					deltaz = sigma * randn()

					x_star = position_mod(x + deltax, Lx)
					y_star = position_mod(y + deltay, Ly)
					z_star = position_mod(z + deltaz, Lz)

					# Check for diffuser-particle intersections.
					current_particle_in_cell = 0
					is_proposed_position_ok = true
					while current_particle_in_cell < number_of_particles_current_cell && is_proposed_position_ok
						current_particle_in_cell += 1

						if particle_type_ss == "ellipse"
							# Make sure we can detect intersection by ensuring that v and v_star are close, not just close mod L.
							# Ellipse is a bit special in this regard.

							# Observe that the arguments to signed_distance_mod are shifted. Calling them
							# the same way as in other place will lead to wrong sign of distances.
							vx = signed_distance_mod(X_ss[current_cell_list_ss[current_particle_in_cell]], x, Lx)
							vy = signed_distance_mod(Y_ss[current_cell_list_ss[current_particle_in_cell]], y, Ly)
							vz = signed_distance_mod(Z_ss[current_cell_list_ss[current_particle_in_cell]], z, Lz)
							vx_star = vx + deltax
							vy_star = vy + deltay
							vz_star = vz + deltaz
							(vx, vy, vz) = (				A11_ss[current_cell_list_ss[current_particle_in_cell]] * vx +	A12_ss[current_cell_list_ss[current_particle_in_cell]] * vy + A13_ss[current_cell_list_ss[current_particle_in_cell]] * vz,
															A21_ss[current_cell_list_ss[current_particle_in_cell]] * vx + A22_ss[current_cell_list_ss[current_particle_in_cell]] * vy + A23_ss[current_cell_list_ss[current_particle_in_cell]] * vz,
															A31_ss[current_cell_list_ss[current_particle_in_cell]] * vx + A32_ss[current_cell_list_ss[current_particle_in_cell]] * vy + A33_ss[current_cell_list_ss[current_particle_in_cell]] * vz)
							(vx_star, vy_star, vz_star) = (	A11_ss[current_cell_list_ss[current_particle_in_cell]] * vx_star + A12_ss[current_cell_list_ss[current_particle_in_cell]] * vy_star + A13_ss[current_cell_list_ss[current_particle_in_cell]] * vz_star,
															A21_ss[current_cell_list_ss[current_particle_in_cell]] * vx_star + A22_ss[current_cell_list_ss[current_particle_in_cell]] * vy_star + A23_ss[current_cell_list_ss[current_particle_in_cell]] * vz_star,
															A31_ss[current_cell_list_ss[current_particle_in_cell]] * vx_star + A32_ss[current_cell_list_ss[current_particle_in_cell]] * vy_star + A33_ss[current_cell_list_ss[current_particle_in_cell]] * vz_star)

							alpha = - vz_star / (vz - vz_star)
							if 0.0 < alpha < 1.0
								w1_intersection = alpha * vx + (1.0 - alpha) * vx_star
								w2_intersection = alpha * vy + (1.0 - alpha) * vy_star
								if (w1_intersection/R_ss[current_cell_list_ss[current_particle_in_cell], 1])^2 + (w2_intersection/R_ss[current_cell_list_ss[current_particle_in_cell], 2])^2 < 1.0
									is_proposed_position_ok = false
								end
							end
						else
							vx_star = signed_distance_mod(x_star, X_ss[current_cell_list_ss[current_particle_in_cell]], Lx)
							vy_star = signed_distance_mod(y_star, Y_ss[current_cell_list_ss[current_particle_in_cell]], Ly)
							vz_star = signed_distance_mod(z_star, Z_ss[current_cell_list_ss[current_particle_in_cell]], Lz)
							if particle_type_ss == "sphere"
								if vx_star^2 + vy_star^2 + vz_star^2 <= R_ss[current_cell_list_ss[current_particle_in_cell], 1]^2
									is_proposed_position_ok = false
								end
							else
								(vx_star, vy_star, vz_star) = (	A11_ss[current_cell_list_ss[current_particle_in_cell]] * vx_star + A12_ss[current_cell_list_ss[current_particle_in_cell]] * vy_star + A13_ss[current_cell_list_ss[current_particle_in_cell]] * vz_star,
																A21_ss[current_cell_list_ss[current_particle_in_cell]] * vx_star + A22_ss[current_cell_list_ss[current_particle_in_cell]] * vy_star + A23_ss[current_cell_list_ss[current_particle_in_cell]] * vz_star,
																A31_ss[current_cell_list_ss[current_particle_in_cell]] * vx_star + A32_ss[current_cell_list_ss[current_particle_in_cell]] * vy_star + A33_ss[current_cell_list_ss[current_particle_in_cell]] * vz_star)
								if particle_type_ss == "ellipsoid"
									if  (vx_star/R_ss[current_cell_list_ss[current_particle_in_cell], 1])^2 +
										(vy_star/R_ss[current_cell_list_ss[current_particle_in_cell], 2])^2 +
										(vz_star/R_ss[current_cell_list_ss[current_particle_in_cell], 3])^2 <= 1.0
										is_proposed_position_ok = false
									end
								elseif particle_type_ss == "cuboid"
									if abs(vx_star) <= R_ss[current_cell_list_ss[current_particle_in_cell], 1] && abs(vy_star) <= R_ss[current_cell_list_ss[current_particle_in_cell], 2] && abs(vz_star) <= R_ss[current_cell_list_ss[current_particle_in_cell], 3]
										is_proposed_position_ok = false
									end
								elseif particle_type_ss == "superellipsoid"
									if (abs(vx_star)/R_ss[current_cell_list_ss[current_particle_in_cell], 1])^R_ss[current_cell_list_ss[current_particle_in_cell], 4] +
										(abs(vy_star)/R_ss[current_cell_list_ss[current_particle_in_cell], 2])^R_ss[current_cell_list_ss[current_particle_in_cell], 4] +
										(abs(vz_star)/R_ss[current_cell_list_ss[current_particle_in_cell], 3])^R_ss[current_cell_list_ss[current_particle_in_cell], 4] <= 1.0
										is_proposed_position_ok = false
									end
								end
							end
						end

						if boundary_condition == "single-rejection"
							if is_proposed_position_ok
								x = x_star
								y = y_star
								z = z_star

								x_abs = x_abs + deltax
								y_abs = y_abs + deltay
								z_abs = z_abs + deltaz

								D_empirical = D_empirical + deltax^2 + deltay^2 + deltaz^2
							else
								is_proposed_position_ok = true # Just we get out of the loop that's really only used for multiple rejection.
							end
						end
					end
				end
				if boundary_condition == "multiple-rejection"
					x = x_star
					y = y_star
					z = z_star

					x_abs = x_abs + deltax
					y_abs = y_abs + deltay
					z_abs = z_abs + deltaz

					D_empirical = D_empirical + deltax^2 + deltay^2 + deltaz^2
				end
			end

			trajectory_x[current_time_coarse] = x_abs
			trajectory_y[current_time_coarse] = y_abs
			trajectory_z[current_time_coarse] = z_abs

			ssd[current_time_coarse] += (trajectory_x[current_time_coarse] - trajectory_x[1])^2 + (trajectory_y[current_time_coarse] - trajectory_y[1])^2 + (trajectory_z[current_time_coarse] - trajectory_z[1])^2
			ssd_x[current_time_coarse] += (trajectory_x[current_time_coarse] - trajectory_x[1])^2
			ssd_y[current_time_coarse] += (trajectory_y[current_time_coarse] - trajectory_y[1])^2
			ssd_z[current_time_coarse] += (trajectory_z[current_time_coarse] - trajectory_z[1])^2
		end
	end
	output::Array{Float64, 1} = vcat(ssd, ssd_x, ssd_y, ssd_z, D_empirical)
	return output
end


function build_primitive()
    # Build a simple metadata.
    metadata = Dict("mappable" => true)
    component = Component("primitive"; metadata = metadata)
    # Instantiate two output and two input ports.
    # Make the metadata for the two input ports slightly different to test routing
    add_port(component, "in", Input)
    add_port(component, "out", Output)

    check(component)
    return component
end

function build_tile()
    # Build a simple metadata.
    metadata = Dict("mappable" => false)
    component = Component("tile"; metadata = metadata)

    # Add general primitive
    add_child(component, build_primitive(), "u0")

    # Instantiate two output and two input ports.
    # Make the metadata for the two input ports slightly different to test routing
    dirs = ("north", "east", "south", "west", "up", "down")
    for dir in dirs
        add_port(component, "$(dir)_in", Input)
        add_port(component, "$(dir)_out", Output)
    end

    # instantiate a mux
    add_child(component, build_mux(7, 7), "mux")

    ## Links

    # Component to mux
    add_link(component, "u0.out", "mux.in[0]")
    add_link(component, "mux.out[0]", "u0.in")

    # Mux to tile i/o
    for (i, d) in enumerate(dirs)
        add_link(component, "mux.out[$i]", "$(d)_out")
        add_link(component, "$(d)_in", "mux.in[$i]")
    end

    check(component)
    return component
end

function build_arch()
    a = TopLevel{3}("arch_3d")
    tile = build_tile()

    # Instantiate tiles
    for (i, j, k) in Iterators.product(1:3, 1:3, 1:3)
        add_child(a, tile, CartesianIndex(i, j, k))
    end

    offsets = [
        Offset((-1, 0, 0), "north_out", "south_in"),
        Offset((1, 0, 0), "south_out", "north_in"),
        Offset((0, 1, 0), "east_out", "west_in"),
        Offset((0, -1, 0), "west_out", "east_in"),
        Offset((0, 0, 1), "up_out", "down_in"),
        Offset((0, 0, -1), "down_out", "up_in"),
    ]

    connection_rule(a, offsets)
    check(a)
    return a
end


using Gen
using Test

include("regression/static_mala.jl")
include("regression/static_map_optimize.jl")
include("regression/static_mh.jl")
include("regression/static_mh_collapsed.jl")
include("regression/quickstart.jl")
include("regression/dynamic_map_optimize_gibbs.jl")
include("regression/dynamic_mh.jl")
include("regression/dynamic_resimulation_mh.jl")
include("coal/coal.jl")
include("gp_structure/lightweight.jl")
include("gp_structure/incremental.jl")
include("mle/static_mle.jl")


__precompile__()

module DistributedArrays

using Distributed
using Serialization
using LinearAlgebra
using Statistics

import Base: +, -, *, div, mod, rem, &, |, xor
import Base.Callable
import LinearAlgebra: axpy!, dot, norm, mul!

import Primes
import Primes: factor

# DArray exports
export DArray, SubDArray, SubOrDArray, @DArray
export dzeros, dones, dfill, drand, drandn, distribute, localpart, localindices, ppeval

# non-array distributed data
export ddata, gather

# immediate release of localparts
export close, d_closeall

include("darray.jl")
include("core.jl")
include("serialize.jl")
include("broadcast.jl")
include("mapreduce.jl")
include("linalg.jl")
include("sort.jl")

include("spmd.jl")
export SPMD

end # module


mutable struct TauLeapSimulator{M,F} <: AbstractSimulator
  algorithm::M
  next_leap_jumps::Vector{Int}
  next_leap_time::Float64
  t::Float64
  tfinal::Float64
  execute_leap!::F
end

###### constructors #####
function TauLeapSimulator(algorithm::M, number_jumps, execute_leap!::F) where {M,F}
  next_leap_jumps = zeros(Int, number_jumps)

  return TauLeapSimulator{M,F}(algorithm, next_leap_jumps, 0.0, 0.0, 0.0, execute_leap!)
end

##### main simulation routines #####

@inline cumulative_intensity(simulator::TauLeapSimulator) = first(simulator.algorithm.total_rate)

# start a new simulation and initialize the stepper with the next event
@inline function initialize!(simulator::TauLeapSimulator, state, model, tfinal)
  # reset the current system time
  simulator.t = 0.0

  simulator.tfinal = tfinal

  # set the number of jumps to zero
  fill!(simulator.next_leap_jumps, 0)

  # initialize the simulation algorithm
  initialize!(simulator.algorithm, state, model)

  # seed the stepper with the first leap
  generate_next_leap!(simulator)

  return nothing
end

# carry out the next leap
@inline function step!(simulator::TauLeapSimulator, state, model)
  # unpack information
  n = simulator.next_leap_jumps
  s = simulator.next_leap_time

  # update the system time
  simulator.t = get_new_time(simulator)

  # update the system state according to the proposed leap
  # NOTE: at this point it is assumed that the leap is valid
  simulator.execute_leap!(state, n)

  # update statistics
  # TODO

  # update the simulation algorithm given the leap event
  update!(simulator.algorithm, state, model)

  # generate the next leap
  generate_next_leap!(simulator)

  return nothing
end

@inline function generate_next_leap!(simulator::TauLeapSimulator)
  # unpack
  v = simulator.next_leap_jumps

  # set a limit on how large a leap may be
  Δt_max = simulator.tfinal - simulator.t

  # ask the algorithm for the next random leap
  v, s = generate_leap!(v, simulator.algorithm, Δt_max)

  # update the stepper's fields
  simulator.next_leap_jumps = v
  simulator.next_leap_time = s

  return nothing
end

@inline get_new_time(simulator::TauLeapSimulator) = get_new_time(simulator.algorithm, simulator.t, simulator.next_leap_time)

##### helper functions #####

@inline function no_update_step!(simulator::TauLeapSimulator, state, model)
  # unpack information
  n = simulator.next_leap_jumps
  s = simulator.next_leap_time

  # update the system time
  simulator.t = get_new_time(simulator)

  # update the system state according to the proposed leap
  # NOTE: at this point it is assumed that the leap is valid
  simulator.execute_leap!(state, n)

  return nothing
end


using LightGBM
using Test
using DelimitedFiles
using StatsBase
using DataFrames,CSV,HTTP

@testset "LightGBM.jl" begin
    # Use binary example for generic tests.
    println("Start runtest.jl ENV[\"LIGHTGBM_PATH\"] is ",ENV["LIGHTGBM_PATH"])
    if isfile( joinpath(ENV["LIGHTGBM_PATH"],"examples/binary_classification/binary.test") )
        binary_test = readdlm(joinpath(ENV["LIGHTGBM_PATH"] , "examples/binary_classification/binary.test"), '\t');
        binary_train = readdlm(joinpath(ENV["LIGHTGBM_PATH"] , "examples/binary_classification/binary.train"), '\t');
    else
        res = HTTP.get("https://raw.githubusercontent.com/microsoft/LightGBM/v2.3.1/examples/binary_classification/binary.test");
        work=String(res.body);
        binary_test =convert(Matrix,CSV.read(IOBuffer(work),delim='\t',header=false));

        res = HTTP.get("https://raw.githubusercontent.com/microsoft/LightGBM/v2.3.1/examples/binary_classification/binary.train");
        work=String(res.body);
        binary_train =convert(Matrix,CSV.read(IOBuffer(work),delim='\t',header=false));
    end

    X_train = binary_train[:, 2:end]
    y_train = binary_train[:, 1]
    X_test = binary_test[:, 2:end]
    y_test = binary_test[:, 1]

    # Test wrapper functions.
    train_ds = LightGBM.LGBM_DatasetCreateFromMat(X_train, "objective=binary");
    @test LightGBM.LGBM_DatasetGetNumData(train_ds) == 7000
    @test LightGBM.LGBM_DatasetGetNumFeature(train_ds) == 28
    @test LightGBM.LGBM_DatasetSetField(train_ds, "label", y_train) == nothing
    @test LightGBM.LGBM_DatasetGetField(train_ds, "label") == y_train
    bst = LightGBM.LGBM_BoosterCreate(train_ds, "lambda_l1=10. metric=auc, verbosity=-1")

    test_ds = LightGBM.LGBM_DatasetCreateFromMat(X_test, "objective=binary", train_ds);
    @test LightGBM.LGBM_DatasetSetField(test_ds, "label", y_test) == nothing
    @test LightGBM.LGBM_BoosterAddValidData(bst, test_ds) == nothing
    @test LightGBM.LGBM_BoosterUpdateOneIter(bst) == 0
    @test LightGBM.LGBM_BoosterGetEvalCounts(bst) == 1
    @test LightGBM.LGBM_BoosterGetEvalNames(bst)[1] == "auc"

    # Test binary estimator.
    estimator = LightGBM.LGBMBinary(num_iterations = 20,
                                    learning_rate = .1,
                                    early_stopping_round = 1,
                                    feature_fraction = .8,
                                    bagging_fraction = .9,
                                    bagging_freq = 1,
                                    num_leaves = 1000,
                                    metric = ["auc", "binary_logloss"],
                                    is_training_metric = true,
                                    max_bin = 255,
                                    min_sum_hessian_in_leaf = 0.,
                                    min_data_in_leaf = 1);

    # Test fitting.
    LightGBM.fit(estimator, X_train, y_train, verbosity = 0);
    LightGBM.fit(estimator, X_train, y_train, (X_test, y_test), verbosity = 0); #boost_from_average=false

    # Test setting feature names
    jl_feature_names = ["testname_$i" for i in 1:28]
    LightGBM.LGBM_DatasetSetFeatureNames(estimator.booster.datasets[1], jl_feature_names)
    lgbm_feature_names = LightGBM.LGBM_DatasetGetFeatureNames(estimator.booster.datasets[1])
    @test jl_feature_names == lgbm_feature_names

    # Test prediction, and loading and saving models.
    test_filename = tempname()
        LightGBM.savemodel(estimator, test_filename);
    try
        pre = LightGBM.predict(estimator, X_train, verbosity = 0);
        LightGBM.loadmodel(estimator, test_filename);
        post = LightGBM.predict(estimator, X_train, verbosity = 0);
        @test isapprox(pre, post)
    finally
        rm(test_filename);
    end

    # Test cross-validation.
    splits = (collect(1:3500), collect(3501:7000));
    LightGBM.cv(estimator, X_train, y_train, splits; verbosity = 0);

    # Test exhaustive search.
    params = [Dict(:num_iterations => num_iterations,
                   :num_leaves => num_leaves) for
                   num_iterations in (1, 2),
                   num_leaves in (5, 10)];
    LightGBM.search_cv(estimator, X_train, y_train, splits, params; verbosity = 0);

    # Test regression estimator.
    if isfile( joinpath(ENV["LIGHTGBM_PATH"],"examples/regression/regression.test") )
        regression_test = readdlm(joinpath(ENV["LIGHTGBM_PATH"] , "examples/regression/regression.test"), '\t');
        regression_train = readdlm(joinpath(ENV["LIGHTGBM_PATH"] , "examples/regression/regression.train"), '\t');
    else
        res = HTTP.get("https://raw.githubusercontent.com/microsoft/LightGBM/v2.3.1/examples/regression/regression.test");
        work=String(res.body);
        regression_test =convert(Matrix,CSV.read(IOBuffer(work),delim='\t',header=false));

        res = HTTP.get("https://raw.githubusercontent.com/microsoft/LightGBM/v2.3.1/examples/regression/regression.train");
        work=String(res.body);
        regression_train =convert(Matrix,CSV.read(IOBuffer(work),delim='\t',header=false));
    end

    X_train = regression_train[:, 2:end]
    y_train = regression_train[:, 1]
    X_test = regression_test[:, 2:end]
    y_test = regression_test[:, 1]

    estimator = LightGBM.LGBMRegression(num_iterations = 100,
                                        learning_rate = .05,
                                        feature_fraction = .9,
                                        bagging_fraction = .8,
                                        bagging_freq = 5,
                                        num_leaves = 31,
                                        metric = ["l2"],
                                        metric_freq = 1,
                                        is_training_metric = true,
                                        max_bin = 255,
                                        min_sum_hessian_in_leaf = 5.,
                                        min_data_in_leaf = 100,
                                        max_depth = -1);

    scores = LightGBM.fit(estimator, X_train, y_train, (X_test, y_test), verbosity = 0);
    @test scores["test_1"]["l2"][end] < .5

    # Test multiclass estimator.
    if isfile( joinpath(ENV["LIGHTGBM_PATH"],"examples/multiclass_classification/multiclass.test") )
        multiclass_test = readdlm(joinpath(ENV["LIGHTGBM_PATH"] , "examples/multiclass_classification/multiclass.test"), '\t');
        multiclass_train = readdlm(joinpath(ENV["LIGHTGBM_PATH"] , "examples/multiclass_classification/multiclass.train"), '\t');
    else
        res = HTTP.get("https://raw.githubusercontent.com/microsoft/LightGBM/v2.3.1/examples/multiclass_classification/multiclass.test");
        work=String(res.body);
        multiclass_test =convert(Matrix,CSV.read(IOBuffer(work),delim='\t',header=false));

        res = HTTP.get("https://raw.githubusercontent.com/microsoft/LightGBM/v2.3.1/examples/multiclass_classification/multiclass.train");
        work=String(res.body);
        multiclass_train =convert(Matrix,CSV.read(IOBuffer(work),delim='\t',header=false));
    end

    X_train = Matrix(multiclass_train[:, 2:end])
    y_train = Array(multiclass_train[:, 1])
    X_test = Matrix(multiclass_test[:, 2:end])
    y_test = Array(multiclass_test[:, 1])

    estimator = LightGBM.LGBMMulticlass(num_iterations = 100,
                                        learning_rate = .05,
                                        feature_fraction = .9,
                                        bagging_fraction = .8,
                                        bagging_freq = 5,
                                        num_leaves = 31,
                                        metric = ["multi_logloss"],
                                        metric_freq = 1,
                                        is_training_metric = true,
                                        max_bin = 255,
                                        min_sum_hessian_in_leaf = 5.,
                                        min_data_in_leaf = 100,
                                        num_class = 5,
                                        early_stopping_round = 10);

    scores = LightGBM.fit(estimator, X_train, y_train, (X_test, y_test), verbosity = 0);
    @test scores["test_1"]["multi_logloss"][end] < 1.4

    # Test row major multiclass
    X_train = Matrix(multiclass_train[:, 2:end]')
    X_test = Matrix(multiclass_test[:, 2:end]')

    estimator = LightGBM.LGBMMulticlass(num_iterations = 100,
                                        learning_rate = .05,
                                        feature_fraction = .9,
                                        bagging_fraction = .8,
                                        bagging_freq = 5,
                                        num_leaves = 31,
                                        metric = ["multi_logloss"],
                                        metric_freq = 1,
                                        is_training_metric = true,
                                        max_bin = 255,
                                        min_sum_hessian_in_leaf = 5.,
                                        min_data_in_leaf = 100,
                                        num_class = 5,
                                        early_stopping_round = 10);

    scores = LightGBM.fit(estimator, X_train, y_train, (X_test, y_test), verbosity = 0,
                          is_row_major = true);
    @test scores["test_1"]["multi_logloss"][end] < 1.4

    include("weightsTest.jl")
    include("initScoreTest.jl")

end


module SpikeNetOpt

using PyCall
using OrderedCollections
using LinearAlgebra
using SpikeSynchrony
using SpikingNeuralNetworks
SNN = SpikingNeuralNetworks
using Evolutionary
using DataStructures
using UnicodePlots
using Statistics
using JLD
using Plots
using Distributions
using LightGraphs
using Metaheuristics
#using SignalAnalysis
# using Distributed
include("current_search.jl")
include("utils.jl")
include("sdo_network.jl")

end