text:
isalpha(c) = isascii(c) && (islowercase(c) || isuppercase(c))
isalnum(c) = isascii(c) && (islowercase(c) || isuppercase(c) || isdigit(c))
"""
    html_entities(ts::TokenBuffer; remove_illegal=true)

Removes entities from text by converting them to their corresponding unicode character.

`remove_illegal::Bool` If `true`, entities that can't be converted are
removed. Otherwise, entities that can't be converted are kept "as
is".

HTML entity can be named or encoded in Decimal/Hex form
- Named_entity : "Δ" => "Δ",
- Decimal : "Δ" => "Δ",
- Hex : "Δ" => "Δ",
However for bytes (hex) 80-9f are interpreted in Windows-1252

"""
function html_entity(ts::TokenBuffer, remove_illegal=true)
    (ts.idx + 1 > length(ts.input) || ts.input[ts.idx] != '&' ) && return false
    if ts.input[ts.idx + 1] != '#'    # Entity is of the type "Δ" => "Δ"
        i = ts.idx + 1
        while i <= length(ts.input) && isalnum(ts[i])
            i += 1
        end
        (i > length(ts.input) || ts[i] != ';') && return false
        entity = lookupname(HTML_Entities.default, String(ts[ts.idx+1:i-1]))
        isempty(entity) && !remove_illegal && return false
        !isempty(entity) && push!(ts.buffer, entity[1])
        ts.idx = i + 1
        return true
    else
        number = -1
        i = ts.idx + 2
        if ts.input[ts.idx + 2] != 'x'    # Entity is of the type "Δ" => "Δ"
            while i <= length(ts.input) && isdigit(ts[i])
                i += 1
            end
            (i > length(ts.input) || ts[i] != ';') && return false
            if ((ts.idx + 2 ) == i)
                !remove_illegal && return false
                ts.idx +=3
                return true
            end
            (number = parse(Int, String(ts[ts.idx+2:i-1]), base=10))
        else   # Entity is of the type "Δ" => "Δ"
            i += 1
            base16letters = ('a', 'b', 'c', 'd', 'e', 'f')
            while i <= length(ts.input) && (isdigit(ts[i]) || ts[i] in base16letters)
                i += 1
            end
            (i > length(ts.input) || ts[i] != ';') && return false

            if (ts.idx + 3) == i
                !remove_illegal && return false
                ts.idx += 4
                return true
            end
            number = parse(Int, String(ts[ts.idx+3:i-1]), base=16)
        end

        windows_1252_chars = ('€', '\u81', '‚', 'ƒ', '„', '…', '†', '‡', 'ˆ', '‰',
                          'Š', '‹', 'Œ', '\u8d','Ž', '\u8f', '\u90', '‘', '’',
                          '“', '”', '•', '–', '—', '˜', '™', 'š', '›', 'œ',
                          '\u9d', 'ž', 'Ÿ')
        if 0x80 <= number <= 0x9F
            push!(ts.buffer, windows_1252_chars[number - 127])
            ts.idx = i + 1
            return true
        end
        if (number <= 0 || !Unicode.isassigned(number))
            !remove_illegal && return false
            ts.idx = i + 1
        else
            push!(ts.buffer, Char(number))
        ts.idx = i + 1
        end
    end
    return true
end

"""
    lookbehind(ts::TokenBuffer)

Checks if the beginning of the detected handle is preceded by alphanumeric
or special characters like('_', '!', '@', '#', '\$', '%', '&', '*')
"""
function lookbehind(ts::TokenBuffer,
                match_pattern = ('_', '!', '@', '#', '$', '%', '&', '*'))
    ts.idx == 1 && return false

    c = ts[ts.idx - 1]
    ( islowercase(c) || isdigit(c) || isuppercase(c) || c ∈ match_pattern ) && return true

    return false
end

"""
    strip_twitter_handle(ts::TokenBuffer)

For removing Twitter Handles. If it detects a twitter handle, then it jumps to
makes the index of TokenBuffer to the desired location skipping the handle.
"""
function strip_twitter_handle(ts)

    (ts.idx + 2 > length(ts.input) || ts.input[ts.idx] != '@' ) && return false
    lookbehind(ts) && return false

    i = ts.idx + 1
    while i <= length(ts.input) && (isalnum(ts[i]) || ts[i] == '_')
        i += 1
    end
    (i <= length(ts.input)) && (i == ts.idx + 1 || ts[i] == '@') && return false

    ts.idx = i
    return true
end

"""
    reduce_all_repeated(ts::TokenBuffer)

For handling repeated characters like "helloooooo" -> :hellooo".
"""
function reduce_all_repeated(ts)
    ts.idx + 4 > length(ts.input) && return false

    (ts[ts.idx] == '\n' || ts[ts.idx] != ts[ts.idx + 1] ||
              ts[ts.idx] != ts[ts.idx + 2]) && return false

    i = ts.idx + 3
    while i <= length(ts.input) && ts[i] == ts[ts.idx]
        i += 1
    end
    for j in 1:3
        push!(ts.buffer, ts[ts.idx])
    end
    ts.idx = i
    return true
end

"""
    safe_text(ts::TokenBuffer)

This feature covers up for the characters where the main tokenizing function lacks
For example - "........" -> "..." and this is detected by the key tokenizer as a
single token of "..."
"""
function safe_text(ts)
    ts.idx + 4 > length(ts.input) && return false

    (isalnum(ts[ts.idx]) || ts[ts.idx] != ts[ts.idx + 1] ||
              ts[ts.idx] != ts[ts.idx + 2] )  && return false

    i = ts.idx + 3

    while i <= length(ts.input) && ts[i] == ts[ts.idx]
        i += 1
    end

    for j in 1:3
        push!(ts.buffer, ts[ts.idx])
    end
    ts.idx = i

    return true
end

"""
    replace_html_entities(input::AbstractString, remove_illegal=true)

`input::AbstractString` The string on which HTML entities need to be replaced
`remove_illegal::Bool` If `true`, entities that can't be converted are
removed. Otherwise, entities that can't be converted are kept "as
is".

"""
function replace_html_entities(input::AbstractString; remove_illegal=true)
    ts = TokenBuffer(input)
    isempty(input) && return ""

    while !isdone(ts)
        html_entity(ts, remove_illegal) || character(ts)
    end
    return ts.tokens[1]
end

"""
    function  pre_process(input::AbstractString, strip_handle::Bool,
                            reduce_len::Bool)

This function processes on the input string and optionally remove twitter handles
and reduce length of repeated characters (like "waaaaay" -> "waaay")
and for elements like ".........?????? -> "...???" to increase the performance
of the key tokenizer.
"""
function pre_process(input::AbstractString, strip_handle::Bool, reduce_len::Bool)
    ts = TokenBuffer(input)
    isempty(input) && return ""

    while !isdone(ts)
        (strip_handle && strip_twitter_handle(ts)) ||   # Remove username handles
        (reduce_len && reduce_all_repeated(ts)) ||    # Reduce Lengthening
        safe_text(ts) ||  # Shorten some sequences of characters
        character(ts)
    end

    return ts.tokens[1]
end

function flushaboutindex!(ts::TokenBuffer, uptoidx)
    flush!(ts, String(ts[ts.idx:uptoidx]))
    ts.idx = uptoidx + 1
    return true
end

const forehead = ('>', '<')
const eyes = (':', ';', '=', '8')
const nose = ('-', 'o', '*', '\'')
const mouth = (')', ']', '}', '(', '[', '{', 'd', 'D', 'p', 'P', '\\', '/', ':', '@', '|')

"""
    function emoticons(ts::TokenBuffer)

This function checks for the emoticons for the type `{forehead}{eyes}{nose}{mouth}
explicitely in this order, with {forehead} and {nose} being optional

Example:
- `:)`, `;p`    # (without nose and forehead)
- `:-)`, `:-p`  # (with nose)
- `>:)`         # (with forehead)
- `>:-)`        # (with forehead and nose)

Also checks for `<3` emoji
"""
function emoticons(ts)
    ts.idx + 1 > length(ts.input) && return false
    idx = ts.idx

    ts[idx] ∈ eyes && (
        (ts[idx + 1] ∈ mouth && return flushaboutindex!(ts, idx + 1)) ||
        (idx + 2 <= length(ts.input) && ts[idx + 1] ∈ nose && ts[idx + 2] ∈ mouth &&
            return flushaboutindex!(ts, idx + 2)) ||
        return false
    )

    idx + 2 <= length(ts.input) && ts[idx] ∈ forehead && ts[idx + 1] ∈ eyes && (
            (ts[idx + 2] ∈ mouth && return flushaboutindex!(ts, idx + 2)) ||
            (idx + 3 <= length(ts.input) && ts[idx + 2] ∈ nose &&
                ts[idx + 3] ∈ mouth && return flushaboutindex!(ts, idx + 3)) ||
            return false
    )

    ts[idx] == '<' && ts[idx + 1] == '3' && return flushaboutindex!(ts, idx + 1)

    return false
end

"""
    function emoticonsreverse(ts::TokenBuffer)

This function checks for the emoticons in reverse order to those of `function emoticons`
explicitely in this order `{mouth}{nose}{eyes}{forehead}`, with {forehead} and {nose} being optional

Example:
- `(:`, `d:`    # (without nose and forehead)
- `(-:`, `d-:`  # (with nose)
- (:<`          # (with forehead)
- `(-:<`        # (with forehead and nose)

"""
function emoticonsreverse(ts)
    ts.idx + 1 > length(ts.input) && return false
    idx = ts.idx

    ts[idx] ∈ mouth || return false
    idx += 1

    if ts[idx] ∈ nose
        idx >= length(ts.input) && return false
        idx += 1
    end

    ts[idx] ∈ eyes|| return false
    idx += 1

    idx <= length(ts.input) && ts[idx] ∈ forehead && return flushaboutindex!(ts, idx)

    return flushaboutindex!(ts, idx -1 )
end

"""
    htmltags(ts::TokenBuffer)

Matches the HTML tags which contain no space inside the tags.
"""
function htmltags(ts)
    (ts.idx + 2 > length(ts.input) || ts[ts.idx] != '<'
                || ts[ts.idx + 1] == '>') && return false

    i = ts.idx
    while i <= length(ts.input) && ts[i] != '>'
        isspace(ts[i]) && return false
        i += 1
    end
    i > length(ts.input) && return false

    return flushaboutindex!(ts, i)
end

"""
    arrowsascii(ts::TokenBuffer)

Matches the ascii arrows - made up of arrows like `<--` and `--->`
"""
function arrowsascii(ts)
    (
        ts.idx + 1 > length(ts.input) ||
        (
            (ts[ts.idx] != '<' || ts[ts.idx + 1] != '-' ) &&
            (ts[ts.idx] != '-')
        )
    )   && return false

    i = ts.idx
    if ts[i] == '<'
        i += 1
        while i <= length(ts.input) && ts[i] == '-'
            i += 1
        end
        return flushaboutindex!(ts, i - 1)
    end
    while i <= length(ts.input) && ts[i] == '-'
        i += 1
    end
    ts[ts.idx] == '>' && return flushaboutindex!(ts, i)
end

# TODO: integrate this with words_including_apostrophe_dashes() to reduce time taken.
# Checks the string till non word char appears, so takes relatively more time.
"""
    emailaddresses(ts)

Matches for email addresses.
"""
function emailaddresses(ts)
    ts.idx + 4 > length(ts.input) && return false

    i = ts.idx
    while i + 3 <= length(ts.input) && (isalnum(ts[i]) || ts[i] ∈ ('.', '+', '-', '_'))
        i += 1
    end
    (i == ts.idx || ts[i] != '@') && return false

    i += 1
    j = i
    while i + 2 <= length(ts.input) && (isalnum(ts[i]) || ts[i] == '-' || ts == '_')
        i += 1
    end

    (j == i || ts[i] != '.') && return false

    j = i
    last_dot = i
    i += 1

    while i <= length(ts.input) && (isalnum(ts[i]) || ts[i] == '-' || ts == '_')

        if i + 1 < length(ts.input) && ts[i + 1] == '.'
            i += 1
            last_dot = i
        end

        i += 1
    end

    i > last_dot + 1 && i > j + 2 && return flushaboutindex!(ts, i - 1)

    return false
end

"""
    twitterhashtags(ts)

Matches for twitter hashtags.
"""
function twitterhashtags(ts)
    (ts.idx + 2 > length(ts.input) || ts[ts.idx] != '#' ||
                    ts[ts.idx + 1] ∈ ('\'', '-')) && return false

    i = ts.idx + 1
    last_word_char = i

    while i <= length(ts.input) && (isalnum(ts[i]) || ts[i] ∈ ('_', '\'', '-'))

        if ts[i] ∉  ('\'', '-')
            last_word_char = i
        end

        i += 1
    end

    last_word_char >= ts.idx + 2 && ts[ts.idx + 1] ∉ ('\'', '-') &&
            ts[last_word_char] ∉ ('\'', '-') && return flushaboutindex!(ts, last_word_char)

    return false
end

"""
    twitterusername(ts)

Matches for twitter usernames.
"""
function twitterusername(ts)
    (ts.idx + 1 > length(ts.input) || ts[ts.idx] != '@' ) && return false

    i = ts.idx + 1
    while i <= length(ts.input) && (isalnum(ts[i]) || ts[i] == '_')
        i += 1
    end
    i > ts.idx + 1 && return flushaboutindex!(ts, i - 1)

    return false
end

