text:
using ITensors
using UnicodePlots

include("ising_model.jl")
include("orthogonalize_eigenvectors.jl")

#
# Compute excitations of the Ising model with DMRG
#

# Define the sites of the Hilbert space
n = 10
i = [Index(2, "S=1/2") for j in 1:n];

# Define the Hamiltonian as an MPO
h = 0.5 # field
H = MPO(ising(n; h), i);

# Define the initial state
psi_init = randomMPS(i; linkdims=2)

# Use DMRG as a reference
println("\nComputing ground state with DMRG")
E0, psi0 = dmrg(H, psi_init; weight=4, nsweeps=10, cutoff=1e-5);

println("\nComputing first excited state with DMRG")
E1, psi1 = dmrg(H, [psi0], psi_init; weight=4, nsweeps=10, cutoff=1e-5, maxdim=10);

println("\nComputing second excited state with DMRG")
E2, psi2 = dmrg(H, [psi0, psi1], psi_init; weight=4, nsweeps=10, cutoff=1e-5, maxdim=10);

# Collect the ground state and excited states in a vector
psis = [psi0, psi1, psi2]
nstates = length(psis)

# How close are our eigenvectors to eigenstates?
# We can use the energy variance:
#
# <psi|H^2|psi> - <psi|H|psi>^2
#
# to find out.
#
# Hints:
#
# 1. You can use `inner(psi', H, psi)` to compute `<psi|H|psi>`.
# 2. You can use `apply(H, psi)` to compute `H|psi>`.
for psi in psis
  Hpsi = apply(H, psi)
  @show inner(Hpsi, Hpsi) - inner(psi', H, psi)^2
end

# Which states did we find? The `expect` function
# computes the local expectation value of an operator
# on each site

# Use the UnicodePlots function `lineplot` to plot
# <Sz> and <Sx> for our eigenvectors
for i in 1:nstates
  plt = lineplot(expect(psis[i], "Sz"); title="Ising model state $i from DMRG", name="Sz", xlim=[1,10], ylim=[-0.5,0.5])
  lineplot!(plt, expect(psis[i], "Sx"); name="Sx")
  display(plt)
end

# Construct an effective Hamiltonian `Heff` for the states we found
Heff = [inner(psis[i]', H, psis[j]) for i in 1:nstates, j in 1:nstates]

# Construct the overlap matrix `N` for the states we found
N = [inner(psis[i], psis[j]) for i in 1:nstates, j in 1:nstates]

println("\nHeff = <psi_i|H|psi_j> from DMRG")
display(Heff)

println("\nN = <psi_i|psi_j> from DMRG")
display(N)

#
# Orthogonalize the eigenvectors by solving the generalized eigenvector
# equations:
#
# <psi_i|H|psi_j><psi_j|v> = lambda_i * <psi_j|v>
#
# See `orthogonalize_eigenvectors.jl` for details.
#
psis = orthogonalize_eigenvectors(H, psis)

# Replot <Sz> and <Sx> for our eigenvectors
for i in 1:nstates
  plt = lineplot(expect(psis[i], "Sz"); title="Ising model state $i from DMRG after orthogonalization", name="Sz", xlim=[1,10], ylim=[-0.5,0.5])
  lineplot!(plt, expect(psis[i], "Sx"); name="Sx")
  display(plt)
end

# Construct an effective Hamiltonian `Heff` for the states we found
Heff = [inner(psis[i]', H, psis[j]) for i in 1:nstates, j in 1:nstates]

# Construct the overlap matrix `N` for the states we found
N = [inner(psis[i], psis[j]) for i in 1:nstates, j in 1:nstates]

println("\nHeff = <psi_i|H|psi_j> from DMRG")
display(Heff)

println("\nN = <psi_i|psi_j> from DMRG")
display(N)


text:
#!/usr/bin/env julia
println("There is more than one way to say hello:")
# This is an array consisting of three strings
helloArray = ["Hello", "G’day", "Shalom", "Aloha"]
for i in 1:4
  println("\t", helloArray[i], ", World!")
end
println("\nThese squares are just perfect:")
# This construct is called a ‘comprehension’ (or ’list comprehension’)
squares = [i^2 for i in 0:10]
# You can loop on elements of arrays without having to use indexing
for s in squares
  print(" ", s)
end
println()
# The last line of every code snippet is also evaluated as output (in addition to
# any figures and printing output generated previously).
println(sqrt.(squares))

ext: jl


text:
abstract OSMElement

# Nodes
# +-----+-------------------+---------------+
# | id  | lonlat            | tags...       |
# +-----+-------------------+---------------+
# | Int | (Float64,Float64) | UTF8String... |
# | .   | .                 | .             |
# | .   | .                 | .             |
# | .   | .                 | .             |
# +-----+-------------------+---------------+

type Node <: OSMElement
    id::Int
    lonlat::Tuple{Float64,Float64}
    tags::Dict{UTF8String,UTF8String}
    Node(id::Int, lonlat::Tuple{Float64,Float64}) = new(id, lonlat)
end

function tags(n::Node) # lazily create tags
    isdefined(n, :tags) || (n.tags = Dict{UTF8String,UTF8String}())
    n.tags
end

# Node(id::Int, lonlat::Tuple{Float64,Float64}) =
#     Node(id, lonlat, Dict{UTF8String,UTF8String}())

# Ways
# +-----+-------------------+---------------+
# | id  | nodes (osm id)    | tags...       |
# +-----+-------------------+---------------+
# | Int | Vector{Int}       | UTF8String... |
# | .   | .                 | .             |
# | .   | .                 | .             |
# | .   | .                 | .             |
# +-----+-------------------+---------------+

type Way <: OSMElement
    id::Int
    nodes::Vector{Int}
    tags::Dict{UTF8String,UTF8String}
    Way(id::Int) = new(id, Vector{Int}(), Dict{UTF8String,UTF8String}())
end
tags(w::Way) = w.tags

# Way(id::Int) = Way(id, Vector{Int}(), Dict{UTF8String,UTF8String}())

# Relations
# +-----+-----------------------+---------------+
# | id  | members               | tags...       |
# +-----+-----------------------+---------------+
# | Int | Vector{Dict{Str,Str}} | UTF8String... |
# | .   | .                     | .             |
# | .   | .                     | .             |
# | .   | .                     | .             |
# +-----+-----------------------+---------------+

type Relation <: OSMElement
    id::Int
    members::Vector{Dict{UTF8String,UTF8String}}
    tags::Dict{UTF8String,UTF8String}
    Relation(id::Int) = new(id, Vector{Dict{UTF8String,UTF8String}}(),
                            Dict{UTF8String,UTF8String}())
end
tags(r::Relation) = r.tags

# Relation(id::Int) = Relation(id, Vector{Dict{UTF8String,UTF8String}}(),
#                              Dict{UTF8String,UTF8String}())


text:
function agatston_vol(image, spacing, mask, min_vol=nothing, max_vol=nothing)
    binary_mask = mask
    voxel_volume = spacing[1] * spacing[2] * spacing[3]

    agatston_score = 0
    calcium_volume = 0

    # Find individual lesions (in 3D) so that we can discard too small or too large lesions
    lesion_map = Images.label_components(binary_mask)
    n_lesions = length(unique(lesion_map))

    for lesion in 1:(n_lesions - 1)
        lesion_mask = map(x -> x == lesion, lesion_map)

        # Ignore too small or too large lesions
        lesion_volume = count(lesion_mask) * voxel_volume
        if ((min_vol != nothing) && (lesion_volume < min_vol))
            continue
        end
        if ((max_vol != nothing) && (lesion_volume > max_vol))
            continue
        end

        calcium_volume += lesion_volume

        # Calculate Agatston score for this lesion
        slices = sort(unique(lesion_mask)) .+ 1
        for z in 1:slices[end]
            fragment_mask = lesion_mask[z, :, :]
            n_pixels = count(fragment_mask)
            maximum_intensity = maximum(image[z, :, :][fragment_mask])
            if maximum_intensity < 200
                coefficient = 1
            elseif maximum_intensity < 300
                coefficient = 2
            elseif maximum_intensity < 400
                coefficient = 3
            else
                coefficient = 4
            end
            agatston_score += coefficient * n_pixels
        end
    end
    agatston_score *= spacing[1] / 3.0 * spacing[2] * spacing[3]
    return agatston_score, calcium_volume
end


text:
using SafeTestsets

@safetestset "Basic Avus Tests" begin include("avus_tests.jl") end
@safetestset "Shortest Paths Tests" begin include("shortestpaths_test.jl") end
@safetestset "Relationship Tests" begin include("relationship_test.jl") end
@safetestset "Avus Linear Algebra Tests" begin include("linalg_test.jl") end


text:
# TODO: make AbstractSymbolicIntervalBounds a subtype of LazySet?
struct SymbolicIntervalFVHeur{F<:AbstractPolytope, N<:Number} <: LazySet{N}
    Low::Matrix{N}
    Up::Matrix{N}
    domain::F
    lbs::Vector{Vector{N}}
    ubs::Vector{Vector{N}}
    var_los::Matrix{N}
    var_his::Matrix{N}
    max_vars::Int
    importance::Vector{N}
    n_layers::Int
end

domain(s::SymbolicIntervalFVHeur) = s.domain
# TODO: should we change this definition? It's not really the radius of the
# symbolic interval, but we need the radius of the domain during splitting
# ALSO: Why do we need LazySets.radius(domain(s)) for NeuralPriorityOptimizer to
# work, why is it not enough to just write radius(domain(s)) ?
LazySets.radius(s::SymbolicIntervalFVHeur) = LazySets.radius(domain(s))
LazySets.dim(s::SymbolicIntervalFVHeur) = size(s.Up, 1)

get_n_sym(s::SymbolicIntervalFVHeur) = size(s.Low, 2) - 1  # number of current symbolic vars
get_n_in(s::SymbolicIntervalFVHeur) = dim(domain(s))  # number of input variables
get_n_vars(s::SymbolicIntervalFVHeur) = get_n_sym(s) - get_n_in(s) # current number of symboli fresh vars

# calculate bounds for a matrix of **equations**, i.e. the lower **or** upper bound of a symbolic interval
function bounds(eq, input_set::H) where H <: Hyperrectangle
    W = eq[:, 1:dim(input_set)]
    b = eq[:, end]

    W⁺ = max.(W, 0)
    W⁻ = min.(W, 0)

    lb = W⁺ * low(input_set) .+ W⁻ * high(input_set) .+ b
    ub = W⁺ * high(input_set) .+ W⁻ * low(input_set) .+ b

    return lb, ub
end

function lower_bounds(eq, input_set::H, lbs, ubs) where H <: Hyperrectangle
    low, up = bounds(eq, input_set)

    low = max.(low, lbs)
    low = min.(low, ubs)

    return low
end

function upper_bounds(eq, input_set::H, lbs, ubs) where H <: Hyperrectangle
    low, up = bounds(eq, input_set)

    up = max.(up, lbs)
    up = min.(up, ubs)

    return up
end

"""
Initialize SymbolicIntervalFVHeur from neural network and input set.
"""
function init_symbolic_interval_fvheur(net::NetworkNegPosIdx, input_set::AbstractPolytope{N}; max_vars=10) where N <: Number
    n = dim(input_set)
    Low = [I zeros(N, n)]
    Up = [I zeros(N, n)]

    lbs = [fill(typemin(N), n_nodes(l)) for l in net.layers]
    ubs = [fill(typemax(N), n_nodes(l)) for l in net.layers]

    var_los = zeros(N, max_vars, dim(input_set) + 1)
    var_his = zeros(N, max_vars, dim(input_set) + 1)

    importance = zeros(N, dim(input_set))

    n_layers = length(net.layers)

    return SymbolicIntervalFVHeur(Low, Up, input_set, lbs, ubs, var_los, var_his,
                                    max_vars, importance, n_layers)
end

"""
Initialize SymbolicIntervalFVHeur from previous SymbolicIntervalFVHeur and new input set.
"""
function init_symbolic_interval_fvheur(s::SymbolicIntervalFVHeur, input_set::AbstractPolytope{N}; max_vars=10) where N <: Number
    n = dim(input_set)
    Low = [I zeros(N, n)]
    Up = [I zeros(N, n)]

    lbs = [copy(ll) for ll in s.lbs]
    ubs = [copy(uu) for uu in s.ubs]

    var_los = zeros(N, max_vars, dim(input_set) + 1)
    var_his = zeros(N, max_vars, dim(input_set) + 1)

    importance = zeros(N, dim(input_set))

    return SymbolicIntervalFVHeur(Low, Up, input_set, lbs, ubs, var_los, var_his,
                                    max_vars, importance, s.n_layers)
end

"""
Initialize SymbolicIntervalFVHeur by changing symbolic lower and upper bound of
existing SymbolicIntervalFVHeur.
"""
function init_symbolic_interval_fvheur(s::SymbolicIntervalFVHeur, Low::Matrix{N}, Up::Matrix{N}) where N <: Number
    return SymbolicIntervalFVHeur(Low, Up, domain(s), s.lbs, s.ubs,
                                    s.var_los, s.var_his, s.max_vars, s.importance, s.n_layers)
end

"""
Directly substitutes all variables in SymbolicIntervalFVHeur s
"""
function substitute_variables(s::SymbolicIntervalFVHeur)
    n_sym = get_n_sym(s)
    n_in = get_n_in(s)
    current_n_vars = get_n_vars(s)
    return substitute_variables(s.Low, s.Up, s.var_los, s.var_his, n_in, current_n_vars)
end

function maximizer(s::SymbolicIntervalFVHeur)
    subs_sym_lo, subs_sym_hi = substitute_variables(s)
    return maximizer(subs_sym_hi, low(domain(s)), high(domain(s)))
end

function minimizer(s::SymbolicIntervalFVHeur)
    subs_sym_lo, subs_sym_hi = substitute_variables(s)
    return minimizer(subs_sym_lo, low(domain(s)), high(domain(s)))
end

"""
Returns number of crossing ReLUs in the network as well as a list
of crossing ReLUs per layer.

lbs, ubs are lower and upper bounds of the intermediate neurons
(no input bounds or output bounds)
"""
function get_num_crossing(lbs, ubs)
    n_layers = length(lbs)
    n_crossings = zeros(Int, n_layers)
    for (i, (l, u)) in enumerate(zip(lbs, ubs))
        n_crossings[i] = sum((l .< 0) .& (u .> 0))
    end

    return sum(n_crossings), n_crossings
end

get_num_crossing(s::SymbolicIntervalFVHeur) = get_num_crossing(s.lbs, s.ubs)

###############################################################################
#### Splitting
###############################################################################

function split_symbolic_interval_fv_heur(s::SymbolicIntervalFVHeur{<:Hyperrectangle}, index::Int)
    domain1, domain2 = split(domain(s), index)

    current_n_vars = get_n_vars(s)
    # can't have more vars than parent node?
    s1 = init_symbolic_interval_fvheur(s, domain1; max_vars=current_n_vars)
    s2 = init_symbolic_interval_fvheur(s, domain2; max_vars=current_n_vars)

    return [s1, s2]
end

"""
Splitting based on intermediate importance scores (i.e. coefficients in all crossing ReLUs)
"""
function split_important_interval(s::SymbolicIntervalFVHeur{<:Hyperrectangle})
    radius = high(domain(s)) - low(domain(s))
    # if there are no more crossing ReLUs, importance will be zero vector -> if we
    # multiply it with radius, all inputs are equally important, and argmax always
    # returns the first index -> infinitely loop splitting
    most_important_dim = sum(s.importance) == 0. ? argmax(radius) : argmax(s.importance .* radius)
    return split_symbolic_interval_fv_heur(s, most_important_dim)
end

"""
Splitting based on intermediate importance scores (i.e. coefficients in all crossing ReLUs) with number of fresh variables to
be introduced following NeuroDiff paper
"""
function split_important_interval_neurodiff(s::SymbolicIntervalFVHeur{<:Hyperrectangle})
    radius = high(domain(s)) - low(domain(s))
    # if there are no more crossing ReLUs, importance will be zero vector -> if we
    # multiply it with radius, all inputs are equally important, and argmax always
    # returns the first index -> infinitely loop splitting
    most_important_dim = sum(s.importance) == 0. ? argmax(radius) : argmax(s.importance .* radius)

    domain1, domain2 = split(domain(s), most_important_dim)

    num_crossing, crossings = get_num_crossing(s)
    # don't count last layer as it has Id activation function
    n_vars = ceil(Integer, sum([ncr/i for (i, ncr) in enumerate(crossings[1:end-1])]))

    s1 = init_symbolic_interval_fvheur(s, domain1; max_vars=n_vars)
    s2 = init_symbolic_interval_fvheur(s, domain2; max_vars=n_vars)

    return [s1, s2]
end

"""
Splitting based on intermediate importance scores (i.e. coefficients in all crossing ReLUs)
but taking output layer into consideration.
"""
function split_important_interval_output(s::SymbolicIntervalFVHeur{<:Hyperrectangle})
    radius = high(domain(s)) - low(domain(s))

    subs_Low_Low, subs_Up_Up = substitute_variables(s.Low, s.Up, s.var_los, s.var_his, get_n_in(s), get_n_vars(s))
    subs_Up_Low, subs_Low_Up = substitute_variables(s.Up, s.Low, s.var_los, s.var_his, get_n_in(s), get_n_vars(s))
    output_importance = sum(abs.(subs_Low_Low), dims=1) .+ sum(abs.(subs_Up_Up), dims=1)

    num_crossing, crossings = get_num_crossing(s)
    in_importance = s.importance ./ sum(s.importance)
    out_importance = output_importance[1:end-1] ./ sum(output_importance[1:end-1])
    importance = num_crossing .* in_importance .+ out_importance

    # if there are no more crossing ReLUs, importance will be zero vector -> if we
    # multiply it with radius, all inputs are equally important, and argmax always
    # returns the first index -> infinitely loop splitting
    most_important_dim = sum(importance) == 0. ? argmax(radius) : argmax(importance .* radius)
    return split_symbolic_interval_fv_heur(s, most_important_dim)
end

################################################################################
#### Visualization
################################################################################
# LazySets can plot the reachable sets, if LazySets.σ(a, sym) (returning the
# support vector of the set sym in direction a) is defined.
# While calculating the maximum in direction a is relatively easy, we need to
# encode the problem as an LP to obtain a point inside the set, where that
# maximum is obtained

function LazySets.constraints_list(sym::SymbolicIntervalFVHeur{<:Hyperrectangle})
    n_sym = size(sym.Low, 2) - 1
    n_in = dim(domain(sym))
    current_n_vars = n_sym - n_in

    hList = HalfSpace[]

    hdim = n_sym + dim(sym)  # need variables for all vars in the symbolic bounds as well as for the current neurons

    # constraints for Box inputs
    for i in 1:n_in
        x_current = zeros(hdim)
        x_current[i] = 1.
        push!(hList, HalfSpace(-x_current, -low(sym.domain, i)))
        push!(hList, HalfSpace(x_current, high(sym.domain, i)))
    end

    # constraints for symbolic upper and lower bounds of current neurons (with fresh vars)
    for i in 1:dim(sym)
        # Lower bounds to halfspaces
        x_current = zeros(dim(sym))
        x_current[i] = 1.
        push!(hList, HalfSpace([sym.Low[i, 1:end-1]; -x_current], -sym.Low[i, end]))

        # Upper bounds to halfspaces
        push!(hList, HalfSpace([-sym.Up[i, 1:end-1]; x_current], sym.Up[i, end]))
    end

    # constraints for fresh vars
    for i in 1:current_n_vars
        x_current = zeros(current_n_vars + dim(sym))  # first n_in positions will be held by input variables,
                                                      # then fresh variables, then current neurons.
                                                      # input variables are already in sym.var_los/var_his
        x_current[i] = 1.

        push!(hList, HalfSpace([sym.var_los[i, 1:end-1]; -x_current], -sym.var_los[i, end]))
        push!(hList, HalfSpace([-sym.var_his[i, 1:end-1]; x_current], sym.var_his[i, end]))
    end

    return hList
end

"""
Calculates support vector of symbolic interval with fresh variables by converting it to an HPolytope and solving a an LP.

Only calculation of support vector is expensive, the maximum can be cheaply calculated (see LazySets.ρ(a, sym) implementation below)
"""
function LazySets.σ(a::AbstractVector, sym::SymbolicIntervalFVHeur{<:Hyperrectangle})
    n_sym = size(sym.Low, 2) - 1
    hlist = constraints_list(sym)

    HP = HPolytope(hList)
    # since we aren't interested in values of other variables than the current neurons their influence is zero
    â = [zeros(n_sym); a]
    x̂ = σ(â, HP)

    # variables of current neurons are at the end of the representation
    return x̂[n_sym+1:end]
end

function LazySets.ρ(a::AbstractVector, s::SymbolicIntervalFVHeur{<:Hyperrectangle})
    W⁺ = max.(0, a)'
    W⁻ = min.(0, a)'
    Low, Up = interval_map(W⁻, W⁺, s.Low, s.Up)

    n_sym = size(s.Low, 2) - 1
    n_in = dim(domain(s))
    current_n_vars = n_sym - n_in

    subs_Low, subs_Up = substitute_variables(Low, Up, s.var_los, s.var_his, n_in, current_n_vars)

    up_low, up_up = bounds(subs_Up, domain(s))

    # return a single number, not a vector!
    # up_up should always have a single element anyways
    return up_up[1]
end


text:
# 2-arg show, mainly for repr():
function Base.show(io::IO, A::NamedDimsArray{L,T,N}) where {L,T,N}
    if get(io, :typeinfo, Any) <: NamedDimsArray
        show(io, parent(A))
    else
        print(io, "NamedDimsArray(")
        show(io, parent(A))
        print(io, ", ", N == 1 ? QuoteNode(L[1]) : L, ")")
    end
end

# This is called by summary(), for main REPL printing:
function Base.showarg(io::IO, A::NamedDimsArray{L,T,N}, outer) where {L,T,N}
    print(io, "NamedDimsArray(")
    Base.showarg(io, parent(A), false)
    print(io, ", ", ColourString(N == 1 ? QuoteNode(L[1]) : L), ")")
    return nothing
end

function Base.print_matrix(io::IO, A::NamedDimsArray)
    s1 = ColourString("↓ ", dimnames(A, 1), "  ")
    if ndims(A) == 2
        println(io, " "^Base.Unicode.textwidth(s1), ColourString("→ ", dimnames(A, 2)))
    end
    ioc = IOContext(io, :displaysize => displaysize(io) .- (1, 0))
    Base.print_matrix(ioc, parent(A), s1)
    return nothing
end

# This exists because including ascii colour codes in the string `s1` passed
# to `Base.print_matrix` will cause it to become confused about the spacing.
struct ColourString <: AbstractString
    string
    ColourString(xs...) = new(string(xs...))
end
Base.alignment(io::IO, x::ColourString) = alignment(io, x.string)
Base.length(x::ColourString) = length(x.string)
Base.ncodeunits(x::ColourString) = ncodeunits(x.string)
Base.textwidth(x::ColourString) = textwidth(x.string)
Base.print(io::IO, x::ColourString) = printstyled(io, x.string; color=:magenta)

if VERSION > v"1.6.0-DEV.1561" # 809f27c53df7a54388a687a847e9494e0d29bd4f
    function Base._show_nd_label(io::IO, A::NamedDimsArray, idxs)
        print(io, "[:, :, ")
        for i in 1:length(idxs)
            print(io, ColourString(dimnames(A, i + 2), "=", idxs[i]))
            i == length(idxs) ? println(io, "] =") : print(io, ", ")
        end
    end
end


text:
export string_to_int, int_to_string

# This file contains some helper functions to map between interaction sequences with String and Int values.
# This is helpful for inference, where we will typically map to integers to run posterior samplers and then map back after.

"""
    string_to_int(::InteractionSequence{String}, ::Dict{String,Int})

    Takes `S::InteractionSequence{String}` and outputs a value of type `InteractionSequence{Int}` by applying the map encoded via the dictionary `mapper::Dict{String,Int}`.
"""
function string_to_int(
    S::InteractionSequence{String},
    mapper::Dict{String,Int}
    )
    out = similar.(S, Int)
    for i in eachindex(S)
        tmp_in = S[i]
        tmp_out = out[i]
        for j in eachindex(tmp_out)
            tmp_out[j] = mapper[tmp_in[j]]
        end
    end
    return out
end

function string_to_int(
    data::InteractionSequenceSample{String},
    mapper::Dict{String,Int}
    )
    return map(x -> string_to_int(x, mapper), data)
end

function string_to_int(
    data::InteractionSequence{String}
    )

    mapper = Dict{String,Int}()
    i = 1
    for v in vcat(data...)
        if v ∈ keys(mapper)
            continue
        else
            mapper[v] = i
            i +=1
        end
    end
    mapper_inv = Dict(v => k for (k,v) in mapper)
    return string_to_int(data, mapper), mapper_inv
end

function string_to_int(
    data::InteractionSequenceSample{String}
    )

    mapper = Dict{String,Int}()
    i = 1
    for v in vcat([vcat(S...) for S in data]...)
        if v ∈ keys(mapper)
            continue
        else
            mapper[v] = i
            i +=1
        end
    end
    iter = zip(keys(mapper), values(mapper))
    mapper_inv = Dict(v => k for (k,v) in iter)
    return string_to_int(data, mapper), mapper_inv
end

function int_to_string(
    S::InteractionSequence{Int},
    mapper::Dict{Int,String}
)

    out = similar.(S, String)
    for i in eachindex(S)
        tmp_in = S[i]
        tmp_out = out[i]
        for j in eachindex(tmp_out)
            tmp_out[j] = mapper[tmp_in[j]]
        end
    end
    return out
end

function int_to_string(
    data::InteractionSequenceSample{Int},
    mapper::Dict{Int,String}
    )
    return map(x -> int_to_string(x, mapper), data)
end

function int_to_string(
    data::InteractionSequenceSample{Int}
    )
    mapper = Dict{Int,String}()
    for v in vcat([vcat(S...) for S in data]...)
        if v ∈ keys(mapper)
            continue
        else
            mapper[v] = string(v)
        end
    end
    iter = zip(keys(mapper), values(mapper))
    mapper_inv = Dict(v => k for (k,v) in iter)
    return int_to_string(data, mapper), mapper_inv
end

function string_to_int(x::Vector{String})
    out = Int[]
    count = 1
    mapper = Dict{String,Int}()
    for v in x
        if v ∉ keys(mapper)
            mapper[v] = count
            count += 1
        end
        push!(out, mapper[v])
    end
    mapper_inv = Dict(v=>k for (k,v) in mapper)
    return out, mapper_inv
end

max_stars_repo_path: src/Data Processing/string_to_int.jl

max_issues_repo_path: src/Data Processing/string_to_int.jl

max_forks_repo_path: src/Data Processing/string_to_int.jl


text:
# ---
# title: 1411. Number of Ways to Paint N × 3 Grid
# id: problem1411
# author: Tian Jun
# date: 2020-10-31
# difficulty: Hard
# categories: Dynamic Programming
# link: <https://leetcode.com/problems/number-of-ways-to-paint-n-3-grid/description/>
# hidden: true
# ---
#
# You have a `grid` of size `n x 3` and you want to paint each cell of the grid
# with exactly one of the three colours: **Red** , **Yellow** or **Green**
# while making sure that no two adjacent cells have the same colour (i.e no two
# cells that share vertical or horizontal sides have the same colour).
#
# You are given `n` the number of rows of the grid.
#
# Return _the number of ways_ you can paint this `grid`. As the answer may grow
# large, the answer **must be** computed modulo `10^9 + 7`.
#
#
#
# **Example 1:**
#
#
#
#     Input: n = 1
#     Output: 12
#     Explanation: There are 12 possible way to paint the grid as shown:
#     ![](https://assets.leetcode.com/uploads/2020/03/26/e1.png)
#
#
# **Example 2:**
#
#
#
#     Input: n = 2
#     Output: 54
#
#
# **Example 3:**
#
#
#
#     Input: n = 3
#     Output: 246
#
#
# **Example 4:**
#
#
#
#     Input: n = 7
#     Output: 106494
#
#
# **Example 5:**
#
#
#
#     Input: n = 5000
#     Output: 30228214
#
#
#
#
# **Constraints:**
#
#   * `n == grid.length`
#   * `grid[i].length == 3`
#   * `1 <= n <= 5000`
#
#
## @lc code=start
using LeetCode

## add your code here:
## @lc code=end


text:
# Pkg.add("DiffEqMonteCarlo")
# Pkg.add("DiffEqParamEstim")
# Pkg.add("CSV")
# Pkg.add("Sundials")
# Pkg.add("IterableTables")
# Pkg.add("DifferentialEquations")

using DiffEqBase, OrdinaryDiffEq, ParameterizedFunctions,
    DiffEqParamEstim, CSV, StatsFuns, DataFrames, LSODA,
    IterableTables, Distributions, DataStructures

# not used
# using  DiffEqMonteCarlo, Sundials, DifferentialEquations

#using PrintLog
#@printlog "DREAM6_identification.log"

# Input data
exp_data = CSV.read("./test/dream6_m1/exp_data.csv", types=Dict("t"=>Float64), nullable=false)
u_data = CSV.read("./test/dream6_m1/u_data.csv", types=Dict("values"=>Float64), nullable=false)
p_data = CSV.read("./test/dream6_m1/p_data.csv", types=Dict("values"=>Float64), nullable=false)

# Variables and parameters names and data
u_names = OrderedDict(Symbol(u_data[:names][i]) => i for i in eachindex(u_data[:names]))
p_names = OrderedDict(Symbol(p_data[:names][i]) => i for i in eachindex(p_data[:names]))
u0 = u_data[:values]
p0 = p_data[:values]

# Constants
const pp1_mrna_degradation_rate = 1.0
const pp2_mrna_degradation_rate = 1.0
const pp3_mrna_degradation_rate = 1.0
const pp4_mrna_degradation_rate = 1.0
const pp5_mrna_degradation_rate = 1.0
const pp6_mrna_degradation_rate = 1.0

# Power function
pow(x, y) =  x >= 0. ? x^y : 0.0

# DREAM6 Model1
Model1 = @ode_def_bare DREAM6 begin
    rs1 = 1. / (1. + pow(p6 / v5_Kd, v5_h))
    rs2 = 1. / (1. + pow(p5 / v8_Kd, v8_h))
    rs3 = 1. / (1. + pow(p4 / v6_Kd, v6_h))
    rs4 = 1. / (1. + pow(p2 / v4_Kd, v4_h))
    rs5 = 1. / (1. + pow(p4 / v7_Kd, v7_h))

    as1 = pow(p1 / v2_Kd, v2_h) / (1. + pow(p1 / v2_Kd, v2_h))
    as2 = pow(p1 / v1_Kd, v1_h) / (1. + pow(p1 / v1_Kd, v1_h))
    as3 = pow(p1 / v3_Kd, v3_h) / (1. + pow(p1 / v3_Kd, v3_h))

    cod1 = pro1_strength
    cod2 = pro2_strength * as1 * rs1
    cod3 = pro3_strength * as3 * rs4
    cod4 = pro4_strength * as2 * rs2
    cod5 = pro5_strength * rs3
    cod6 = pro6_strength * rs5

    # V = zeros(24)
    V_1 = cod1
    V_2 = pp1_mrna_degradation_rate * pp1_mrna
    V_3 = rbs1_strength * pp1_mrna
    V_4 = p_degradation_rate * p1
    V_5 = cod5
    V_6 = pp5_mrna_degradation_rate * pp5_mrna
    V_7 = rbs5_strength * pp5_mrna
    V_8 = p_degradation_rate * p5
    V_9 = cod2
    V_10 = pp2_mrna_degradation_rate * pp2_mrna
    V_11 = rbs2_strength * pp2_mrna
    V_12 = p_degradation_rate * p2
    V_13 = cod6
    V_14 = pp6_mrna_degradation_rate * pp6_mrna
    V_15 = rbs6_strength * pp6_mrna
    V_16 = p_degradation_rate * p6
    V_17 = cod3
    V_18 = pp3_mrna_degradation_rate * pp3_mrna
    V_19 = rbs3_strength * pp3_mrna
    V_20 = p_degradation_rate * p3
    V_21 = cod4
    V_22 = pp4_mrna_degradation_rate * pp4_mrna
    V_23 = rbs4_strength * pp4_mrna
    V_24 = p_degradation_rate * p4

    # for pp1_mrna
    dpp1_mrna = V_1 - V_2
    # for p1
    dp1 = V_3 - V_4
    # for pp5_mrna
    dpp5_mrna = V_5 - V_6
    # for p5
    dp5 = V_7 - V_8
    # for pp2_mrna
    dpp2_mrna = V_9 - V_10
    # for p2
    dp2 = V_11 - V_12
    # for pp6_mrna
    dpp6_mrna = V_13 - V_14
    # for p6
    dp6 = V_15 - V_16
    # for pp3_mrna
    dpp3_mrna = V_17 - V_18
    # for p3
    dp3 = V_19 - V_20
    # for pp4_mrna
    dpp4_mrna = V_21 - V_22
    # for p4
    dp4 = V_23 - V_24
end p_degradation_rate pro1_strength pro2_strength pro3_strength pro4_strength pro5_strength pro6_strength rbs1_strength rbs2_strength rbs3_strength rbs4_strength rbs5_strength rbs6_strength v1_Kd v1_h v2_Kd v2_h v3_Kd v3_h v4_Kd v4_h v5_Kd v5_h v6_Kd v6_h v7_Kd v7_h v8_Kd v8_h

#prob = ODEProblem(Model1, u0, (0., 30.), params)

#sol = solve(ODEProblem(Model1, u0, (0., 20.), params), Tsit5(), saveat=exp_data[:t])

# Loss function
function ofv_loss(sol)
    loss = 0.0

    if sol.retcode != :Success #any((s.retcode != :Success for s in sol))
        loss = Inf
        throw(ForcedStop())
        println(sol.retcode, sol)
    else
        # println(sol.prob.p)
        for u in names(exp_data)[2:end]
            loss += sum(((sol(exp_data[:t], idxs = u_names[u]).u - exp_data[u]).^2) ./ ((0.1)^2+(0.2*sol(exp_data[:t],idxs = u_names[u]).u).^2))
        end
    end
    return loss
end

# Obj function to optimize
obj = build_loss_objective(
    ODEProblem(Model1, u0, (0., 20.), p0),
    lsoda(), # Rosenbrock23() Tsit5()
    ofv_loss,
    verbose = false,
    maxiters = 1e6,
    saveat = exp_data[:t]
)

#=
using PrintLog
@printlog "DREAM6_param_SBPLX.log"

for scale in (direct_scale, log_scale, mixed_scale), bounds in (bounds_params_open, bounds_params_closed, bounds_params_half_open), alg in (:LN_SBPLX, :LN_COBYLA)

    scale == direct_scale && println("direct_scale")
    scale == log_scale && println("log_scale")
    scale == mixed_scale && println("mixed_scale")

    bounds == bounds_params_open && println("bounds_params_open")
    bounds == bounds_params_closed && println("bounds_params_closed")
    bounds == bounds_params_half_open && println("bounds_params_half_open")
    println(alg)

    params_intervals(p0, 1, obj0, obj; logscale = scale, local_alg = alg, bounds_id = bounds_id[1], bounds_params = bounds)
end

for i in 1:length(p_names)
    params_intervals(p0, i, obj0, obj; logscale = direct_scale, local_alg = :LN_SBPLX, bounds_id = bounds_id[i], bounds_params = bounds_params_half_open)
end
=#


text:
struct CDCapacitanceReport{U1,U2,U3,U4,U5,U6,U7,U8,U9,U10}
    Δt::U1
    ΔV::U2
    area::U3
    I::U4
    C::U5
    C_specific::U6
    E::U7
    E_specific::U8
    P::U9
    P_specific::U10
end

function capacitance(df, I, fixed_ΔV)
    t, V = df[!, Symbol("Time (s)")], df[!, Symbol("Potential (V)")]
    idxs = findall(v->v > 0, V)
    t, V = t[idxs], V[idxs]

    ∫Vdt = integrate(t, V) * u"V*s"
    Δt = (t[end] - t[begin]) * u"s"
    ΔV = !isnothing(fixed_ΔV) ? fixed_ΔV : -(reverse(extrema(V))...)

    return Δt, ΔV, ∫Vdt, 2*I*∫Vdt/ΔV^2
end

function CDCapacitanceReport(datafile, df, folder, setup)
    I = parse(Float64, filevalue(datafile)) * uparse(datafile.legend_units)
    porosity = parse(Int, foldervalue(datafile))
    @unpack a, A, fixed_ΔV = setup

    Δt, ΔV, area, C = capacitance(df, I, fixed_ΔV)
    C_specific = specific_capacitance(C, porosity, folder, a, A)
    E = energy(C, ΔV)
    E_specific = energy(C_specific, ΔV)
    P = power(E, Δt)
    P_specific = power(E_specific, Δt)

    CDCapacitanceReport(Δt, ΔV, area, I, C, C_specific, E, E_specific, P, P_specific)
end

function add_aux!(df, cr::CDCapacitanceReport)
    push!(df[!, :Area], ustrip(u"V*s", cr.area))
    push!(df[!, :Δt], ustrip(u"s", cr.Δt))
    push!(df[!, :ΔV], ustrip(u"V", cr.ΔV))
    push!(df[!, :I], ustrip(u"mA", cr.I))

    return nothing
end

function cd_report_units()
    join(["V s", "s", "V", "mA", replace_unicode("μF"), "F/g", "W*h", "Wh/kg", "W", "W/kg"], ',')
end

function cd_result()
    DataFrame(
        :Area=>Float64[],
        :Δt=>Float64[],
        :ΔV=>Float64[],
        :I=>Float64[],
        :C=>Float64[],
        :C_specific=>Float64[],
        :E=>Float64[],
        :E_specific=>Float64[],
        :P=>Float64[],
        :P_specific=>Float64[])
end


text:
@testset "Hostcall" begin

@testset "Call: No return or arguments" begin
    function kernel(a,b,sig)
        hostcall!(sig)
        b[1] = a[1]

        nothing
    end

    A = ones(Float32, 1)
    B = zeros(Float32, 1)
    RA = ROCArray(A)
    RB = ROCArray(B)

    dref = Ref{Bool}(false)
    hc = HostCall(Nothing, Tuple{}) do
        dref[] = true
        nothing
    end

    wait(@roc kernel(RA, RB, hc))

    @test Array(RB)[1] == 1f0
    sleep(1)
    @test dref[] == true
end

@testset "Call: Error" begin
    function kernel(a,b,sig)
        hostcall!(sig)
        b[1] = a[1]

        nothing
    end

    A = ones(Float32, 1)
    B = zeros(Float32, 1)
    RA = ROCArray(A)
    RB = ROCArray(B)

    dref = Ref{Bool}(false)

    # This should throw an exception and the error message should be logged.
    @test_logs (:error, "Hostcall error") begin
        hc, hc_task = HostCall(Nothing, Tuple{}; return_task=true) do
            error("Some error")
            dref[] = true
            nothing
        end

        @roc kernel(RA, RB, hc)
        sleep(1)

        @test Array(RB)[1] == 0f0
        @test dref[] == false
        @test Base.istaskfailed(hc_task)
    end
end

@testset "Call: (0 args)" begin
    function kernel(a,b,sig)
        inc = hostcall!(sig)::Float32
        b[1] = a[1] + inc

        nothing
    end

    A = ones(Float32, 1)
    B = zeros(Float32, 1)
    RA = ROCArray(A)
    RB = ROCArray(B)

    hc = HostCall(Float32, Tuple{}) do
        1f0
    end

    wait(@roc kernel(RA, RB, hc))

    sleep(1)
    @test Array(RB)[1] == 2f0
end

@testset "Call: (1 arg)" begin
    function kernel(a,b,sig)
        inc = hostcall!(sig, 42f0)::Float32
        b[1] = a[1] + inc

        nothing
    end

    A = ones(Float32, 1)
    B = zeros(Float32, 1)
    RA = ROCArray(A)
    RB = ROCArray(B)

    hc = HostCall(Float32, Tuple{Float32}) do arg1
        arg1 + 1f0
    end

    wait(@roc kernel(RA, RB, hc))

    sleep(1)
    @test Array(RB)[1] == 44f0
end

@testset "Call: (2 homogeneous args)" begin
    function kernel(a,b,sig)
        inc = hostcall!(sig, 42f0, 3f0)::Float32
        b[1] = a[1] + inc

        nothing
    end

    A = ones(Float32, 1)
    B = zeros(Float32, 1)
    RA = ROCArray(A)
    RB = ROCArray(B)

    hc = HostCall(Float32, Tuple{Float32,Float32}) do arg1, arg2
        arg1 + arg2 + 1f0
    end

    wait(@roc kernel(RA, RB, hc))

    sleep(1)
    @test Array(RB)[1] == 47f0
end

@testset "Call: (2 heterogeneous args)" begin
    function kernel(a,b,sig)
        inc = hostcall!(sig, 42f0, Int16(3))::Float32
        b[1] = a[1] + inc

        nothing
    end

    A = ones(Float32, 1)
    B = zeros(Float32, 1)
    RA = ROCArray(A)
    RB = ROCArray(B)

    hc = HostCall(Float32, Tuple{Float32,Int16}) do arg1, arg2
        arg1 + Float32(arg2) + 1f0
    end

    wait(@roc kernel(RA, RB, hc))

    sleep(1)
    @test Array(RB)[1] == 47f0
end

@testset "Call: (2 heterogeneous args, return homogeneous tuple)" begin
    function kernel(a,b,sig)
        inc1, inc2 = hostcall!(sig, 42f0, Int16(3))::Tuple{Float32,Float32}
        b[1] = a[1] + inc1 + inc2

        nothing
    end

    A = ones(Float32, 1)
    B = zeros(Float32, 1)
    RA = ROCArray(A)
    RB = ROCArray(B)

    hc = HostCall(Tuple{Float32,Float32}, Tuple{Float32,Int16}) do arg1, arg2
        (arg1 + Float32(arg2) + 1f0, 1f0)
    end

    wait(@roc kernel(RA, RB, hc))

    sleep(1)
    @test Array(RB)[1] == 48f0
end

@testset "Call: (2 heterogeneous args, return heterogeneous tuple)" begin
    function kernel(a,b,sig)
        inc1, inc2 = hostcall!(sig, 42f0, Int16(3))::Tuple{Float32,Int64}
        b[1] = a[1] + inc1 + Float32(inc2)

        nothing
    end

    A = ones(Float32, 1)
    B = zeros(Float32, 1)
    RA = ROCArray(A)
    RB = ROCArray(B)

    hc = HostCall(Tuple{Float32,Int64}, Tuple{Float32,Int16}) do arg1, arg2
        (arg1 + Float32(arg2) + 1f0, 1)
    end

    wait(@roc kernel(RA, RB, hc))

    sleep(1)
    @test Array(RB)[1] == 48f0
end

@testset "Call: (2 hostcalls, 1 kernel)" begin
    function kernel(a,b,sig1,sig2)
        inc1 = hostcall!(sig1, 3f0)::Float32
        inc2 = hostcall!(sig2, 4f0)::Float32
        b[1] = a[1] + inc1 + inc2

        nothing
    end

    A = ones(Float32, 1)
    B = zeros(Float32, 1)
    RA = ROCArray(A)
    RB = ROCArray(B)

    hc1 = HostCall(Float32, Tuple{Float32}) do arg1
        arg1 + 1f0
    end
    hc2 = HostCall(Float32, Tuple{Float32}) do arg1
        arg1 + 2f0
    end

    wait(@roc kernel(RA, RB, hc1, hc2))

    sleep(1)
    @test Array(RB)[1] == 11f0
end

@testset "Call: (1 hostcall, 2 kernels)" begin
    function kernel(a,b,sig)
        inc = hostcall!(sig, 3f0)::Float32
        b[1] = a[1] + inc

        nothing
    end

    A = ones(Float32, 1)
    B = zeros(Float32, 1)
    RA = ROCArray(A)
    RB = ROCArray(B)

    hc = HostCall(Float32, Tuple{Float32}; continuous=true) do arg1
        arg1 + 1f0
    end

    # FIXME: wait() hangs here, probably race condition?
    @roc kernel(RA, RB, hc)
    @roc kernel(RA, RB, hc)

    sleep(1)
    @test Array(RB)[1] == 5f0
end

end


text:
println("Hello there again")

# something fot the main branch
#adding info for the branch main

# something again

# something for the third time and now adding to the branch temp


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

## SubString and Cstring tests ##

## SubString tests ##
u8str = "∀ ε > 0, ∃ δ > 0: |x-y| < δ ⇒ |f(x)-f(y)| < ε"
u8str2 = u8str^2
len_u8str = length(u8str)
slen_u8str = length(u8str)
len_u8str2 = length(u8str2)
slen_u8str2 = length(u8str2)

@test len_u8str2 == 2 * len_u8str
@test slen_u8str2 == 2 * slen_u8str

u8str2plain = String(u8str2)

for i1 = 1:length(u8str2)
    if !isvalid(u8str2, i1); continue; end
    for i2 = i1:length(u8str2)
        if !isvalid(u8str2, i2); continue; end
        @test length(u8str2[i1:i2]) == length(u8str2plain[i1:i2])
        @test length(u8str2[i1:i2]) == length(u8str2plain[i1:i2])
        @test u8str2[i1:i2] == u8str2plain[i1:i2]
    end
end

# tests that SubString of a single multibyte `Char` string, like "∀" which takes 3 bytes
# gives the same result as `getindex` (except that it is a veiw not a copy)
for idx in 0:1
    @test SubString("∀", 1, idx) == "∀"[1:idx]
end

# Substring provided with invalid end index throws BoundsError
@test_throws StringIndexError SubString("∀", 1, 2)
@test_throws StringIndexError SubString("∀", 1, 3)
@test_throws BoundsError SubString("∀", 1, 4)

# Substring provided with invalid start index throws BoundsError
@test SubString("∀∀", 1:1) == "∀"
@test SubString("∀∀", 1:4) == "∀∀"
@test SubString("∀∀", 4:4) == "∀"
@test_throws StringIndexError SubString("∀∀", 1:2)
@test_throws StringIndexError SubString("∀∀", 1:5)
@test_throws StringIndexError SubString("∀∀", 2:4)
@test_throws BoundsError SubString("∀∀", 0:1)
@test_throws BoundsError SubString("∀∀", 0:4)
@test_throws BoundsError SubString("∀∀", 1:7)
@test_throws BoundsError SubString("∀∀", 4:7)

# tests for SubString of more than one multibyte `Char` string
# we are consistent with `getindex` for `String`
for idx in [0, 1, 4]
    @test SubString("∀∀", 1, idx) == "∀∀"[1:idx]
    @test SubString("∀∀", 4, idx) == "∀∀"[4:idx]
end

# index beyond lastindex("∀∀")
for idx in [2:3; 5:6]
    @test_throws StringIndexError SubString("∀∀", 1, idx)
end
for idx in 7:8
    @test_throws BoundsError SubString("∀∀", 1, idx)
end

let str="tempus fugit"              #length(str)==12
    ss=SubString(str,1,lastindex(str)) #match source string
    @test length(ss)==length(str)

    ss=SubString(str,1:lastindex(str))
    @test length(ss)==length(str)

    ss=SubString(str,1,0)    #empty SubString
    @test length(ss)==0

    ss=SubString(str,1:0)
    @test length(ss)==0

    @test_throws BoundsError SubString(str, 14, 20)  #start indexing beyond source string length
    @test_throws BoundsError SubString(str, 10, 16)  #end indexing beyond source string length

    @test_throws BoundsError SubString("", 1, 4)  #empty source string
    @test_throws BoundsError SubString("", 1, 1)  #empty source string, identical start and end index
    @test_throws BoundsError SubString("", 10, 12)
    @test SubString("", 12, 10) == ""
end

@test SubString("foobar", big(1), big(3)) == "foo"

let str = "aa\u2200\u2222bb"
    u = SubString(str, 3, 6)
    @test length(u) == 2
    b = IOBuffer()
    write(b, u)
    @test String(take!(b)) == "\u2200\u2222"

    @test_throws StringIndexError SubString(str, 4, 5)
    @test_throws BoundsError next(u, 0)
    @test_throws BoundsError next(u, 7)
    @test_throws BoundsError getindex(u, 0)
    @test_throws BoundsError getindex(u, 7)
    @test_throws BoundsError getindex(u, 0:1)
    @test_throws BoundsError getindex(u, 7:7)
    @test reverseind(u, 1) == 4
    @test typeof(Base.cconvert(Ptr{Int8}, u)) == SubString{String}
    @test Base.cconvert(Ptr{Int8}, u) == u
end

let str = "føøbar"
    @test_throws BoundsError SubString(str, 10, 10)
    u = SubString(str, 4, 3)
    @test length(u) == 0
    b = IOBuffer()
    write(b, u)
    @test String(take!(b)) == ""
end

# search and SubString (issue #5679)
let str = "Hello, world!"
    u = SubString(str, 1, 5)
    @test findlast("World", u) == 0:-1
    @test findlast(equalto('z'), u) == nothing
    @test findlast("ll", u) == 3:4
end

# SubString created from SubString
let str = "Hello, world!"
    u = SubString(str, 2, 5)
    for idx in 1:4
        @test SubString(u, 2, idx) == u[2:idx]
        @test SubString(u, 2:idx) == u[2:idx]
    end
    @test_throws BoundsError SubString(u, 1, 10)
    @test_throws BoundsError SubString(u, 1:10)
    @test_throws BoundsError SubString(u, 20:30)
    @test SubString(u, 20:15) == ""
    @test_throws BoundsError SubString(u, -1:10)
    @test SubString(u, -1, -10) == ""
    @test SubString(SubString("123", 1, 2), -10, -20) == ""
end

# sizeof
@test sizeof(SubString("abc\u2222def",4,4)) == 3

# issue #3710
@test prevind(SubString("{var}",2,4),4) == 3

# issue #4183
@test split(SubString("x", 2, 0), "y") == [""]

# issue #6772
@test parse(Float64, SubString("10",1,1)) === 1.0
@test parse(Float64, SubString("1 0",1,1)) === 1.0
@test parse(Float32, SubString("10",1,1)) === 1.0f0

# issue #5870
@test !contains(SubString("",1,0), Regex("aa"))
@test contains(SubString("",1,0), Regex(""))

# isvalid, length, prevind, nextind for SubString{String}
let s = "lorem ipsum", sdict = Dict(
    SubString(s, 1, 11)  => "lorem ipsum",
    SubString(s, 1, 6)   => "lorem ",
    SubString(s, 1, 0)   => "",
    SubString(s, 2, 4)   => "ore",
    SubString(s, 2, 11)  => "orem ipsum",
    SubString(s, 15, 14) => "",
)
    for (ss, s) in sdict
        @test ncodeunits(ss) == ncodeunits(s)
        for i in -2:13
            @test isvalid(ss, i) == isvalid(s, i)
        end
        for i in 1:ncodeunits(ss), j = i-1:ncodeunits(ss)
            @test length(ss, i, j) == length(s, i, j)
        end
    end
    for (ss, s) in sdict
        @test length(ss) == length(s)
        for i in 0:ncodeunits(ss), j = 0:length(ss)+1
            @test prevind(ss, i+1, j) == prevind(s, i+1, j)
            @test nextind(ss, i, j) == nextind(s, i, j)
        end
        @test_throws BoundsError prevind(s, 0)
        @test_throws BoundsError prevind(ss, 0)
        @test_throws BoundsError nextind(s, ncodeunits(ss)+1)
        @test_throws BoundsError nextind(ss, ncodeunits(ss)+1)
    end
end

# proper nextind/prevind/thisind for SubString{String}
let rng = MersenneTwister(1), strs = ["∀∃∀"*String(rand(rng, UInt8, 40))*"∀∃∀",
                                      String(rand(rng, UInt8, 50))]
    for s in strs
        a = 0
        while !done(s, a)
            a = nextind(s, a)
            b = a - 1
            while !done(s, b)
                ss = SubString(s, a:b)
                s2 = s[a:b]
                @test ncodeunits(ss) == ncodeunits(s2)
                for i in 0:ncodeunits(ss)+1
                    @test thisind(ss, i) == thisind(s2, i)
                end
                for i in 0:ncodeunits(ss)
                    @test nextind(ss, i) == nextind(s2, i)
                    for j in 0:ncodeunits(ss)+5
                        if j > 0 || isvalid(ss, i)
                            @test nextind(ss, i, j) == nextind(s2, i, j)
                        end
                    end
                end
                for i in 1:ncodeunits(ss)+1
                    @test prevind(ss, i) == prevind(s2, i)
                    for j in 0:ncodeunits(ss)+5
                        if j > 0 || isvalid(ss, i)
                            @test prevind(ss, i, j) == prevind(s2, i, j)
                        end
                    end
                end
                b = nextind(s, b)
            end
        end
    end
end

# for isvalid(SubString{String})
let s = "Σx + βz - 2"
    for i in -1:ncodeunits(s)+2
        if checkbounds(Bool, s, i)
            if isvalid(s, i)
                ss = SubString(s, 1, i)
                for j = 1:ncodeunits(ss)
                    @test isvalid(ss, j) == isvalid(s, j)
                end
            else
                @test_throws StringIndexError SubString(s, 1, i)
            end
        elseif i > 0
            @test_throws BoundsError SubString(s, 1, i)
        else
            @test SubString(s, 1, i) == ""
        end
    end
end

let ss = SubString("hello", 1, 5)
    @test length(ss, 1, 0) == 0
    @test_throws BoundsError length(ss, 1, -1)
    @test_throws BoundsError length(ss, 1, 6)
    @test_throws BoundsError length(ss, 1, 10)
    @test_throws BoundsError prevind(ss, 0, 1)
    @test prevind(ss, 1, 1) == 0
    @test prevind(ss, 6, 1) == 5
    @test_throws BoundsError prevind(ss, 7, 1)
    @test_throws BoundsError nextind(ss, -1, 1)
    @test nextind(ss, 0, 1) == 1
    @test nextind(ss, 5, 1) == 6
    @test_throws BoundsError nextind(ss, 6, 1)
end

# length(SubString{String}) performance specialization
let s = "|η(α)-ϕ(κ)| < ε"
    @test length(SubString(s, 1, 0)) == length(s[1:0])
    @test length(SubString(s, 4, 4)) == length(s[4:4])
    @test length(SubString(s, 1, 7)) == length(s[1:7])
    @test length(SubString(s, 4, 11)) == length(s[4:11])
end

@testset "reverseind" for T in (String, SubString, GenericString)
    for prefix in ("", "abcd", "\U0001d6a4\U0001d4c1", "\U0001d6a4\U0001d4c1c", " \U0001d6a4\U0001d4c1")
        for suffix in ("", "abcde", "\U0001d4c1β\U0001d6a4", "\U0001d4c1β\U0001d6a4c", " \U0001d4c1β\U0001d6a4")
            for c in ('X', 'δ', '\U0001d6a5')
                s = convert(T, string(prefix, c, suffix))
                r = reverse(s)
                ri = findfirst(equalto(c), r)
                @test c == s[reverseind(s, ri)] == r[ri]
                s = convert(T, string(prefix, prefix, c, suffix, suffix))
                pre = convert(T, prefix)
                sb = SubString(s, nextind(pre, lastindex(pre)),
                               lastindex(convert(T, string(prefix, prefix, c, suffix))))
                r = reverse(sb)
                ri = findfirst(equalto(c), r)
                @test c == sb[reverseind(sb, ri)] == r[ri]
            end
        end
    end
end

@testset "reverseind of empty strings" begin
    for s in ("",
              SubString("", 1, 0),
              SubString("ab", 1, 0),
              SubString("ab", 2, 1),
              SubString("ab", 3, 2),
              GenericString(""))
        @test reverseind(s, 0) == 1
        @test reverseind(s, 1) == 0
    end
end

## Cstring tests ##

# issue #13974: comparison against pointers
let
    str = String("foobar")
    ptr = pointer(str)
    cstring = Cstring(ptr)
    @test ptr == cstring
    @test cstring == ptr

    # convenient NULL string creation from Ptr{Cvoid}
    nullstr = Cstring(C_NULL)

    # Comparisons against NULL strings
    @test ptr != nullstr
    @test nullstr != ptr

    # Short-hand comparison against C_NULL
    @test nullstr == C_NULL
    @test C_NULL == nullstr
    @test cstring != C_NULL
    @test C_NULL != cstring
end

max_stars_repo_name: blegat/julia

max_issues_repo_name: blegat/julia

max_forks_repo_name: blegat/julia