"""
    ellipsis_dots(ts)

Matches for ellipsis and dots, ignoring the spaces, tabs, newlines between them.
"""
function ellipsis_dots(ts)
    (ts.idx + 1 > length(ts.input) || ts[ts.idx] != '.' ) && return false

    i = ts.idx + 1
    last_dot = ts.idx

    while i <= length(ts.input) && (isspace(ts[i]) || ts[i] == '.')
        if ts[i] == '.'
            last_dot = i
        end
        i += 1
    end

    last_dot != ts.idx && return flushaboutindex!(ts, last_dot)

    return false
end

"""
    words_including_apostrophe_dashes(ts)

TokenBuffer matcher for words that may or maynot have dashes or apostrophe in it.
"""
function words_including_apostrophe_dashes(ts)
    ts.idx + 1 > length(ts.input) && return false
    isalnum(ts[ts.idx]) || ts[ts.idx] == '_'  || return false

    has_apostrophe_dashes = false
    i = ts.idx + 1
    last_char = ts.idx

    if isuppercase(ts[ts.idx]) || islowercase(ts[ts.idx])
        while i <= length(ts.input) && (isalpha(ts[i]) || ts[i] ∈ ('_', '\'', '-'))
            if has_apostrophe_dashes == false && ts[i] ∈ ('\'', '-')
                has_apostrophe_dashes = true
            else
                last_char = i
            end
            i += 1
        end
    end

    has_apostrophe_dashes && last_char != ts.idx && return flushaboutindex!(ts, last_char)

    while i <= length(ts.input) && (isalnum(ts[i]) || ts[i] == '_')
        i += 1
    end

    return flushaboutindex!(ts, i - 1)
end

const allowed_chars_phone_numbers = (' ', '*', '-', '.', ')')

"""
    nltk_casual_phonenumbers(ts)

The TokenBuffer function for nltk's tweet tokenizer regex for phonenumbers.
"""
function nltk_phonenumbers(ts)
    (ts.idx + 5 > length(ts.input) || !(isdigit(ts[ts.idx]) ||
                                    ts[ts.idx] ∈ ('+', '('))) && return false

    i = ts.idx
    optional_1_confirmed = false

    # Checking for the part 1 of regex which is optional
    if ts[i] == '+'
        ts[i + 1] == '0' || ts[i + 1] == '1' || return false
        i += 2

        while i <= length(ts.input) && ts[i] ∈ allowed_chars_phone_numbers
            i += 1
        end

        i + 5 > length(ts.input) && return false

        optional_1_confirmed = true
    elseif ts[i] == '0' || ts[i] == '1'
        i += 1

        while i <= length(ts.input) && ts[i] ∈ allowed_chars_phone_numbers
            i += 1
        end

        i + 5 > length(ts.input) && return false

        if i - ts.idx > 1  || ts[i] == '('
            optional_1_confirmed = true
        end
    end

    if i == ts.idx || optional_1_confirmed
        # This is called when either the first part is sure to present or absent, otherwise next one called
        if ts[i] == '('
            i += 1

            for repeat in 1:2 # repeat is unused variable inside loop
                if !(i + 3 <= length(ts.input) && isdigit(ts[i]) && isdigit(ts[i + 1]) &&
                                            isdigit(ts[i + 2]))

                    return false
                end

                i += 3
                while i <= length(ts.input) && ts[i] ∈ allowed_chars_phone_numbers
                    i += 1
                end
            end

            !(i + 3 <= length(ts.input) && isdigit(ts[i]) && isdigit(ts[i + 1]) &&
                        isdigit(ts[i + 2]) && isdigit(ts[i + 3])) && return false

            return flushaboutindex!(ts, i + 3)
        else
            if !(i + 3 <= length(ts.input) && isdigit(ts[i]) && isdigit(ts[i + 1]) &&
                                        isdigit(ts[i + 2]))

                return false
            end
            i += 3

            while i <= length(ts.input) && ts[i] ∈ allowed_chars_phone_numbers
                i += 1
            end

            if !(i + 3 <= length(ts.input) && isdigit(ts[i]) && isdigit(ts[i + 1]) &&
                                        isdigit(ts[i + 2]))

                return false
            end
            i += 3
            j = i

            while i <= length(ts.input) && ts[i] ∈ allowed_chars_phone_numbers
                i += 1
            end

            i + 3 <= length(ts.input) && isdigit(ts[i]) && isdigit(ts[i + 1]) &&
                    isdigit(ts[i + 2]) && isdigit(ts[i + 3]) && return flushaboutindex!(ts, i + 3)

            isdigit(ts[j]) && return flushaboutindex!(ts, j)

            return false
        end
    else
        function index_including_part_1(ts, i)
            index_including_1 = 0
            j = i

            i + 3 <= length(ts.input) && isdigit(ts[i]) && isdigit(ts[i + 1]) &&
                                            isdigit(ts[i + 2]) || return -1

            i += 3

            while i <= length(ts.input) && ts[i] ∈ allowed_chars_phone_numbers
                i += 1
            end

            i + 3 <= length(ts.input) && isdigit(ts[i]) && isdigit(ts[i + 1]) &&
                                            isdigit(ts[i + 2]) || return -1

            i += 3
            j = i

            while i <= length(ts.input) && ts[i] ∈ allowed_chars_phone_numbers
                i += 1
            end

            i + 3 <= length(ts.input) && isdigit(ts[i]) && isdigit(ts[i + 1]) &&
                            isdigit(ts[i + 2]) && isdigit(ts[i + 3]) && return i + 3

            isdigit(ts[j]) && return j

            return -1
        end

        function index_excluding_part_1(ts)
            i = ts.idx

            i + 3 <= length(ts.input) && isdigit(ts[i]) && isdigit(ts[i + 1]) &&
                isdigit(ts[i + 2]) || return -1

            i += 3

            while i <= length(ts.input) && ts[i] ∈ allowed_chars_phone_numbers
                i += 1
            end

            i + 3 <= length(ts.input) && isdigit(ts[i]) && isdigit(ts[i + 1]) &&
                isdigit(ts[i + 2]) || return -1

            i += 3
            j = i

            while i <= length(ts.input) && ts[i] ∈ allowed_chars_phone_numbers
                i += 1
            end

            i + 3 <= length(ts.input) && isdigit(ts[i]) && isdigit(ts[i + 1]) &&
                        isdigit(ts[i + 2]) && isdigit(ts[i + 3]) && return i + 3

            isdigit(ts[j]) && return j

            return -1
        end

        index_including_1 = index_including_part_1(ts, i)
        index_excluding_1 = index_excluding_part_1(ts)
        # Flushing out the bigger of the two.
        index_including_1 <= 0 && index_excluding_1 <= 0 && return false
        index_excluding_1 > index_including_1 && return flushaboutindex!(ts, index_excluding_1)
        return flushaboutindex!(ts, index_including_1)
    end

    return false
end

"""
    extra_phonenumbers(ts)

Extra matching patterns for phone numbers.
"""
function extra_phonenumbers(ts)
    return false
end

"""
    nltk_url1(ts)

Matches the url patterns starting with `http/https`.
"""
function nltk_url1(ts)
    ts.idx + 3 > length(ts.input) && return false
    i = ts.idx

    # Checking for part 1 of regex
    if ts[i:i+3] == ['h', 't', 't', 'p'] # Check if url starts with pattern - https?:(?:\/{1,3}|[a-z0-9%])
        i += 4
        i + 2 > length(ts.input) && return false

        if ts[i] == 's'
            i += 1
        end

        ts[i] == ':' || return false
        i += 1

        if i >= length(ts.input) || !(isascii(ts[i]) && (islowercase(ts[i]) ||
                     isdigit(ts[i]) || ts[i] == '%' || ts[i] == '/'))
            return false
        end

        i += 1
    else # Check if url starts with the regex pattern - [a-z0-9.\-]+[.](?:[a-z]{2,13})\/
        last_dot = ts.idx

        while i <= length(ts.input) && isascii(ts[i]) && (islowercase(ts[i]) || isdigit(ts[i]) ||
                                                            ts[i] == '.' || ts[i] == '-')
            if ts[i] == '.'
                last_dot = i
            elseif !islowercase(ts[i])
                last_dot = ts.idx
            end

            i += 1
        end

        if i + 2 > length(ts.input) || last_dot <= ts.idx + 1 || i - last_dot > 14 ||
                                    i - last_dot <= 2 || ts[i] != '/'
            return false
        end
        i += 1
    end

    # URL is supposed to have 2 more parts.
    # Both Part 2 and Part 3 each having 3 possible alternatives.
    # Part 2 occurs at least once and Part 3 exactly once.
    # After every match of the first part, we keep a track if the second one follows it.
    # and store the maximum index in `index_matched`.
    # Finally, we flush about the index = index_matched then.

    index_matched = ts.idx

    while i + 1 <= length(ts.input) && !(isspace(ts[i]))

        # Check if part 2 matches otherwise break.
        # Part 2 could be one of the three patterns.
        #   i.   ` \([^\s]+?\)`
        #   ii.  `\([^\s()]*?\([^\s()]+?\)[^\s()]*?\)`
        #   iii. `[^\s()<>{}\[\]]+`
        if ts[i] == '(' # Checking for i. and ii. above.
            i += 1
            (i > length(ts.input) || isspace(ts[i])) && break
            j = i

            while j <= length(ts.input) && ts[j] != ')' && ts[j] != '(' && !isspace(ts[j])
                j += 1
            end

            (j > length(ts.input) || isspace(ts[j])) && break

            if ts[j] == ')' # Checking for i.
                j - i <= 1 && break
                i = j
            else # Checking for ii.
                i = j
                i > length(ts.input) && break

                while j <= length(ts.input) && ts[j] != ')' && ts[j] != '(' && !isspace(ts[j])
                    j += 1
                end

                (j > length(ts.input) || isspace(ts[j]) || ts[j] == '(') && break
                j - i <= 1 && break
                j += 1

                while j <= length(ts.input) && ts[j] != ')' && ts[j] != '(' && !isspace(ts[j])
                    j += 1
                end

                (j > length(ts.input) || isspace(ts[j]) || ts[j] == '(') && break
                i = j
            end
            i += 1
        else # Checking for iii.
            (isspace(ts[i])|| ts[i] ∈ (')', '<', '>', '{', '}', '[', ']') ) && break
            i += 1
        end

        i > length(ts.input)  && break
        k = i # Just for temporarily storing i.

        # Check if part 3 matches otherwise continue.
        # Part 3 could be one of the three patterns.
        #   i.   `\([^\s()]*?\([^\s()]+?\)[^\s()]*?\)`
        #   ii.  `[^\s`!()\[\]{};:'".,<>?«»“”‘’]`
        #   iii. ` \([^\s]+?\)`
        if ts[i] == '(' # Check for part i. and iii.

            i += 1
            (i > length(ts.input) || isspace(ts[i])) && continue
            j = i

            while j <= length(ts.input) && ts[j] != ')' && ts[j] != '(' && !isspace(ts[j])
                j += 1
            end

            (j > length(ts.input) || isspace(ts[j])) && continue

            if ts[j] == ')' # Check for part iii.
                j - i <= 1 && break
                i = j
            else # Check for part i.
                i = j
                i > length(ts.input) && continue

                while j <= length(ts.input) && ts[j] != ')' && ts[j] != '(' && !isspace(ts[j])
                    j += 1
                end

                (j > length(ts.input) || isspace(ts[j]) || ts[j] == '(') && continue
                j - i <= 1 && continue

                j += 1
                while j <= length(ts.input) && ts[j] != ')' && ts[j] != '(' && !isspace(ts[j])
                    j += 1
                end

                (j > length(ts.input) || isspace(ts[j]) || ts[j] == '(') && continue
                i = j
            end
            index_matched = i
            i += 1
        else # Check for part ii.
            isspace(ts[i]) && break
            ts[i] ∈ ('`', '!', ')', '[', ']', '{', '}', ';', ':', '\'', '"', '.',
                     ',', '<', '>', '?', '«', '»', '“', '”', '‘', '’') && continue
            index_matched = i
        end
        i = k
    end

    index_matched == ts.idx && return false
    return flushaboutindex!(ts, index_matched)
end

function nltk_url2(ts)
    (ts.idx > length(ts.input) || (ts.idx > 1 && ts[ts.idx - 1] == '@')) && return false

    (isascii(ts[ts.idx]) && (isdigit(ts[ts.idx]) || islowercase(ts[ts.idx]))) || return false

    i = ts.idx + 1
    while i <= length(ts.input) && isascii(ts[i]) && (isdigit(ts[i]) || islowercase(ts[i]))
        i += 1
    end

    flush_about = 0

    while i < length(ts.input) && (ts[i] == '.' || ts[i] == '-')
        j = ts[i] == '.' ? i : 0
        i += 1

        (isascii(ts[i]) && (isdigit(ts[i]) || islowercase(ts[i]))) || break
        i += 1

        while i <= length(ts.input) && isascii(ts[i]) && (isdigit(ts[i]) || islowercase(ts[i]))
            i += 1
        end

        if j != 0 && 3 <= i - j <= 14
            if i <= length(ts.input) && ts[i] == '/' && (i + 1 > length(ts.input) || ts[i + 1] != '@')
                flush_about = i
                break
            end
            flush_about = i - 1
        end
    end

    flush_about == 0 && return false
    (flush_about >= length(ts.input) || ts[flush_about + 1] != '@') && return flushaboutindex!(ts, flush_about)

    return false
end

"""
    tweet_tokenize(input::AbstractString) => tokens

Twitter-aware tokenizer, designed to be flexible and
easy to adapt to new domains and tasks.

The basic logic is following:

1. The regular expressions are made for WORD_REGEX (core tokenizer), HANG_REGEX
   and EMOTICONS_REGEX.
2  Replacing HTML entities, tweet handles, reducing length of repeated characters
   and other features, make it suitable for tweets.
3. The String is tokenized and returned.
4. `preserve_case` By default is set to `true`. If it is set to `false`,
   then the tokenizer will downcase everything except for emoticons.

Example:

```
julia> tweet_tokenize("This is a cooool #dummysmiley: :-) :-P <3 and some arrows < > -> <--")

16-element Array{SubString{String},1}:
 "This"
 "is"
 "a"
 "cooool"
 "#dummysmiley"
 ":"
 ":-)"
 ":-P"
 "<3"
 "and"
 "some"
 "arrows"
 "<"
 ">"
 "->"
 "<--"
```
"""
function tweet_tokenize(source::AbstractString; strip_handle=false,
                                            reduce_len=false)

    phonenumbers(ts) = nltk_phonenumbers(ts) || extra_phonenumbers(ts)
    urls(ts) = nltk_url1(ts) || nltk_url2(ts)

    # Fix HTML Character entities
    length(source) == 0 && return []
    source = replace_html_entities(source)

    length(source) == 0 && return []
    safe_text = pre_process(source, strip_handle, reduce_len)

    # The key tokenizing function begins
    ts = TokenBuffer(safe_text)
    isempty(safe_text) && return ts.tokens

    # # TODO: OpenQuotes and Closing quotes
    while !isdone(ts)
        spaces(ts) && continue
        emoticons(ts) ||
        emoticonsreverse(ts) ||
        htmltags(ts) ||
        arrowsascii(ts) ||
        twitterhashtags(ts) ||
        ellipsis_dots(ts) ||
        urls(ts) || # urls must be called before words.
        twitterusername(ts) ||
        emailaddresses(ts) || # emailaddresses must be called before words
        phonenumbers(ts) || # Phone numbers must be called before numbers.
        atoms(ts, []) ||
        words_including_apostrophe_dashes(ts) ||
        number(ts, check_sign = true) ||
        character(ts)
    end

    tokens = ts.tokens

    return tokens
end


text:
# commutation(n, m)
# returns Magnus and Neudecker's commutation matrix of dimensions n by m
# Luca Brugnolini
# Based on Thomas P Minka (tpminka@media.mit.edu)

function commutation(n, m)

    k = reshape(kron(vec(eye(n)), eye(m)), n*m, n*m);

    return k
end

ext: jl


text:
function triple(k1,k2,k3)
#
#  test polynomial suggested by Goedecker
#  square of Fibocacci polynomial
#
    poly([0.9*ones(k1);ones(k2);1.1*ones(k3)])
end


text:
#ДАНО: Робот - в произвольной клетке
#ограниченного прямоугольного поля,
# на котором могут находиться также внутренние
# прямоугольные перегородки
#(все перегородки изолированы друг от друга,
#прямоугольники могут вырождаться в отрезки)
#РЕЗУЛЬТАТ: Робот - в исходном положении и
# в углах поля стоят маркеры
function movement!(r,side)
    while !isborder(r,side)
        move!(r,side)
    end
end
function go!(r)
    a1 = 0
    a2 = 0
    a3 = 0
    a4 = 0
    while (a1 != 1) || (a2 != 1) || (a3 != 1) || (a4 != 1)
        for side in (Ost,Sud,West,Nord)
            movement!(r,side)
            if (isborder(r,West) && isborder(r,Sud) && (a1!=1))==true
                putmarker!(r)
                a1=1
            end
            if (isborder(r,West) && isborder(r,Nord) && (a2!=1))==true
                putmarker!(r)
                a2=1
            end
            if (isborder(r,Ost) && isborder(r,Nord) && (a3!=1))==true
                putmarker!(r)
                a3=1
            end
            if (isborder(r,Ost) && isborder(r,Sud) && (a4!=1))==true
                putmarker!(r)
                a4=1
            end
        end
    end
end


text:
struct LineNum
    v::Int
end

# POSSIBLE_BUG: I worry that to make kwarg functions work
# this code needs to be moved into the Rules themselves,
# and run at check-time against each statement_ind
function rules(path::AbstractString, line::LineNum)
    methods = containing_methods(path, line.v)
    statement_inds = src_line2ir_statement_ind.(methods, line.v)
    new_rules = Rule.(methods, statement_inds)
    if isempty(new_rules)
        @warn "No matching rules could be constructed)" methods statement_inds
    end
    return new_rules
end

rules(path::AbstractString, line::Integer) = rules(path, LineNum(line))
rules(args...) = (Rule(args...),)  # default to just constructing a Rule

#TODO: think hard about API, eg maybe `set!(Breakpoint(foo,1))` might be nicest

function set_breakpoint!(the_rules::BreakpointRules, args...)
    return append!(the_rules.breakon_rules, rules(args...))
end

function set_uninstrumented!(the_rules::BreakpointRules, arg::T) where T
    T !== Method || throw(ArgumentError("Disabling instrumentation per method, is not supported."))
    return push!(the_rules.no_instrument_rules, Rule(arg))
end

for (name, list) in ((:breakpoint, :breakon_rules), (:uninstrumented, :no_instrument_rules))
    set! = Symbol(:set_, name, :!)
    @eval export $(set!)
    @eval $(set!)(args...) = $(set!)(GLOBAL_BREAKPOINT_RULES, args...)
    # actual set definitions are above
    rm! = Symbol(:rm_, name, :!)
    @eval export $(rm!)
    @eval $(rm!)(args...) = $(rm!)(GLOBAL_BREAKPOINT_RULES, args...)
    @eval function $(rm!)(the_rules::BreakpointRules, args...)
        old_num_rules = length(the_rules.$list)
        to_remove = rules(args...)
        filter!(!in(to_remove), the_rules.$list)
        if length(the_rules.$list) == old_num_rules
            @info("No matching rule was found, so none removed")
        end
        return the_rules.$list
    end

    list_all = Symbol(:list_, name,:s)
    @eval export $(list_all)
    @eval $(list_all)()=$(list_all)(GLOBAL_BREAKPOINT_RULES)
    @eval $(list_all)(the_rules::BreakpointRules) = the_rules.$list

    clear_all = Symbol(:clear_,name, :s!)
    @eval export $clear_all
    @eval $(clear_all)()=$(clear_all)(GLOBAL_BREAKPOINT_RULES)
    @eval $(clear_all)(the_rules::BreakpointRules) = empty!(the_rules.$list)
end


text:
# Smith-Waterman Algorithm
# ========================
#
# The Smith-Waterman algorithm.
#
# This file is a part of BioJulia.
# License is MIT: https://github.com/BioJulia/Bio.jl/blob/master/LICENSE.md

type SmithWaterman{T<:Union{Signed,AbstractFloat}}
    trace::Matrix{Trace}
    H::Vector{T}
    E::Vector{T}

    function SmithWaterman(m::Integer, n::Integer)
        trace = Matrix{Trace}(m + 1, n + 1)
        fill!(trace, 0xff)
        H = Vector{T}(m + 1)
        E = Vector{T}(m)
        return new(trace, H, E)
    end

    function SmithWaterman()
        return SmithWaterman{T}(0, 0)
    end
end

function ensureroom!(sw::SmithWaterman, m, n)
    if size(sw.trace, 1) < m + 1 || size(sw.trace, 2) < n + 1
        # TODO: resize the trace matrix if possible
        sw.trace = Matrix{Trace}(m + 1, n + 1)
        resize!(sw.H, m + 1)
        resize!(sw.E, m)
    end
    return sw
end

function run!{T}(
        sw::SmithWaterman{T},
        a, b,
        submat::AbstractSubstitutionMatrix{T},
        gap_open_a::T,
        gap_extend_a::T,
        gap_open_b::T,
        gap_extend_b::T)

    m = length(a)
    n = length(b)
    ensureroom!(sw, m, n)
    H = sw.H
    E = sw.E
    trace = sw.trace

    gap_init_a = gap_open_a + gap_extend_a
    gap_init_b = gap_open_b + gap_extend_b

    H[1] = T(0)
    trace[1,1] = TRACE_NONE
    for i in 1:m
        H[i+1] = T(0)
        E[i] = H[i+1] + gap_init_a
        trace[i+1,1] = TRACE_NONE
        if n ≥ 1
            trace[i+1,2] = TRACE_NONE
        end
    end

    best_score = H[1]
    best_endpos = (0, 0)
    @inbounds for j in 1:n
        b_j = b[j]
        h_diag = H[1]
        H[1] = T(0)
        f = H[1] + gap_init_b
        ft = TRACE_NONE
        trace[1,j+1] = TRACE_NONE
        for i in 1:m
            e = E[i]
            g = h_diag + submat[a[i],b_j]
            h = max(T(0), e, f, g)
            h_diag = H[i+1]
            H[i+1] = h
            t = trace[i+1,j+1] | ft
            e == h && (t |= TRACE_DELETE)
            f == h && (t |= TRACE_INSERT)
            g == h && (t |= TRACE_MATCH)
            trace[i+1,j+1] = t
            if h ≥ best_score
                best_score = h
                best_endpos = (i, j)
            end
            # next E
            if j != n
                e′ = e + gap_extend_a
                e = max(e′, h + gap_init_a)
                E[i] = e
                et = TRACE_NONE
                e == e′ && (et |= TRACE_EXTDEL)
                trace[i+1,j+2] = et
            end
            # next F
            f′ = f + gap_extend_b
            f = max(f′, h + gap_init_b)
            ft = TRACE_NONE
            f == f′ && (ft |= TRACE_EXTINS)
        end
    end
    return best_score, best_endpos
end

function run!{T}(sw::SmithWaterman{T}, a, b, submat, gap_open, gap_extend)
    return run!(sw, a, b, submat, gap_open, gap_extend, gap_open, gap_extend)
end

function traceback(sw::SmithWaterman, a, b, endpos)
    anchors = Vector{AlignmentAnchor}()
    i, j = endpos
    was_extins = false
    was_extdel = false
    @start_traceback
    while sw.trace[i+1,j+1] & (TRACE_MATCH | TRACE_INSERT | TRACE_DELETE) > 0
        t = sw.trace[i+1,j+1]
        @assert !(was_extins && was_extdel)
        if was_extins
            was_extins = t & TRACE_EXTINS > 0
            @anchor OP_INSERT
        elseif was_extdel
            was_extdel = t & TRACE_EXTDEL > 0
            @anchor OP_DELETE
        elseif t & TRACE_INSERT > 0
            was_extins = t & TRACE_EXTINS > 0
            @anchor OP_INSERT
        elseif t & TRACE_DELETE > 0
            was_extdel = t & TRACE_EXTDEL > 0
            @anchor OP_DELETE
        elseif t & TRACE_MATCH > 0
            if a[i] == b[j]
                @anchor OP_SEQ_MATCH
            else
                @anchor OP_SEQ_MISMATCH
            end
        else
            error("failed to trace back")
        end
    end
    @finish_traceback
    return AlignedSequence(a, anchors)
end


text:
export run_simulation!
export save_simulation
export load_simulation

"""
    hr_min_sec(time)

Converts `time` (seconds) to 00:00:00 (hr:min:sec) format.
"""
@inline function hr_min_sec(time::Float64)
    hours = trunc(Int64, time / 3600.0)
    minutes = trunc(Int64, mod(time, 3600.0) / 60.0)
    seconds = trunc(Int64, mod(time, 60.0))

    return string(hours < 10 ? "0" : "", hours,
                  minutes < 10 ? ":0" : ":", minutes,
                  seconds < 10 ? ":0" : ":", seconds)
end

"""
    run_simulation(simulation; message_interval, save_as)

Run the simulation.  `message_interval` (seconds) controls how often a time
update is printed.  `save_as` is the file to which `simulation` is saved.
"""
function run_simulation!(simulation::Simulation; message_interval::Float64 = 10.0, save_as::String = "", overwrite::Bool = true)
    print_message("SIMULATION STARTED")
    println("Number of threads available: ", Threads.nthreads())
    println("Number of particles: ", length(simulation.particles))
    if length(simulation.bonds) > 0
        println("Number of bonds: ", length(simulation.bonds))
    end
    if length(simulation.angles) > 0
        println("Number of angles: ", length(simulation.angles))
    end
    println("Description: ", simulation.descriptor)
    println("")

    period_x = simulation.periodic_in_x ? simulation.L_x : -1.0
    period_y = simulation.periodic_in_y ? simulation.L_y : -1.0

    if overwrite
        simulation.history = NamedTuple{(:particles, :bonds), Tuple{Array{Particle, 1}, Array{NTuple{2, Particle}, 1}}}[]
        push!(simulation.history, deepcopy(simulation.things_to_save)) # save initial data
    end

    print_message("SIMULATION PROGRESS")
    prev_step = 0
    time_elapsed = 0.0
    interval_start = time()
    for step = 1 : simulation.num_steps
        for interaction in simulation.interactions
            compute_interactions!(interaction; period_x = period_x, period_y = period_y)
        end
        for external_force in simulation.external_forces
            compute_external_forces!(external_force)
        end

        if step % simulation.save_interval == 0
            push!(simulation.history, deepcopy(simulation.things_to_save))
        end

        for integrator in simulation.integrators
            update_particles!(integrator; period_x = period_x, period_y = period_y)
        end
        for cell_list in simulation.cell_lists
            update_cell_list!(cell_list)
        end

        interval_time = time() - interval_start
        if interval_time > message_interval || step == simulation.num_steps
            time_elapsed += interval_time
            rate = (step - prev_step) / interval_time
            println(hr_min_sec(time_elapsed), " | ",
                    step, "/", simulation.num_steps, " (", round(step / simulation.num_steps * 100, digits = 1), "%) | ",
                    round(rate, digits = 1), " steps/s | ",
                    hr_min_sec((simulation.num_steps - step) / rate))
            prev_step = step
            interval_start = time()
        end
    end
    println("Average steps/s: ", round(simulation.num_steps / time_elapsed, digits = 1))

    if !isempty(save_as)
        save_simulation(simulation; save_as = save_as)
    end
    print_message("SIMULATION COMPLETED")
end

"""
    save_simulation(simulation; save_as)

Saves `simulation` to `save_as`.
"""
function save_simulation(simulation::Simulation; save_as::String)
    if !isdir(dirname(save_as))
        mkpath(dirname(save_as))
    end

    open(save_as, "w") do f
        serialize(f, simulation)
    end
    println("\nSimulation saved to $save_as\n")
end

"""
    load_simulation(; file)

Loads simulation from `file`
"""
function load_simulation(; file::String)
    simulation = begin
        open(file, "r") do f
            deserialize(f)
        end
    end
    println("\n$file loaded\n")
    return simulation
end


text:
export create_function, call_function, isboxed, isghosttype

"""
    create_function(rettyp::LLVMType, argtyp::Vector{LLVMType}, [name::String])

Create an LLVM function, given its return type `rettyp` and a vector of argument types
`argtyp`. The function is marked for inlining, to be embedded in the caller's body.
Returns both the newly created function, and its type.
"""
function create_function(rettyp::LLVMType=LLVM.VoidType(Context()),
                         argtyp::Vector{<:LLVMType}=LLVMType[],
                         name::String="entry")
    ctx = context(rettyp)
    mod = LLVM.Module("llvmcall"; ctx)
    isempty(name) && throw(ArgumentError("Function name cannot be empty"))

    ft = LLVM.FunctionType(rettyp, argtyp)
    f = LLVM.Function(mod, name, ft)
    push!(function_attributes(f), EnumAttribute("alwaysinline", 0; ctx))

    return f, ft
end

"""
    call_function(f::LLVM.Function, rettyp::Type, argtyp::Type, args...)

Generate a call to an LLVM function `f`, given its return type `rettyp` and a tuple-type for
the arguments. The arguments should be passed as a tuple expression containing the argument
values (eg. `:((1,2))`), which will be splatted into the call to the function.
"""
function call_function(llvmf::LLVM.Function, rettyp::Type=Nothing, argtyp::Type=Tuple{},
                       args...)
    mod = LLVM.parent(llvmf)
    ir = string(mod)
    fn = LLVM.name(llvmf)
    @assert !isempty(fn)
    if VERSION >= v"1.8.0-DEV.410"
        quote
            Base.@inline
            Base.llvmcall(($ir,$fn), $rettyp, $argtyp, $(args...))
        end
    else
        quote
            Base.@_inline_meta
            Base.llvmcall(($ir,$fn), $rettyp, $argtyp, $(args...))
        end
    end
end

"""
    isboxed(typ::Type; [ctx::Context])

Return if a type would be boxed when instantiated in the code generator.
"""
function isboxed(typ::Type; ctx::Union{Nothing,Context}=nothing)
    isboxed_ref = Ref{Bool}()
    if VERSION >= v"1.9.0-DEV.115"
        if ctx === nothing
            Context() do ctx
                ccall(:jl_type_to_llvm, LLVM.API.LLVMTypeRef,
                      (Any, LLVM.API.LLVMContextRef, Ptr{Bool}), typ, ctx, isboxed_ref)
            end
        else
            ccall(:jl_type_to_llvm, LLVM.API.LLVMTypeRef,
                  (Any, LLVM.API.LLVMContextRef, Ptr{Bool}), typ, ctx, isboxed_ref)
        end
    else
        ccall(:jl_type_to_llvm, LLVM.API.LLVMTypeRef,
              (Any, Ptr{Bool}), typ, isboxed_ref)
    end
    return isboxed_ref[]
end

"""
    convert(LLVMType, typ::Type, ctx::Context; allow_boxed=true)

Convert a Julia type `typ` to its LLVM representation in context `ctx`.
The `allow_boxed` argument determines whether boxed types are allowed.
"""
function Base.convert(::Type{LLVMType}, typ::Type; ctx::Context,
                      allow_boxed::Bool=false)
    isboxed_ref = Ref{Bool}()
    llvmtyp = if VERSION >= v"1.9.0-DEV.115"
        LLVMType(ccall(:jl_type_to_llvm, LLVM.API.LLVMTypeRef,
                        (Any, Context, Ptr{Bool}), typ, ctx, isboxed_ref))
    else
        LLVMType(ccall(:jl_type_to_llvm, LLVM.API.LLVMTypeRef,
                        (Any, Ptr{Bool}), typ, isboxed_ref))
    end
    if !allow_boxed && isboxed_ref[]
        error("Conversion of boxed type $typ is not allowed")
    end

    # HACK: older versions of Julia don't offer an API to fetch types in a specific context
    if VERSION < v"1.9.0-DEV.115" && ctx != context(llvmtyp)
        if llvmtyp == LLVM.VoidType(context(llvmtyp))
            return LLVM.VoidType(ctx)
        end

        # serialize
        buf = let
            mod = LLVM.Module(""; ctx=context(llvmtyp))
            gv = LLVM.GlobalVariable(mod, llvmtyp, "")
            convert(MemoryBuffer, mod)
        end

        # deserialize in different context
        llvmtyp = let
            mod = parse(LLVM.Module, buf; ctx)
            gv = first(globals(mod))
            llvmeltype(gv)
        end
    end

    return llvmtyp
end

"""
    isghosttype(t::Type; [ctx::Context])
    isghosttype(T::LLVMType)

Check if a type is a ghost type, implying it would not be emitted by the Julia compiler.
This only works for types created by the Julia compiler (living in its LLVM context).
"""
isghosttype(@nospecialize(T::LLVMType)) = T == LLVM.VoidType(context(T)) || isempty(T)
function isghosttype(@nospecialize(t::Type); ctx::Union{Nothing,Context}=nothing)
    if ctx === nothing
        Context() do ctx′
            T = convert(LLVMType, t; ctx=ctx′, allow_boxed=true)
            isghosttype(T)
        end
    else
        T = convert(LLVMType, t; ctx, allow_boxed=true)
        isghosttype(T)
    end
end


text:
isdefined(Base, :__precompile__) && __precompile__()

module AutonomousControl

using NLOptControl
#using RobotOS

include("CaseModule.jl")
using .CaseModule

include("VehicleModels/VehicleModels.jl")
using .VehicleModels

export
      initializeAutonomousControl,
      updateAutoParams!,
      solverConfig,
      avMpc,
      OptData,
      fixYAML,
      configProb!,    #TMP
      obstacleAvoidanceConstraints!,
      lidarConstraints!,
      objFunc!,
      goalRange

type OptData
      t
      sa
      ax
      obj
      X0
end
"""
--------------------------------------------------------------------------------------\n
Author: Huckleberry Febbo, Graduate Student, University of Michigan
Date Create: 2/15/2018, Last Modified: 3/12/2018 \n
-------------------------------------------------------------------------------------\n
"""
function solverConfig(c,init)

   if init      # initially giving solver more time
       maxtime = c["solver"]["maxtime_cpu_init"]
   else
       maxtime = c["solver"]["maxtime_cpu"]
   end

  # settings for both KNITRO and IPOPT
  outlev = (c["misc"]["solver"]==:Ipopt) ? :print_level : :outlev
  feastol_abs = (c["misc"]["solver"]==:Ipopt) ? :constr_viol_tol : :feastol_abs
  maxit = (c["misc"]["solver"]==:Ipopt) ? :max_iter : :maxit
  maxtime_cpu = (c["misc"]["solver"]==:Ipopt) ? :max_cpu_time : :maxtime_cpu
 if c["misc"]["solver"] == :KNITRO
   SS = ((:name=>c["misc"]["solver"]),(outlev=>c["solver"]["outlev"]),(feastol_abs=>c["solver"]["feastol_abs"]),(maxit=>c["solver"]["maxit"]),(maxtime_cpu=>maxtime),(maxit=>c["solver"]["maxit"]),(:ftol=>c["solver"]["ftol"]),(:feastol=>c["solver"]["feastol"]),(:ftol_iters=>c["solver"]["ftol_iters"]),(:infeastol=>c["solver"]["infeastol"]),(:maxfevals=>c["solver"]["maxfevals"]),(:opttol=>c["solver"]["opttol"]),(:opttol_abs=>c["solver"]["opttol_abs"]),(:xtol=>c["solver"]["xtol"]),(:algorithm=>c["solver"]["algorithm"]),(:bar_murule=>c["solver"]["bar_murule"]),(:linsolver=>c["solver"]["linsolver"]),(:cg_pmem=>c["solver"]["cg_pmem"]),(:bar_initpt=>c["solver"]["bar_initpt"]),(:bar_penaltycons=>c["solver"]["bar_penaltycons"]),(:bar_penaltyrule=>c["solver"]["bar_penaltyrule"]),(:bar_switchrule=>c["solver"]["bar_switchrule"]),(:linesearch=>c["solver"]["linesearch"]))
 elseif c["misc"]["solver"] == :Ipopt
   SS = ((:name=>c["misc"]["solver"]),(outlev=>c["solver"]["outlev"]),(feastol_abs=>c["solver"]["feastol_abs"]),(maxit=>c["solver"]["maxit"]),(maxtime_cpu=>maxtime),(maxit=>c["solver"]["maxit"]),(:acceptable_obj_change_tol=>c["solver"]["acceptable_obj_change_tol"]),(:warm_start_init_point=>c["solver"]["warm_start_init_point"]),(:dual_inf_tol=>c["solver"]["dual_inf_tol"]),(:compl_inf_tol=>c["solver"]["compl_inf_tol"]),(:acceptable_tol=>c["solver"]["acceptable_tol"]),(:acceptable_constr_viol_tol=>c["solver"]["acceptable_constr_viol_tol"]),(:acceptable_dual_inf_tol=>c["solver"]["acceptable_dual_inf_tol"]),(:acceptable_compl_inf_tol=>c["solver"]["acceptable_compl_inf_tol"]),(:acceptable_obj_change_tol=>c["solver"]["acceptable_obj_change_tol"]))
 end

return SS
end

"""
n = initializeAutonomousControl(c);
--------------------------------------------------------------------------------------\n
Author: Huckleberry Febbo, Graduate Student, University of Michigan
Date Create: 2/1/2017, Last Modified: 4/08/2018 \n
--------------------------------------------------------------------------------------\n
"""
function initializeAutonomousControl(c)

 pa = Vpara(ax_min=copy(c["vehicle"][:ax_min]),ax_max=copy(c["vehicle"][:ax_max]),m=copy(c["vehicle"][:m]),Izz=copy(c["vehicle"][:Izz]), la=copy(c["vehicle"][:la]), lb=copy(c["vehicle"][:lb]), FzF0=copy(c["vehicle"][:FzF0]), FzR0=copy(c["vehicle"][:FzR0]), KZX=copy(c["vehicle"][:KZX]), KZYR=copy(c["vehicle"][:KZYR]), KZYF=copy(c["vehicle"][:KZYF]), AXC=copy(c["vehicle"][:AXC]),x_min=copy(c["misc"]["Xmin"]),x_max=copy(c["misc"]["Xmax"]),y_min=copy(c["misc"]["Ymin"]),y_max=copy(c["misc"]["Ymax"]),sa_min=copy(c["vehicle"][:sa_min]),sa_max=copy(c["vehicle"][:sa_max]),psi_min=copy(c["vehicle"][:psi_min]),psi_max=copy(c["vehicle"][:psi_max]),u_min=copy(c["vehicle"][:u_min]),u_max=copy(c["vehicle"][:u_max]),sr_min=copy(c["vehicle"][:sr_min]),sr_max=copy(c["vehicle"][:sr_max]),jx_min=copy(c["vehicle"][:jx_min]),jx_max=copy(c["vehicle"][:jx_max]),FZ0=copy(c["vehicle"][:FZ0]),PCY1=copy(c["vehicle"][:PCY1]),PDY1=copy(c["vehicle"][:PDY1]),PDY2=copy(c["vehicle"][:PDY2]),PEY1=copy(c["vehicle"][:PEY1]),PEY2=copy(c["vehicle"][:PEY2]),PEY3=copy(c["vehicle"][:PEY3]),PKY1=copy(c["vehicle"][:PKY1]),PKY2=copy(c["vehicle"][:PKY2]),PHY1=copy(c["vehicle"][:PHY1]),PHY2=copy(c["vehicle"][:PHY2]),PVY1=copy(c["vehicle"][:PVY1]),PVY2=copy(c["vehicle"][:PVY2]),Caf=copy(c["vehicle"][:Caf]),Car=copy(c["vehicle"][:Car]),Fy_min=copy(c["vehicle"][:Fy_min]),Fy_max=copy(c["vehicle"][:Fy_max]),Fz_min=copy(c["vehicle"][:Fz_min]),Fz_off=copy(c["vehicle"][:Fz_off]),EP=copy(c["misc"]["EP"]))

@unpack_Vpara(pa)
if isequal(c["misc"]["model"],:ThreeDOFv2)
    XF = [copy(c["goal"]["x"]), copy(c["goal"]["yVal"]), NaN, NaN, copy(c["goal"]["psi"]), NaN, NaN, NaN]
    XL = [x_min, y_min, NaN, NaN, psi_min, sa_min, u_min, NaN]
    XU = [x_max, y_max, NaN, NaN, psi_max, sa_max, u_max, NaN]
    CL = [sr_min, jx_min]; CU = [sr_max, jx_max]
    X0 = [copy(c["X0"]["x"]),copy(c["X0"]["yVal"]),copy(c["X0"]["v"]),copy(c["X0"]["r"]),copy(c["X0"]["psi"]),copy(c["X0"]["sa"]),copy(c["X0"]["ux"]),copy(c["X0"]["ax"])]
    n = define(numStates=8,numControls=2,X0=copy(X0),XF=XF,XL=XL,XU=XU,CL=CL,CU=CU)
elseif isequal(c["misc"]["model"],:ThreeDOFv3)
    XF = [copy(c["goal"]["x"]), copy(c["goal"]["yVal"]), NaN, NaN, NaN, NaN]
    XL = [x_min, y_min, NaN, NaN, psi_min, sa_min]
    XU = [x_max, y_max, NaN, NaN, psi_max, sa_max]
    CL = [sr_min]; CU = [sr_max]
    X0 = [copy(c["X0"]["x"]),copy(c["X0"]["yVal"]),copy(c["X0"]["v"]),copy(c["X0"]["r"]),copy(c["X0"]["psi"]),copy(c["X0"]["sa"])]
    n = define(numStates=6,numControls=1,X0=copy(X0),XF=XF,XL=XL,XU=XU,CL=CL,CU=CU)
elseif isequal(c["misc"]["model"],:KinematicBicycle2)
    XF = [copy(c["goal"]["x"]), copy(c["goal"]["yVal"]),NaN,NaN]
    XL = [x_min,y_min,psi_min,u_min]
    XU = [x_max,y_max,psi_max,u_max]
    CL = [sa_min,ax_min]; CU = [sa_max,ax_max]
    X0 = [copy(c["X0"]["x"]),copy(c["X0"]["yVal"]),copy(c["X0"]["psi"]),copy(c["X0"]["ux"])]
    n = define(numStates=4,numControls=2,X0=copy(X0),XF=XF,XL=XL,XU=XU,CL=CL,CU=CU)
else
    error(string("c[misc][model] = " ,c["misc"]["model"]," needs to be set to either; :ThreeDOFv2 ||:ThreeDOFv3 || :KinematicBicycle2 "))
end
 n.s.ocp.tfMax = copy(c["misc"]["tfMax"])
 # need c for goalRange() in updateAutoParams!() which gets passed to simMpc!()
 n.ocp.params = [pa,NaN,NaN,NaN,c]

  # TODO check if Chrono is being used
if isequal(c["misc"]["model"],:ThreeDOFv2)
   IPModel = ThreeDOFv2

  # define tolerances on slack variables
  X0_tol = [c["tolerances"]["ix"], c["tolerances"]["iy"], c["tolerances"]["iv"], c["tolerances"]["ir"], c["tolerances"]["ipsi"], c["tolerances"]["isa"], c["tolerances"]["iu"], c["tolerances"]["iax"]]
  if goalRange(n)
    XF_tol = [c["tolerances"]["fx"], c["tolerances"]["fy"], c["tolerances"]["fv"], c["tolerances"]["fr"], c["tolerances"]["fpsi"], c["tolerances"]["fsa"], c["tolerances"]["fu"], c["tolerances"]["fax"]]
  else
    XF_tol = [1e6, 1e6, NaN, NaN, NaN, NaN, NaN, NaN]
  end
  defineTolerances!(n;X0_tol=X0_tol,XF_tol=XF_tol)

          # 1  2  3  4  5    6   7   8
  names = [:x,:y,:v,:r,:psi,:sa,:ux,:ax];
  descriptions = ["X (m)","Y (m)","Lateral Velocity (m/s)", "Yaw Rate (rad/s)","Yaw Angle (rad)", "Steering Angle (rad)", "Longitudinal Velocity (m/s)", "Longitudinal Acceleration (m/s^2)"];
  states!(n,names,descriptions=descriptions)

           # 1   2
  names = [:sr,:jx];
  descriptions = ["Steering Rate (rad/s)","Longitudinal Jerk (m/s^3)"];
  controls!(n,names,descriptions=descriptions)

  # dynamic constraints and additional constraints
  dx,con,tire_expr = ThreeDOFv2_expr(n)
  dynamics!(n,dx)
  constraints!(n,con)
elseif isequal(c["misc"]["model"],:ThreeDOFv3)
   IPModel = ThreeDOFv3

   # define tolerances on slack variables
   X0_tol = [c["tolerances"]["ix"], c["tolerances"]["iy"], c["tolerances"]["iv"], c["tolerances"]["ir"], c["tolerances"]["ipsi"], c["tolerances"]["isa"]]
   if goalRange(n)
     XF_tol = [c["tolerances"]["fx"], c["tolerances"]["fy"], c["tolerances"]["fv"], c["tolerances"]["fr"], c["tolerances"]["fpsi"], c["tolerances"]["fsa"]]
   else
     XF_tol = [1e6, 1e6, NaN, NaN, NaN, NaN]
   end
   defineTolerances!(n;X0_tol=X0_tol,XF_tol=XF_tol)

           # 1  2  3  4  5    6
   names = [:x,:y,:v,:r,:psi,:sa];
   descriptions = ["X (m)","Y (m)","Lateral Velocity (m/s)", "Yaw Rate (rad/s)","Yaw Angle (rad)", "Steering Angle (rad)"];
   states!(n,names,descriptions=descriptions)

            # 1
   names = [:sr];
   descriptions = ["Steering Rate (rad/s)"];
   controls!(n,names,descriptions=descriptions)

   # dynamic constraints and additional constraints
   dx,con,tire_expr = ThreeDOFv3_expr(n)
   dynamics!(n,dx)
   constraints!(n,con)
elseif isequal(c["misc"]["model"],:KinematicBicycle2)
    IPModel = KinematicBicycle2

   # define tolerances on slack variables
   X0_tol = [c["tolerances"]["ix"], c["tolerances"]["iy"], c["tolerances"]["ipsi"], c["tolerances"]["iu"]]
   if goalRange(n)
    XF_tol = [c["tolerances"]["fx"], c["tolerances"]["fy"], c["tolerances"]["fpsi"], c["tolerances"]["fu"]]
   else
     XF_tol = [1e6,1e6,NaN,NaN]
   end
   defineTolerances!(n;X0_tol=X0_tol,XF_tol=XF_tol)

           # 1  2   3   4
   names = [:x,:y,:psi,:ux];
   descriptions = ["X (m)","Y (m)","Yaw Angle (rad)","Longitudinal Velocity (m/s)"];
   states!(n,names,descriptions=descriptions)

            # 1  2
   names = [:sa,:ax];
   descriptions = [ "Steering Angle (rad)","Longitudinal Acceleration (m/s^2)"];
   controls!(n,names,descriptions=descriptions)

   # dynamic constraints and additional constraints
   dx = KinematicBicycle_expr2(n)
   dynamics!(n,dx)
   tire_expr = NaN
 end

 configProb!(n,c)

 obs_params = obstacleAvoidanceConstraints!(n,c)
 if c["misc"]["lidarConstraints"]
     LiDAR_params = lidarConstraints!(n,c)
 else
     LiDAR_params = NaN
 end
 obj_params = objFunc!(n,c,tire_expr)
               #  1      2          3          4       5
 n.ocp.params = [pa,obs_params,LiDAR_params,obj_params,c]
 initOpt!(n;save=c["misc"]["ocpSave"],evalConstraints=c["misc"]["evalConstraints"])

 # after the initial optimization, reconfigure solver so that it does not have as much time as it needs
 defineSolver!(n,Dict(solverConfig(c,false)))

 # set mpc parameters
 if isequal(c["misc"]["model"],:ThreeDOFv2)
   goal = [c["goal"]["x"],c["goal"]["yVal"],NaN,NaN,NaN,NaN,NaN,NaN]
   goalTol = [c["goal"]["tol"],c["goal"]["tol"],NaN,NaN,NaN,NaN,NaN,NaN]
 elseif isequal(c["misc"]["model"],:ThreeDOFv3)
   goal = [c["goal"]["x"],c["goal"]["yVal"],NaN,NaN,NaN,NaN]
   goalTol = [c["goal"]["tol"],c["goal"]["tol"],NaN,NaN,NaN,NaN]
 elseif isequal(c["misc"]["model"],:KinematicBicycle2)
   goal = [c["goal"]["x"],c["goal"]["yVal"],NaN,NaN]
   goalTol = [c["goal"]["tol"],c["goal"]["tol"],NaN,NaN]
 end

 defineMPC!(n;onlyOptimal=c["misc"]["onlyOptimal"],mode=c["misc"]["mode"],goal=goal,goalTol=goalTol,fixedTp=c["misc"]["FixedTp"],predictX0=c["misc"]["PredictX0"],tp=c["misc"]["tp"],tex=copy(c["misc"]["tex"]),maxSim=copy(c["misc"]["mpc_max_iter"]))
 if isequal(c["misc"]["mode"], :OCP)
   defineIP!(n, IPModel)
 elseif isequal(c["misc"]["mode"], :IP) # NOTE for now assuming that the ThreeDOFv1 is always used for the IP
   error("TODO")
   defineIP!(n,IPModel)
 end

 return n
end

"""
--------------------------------------------------------------------------------------\n
Author: Huckleberry Febbo, Graduate Student, University of Michigan
Date Create: 3/20/2017, Last Modified: 3/12/2018 \n
--------------------------------------------------------------------------------------\n
"""
function updateAutoParams!(n)

  # obstacle information-> only show if it is in range at the start TODO
  goal_in_range = goalRange(n)
  if goal_in_range || !c["misc"]["lidarConstraints"] # TODO make a flag that indicates this switch has been flipped
    # otherwise it will keep setting the same parameters..
    println("goal is in range")
    c = n.ocp.params[5]

    if c["misc"]["movingGoal"]
        setRHS(n.r.ocp.xfCon[1,1], c["goal"]["x"] + c["goal"]["vx"]*getvalue(n.ocp.tV[1]))
        setRHS(n.r.ocp.xfCon[1,2], -c["goal"]["x"] - c["goal"]["vx"]*getvalue(n.ocp.tV[1]))
        setRHS(n.r.ocp.xfCon[2,1], c["goal"]["yVal"] + c["goal"]["vy"]*getvalue(n.ocp.tV[1]))
        setRHS(n.r.ocp.xfCon[2,2], -c["goal"]["yVal"] - c["goal"]["vy"]*getvalue(n.ocp.tV[1]))
    end

    # enforce final state constraints on x and y position
    # TODO consider throwing error if not using slack variables as they are not tested
    setRHS(n.r.ocp.xfsCon[1], c["tolerances"]["fx"])
    setRHS(n.r.ocp.xfsCon[2], c["tolerances"]["fy"])

    # add in slack variables in cost function for final state constraints
    setvalue(n.ocp.params[4][3],c["weights"]["xf"])

    # remove terms in cost function for line to goal
    setvalue(n.ocp.params[4][1],0.0)
    setvalue(n.ocp.params[4][2],0.0)

    # relax LiDAR constraints
    if c["misc"]["lidarConstraints"]
        setvalue(n.ocp.params[3][1], 1e6)
        setvalue(n.ocp.params[3][2],-1e6)
    end
  end
  #NOTE assuming it is not going in and out of range

 return goal_in_range
end

"""
# TODO use plant instead of mpc
# TODO add time
--------------------------------------------------------------------------------------\n
Author: Huckleberry Febbo, Graduate Student, University of Michigan
Date Create: 7/04/2017, Last Modified: 3/12/2018 \n
--------------------------------------------------------------------------------------\n
"""
function goalRange(n)
  if isequal(n.r.ocp.evalNum,1)
    X = [n.ocp.X0[1],n.ocp.X0[2]]
  else
    X = currentIPState(n)[1]
  end
  c = n.ocp.params[5]
  return ( (X[1] - c["goal"]["x"])^2 + (X[2] - c["goal"]["yVal"])^2 )^0.5 < c["misc"]["Lr"]
end

"""
--------------------------------------------------------------------------------------\n
Author: Huckleberry Febbo, Graduate Student, University of Michigan
Date Create: 3/12/2018, Last Modified: 11/02/2018 \n
--------------------------------------------------------------------------------------\n
"""
function fixYAML(c)
   # need symbols to work with VehicleModels.jl
  c["vehicle"] = convert(Dict{Symbol,Any}, c["vehicle"])

  for keyA in ["weights", "misc", "X0", "solver", "tolerances"]
    for (key,value) in c[keyA]
      if isequal(value, "NaN")
        c[keyA][key] = NaN
      end
      if isequal(value, "true")
        c[keyA][key] = true
      end
      if isequal(value, "false")
        c[keyA][key] = false
      end
    end
  end

  for keyA in ["misc"]
    for (key,value) in c[keyA]
      if isequal(typeof(c[keyA][key]),String); c[keyA][key] = Symbol(c[keyA][key]); end
    end
  end

  return c
end

"""
--------------------------------------------------------------------------------------\n
Author: Huckleberry Febbo, Graduate Student, University of Michigan
Date Create: 4/08/2018, Last Modified: 1/25/2019 \n
--------------------------------------------------------------------------------------\n
"""
function configProb!(n,c)

  # configure problem
  if c["misc"]["integrationScheme"]==:lgrImplicit || c["misc"]["integrationScheme"]==:lgrExplicit
    configure!(n;(:xFslackVariables=>c["misc"]["xFslackVariables"]),(:x0slackVariables=>c["misc"]["x0slackVariables"]),(:Nck=>c["misc"]["Nck"]),(:integrationScheme=>c["misc"]["integrationScheme"]),(:finalTimeDV=>c["misc"]["finalTimeDV"]),(:solverSettings=>solverConfig(c,true)))
  else
    configure!(n;(:xFslackVariables=>c["misc"]["xFslackVariables"]),(:x0slackVariables=>c["misc"]["x0slackVariables"]),(:N=>c["misc"]["N"]),(:integrationScheme=>c["misc"]["integrationScheme"]),(:finalTimeDV=>c["misc"]["finalTimeDV"]),(:solverSettings=>solverConfig(c,true)))
  end
  return nothing
end

"""
--------------------------------------------------------------------------------------\n
Author: Huckleberry Febbo, Graduate Student, University of Michigan
Date Create: 4/08/2018, Last Modified: 4/08/2018 \n
--------------------------------------------------------------------------------------\n
"""
function obstacleAvoidanceConstraints!(n,c)

  rObs = copy(c["obstacle"]["radius"])
  xObs = copy(c["obstacle"]["x0"])
  yObs = copy(c["obstacle"]["y0"])
  vxObs = copy(c["obstacle"]["vx"])
  vyObs = copy(c["obstacle"]["vy"])

  Q = length(rObs); # number of obstacles
  @NLparameter(n.ocp.mdl, a[i=1:Q] == rObs[i])
  @NLparameter(n.ocp.mdl, b[i=1:Q] == rObs[i])
  @NLparameter(n.ocp.mdl, X_0[i=1:Q] == xObs[i])
  @NLparameter(n.ocp.mdl, Y_0[i=1:Q] == yObs[i])
  @NLparameter(n.ocp.mdl, speed_x[i=1:Q] == vxObs[i])
  @NLparameter(n.ocp.mdl, speed_y[i=1:Q] == vyObs[i])
  obs_params = [a,b,X_0,Y_0,speed_x,speed_y,Q]

  # obstacle postion after the initial postion
  if c["misc"]["movingObstacles"]
    X_obs = @NLexpression(n.ocp.mdl, [j=1:Q,i=1:n.ocp.state.pts], X_0[j] + speed_x[j]*n.ocp.tV[i])
    Y_obs = @NLexpression(n.ocp.mdl, [j=1:Q,i=1:n.ocp.state.pts], Y_0[j] + speed_y[j]*n.ocp.tV[i])
  else
    X_obs = @NLexpression(n.ocp.mdl, [j=1:Q,i=1:n.ocp.state.pts], X_0[j] + speed_x[j]*n.ocp.tV[1])
    Y_obs = @NLexpression(n.ocp.mdl, [j=1:Q,i=1:n.ocp.state.pts], Y_0[j] + speed_y[j]*n.ocp.tV[1])
  end

  # constraint on position
  x = n.r.ocp.x[:,1];y = n.r.ocp.x[:,2]; # pointers to JuMP variables

  sm = linspace(c["misc"]["sm1"],c["misc"]["sm2"],n.ocp.state.pts-1)
  obs_con = @NLconstraint(n.ocp.mdl, [j=1:Q,i=1:n.ocp.state.pts-1], 1 <= ((x[(i+1)]-X_obs[j,i])^2)/((a[j]+sm[i])^2) + ((y[(i+1)]-Y_obs[j,i])^2)/((b[j]+sm[i])^2))
  newConstraint!(n,obs_con,:obs_con);

  return obs_params
end

"""
--------------------------------------------------------------------------------------\n
Author: Huckleberry Febbo, Graduate Student, University of Michigan
Date Create: 4/08/2018, Last Modified: 4/08/2018 \n
--------------------------------------------------------------------------------------\n
"""
function lidarConstraints!(n,c)
  # ensure that the final x and y states are near the LiDAR boundary
  @NLparameter(n.ocp.mdl, LiDAR_param_1==(c["misc"]["Lr"] + c["misc"]["L_rd"])^2)
  @NLparameter(n.ocp.mdl, LiDAR_param_2==(c["misc"]["Lr"] - c["misc"]["L_rd"])^2)

  x = n.r.ocp.x[:,1];y = n.r.ocp.x[:,2]; # pointers to JuMP variables
  LiDAR_edge_high = @NLconstraint(n.ocp.mdl,[j=1], (x[end]-x[1])^2+(y[end]-y[1])^2  <= LiDAR_param_1)
  LiDAR_edge_low = @NLconstraint(n.ocp.mdl,[j=1], (x[end]-x[1])^2+(y[end]-y[1])^2  >= LiDAR_param_2)
  newConstraint!(n,LiDAR_edge_high,:LiDAR_edge_high)
  newConstraint!(n,LiDAR_edge_low,:LiDAR_edge_low)

 # constrain all state points to be within LiDAR boundary
  LiDAR_range = @NLconstraint(n.ocp.mdl, [j=1:n.ocp.state.pts-1], (x[j+1]-x[1])^2+(y[j+1]-y[1])^2 <= (c["misc"]["Lr"] + c["misc"]["L_rd"])^2 )

  newConstraint!(n,LiDAR_range,:LiDAR_range)

  if goalRange(n)   # relax LiDAR boundary constraints
     setvalue(LiDAR_param_1, 1e6)
     setvalue(LiDAR_param_2,-1e6)
  end
  LiDAR_params = [LiDAR_param_1,LiDAR_param_2]
  return LiDAR_params
end

"""
--------------------------------------------------------------------------------------\n
Author: Huckleberry Febbo, Graduate Student, University of Michigan
Date Create: 4/08/2018, Last Modified: 5/30/2010 \n
--------------------------------------------------------------------------------------\n
"""
function objFunc!(n,c,tire_expr)

    if c["misc"]["followPath"]
        # follow the path -> min((X_path(Yt)-Xt)^2)
        #if c["misc"]["trackFunc"]==:poly
        path_obj = @NLexpression(mdl,c.w.path*sum(  (  (c.t.a[1] + c.t.a[2]*r.x[(i+1),2] + c.t.a[3]*r.x[(i+1),2]^2 + c.t.a[4]*r.x[(i+1),2]^3 + c.t.a[5]*r.x[(i+1),2]^4) - r.x[(i+1),1]  )^2 for i in 1:n.numStatePoints-1)  );
        #elseif c["misc"]["trackFunc"]==:fourier
        #path_obj = @NLexpression(mdl,c.w.path*sum(  (  (c.t.a[1]*sin(c.t.b[1]*r.x[(i+1),1]+c.t.c[1]) + c.t.a[2]*sin(c.t.b[2]*r.x[(i+1),1]+c.t.c[2]) + c.t.a[3]*sin(c.t.b[3]*r.x[(i+1),1]+c.t.c[3]) + c.t.a[4]*sin(c.t.b[4]*r.x[(i+1),1]+c.t.c[4]) + c.t.a[5]*sin(c.t.b[5]*r.x[(i+1),1]+c.t.c[5]) + c.t.a[6]*sin(c.t.b[6]*r.x[(i+1),1]+c.t.c[6]) + c.t.a[7]*sin(c.t.b[7]*r.x[(i+1),1]+c.t.c[7]) + c.t.a[8]*sin(c.t.b[8]*r.x[(i+1),1]+c.t.c[8])+c.t.y0) - r.x[(i+1),2]  )^2 for i in 1:n.numStatePoints-1)  );
        #end
    else
        path_obj = 0
    end

  # parameters
  if goalRange(n)
   println("\n goal in range")
   @NLparameter(n.ocp.mdl, w_xf == c["weights"]["xf"])
   @NLparameter(n.ocp.mdl, w_goal_param == 0.0)
   @NLparameter(n.ocp.mdl, w_psi_param == 0.0)  # NOTE this param is not in use
  else
   @NLparameter(n.ocp.mdl, w_xf == 0.0)
   @NLparameter(n.ocp.mdl, w_goal_param == c["weights"]["goal"])
   @NLparameter(n.ocp.mdl, w_psi_param == c["weights"]["psi"])
  end

  obj_params = [w_goal_param,w_psi_param,w_xf]
  x = n.r.ocp.x[:,1];y = n.r.ocp.x[:,2]; # pointers to JuMP variables
  if isequal(c["misc"]["model"],:ThreeDOFv2)
    psi = n.r.ocp.x[:,5]
    sa = n.r.ocp.x[:,6]
    ax = n.r.ocp.x[:,8]
    sr = n.r.ocp.u[:,1]
    jx = n.r.ocp.u[:,2]
    ux = n.r.ocp.x[:,7]
  elseif isequal(c["misc"]["model"],:ThreeDOFv3)
    psi = n.r.ocp.x[:,5]
    sa = n.r.ocp.x[:,6]
    sr = n.r.ocp.u[:,1]
  elseif isequal(c["misc"]["model"],:KinematicBicycle2)
    psi = n.r.ocp.x[:,3]
    sa = n.r.ocp.u[:,1]
    ax = n.r.ocp.u[:,2]
  end

  # penalize distance to goal
  goal_obj = @NLexpression(n.ocp.mdl,w_goal_param*((x[end] - c["goal"]["x"])^2 + (y[end] - c["goal"]["yVal"])^2)^0.5/(((x[1] - c["goal"]["x"])^2 + (y[1] - c["goal"]["yVal"])^2)^0.5 + c["misc"]["EP"]))

  # minimizing the integral over the entire prediction horizon of the line that passes through the goal
  haf_obj=integrate!(n,:( $c["weights"]["haf"]*( sin($c["goal"]["psi"])*(x[j]-$c["goal"]["x"]) - cos($c["goal"]["psi"])*(y[j]-$c["goal"]["yVal"]) )^2 ))

  if c["misc"]["xFslackVariables"]
    xf_obj = @NLexpression(n.ocp.mdl, w_xf*(n.ocp.xFs[1] + n.ocp.xFs[2] +  n.ocp.xFs[5]) )
  else
    xf_obj = 0
  end

  if isequal(c["misc"]["model"],:ThreeDOFv2)
    # initial conditions slack variables
    if c["misc"]["x0slackVariables"]
      x0_obj = @NLexpression(n.ocp.mdl, c["weights"]["ic"]*(c["weights"]["x0"]*(n.ocp.x0s[1]) + c["weights"]["y0"]*(n.ocp.x0s[2]) + c["weights"]["v0"]*(n.ocp.x0s[3]) + c["weights"]["r0"]*(n.ocp.x0s[4]) + c["weights"]["psi0"]*(n.ocp.x0s[5]) + c["weights"]["sa0"]*(n.ocp.x0s[6]) + c["weights"]["ux0"]*(n.ocp.x0s[7]) + c["weights"]["ax0"]*(n.ocp.x0s[8])) )
    else
      x0_obj = 0
    end

    # penalize difference between final heading angle and angle relative to the goal NOTE currently this is broken becasue atan2() is not available
    #psi_frg=@NLexpression(n.ocp.mdl,asin(c.g.y_ref-y[end])/(acos(c.g.x_ref-x[end]) + c["misc"]["EP"]) )
    #psi_obj=@NLexpression(n.ocp.mdl,w_psi_param*(asin(sin(psi[end] - psi_frg))/(acos(cos(psi[end] - psi_frg)) + c["misc"]["EP"]) )^2 )
    psi_obj = 0
    # psi_obj=@NLexpression(n.ocp.mdl,w_psi_param*(asin(sin(psi[end] - asin(c.g.y_ref-y[end])/acos(c.g.x_ref-x[end])))/(acos(cos(psi[end] - asin(c.g.y_ref-y[end])/acos(c.g.x_ref-x[end]))) + c["misc"]["EP"]) )^2 )

    # soft constraints on vertical tire load
    tire_obj = integrate!(n,tire_expr)

    # penalize control effort
    ce_obj = integrate!(n,:($c["weights"]["ux"]/(ux[j] + $c["misc"]["EP"]) + $c["weights"]["ce"]*($c["weights"]["sa"]*(sa[j]^2)+$c["weights"]["sr"]*(sr[j]^2)+$c["weights"]["jx"]*(jx[j]^2))) )
    @NLobjective(n.ocp.mdl, Min,  xf_obj + goal_obj + x0_obj + psi_obj + c["weights"]["Fz"]*tire_obj + haf_obj + c["weights"]["time"]*n.ocp.tf + ce_obj )
  elseif isequal(c["misc"]["model"],:ThreeDOFv3)
      # initial conditions slack variables
      if c["misc"]["x0slackVariables"]
        x0_obj = @NLexpression(n.ocp.mdl, c["weights"]["ic"]*(c["weights"]["x0"]*(n.ocp.x0s[1]) + c["weights"]["y0"]*(n.ocp.x0s[2]) + c["weights"]["v0"]*(n.ocp.x0s[3]) + c["weights"]["r0"]*(n.ocp.x0s[4]) + c["weights"]["psi0"]*(n.ocp.x0s[5]) + c["weights"]["sa0"]*(n.ocp.x0s[6]) ) )
      else
        x0_obj = 0
      end
      psi_obj = 0
      # soft constraints on vertical tire load
      tire_obj = integrate!(n,tire_expr)

      # penalize control effort
      ce_obj = integrate!(n,:($c["weights"]["ce"]*($c["weights"]["sa"]*(sa[j]^2)+$c["weights"]["sr"]*(sr[j]^2))) )
      @NLobjective(n.ocp.mdl, Min, xf_obj + goal_obj + x0_obj + psi_obj + c["weights"]["Fz"]*tire_obj + haf_obj + c["weights"]["time"]*n.ocp.tf + ce_obj )

  elseif isequal(c["misc"]["model"],:KinematicBicycle2)
    # penalize control effort
    ce_obj = integrate!(n,:($c["weights"]["ce"]*($c["weights"]["sa"]*sa[j]^2 + $c["weights"]["ax"]*ax[j]^2 ) ) )
    @NLobjective(n.ocp.mdl, Min, xf_obj + goal_obj + c["weights"]["time"]*n.ocp.tf + ce_obj + haf_obj )
  else
    error("Please select use a model in the misc YAML params. Either ThreeDOFv2 or KinematicBicycle2.")
  end
 return obj_params
end

end # module


text:
using PyCall
using JSON
using DataStructures
using LinearAlgebra

export TensorNetworkCircuit
export Node, Edge, add_gate!, edges, getnode, getedge
export new_label!, add_input!, add_output!
export load_qasm_as_circuit_from_file, load_qasm_as_circuit
export convert_qiskit_circ_to_network, transpile_circuit
export to_dict, to_json, network_from_dict, edge_from_dict, node_from_dict
export network_from_json
export inneighbours, outneighbours, virtualneighbours, neighbours
export inedges, outedges, virtualedges
export decompose_gate!
export gate_tensor

# *************************************************************************** #
#           Tensor network circuit data structure and functions
# *************************************************************************** #

"Struct to represent a node in tensor network graph"
struct Node
    # the indices that the node contains.
    indices::Array{Symbol, 1}
    # tensor dimensions.
    dims::Array{Int, 1}
    # The symbol used to identify the tensor associated with this node.
    data_label::Symbol
end

"""
    Node(node_label::Symbol)

Outer constructor to create an instance of Node with the given node label and no
index labels
"""
function Node(data_label::Symbol)
    Node(Array{Symbol, 1}(), Array{Int, 1}(), data_label)
end

"""Struct to represent an edge"""
mutable struct Edge
    src::Union{Symbol, Nothing}
    dst::Union{Symbol, Nothing}
    qubit::Union{Int, Nothing}
    virtual::Bool
    Edge(a::Union{Symbol, Nothing},
         b::Union{Symbol, Nothing},
         qubit::Union{Int, Nothing},
         virtual::Bool=false) = new(a, b, qubit, virtual)
    Edge() = new(nothing, nothing, nothing, false)
end

"""Struct for tensor network graph of a circuit"""
struct TensorNetworkCircuit
    # Backend used to store tensors
    #FIXME: This should be parameterised, along with the TensorNetworkCircuit
    backend::AbstractBackend

    number_qubits::Int

    # Reference to indices connecting to input qubits
    input_qubits::Array{Symbol, 1}

    # Reference to indices connecting to output qubits
    output_qubits::Array{Symbol, 1}

    # Map of nodes by symbol
    nodes::OrderedDict{Symbol, Node}

    # Dictionary of edges indexed by index symbol where value is node pair
    edges::OrderedDict{Symbol, Edge}

    # Array with indices of quantum register positions corresponding to
    # each classical register position
    qubit_ordering::Array{Int, 1}

    # implementation details, not shared outside module
    # counters for assigning unique symbol names to nodes and indices
    counters::Dict{String, Int}

    # maps nodes to layer numbers
    node_layers::Dict{Symbol, Int}
end

"""
    TensorNetworkCircuit(qubits::Int, backend::AbstractBackend=InteractiveBackend())

Outer constructor to create an instance of TensorNetworkCircuit for an empty
circuit with the given number of qubits.
"""
function TensorNetworkCircuit(qubits::Int, backend::AbstractBackend=InteractiveBackend())
    # Create labels for edges of the network connecting input to output qubits
    index_labels = [Symbol("index_", i) for i in 1:qubits]

    # Create dictionary map from index label to node positions
    edges = OrderedDict{Symbol, Edge}()
    for i in 1:qubits
        edges[index_labels[i]] = Edge(nothing, nothing, i, false)
    end

    input_indices = index_labels
    output_indices = copy(index_labels)

    nodes = OrderedDict{Symbol, Node}()

    counters = Dict{String, Int}()
    counters["index"] = qubits
    counters["node"] = 0
    counters["layer"] = 0

    node_layers = Dict{Symbol, Int}()

    # Create the tensor network
    TensorNetworkCircuit(backend, qubits, input_indices, output_indices, nodes,
                         edges, collect(1:qubits), counters, node_layers)
end

# *************************************************************************** #
#                  TensorNetworkCircuit backend functions
# *************************************************************************** #

for func in [:save_tensor_data , :load_tensor_data, :contract_tensors, :save_output,
             :reshape_tensor, :permute_tensor, :decompose_tensor!, :delete_tensor!,
             :view_tensor!]
    @eval begin
        #TODO: Is this iffy since the generated functions will expect an AbstractBackend
        #      rather than a parameterised backend
        function $func(network::TensorNetworkCircuit, args...; kwargs...)
            $func(network.backend, args...; kwargs...)
        end
    end
end

# *************************************************************************** #
#                  TensorNetworkCircuit mutation functions
# *************************************************************************** #

"""
    new_label!(network::TensorNetworkCircuit, label_str)

Function to create a unique symbol by incrememting relevant counter
"""
function new_label!(network::TensorNetworkCircuit, label_str)
    network.counters[label_str] += 1
    Symbol(label_str, "_", network.counters[label_str])
end

"""
    add_gate!(network::TensorNetworkCircuit,
                       gate_data::Array{<:Number},
                       targetqubits::Array{Int, 1};
                       decompose::Bool=false)

Add a node to the tensor network for the given gate acting on the given quibits
"""
function add_gate!(network::TensorNetworkCircuit,
                   gate_data::Array{<:Number},
                   target_qubits::Array{Int,1};
                   decompose::Bool=false)

    n = length(target_qubits)
    # create new indices for connecting gate to outputs
    input_indices = [network.output_qubits[i] for i in target_qubits]
    output_indices = [new_label!(network, "index") for _ in 1:n]

    # remap output qubits
    for (i, target_qubit) in enumerate(target_qubits)
        network.output_qubits[target_qubit] = output_indices[i]
    end

    layer = network.counters["layer"] += 1
    if decompose && n == 2
        gates_data = decompose_gate!(gate_data)
        virtual_index = new_label!(network, "index")
        node_labels = [new_label!(network, "node") for x in 1:2]
        network.edges[virtual_index] = Edge(node_labels[1],
                                            node_labels[2],
                                            nothing,
                                            true)
        for i in 1:2
            node_label = node_labels[i]
            data_label = node_label
            if i == 1
                indices = [input_indices[i], output_indices[i], virtual_index]
            else
                indices = [virtual_index, input_indices[i], output_indices[i]]
            end
            new_node = Node(indices, [size(gates_data[i])...], data_label)
            network.nodes[node_label] = new_node
            network.node_layers[node_label] = layer

            # Save the gate data to the executer
            save_tensor_data(network, data_label, gates_data[i])

            input_index = input_indices[i]
            output_index = output_indices[i]
            network.edges[output_index] = Edge(node_label,
                                               network.edges[input_index].dst,
                                               target_qubits[i])

            # if there is an output node, we need to update incoming index
            if network.edges[input_index].dst !== nothing
                out_node = network.nodes[network.edges[input_index].dst]
                for i in 1:length(out_node.indices)
                    if out_node.indices[i] == input_index
                        out_node.indices[i] = output_index
                    end
                end
            end
            network.edges[input_index].dst = node_label
        end
        return node_labels
    else
        # create a node object for the gate
        # TODO: Having data_label equal the node_label is redundant
        # but thinking these can differ when avoiding duplication of tensor data.
        node_label = new_label!(network, "node")
        data_label = node_label
        new_node = Node(vcat(input_indices, output_indices), [size(gate_data)...], data_label)
        network.nodes[node_label] = new_node
        network.node_layers[node_label] = layer

        # Save the gate data to the executer
        save_tensor_data(network, data_label, gate_data)

        # remap nodes that edges are connected to
        for qubit in 1:length(input_indices)
            input_index = input_indices[qubit]
            output_index = output_indices[qubit]
            network.edges[output_index] = Edge(node_label,
                                               network.edges[input_index].dst,
                                               target_qubits[qubit])

            # if there is an output node, we need to update incoming index
            if network.edges[input_index].dst !== nothing
                out_node = network.nodes[network.edges[input_index].dst]
                for i in 1:length(out_node.indices)
                    if out_node.indices[i] == input_index
                        out_node.indices[i] = output_index
                    end
                end
            end
            network.edges[input_index].dst = node_label
        end
        return node_label
    end
end

"""
    add_input!(network::TensorNetworkCircuit, config::String)

Function to add input nodes to a tensor network circuit with a given input
configuration
"""
function add_input!(network::TensorNetworkCircuit, config::String)
    @assert length(config) == network.number_qubits
    for (input_index, config_char) in zip(network.input_qubits, config)

        # add guard to check if node does not already exist
        if network.edges[input_index].src === nothing
            node_label = new_label!(network, "node")
            data_label = node_label

            node_map = Dict('0' => [1., 0],
                            '1' => [0., 1.],
                            '+' => 1/sqrt(2)*[1., 1.],
                            '-' => 1/sqrt(2)*[1., -1.])

            node_data = node_map[config_char]
            dims = [size(node_data)...]
            network.nodes[node_label] = Node([input_index], dims, data_label)
            network.node_layers[node_label] = 0

            # Save the gate data to the executer
            save_tensor_data(network, data_label, node_data)

            network.edges[input_index].src = node_label
        else
            @warn "Input node already exists"
        end
    end
end

"""
    add_output!(network::TensorNetworkCircuit, config::String)

Function to add output nodes to a tensor network circuit with a given output
configuration
"""
function add_output!(network::TensorNetworkCircuit, config::String)
    @assert length(config) == network.number_qubits
    for i in 1:network.number_qubits
        qubit_pos = network.qubit_ordering[i]
        output_index, config_char = network.output_qubits[qubit_pos], config[i]
        # add guard to check if node does not already exist
        if network.edges[output_index].dst === nothing
            node_label = new_label!(network, "node")
            data_label = node_label

            node_data = (config_char == '0') ? [1., 0.] : [0., 1.]
            dims = [size(node_data)...]
            network.nodes[node_label] = Node([output_index], dims, data_label)
            network.node_layers[node_label] = -1

            # Save the gate data to the executer
            save_tensor_data(network, data_label, node_data)

            network.edges[output_index].dst = node_label
        end
    end
end

# *************************************************************************** #
#                  TensorNetworkCircuit accessor functions
# *************************************************************************** #

"""
    edges(network::TensorNetworkCircuit)

Function to return edges from a given network
"""
function edges(network::TensorNetworkCircuit)
    network.edges
end

"""
    inneighbours(network::TensorNetworkCircuit,
                          node_label::Symbol)

Function to return the nodes which are connected to the given node with
incoming edges
"""
function inneighbours(network::TensorNetworkCircuit,
                      node_label::Symbol)
    node = network.nodes[node_label]
    myarray = Array{Symbol, 1}()
    for index in node.indices
        edge = network.edges[index]
        if edge.src !== nothing && edge.src != node_label && !edge.virtual
            push!(myarray, edge.src)
        end
    end
    myarray
end

"""
    outneighbours(network::TensorNetworkCircuit,
                           node_label::Symbol)

Function to return the nodes which are connected to the given node with
outgoing edges
"""
function outneighbours(network::TensorNetworkCircuit,
                       node_label::Symbol)
    node = network.nodes[node_label]
    myarray = Array{Symbol, 1}()
    for index in node.indices
        edge = network.edges[index]
        if edge.dst !== nothing && edge.dst != node_label && !edge.virtual
            push!(myarray, edge.dst)
        end
    end
    myarray
end

"""
    virtualneighbours(network::TensorNetworkCircuit,
                           node_label::Symbol)

Function to return the nodes which are connected to the given node with
virtual edges
"""
function virtualneighbours(network::TensorNetworkCircuit,
                           node_label::Symbol)
    node = network.nodes[node_label]
    myarray = Array{Symbol, 1}()
    for index in node.indices
        edge = network.edges[index]
        if edge.dst !== nothing && edge.dst != node_label && edge.virtual
            push!(myarray, edge.dst)
        elseif edge.src !== nothing && edge.src != node_label && edge.virtual
            push!(myarray, edge.src)
        end
    end
    myarray
end

"""
    neighbours(network::TensorNetworkCircuit,

Function to get all neighbouring nodes of the given node (incoming, outgoing and
virtual)
"""
function neighbours(network::TensorNetworkCircuit,
                    node_label::Symbol)
    vcat(inneighbours(network, node_label),
         outneighbours(network, node_label),
         virtualneighbours(network, node_label))
end

"""
    inedges(network::TensorNetworkCircuit,
                     node_label::Symbol)

Function to get all incoming edges to the current node
"""
function inedges(network::TensorNetworkCircuit,
                 node_label::Symbol)
    idxs = network.nodes[node_label].indices
    [x for x in idxs if !network.edges[x].virtual && network.edges[x].dst == node_label]
end

"""
    outedges(network::TensorNetworkCircuit,
                      node_label::Symbol)

Function to get all outgoing edges to the current node
"""
function outedges(network::TensorNetworkCircuit,
                  node_label::Symbol)
    idxs = network.nodes[node_label].indices
    [x for x in idxs if !network.edges[x].virtual && network.edges[x].src == node_label]
end

"""
    virtualedges(network::TensorNetworkCircuit,
                          node_label::Symbol)

Function to get all virtual edges connected to the current node
"""
function virtualedges(network::TensorNetworkCircuit,
                  node_label::Symbol)
    idxs = network.nodes[node_label].indices
    [x for x in idxs if network.edges[x].virtual]
end

"""
    function getedge(network::TensorNetworkCircuit, edge_label::Symbol)

Return the requested edge
"""
function getedge(network::TensorNetworkCircuit, edge_label::Symbol)
    network.edges[edge_label]
end

"""
    function getnode(network::TensorNetworkCircuit, node_label::Symbol)

Return the requested node
"""
function getnode(network::TensorNetworkCircuit, node_label::Symbol)
    network.nodes[node_label]
end

# *************************************************************************** #
#                  Functions to read circuit from qasm
# *************************************************************************** #

"""
    load_qasm_as_circuit_from_file(qasm_path::String)

Function to load qasm from the given path and return a qiskit circuit
"""
function load_qasm_as_circuit_from_file(qasm_path::String)
    qiskit = pyimport("qiskit")
    if isfile(qasm_path)
        return qiskit.QuantumCircuit.from_qasm_file(qasm_path)
    else
        return false
    end
end

"""
    load_qasm_as_circuit(qasm_str::String)

Function to load qasm from a given qasm string and return a qiskit circuit
"""
function load_qasm_as_circuit(qasm_str::String)
    qiskit = pyimport("qiskit")
    return qiskit.QuantumCircuit.from_qasm_str(qasm_str)
end

"""
    transpile_circuit(circ,
                               couplings::Union{Nothing,
                               Array{Array{Int, 1}, 1})} = nothing))

Transpile circuit so only neighbouring qubits have gates applied to them
"""
function transpile_circuit(circ,
                           couplings::Union{Nothing,
                           Array{Array{Int, 1}, 1}} = nothing)
    n_qubits = convert(Int, circ.n_qubits)
    transpiler = pyimport("qiskit.transpiler")
    passes = pyimport("qiskit.transpiler.passes")

    # create a copy of the circuit with measurements removed to prevent
    # duplicate measurements
    circ = circ.remove_final_measurements(inplace=false)
    # now add measurments to all output qubits
    circ.measure_all()

    if couplings === nothing
        couplings = [[i-1, i] for i = 1:n_qubits]
    end
    coupling_map = transpiler.CouplingMap(
                    PyCall.array2py([PyCall.array2py(x) for x in couplings]))

    pass = passes.BasicSwap(coupling_map=coupling_map)
    pass_manager = transpiler.PassManager(pass)
    circ = pass_manager.run(circ)

    # now look at measurements and find out if any qubits have been remapped
    ngates = length(circ.data)
    qubit_ordering = zeros(Int, n_qubits)
    for i in (ngates-n_qubits):ngates-1
        measurement = get(circ, i)
        qbit = measurement[2][1].index
        cbit = measurement[3][1].index
        qubit_ordering[cbit+1] = qbit + 1
    end
    circ.remove_final_measurements()
    circ, qubit_ordering
end

"""
    convert_qiskit_circ_to_network(circ, backend::AbstractBackend=InteractiveBackend();
                                            decompose::Bool=false,
                                            transpile::Bool=false,
                                            couplings::Union{Nothing,
                                                            Array{<: Array{<:Integer, 1}, 1}}
                                                            = nothing))

Given a qiskit circuit object, this function will convert this to a tensor
network circuit with a backend set to `backend`. If the decompose option is
true it will decompose two qubit gates to two tensors acting on each qubit
with a virtual bond connecting them. The transpile option will transpile the
circuit using the basicswap pass from qiskit to ensure that two qubit gates
are only applied between neighbouring qubits.
"""
function convert_qiskit_circ_to_network(circ, backend::AbstractBackend=InteractiveBackend();
                                        decompose::Bool=false,
                                        transpile::Bool=false,
                                        couplings::Union{Nothing,
                                                         Array{<: Array{<:Integer, 1}, 1}}
                                                         = nothing)
    barrier = pyimport("qiskit.extensions.standard.barrier")
    qi = pyimport("qiskit.quantum_info")
    n_qubits = convert(Int, circ.n_qubits)
    if transpile
        circ, qubit_ordering = transpile_circuit(circ, couplings)
    else
        qubit_ordering = collect(1:n_qubits)
    end

    tng = TensorNetworkCircuit(n_qubits, backend)
    tng.qubit_ordering[:] = qubit_ordering[:]
    for gate in circ.data
        # If the gate is a barrier then skip it
        if ! pybuiltin(:isinstance)(gate[1], barrier.Barrier)
            # Need to add 1 to index when converting from python
            target_qubits = [target.index+1 for target in gate[2]]
            dims = [2 for i = 1:2*length(target_qubits)]
            data = permutedims(qi.Operator(gate[1]).data, (2, 1))
            data = reshape(data, dims...)
            add_gate!(tng, data, target_qubits, decompose=decompose)
        end
    end
    tng
end

# *************************************************************************** #
#                    To/from functions for TN circuits
# *************************************************************************** #

"""
    to_dict(edge::Edge)

Function to convert an edge instance to a serialisable dictionary
"""
function to_dict(edge::Edge)
    Dict("src" => edge.src, "dst" => edge.dst,
         "virtual" => edge.virtual, "qubit" => edge.qubit)
end

"""
    edge_from_dict(dict::Dict)

Function to create an edge instance from a dictionary
"""
function edge_from_dict(d::AbstractDict)
    Edge((d["src"] === nothing) ? nothing : Symbol(d["src"]),
         (d["dst"] === nothing) ? nothing : Symbol(d["dst"]),
         d["qubit"],
         d["virtual"])
end

"""
    to_dict(node::Node)

Function to serialise node instance to json format
"""
function to_dict(node::Node)
    node_dict = Dict{String, Any}("indices" => [String(x) for x in node.indices])
    node_dict["dims"] = string.(node.dims)
    node_dict["data_label"] = String(node.data_label)
    node_dict
end

"""
    node_from_dict(d::Dict)

Function to create a node instance from a json string
"""
function node_from_dict(d::AbstractDict)
    indices = [Symbol(x) for x in d["indices"]]
    dims = parse.(Int, d["dims"])
    data_label = Symbol(d["data_label"])
    Node(indices, dims, data_label)
end

"""
    to_dict(network::TensorNetworkCircuit)

Convert a tensor network to a nested dictionary
"""
function to_dict(network::TensorNetworkCircuit)
    top_level = OrderedDict{String, Any}()
    top_level["number_qubits"] = network.number_qubits
    top_level["edges"] = OrderedDict(String(k) => to_dict(v)
                                for (k,v) in pairs(network.edges))

    top_level["nodes"] = OrderedDict{String, Any}()
    nodes_dict = top_level["nodes"]
    for (node_label, node) in pairs(network.nodes)
        nodes_dict[String(node_label)] = to_dict(node)
    end
    top_level["input_qubits"] = [String(x) for x in network.input_qubits]
    top_level["output_qubits"] = [String(x) for x in network.output_qubits]
    top_level["qubit_ordering"] = [x for x in network.qubit_ordering]
    top_level["node_layers"] = OrderedDict(String(k) => v
                                           for (k, v) in network.node_layers)
    top_level
end

"""
    network_from_dict(dict::Dict{String, Any}, backend::AbstractBackend=InteractiveBackend())

Convert a dictionary to a tensor network with a given backend (default: InteractiveBackend)
"""
function network_from_dict(dict::AbstractDict{String, Any}, backend::AbstractBackend=InteractiveBackend())
    number_qubits = dict["number_qubits"]
    # initialise counters
    counters = Dict("index" => 0, "node" => 0)

    # Get the index map and convert it to the right type
    edges = OrderedDict{Symbol, Edge}()
    for (k,v) in pairs(dict["edges"])
        edge_num = parse(Int64, split(k, "_")[end])
        counters["index"] = max(counters["index"], edge_num)
        edges[Symbol(k)] = edge_from_dict(v)
    end

    nodes = OrderedDict{Symbol, Node}()
    for (k, v) in dict["nodes"]
        node_num = parse(Int64, split(k, "_")[end])
        counters["node"] = max(counters["node"], node_num)
        nodes[Symbol(k)] = node_from_dict(v)
    end

    input_qubits = [Symbol(x) for x in dict["input_qubits"]]
    output_qubits = [Symbol(x) for x in dict["output_qubits"]]
    qubit_ordering = dict["qubit_ordering"]
    node_layers = Dict(Symbol(k) => v for (k, v) in dict["node_layers"])

    TensorNetworkCircuit(backend, number_qubits, input_qubits, output_qubits, nodes,
                         edges, qubit_ordering, counters, node_layers)
end

"""
    to_json(tng::TensorNetworkCircuit)

Convert a tensor network to a json string
"""
function to_json(tng::TensorNetworkCircuit, indent::Int=0)
    dict = to_dict(tng)
    if indent == 0
        return JSON.json(dict)
    else
        return JSON.json(dict, indent)
    end
end

"""
    network_from_json(json_str::String, backend::AbstractBackend=InteractiveBackend())

Convert a json string to a tensor network
"""
function network_from_json(json_str::String, backend::AbstractBackend=InteractiveBackend())
    dict = JSON.parse(json_str, dicttype=OrderedDict)
    network_from_dict(dict, backend)
end

"""
    decompose_gate!(gate_data::Array{<:Number, 4},
                             threshold::AbstractFloat=1e-15)

Decompose a tensor into two smaller tensors
"""
function decompose_gate!(gate_data::Array{<:Number, 4},
                         threshold::AbstractFloat=1e-15)
    left_positions = [1, 3]
    right_positions = [2, 4]
    dims = size(gate_data)
    left_dims = [dims[x] for x in left_positions]
    right_dims = [dims[x] for x in right_positions]

    A = permutedims(gate_data, vcat(left_positions, right_positions))
    A = reshape(A, Tuple([prod(left_dims), prod(right_dims)]))

    # Use SVD here but QR could also be used
    F = svd(A)

    # find number of singular values above the threshold
    chi = sum(F.S .> threshold)
    s = sqrt.(F.S[1:chi])

    # assume that singular values and basis of U and V matrices are sorted
    # in descending order of singular value
    B = reshape(F.U[:, 1:chi] * Diagonal(s), Tuple(vcat(left_dims, [chi,])))
    C = reshape(Diagonal(s) * F.Vt[1:chi, :], Tuple(vcat([chi,], right_dims)))

    return B, C
end

# *************************************************************************** #
#                            User utility functions
# *************************************************************************** #

# A dictionary of commonly used quantum gates.
const GATE_TENSORS = Dict{Symbol, Array{ComplexF64}}()

GATE_TENSORS[:I] = [1 0; 0 1]
GATE_TENSORS[:X] = [0 1; 1 0]
GATE_TENSORS[:Y] = [0 -1im; 1im 0]
GATE_TENSORS[:Z] = [1 0; 0 -1]

GATE_TENSORS[:H] = [1 1; 1 -1]/sqrt(2)
GATE_TENSORS[:S] = [1 0; 0 1im]
GATE_TENSORS[:T] = [1 0; 0 (1 + 1im)/sqrt(2)]

GATE_TENSORS[:CX] = reshape([1 0 0 0; 0 0 0 1; 0 0 1 0; 0 1 0 0], 2, 2, 2, 2)
GATE_TENSORS[:CZ] = reshape([1 0 0 0; 0 1 0 0; 0 0 1 0; 0 0 0 -1], 2, 2, 2, 2)
GATE_TENSORS[:SWAP] = reshape([1 0 0 0; 0 0 1 0; 0 1 0 0; 0 0 0 1], 2, 2, 2, 2)

# Toffoli gate
# TOFF = zeros(8,8); TOFF[7,8] = 1; TOFF[8,7] = 1
# for i = 1:6
#     TOFF[i,i] = 1
# end
# GATE_TENSORS[:TOFF] = reshape(TOFF, 2, 2, 2, 2, 2, 2)

# # IBM gates
# GATE_TENSORS[:U3] = function (θ::Real,ϕ::Real,λ::Real)
#     [cos(θ/2) -exp(1im*λ)*sin(θ/2);
#      exp(1im*ϕ)*sin(θ/2) exp(1im*(ϕ+λ))*cos(θ/2)]
# end

# GATE_TENSORS[:U2] = function (ϕ::Real, λ::Real)
#     GATE_TENSORS[:U3](0, ϕ, λ)
# end

# GATE_TENSORS[:U1] = function (λ::Real)
#     GATE_TENSORS[:U3](0, 0, λ)
# end

"""
    gate_tensor(gate::Symbol)

Function to return tensors commonly used in quantum circuits. Input argument
should be one of the following symbols:
:I, :X, :Y, :Z, :H, :S, :T, :CX, :CZ, :SWAP, :TOFF, :U3, :U2, :U1
"""
function gate_tensor(gate::Symbol)

    if !(gate in keys(GATE_TENSORS))
        inputs = [":$key" for key in keys(GATE_TENSORS)]
        println("Accepted input: ", join(inputs, ", "))
        error("Invalid input to gate_tensor: $(gate)")
    end

    gate = GATE_TENSORS[gate]
    if typeof(gate) <: Array{<:Number}
        return copy(gate)
    end
    gate
end


text:
# Author: Youngjun Kim, youngjun@stanford.edu
# Date: 06/16/2014

module AbstractSensorImpl

export AbstractSensor

abstract AbstractSensor

end

module AbstractSensorInterfaces

export updateSensor

function updateSensor()
end

end

max_stars_repo_name: sisl/RLES-SISLES

max_issues_repo_name: sisl/RLES-SISLES

max_forks_repo_name: sisl/RLES-SISLES


text:
using Test
using SymDiff

@test simplify(Val{:+}, :(a + b + 0)) == :(a + b)
@test simplify(Val{:+}, :(a + 0)) == :a

@test simplify(Val{:-}, :(a - b - 0)) == :(a - b)
@test simplify(Val{:-}, :(a - 0)) == :a


text:
################################################################################
# InteratomicPotentials API default implementations
################################################################################

get_rcutoff(::AbstractPotential) = Inf
get_species(::AbstractPotential) = missing

potential_energy(s::AbstractSystem, p::AbstractPotential) = energy_and_force(s, p).e
force(s::AbstractSystem, p::AbstractPotential) = energy_and_force(s, p).f
virial(s::AbstractSystem, p::AbstractPotential) = sum(virial_stress(s, p)[1:3])


text:
# This file was generated, do not modify it. # hide
fi = rpt.evo_tree_classifier.feature_importances
feature_importance_table =
    (feature=Symbol.(first.(fi)), importance=last.(fi)) |> DataFrames.DataFrame

max_stars_repo_name: alan-turing-institute/MLJTutorials

max_issues_repo_name: alan-turing-institute/MLJTutorials

max_forks_repo_name: alan-turing-institute/MLJTutorials