text:
# This file is auto-generated by AWSMetadata.jl
using AWS
using AWS.AWSServices: appflow
using AWS.Compat
using AWS.UUIDs
"""
    CreateConnectorProfile()

 Creates a new connector profile associated with your AWS account. There is a soft quota of 100 connector profiles per AWS account. If you need more connector profiles than this quota allows, you can submit a request to the Amazon AppFlow team through the Amazon AppFlow support channel.

# Required Parameters
- `connectionMode`:  Indicates the connection mode and specifies whether it is public or private. Private flows use AWS PrivateLink to route data over AWS infrastructure without exposing it to the public internet.
- `connectorProfileConfig`:  Defines the connector-specific configuration and credentials.
- `connectorProfileName`:  The name of the connector profile. The name is unique for each ConnectorProfile in your AWS account.
- `connectorType`:  The type of connector, such as Salesforce, Amplitude, and so on.

# Optional Parameters
- `kmsArn`:  The ARN (Amazon Resource Name) of the Key Management Service (KMS) key you provide for encryption. This is required if you do not want to use the Amazon AppFlow-managed KMS key. If you don't provide anything here, Amazon AppFlow uses the Amazon AppFlow-managed KMS key.
"""

create_connector_profile(connectionMode, connectorProfileConfig, connectorProfileName, connectorType; aws_config::AWSConfig=global_aws_config()) = appflow("POST", "/create-connector-profile", Dict{String, Any}("connectionMode"=>connectionMode, "connectorProfileConfig"=>connectorProfileConfig, "connectorProfileName"=>connectorProfileName, "connectorType"=>connectorType); aws_config=aws_config)
create_connector_profile(connectionMode, connectorProfileConfig, connectorProfileName, connectorType, args::AbstractDict{String, <:Any}; aws_config::AWSConfig=global_aws_config()) = appflow("POST", "/create-connector-profile", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("connectionMode"=>connectionMode, "connectorProfileConfig"=>connectorProfileConfig, "connectorProfileName"=>connectorProfileName, "connectorType"=>connectorType), args)); aws_config=aws_config)

"""
    CreateFlow()

 Enables your application to create a new flow using Amazon AppFlow. You must create a connector profile before calling this API. Please note that the Request Syntax below shows syntax for multiple destinations, however, you can only transfer data to one item in this list at a time. Amazon AppFlow does not currently support flows to multiple destinations at once.

# Required Parameters
- `destinationFlowConfigList`:  The configuration that controls how Amazon AppFlow places data in the destination connector.
- `flowName`:  The specified name of the flow. Spaces are not allowed. Use underscores (_) or hyphens (-) only.
- `sourceFlowConfig`:  The configuration that controls how Amazon AppFlow retrieves data from the source connector.
- `tasks`:  A list of tasks that Amazon AppFlow performs while transferring the data in the flow run.
- `triggerConfig`:  The trigger settings that determine how and when the flow runs.

# Optional Parameters
- `description`:  A description of the flow you want to create.
- `kmsArn`:  The ARN (Amazon Resource Name) of the Key Management Service (KMS) key you provide for encryption. This is required if you do not want to use the Amazon AppFlow-managed KMS key. If you don't provide anything here, Amazon AppFlow uses the Amazon AppFlow-managed KMS key.
- `tags`:  The tags used to organize, track, or control access for your flow.
"""

create_flow(destinationFlowConfigList, flowName, sourceFlowConfig, tasks, triggerConfig; aws_config::AWSConfig=global_aws_config()) = appflow("POST", "/create-flow", Dict{String, Any}("destinationFlowConfigList"=>destinationFlowConfigList, "flowName"=>flowName, "sourceFlowConfig"=>sourceFlowConfig, "tasks"=>tasks, "triggerConfig"=>triggerConfig); aws_config=aws_config)
create_flow(destinationFlowConfigList, flowName, sourceFlowConfig, tasks, triggerConfig, args::AbstractDict{String, <:Any}; aws_config::AWSConfig=global_aws_config()) = appflow("POST", "/create-flow", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("destinationFlowConfigList"=>destinationFlowConfigList, "flowName"=>flowName, "sourceFlowConfig"=>sourceFlowConfig, "tasks"=>tasks, "triggerConfig"=>triggerConfig), args)); aws_config=aws_config)

"""
    DeleteConnectorProfile()

 Enables you to delete an existing connector profile.

# Required Parameters
- `connectorProfileName`:  The name of the connector profile. The name is unique for each ConnectorProfile in your account.

# Optional Parameters
- `forceDelete`:  Indicates whether Amazon AppFlow should delete the profile, even if it is currently in use in one or more flows.
"""

delete_connector_profile(connectorProfileName; aws_config::AWSConfig=global_aws_config()) = appflow("POST", "/delete-connector-profile", Dict{String, Any}("connectorProfileName"=>connectorProfileName); aws_config=aws_config)
delete_connector_profile(connectorProfileName, args::AbstractDict{String, <:Any}; aws_config::AWSConfig=global_aws_config()) = appflow("POST", "/delete-connector-profile", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("connectorProfileName"=>connectorProfileName), args)); aws_config=aws_config)

"""
    DeleteFlow()

 Enables your application to delete an existing flow. Before deleting the flow, Amazon AppFlow validates the request by checking the flow configuration and status. You can delete flows one at a time.

# Required Parameters
- `flowName`:  The specified name of the flow. Spaces are not allowed. Use underscores (_) or hyphens (-) only.

# Optional Parameters
- `forceDelete`:  Indicates whether Amazon AppFlow should delete the flow, even if it is currently in use.
"""

delete_flow(flowName; aws_config::AWSConfig=global_aws_config()) = appflow("POST", "/delete-flow", Dict{String, Any}("flowName"=>flowName); aws_config=aws_config)
delete_flow(flowName, args::AbstractDict{String, <:Any}; aws_config::AWSConfig=global_aws_config()) = appflow("POST", "/delete-flow", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("flowName"=>flowName), args)); aws_config=aws_config)

"""
    DescribeConnectorEntity()

 Provides details regarding the entity used with the connector, with a description of the data model for each entity.

# Required Parameters
- `connectorEntityName`:  The entity name for that connector.

# Optional Parameters
- `connectorProfileName`:  The name of the connector profile. The name is unique for each ConnectorProfile in the AWS account.
- `connectorType`:  The type of connector application, such as Salesforce, Amplitude, and so on.
"""

describe_connector_entity(connectorEntityName; aws_config::AWSConfig=global_aws_config()) = appflow("POST", "/describe-connector-entity", Dict{String, Any}("connectorEntityName"=>connectorEntityName); aws_config=aws_config)
describe_connector_entity(connectorEntityName, args::AbstractDict{String, <:Any}; aws_config::AWSConfig=global_aws_config()) = appflow("POST", "/describe-connector-entity", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("connectorEntityName"=>connectorEntityName), args)); aws_config=aws_config)

"""
    DescribeConnectorProfiles()

 Returns a list of connector-profile details matching the provided connector-profile names and connector-types. Both input lists are optional, and you can use them to filter the result.  If no names or connector-types are provided, returns all connector profiles in a paginated form. If there is no match, this operation returns an empty list.

# Optional Parameters
- `connectorProfileNames`:  The name of the connector profile. The name is unique for each ConnectorProfile in the AWS account.
- `connectorType`:  The type of connector, such as Salesforce, Amplitude, and so on.
- `maxResults`:  Specifies the maximum number of items that should be returned in the result set. The default for maxResults is 20 (for all paginated API operations).
- `nextToken`:  The pagination token for the next page of data.
"""

describe_connector_profiles(; aws_config::AWSConfig=global_aws_config()) = appflow("POST", "/describe-connector-profiles"; aws_config=aws_config)
describe_connector_profiles(args::AbstractDict{String, Any}; aws_config::AWSConfig=global_aws_config()) = appflow("POST", "/describe-connector-profiles", args; aws_config=aws_config)

"""
    DescribeConnectors()

 Describes the connectors vended by Amazon AppFlow for specified connector types. If you don't specify a connector type, this operation describes all connectors vended by Amazon AppFlow. If there are more connectors than can be returned in one page, the response contains a nextToken object, which can be be passed in to the next call to the DescribeConnectors API operation to retrieve the next page.

# Optional Parameters
- `connectorTypes`:  The type of connector, such as Salesforce, Amplitude, and so on.
- `nextToken`:  The pagination token for the next page of data.
"""

describe_connectors(; aws_config::AWSConfig=global_aws_config()) = appflow("POST", "/describe-connectors"; aws_config=aws_config)
describe_connectors(args::AbstractDict{String, Any}; aws_config::AWSConfig=global_aws_config()) = appflow("POST", "/describe-connectors", args; aws_config=aws_config)

"""
    DescribeFlow()

 Provides a description of the specified flow.

# Required Parameters
- `flowName`:  The specified name of the flow. Spaces are not allowed. Use underscores (_) or hyphens (-) only.

"""

describe_flow(flowName; aws_config::AWSConfig=global_aws_config()) = appflow("POST", "/describe-flow", Dict{String, Any}("flowName"=>flowName); aws_config=aws_config)
describe_flow(flowName, args::AbstractDict{String, <:Any}; aws_config::AWSConfig=global_aws_config()) = appflow("POST", "/describe-flow", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("flowName"=>flowName), args)); aws_config=aws_config)

"""
    DescribeFlowExecutionRecords()

 Fetches the execution history of the flow.

# Required Parameters
- `flowName`:  The specified name of the flow. Spaces are not allowed. Use underscores (_) or hyphens (-) only.

# Optional Parameters
- `maxResults`:  Specifies the maximum number of items that should be returned in the result set. The default for maxResults is 20 (for all paginated API operations).
- `nextToken`:  The pagination token for the next page of data.
"""

describe_flow_execution_records(flowName; aws_config::AWSConfig=global_aws_config()) = appflow("POST", "/describe-flow-execution-records", Dict{String, Any}("flowName"=>flowName); aws_config=aws_config)
describe_flow_execution_records(flowName, args::AbstractDict{String, <:Any}; aws_config::AWSConfig=global_aws_config()) = appflow("POST", "/describe-flow-execution-records", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("flowName"=>flowName), args)); aws_config=aws_config)

"""
    ListConnectorEntities()

 Returns the list of available connector entities supported by Amazon AppFlow. For example, you can query Salesforce for Account and Opportunity entities, or query ServiceNow for the Incident entity.

# Optional Parameters
- `connectorProfileName`:  The name of the connector profile. The name is unique for each ConnectorProfile in the AWS account, and is used to query the downstream connector.
- `connectorType`:  The type of connector, such as Salesforce, Amplitude, and so on.
- `entitiesPath`:  This optional parameter is specific to connector implementation. Some connectors support multiple levels or categories of entities. You can find out the list of roots for such providers by sending a request without the entitiesPath parameter. If the connector supports entities at different roots, this initial request returns the list of roots. Otherwise, this request returns all entities supported by the provider.
"""

list_connector_entities(; aws_config::AWSConfig=global_aws_config()) = appflow("POST", "/list-connector-entities"; aws_config=aws_config)
list_connector_entities(args::AbstractDict{String, Any}; aws_config::AWSConfig=global_aws_config()) = appflow("POST", "/list-connector-entities", args; aws_config=aws_config)

"""
    ListFlows()

 Lists all of the flows associated with your account.

# Optional Parameters
- `maxResults`:  Specifies the maximum number of items that should be returned in the result set.
- `nextToken`:  The pagination token for next page of data.
"""

list_flows(; aws_config::AWSConfig=global_aws_config()) = appflow("POST", "/list-flows"; aws_config=aws_config)
list_flows(args::AbstractDict{String, Any}; aws_config::AWSConfig=global_aws_config()) = appflow("POST", "/list-flows", args; aws_config=aws_config)

"""
    ListTagsForResource()

 Retrieves the tags that are associated with a specified flow.

# Required Parameters
- `resourceArn`:  The Amazon Resource Name (ARN) of the specified flow.

"""

list_tags_for_resource(resourceArn; aws_config::AWSConfig=global_aws_config()) = appflow("GET", "/tags/$(resourceArn)"; aws_config=aws_config)
list_tags_for_resource(resourceArn, args::AbstractDict{String, <:Any}; aws_config::AWSConfig=global_aws_config()) = appflow("GET", "/tags/$(resourceArn)", args; aws_config=aws_config)

"""
    StartFlow()

 Activates an existing flow. For on-demand flows, this operation runs the flow immediately. For schedule and event-triggered flows, this operation activates the flow.

# Required Parameters
- `flowName`:  The specified name of the flow. Spaces are not allowed. Use underscores (_) or hyphens (-) only.

"""

start_flow(flowName; aws_config::AWSConfig=global_aws_config()) = appflow("POST", "/start-flow", Dict{String, Any}("flowName"=>flowName); aws_config=aws_config)
start_flow(flowName, args::AbstractDict{String, <:Any}; aws_config::AWSConfig=global_aws_config()) = appflow("POST", "/start-flow", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("flowName"=>flowName), args)); aws_config=aws_config)

"""
    StopFlow()

 Deactivates the existing flow. For on-demand flows, this operation returns an unsupportedOperationException error message. For schedule and event-triggered flows, this operation deactivates the flow.

# Required Parameters
- `flowName`:  The specified name of the flow. Spaces are not allowed. Use underscores (_) or hyphens (-) only.

"""

stop_flow(flowName; aws_config::AWSConfig=global_aws_config()) = appflow("POST", "/stop-flow", Dict{String, Any}("flowName"=>flowName); aws_config=aws_config)
stop_flow(flowName, args::AbstractDict{String, <:Any}; aws_config::AWSConfig=global_aws_config()) = appflow("POST", "/stop-flow", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("flowName"=>flowName), args)); aws_config=aws_config)

"""
    TagResource()

 Applies a tag to the specified flow.

# Required Parameters
- `resourceArn`:  The Amazon Resource Name (ARN) of the flow that you want to tag.
- `tags`:  The tags used to organize, track, or control access for your flow.

"""

tag_resource(resourceArn, tags; aws_config::AWSConfig=global_aws_config()) = appflow("POST", "/tags/$(resourceArn)", Dict{String, Any}("tags"=>tags); aws_config=aws_config)
tag_resource(resourceArn, tags, args::AbstractDict{String, <:Any}; aws_config::AWSConfig=global_aws_config()) = appflow("POST", "/tags/$(resourceArn)", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("tags"=>tags), args)); aws_config=aws_config)

"""
    UntagResource()

 Removes a tag from the specified flow.

# Required Parameters
- `resourceArn`:  The Amazon Resource Name (ARN) of the flow that you want to untag.
- `tagKeys`:  The tag keys associated with the tag that you want to remove from your flow.

"""

untag_resource(resourceArn, tagKeys; aws_config::AWSConfig=global_aws_config()) = appflow("DELETE", "/tags/$(resourceArn)", Dict{String, Any}("tagKeys"=>tagKeys); aws_config=aws_config)
untag_resource(resourceArn, tagKeys, args::AbstractDict{String, <:Any}; aws_config::AWSConfig=global_aws_config()) = appflow("DELETE", "/tags/$(resourceArn)", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("tagKeys"=>tagKeys), args)); aws_config=aws_config)

"""
    UpdateConnectorProfile()

 Updates a given connector profile associated with your account.

# Required Parameters
- `connectionMode`:  Indicates the connection mode and if it is public or private.
- `connectorProfileConfig`:  Defines the connector-specific profile configuration and credentials.
- `connectorProfileName`:  The name of the connector profile and is unique for each ConnectorProfile in the AWS Account.

"""

update_connector_profile(connectionMode, connectorProfileConfig, connectorProfileName; aws_config::AWSConfig=global_aws_config()) = appflow("POST", "/update-connector-profile", Dict{String, Any}("connectionMode"=>connectionMode, "connectorProfileConfig"=>connectorProfileConfig, "connectorProfileName"=>connectorProfileName); aws_config=aws_config)
update_connector_profile(connectionMode, connectorProfileConfig, connectorProfileName, args::AbstractDict{String, <:Any}; aws_config::AWSConfig=global_aws_config()) = appflow("POST", "/update-connector-profile", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("connectionMode"=>connectionMode, "connectorProfileConfig"=>connectorProfileConfig, "connectorProfileName"=>connectorProfileName), args)); aws_config=aws_config)

"""
    UpdateFlow()

 Updates an existing flow.

# Required Parameters
- `destinationFlowConfigList`:  The configuration that controls how Amazon AppFlow transfers data to the destination connector.
- `flowName`:  The specified name of the flow. Spaces are not allowed. Use underscores (_) or hyphens (-) only.
- `tasks`:  A list of tasks that Amazon AppFlow performs while transferring the data in the flow run.
- `triggerConfig`:  The trigger settings that determine how and when the flow runs.

# Optional Parameters
- `description`:  A description of the flow.
- `sourceFlowConfig`:
"""

update_flow(destinationFlowConfigList, flowName, tasks, triggerConfig; aws_config::AWSConfig=global_aws_config()) = appflow("POST", "/update-flow", Dict{String, Any}("destinationFlowConfigList"=>destinationFlowConfigList, "flowName"=>flowName, "tasks"=>tasks, "triggerConfig"=>triggerConfig); aws_config=aws_config)
update_flow(destinationFlowConfigList, flowName, tasks, triggerConfig, args::AbstractDict{String, <:Any}; aws_config::AWSConfig=global_aws_config()) = appflow("POST", "/update-flow", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("destinationFlowConfigList"=>destinationFlowConfigList, "flowName"=>flowName, "tasks"=>tasks, "triggerConfig"=>triggerConfig), args)); aws_config=aws_config)


text:
using ReadStat
using DataValues
using Test

@testset "ReadStat" begin

@testset "DTA files" begin

dtafile = joinpath(dirname(@__FILE__), "types.dta")
rsdf = read_dta(dtafile)
data = rsdf.data

@test length(data) == 6
@test rsdf.headers == [:vfloat, :vdouble, :vlong, :vint, :vbyte, :vstring]
@test data[1] == DataValueArray{Float32}([3.14, 7., NA])
@test data[2] == DataValueArray{Float64}([3.14, 7., NA])
@test data[3] == DataValueArray{Int32}([2, 7, NA])
@test data[4] == DataValueArray{Int16}([2, 7, NA])
@test data[5] == DataValueArray{Int8}([2, 7., NA])
@test data[6] == DataValueArray{String}(["2", "7", ""])
end

@testset "SAV files" begin

dtafile = joinpath(dirname(@__FILE__), "types.sav")
rsdf = read_sav(dtafile)
data = rsdf.data

@test length(data) == 6
@test rsdf.headers == [:vfloat, :vdouble, :vlong, :vint, :vbyte, :vstring]
@test data[1] == DataValueArray{Float32}([3.14, 7., NA])
@test data[2] == DataValueArray{Float64}([3.14, 7., NA])
@test data[3] == DataValueArray{Int32}([2, 7, NA])
@test data[4] == DataValueArray{Int16}([2, 7, NA])
@test data[5] == DataValueArray{Int8}([2, 7., NA])
@test data[6] == DataValueArray{String}(["2", "7", ""])
end

@testset "SAS7BDAT files" begin

dtafile = joinpath(dirname(@__FILE__), "types.sas7bdat")
rsdf = read_sas7bdat(dtafile)
data = rsdf.data

@test length(data) == 6
@test rsdf.headers == [:vfloat, :vdouble, :vlong, :vint, :vbyte, :vstring]
@test data[1] == DataValueArray{Float32}([3.14, 7., NA])
@test data[2] == DataValueArray{Float64}([3.14, 7., NA])
@test data[3] == DataValueArray{Int32}([2, 7, NA])
@test data[4] == DataValueArray{Int16}([2, 7, NA])
@test data[5] == DataValueArray{Int8}([2, 7., NA])
@test data[6] == DataValueArray{String}(["2", "7", ""])
end

end

ext: jl


text:
using Visualize, GeometryTypes
using Visualize: TextUniforms, get_texture_atlas, Sprite2
using Visualize: orthographicprojection, perspectiveprojection, lookat, normalmesh, ColorBuffer, Area
using Visualize.JLRasterization: JLCanvas, JLRasterizer

resolution = (1024, 1024)
canvas = JLCanvas(Area => SimpleRectangle(0, 0, resolution...))

proj = orthographicprojection(SimpleRectangle(0, 0, resolution...), -10_000f0, 10_000f0)
uniforms = TextUniforms(
    proj,
    Vec4f0(1, 0, 0, 1),
    Vec4f0(1, 0, 1, 1)
)

atlas = get_texture_atlas();

text = Visualize.Text(
    Sprite2[],
    Char[],
    atlas,
    [0],
    Visualize.defaultfont(),
    45f0,
    0f0,
    Vec4f0(0f0, 0f0, 0f0, 1f0),
    Point2f0(0),
    1.5
);

print(text, 'c')
print(text, " lololol")
text.color = Vec4f0(1f0, 0f0, 0f0, 1f0)
print(text, " Muashahahaha@")

uniform_buff = uniforms
vbo = reinterpret(Tuple{eltype(text.data)}, text.data)
atlas_tex = atlas.images;

draw_particles = JLRasterizer(
    vbo, (uniform_buff, atlas_tex),
    Visualize.vertex_main, Visualize.fragment_main;
    geometryshader = Visualize.geometry_main
)
canvas = JLCanvas(Area => SimpleRectangle(0, 0, resolution...))
draw_particles(canvas, vbo, (uniform_buff, atlas_tex))
using FileIO, Images
save("test.png", clamp01.(canvas[Visualize.ColorBuffer][1]))


text:
using HCIToolbox
using Test
using Statistics
using Random
Random.seed!(8799)

@testset "HCIToolbox.jl" begin
    include("morphology.jl")
    include("masking.jl")
end


text:
import Tokenize:Tokens, Lexers
import Tokenize.Tokens: Token, kind, untokenize
using Printf

const RESET = Crayon(reset = true)

mutable struct Pass
    f!
    enabled::Bool
    update_on_cursormovement::Bool
end

struct PassHandler
    accum_crayons::Vector{Crayon}
    crayons::Vector{Crayon}
    passes::Vector{Tuple{String, Pass}} # This is a stupid type and I should feel stupid
end

PassHandler() = PassHandler(Crayon[], Crayon[], Tuple{String, Pass}[])
const PASS_HANDLER = PassHandler()

function test_pass(io::IO, f, str::Union{String, IO}, cursorpos::Int = 1, cursormovement::Bool = false)
    rpc = PassHandler()
    add_pass!(rpc, "TestPass", f)
    tokens = collect(Lexers.Lexer(str))
    apply_passes!(rpc, tokens, cursorpos, cursormovement)
    untokenize_with_ANSI(io, rpc.accum_crayons, tokens)
end

test_pass(f, str::Union{String, IOBuffer}, cursorpos::Int = 1, cursormovement::Bool = false) =
        test_pass(stdout, f, str, cursorpos, cursormovement)

function test_passes(io::IO, rpc::PassHandler, str::Union{String, IOBuffer}, cursorpos::Int = 1, cursormovement::Bool = false)
    b = IOBuffer()
    tokens = collect(Lexers.Lexer(str))
    apply_passes!(rpc, tokens, cursorpos, cursormovement)
    untokenize_with_ANSI(io, rpc.accum_crayons, tokens)
end

test_passes(rpc::PassHandler, str::Union{String, IOBuffer}, cursorpos::Int = 1, cursormovement::Bool = false) =
    test_passes(stdout, rpc, str, cursorpos, cursormovement)

function untokenize_with_ANSI(io::IO, crayons::Vector{Crayon}, tokens::Vector{Token}, indent = 7)
    @assert length(tokens) == length(crayons)
    print(io, RESET)
    z = 1
    for (token, crayon) in zip(tokens, crayons)
        print(io, crayon)
        for c in untokenize(token)
            print(io, c)
            c == '\n' && print(io, " "^indent)
        end
        print(io, RESET)
    end
end
untokenize_with_ANSI(io::IO, rpc::PassHandler, tokens::Vector{Token}, indent = 7) = untokenize_with_ANSI(io, rpc.accum_crayons, tokens, indent)
untokenize_with_ANSI(crayons::Vector{Crayon}, tokens::Vector{Token}, indent = 7) =
    untokenize_with_ANSI(stdout, crayons, tokens, indent)

function merge!(t1::Vector{Crayon}, t2::Vector{Crayon})
    @assert length(t1) == length(t2)
    for i in eachindex(t1)
        t1[i] = merge(t1[i], t2[i])
    end
    return t1
end

function apply_passes!(rpc::PassHandler, tokens::Vector{Token}, cursorpos::Int = 1, cursormovement::Bool = false)
    resize!(rpc.crayons, length(tokens))
    resize!(rpc.accum_crayons, length(tokens))
    fill!(rpc.accum_crayons, Crayon())
    for i in reverse(1:length(rpc.passes))
        fill!(rpc.crayons, Crayon())
        pass = rpc.passes[i][2]
        if pass.enabled
            if cursormovement && !pass.update_on_cursormovement
                continue
            end
            # This is an enabled pass that should be run, so run it!
            pass.f!(rpc.crayons, tokens, cursorpos)
            merge!(rpc.accum_crayons, rpc.crayons)
        end
    end
end

write_results!(buff::IO, rpc::PassHandler, tokens::Vector{Token}) =
    write_results(buff, rpc.accum_crayons, tokens)

# Returns -1 if not found else index of where the pass is
function _find_pass(rpc::PassHandler, name::String)
    for i in 1:length(rpc.passes)
        if rpc.passes[i][1] == name
            return i
        end
    end
    return -1
end

function _check_pass_name(rpc::PassHandler, name::String, shouldexist::Bool)
    idx = _find_pass(rpc, name)
    if idx == -1 && shouldexist
        throw(ArgumentError("pass $name could not be found"))
    elseif idx != -1 && !shouldexist
        throw(ArgumentError("pass $name already exists"))
    end
    return idx
end

function get_pass(rpc::PassHandler, name::String)
    idx = idx = _check_pass_name(rpc, name, true)
    return rpc.passes[idx][2].f!
end

function add_pass!(rpc::PassHandler, name::String, f, update_on_cursormovement::Bool = true)
    idx = _check_pass_name(rpc, name, false)
    insert!(rpc.passes, 1, (name, Pass(f, true, update_on_cursormovement)))
    return rpc
end
add_pass!(name::String, f, update_on_cursormovement::Bool = true) = add_pass!(PASS_HANDLER, name, f, update_on_cursormovement)

function enable_pass!(rpc::PassHandler, name::String, enabled::Bool)
    idx = _check_pass_name(rpc, name, true)
    rpc.passes[idx][2].enabled = enabled
    return rpc
end
enable_pass!(name::String, enabled::Bool) = enable_pass!(PASS_HANDLER, name, enabled)

function Base.show(io::IO, rpc::PassHandler)
    println(io, Crayon(bold = true),
                "──────────────────────────────────", RESET)
    println(io, " #   Pass name             Enabled  ")
    println(io, "──────────────────────────────────")
    for (i, pass) in enumerate(rpc.passes)
        name = pass[1]
        if length(name) >= 21
            name = name[1:21-3] * "..."
        end
        println(io, " $i   ", @sprintf("%-21s %-8s ", name, pass[2].enabled))
    end
    print(io, Crayon(bold = true),
                "──────────────────────────────────", RESET)
end

prescedence!(rpc::PassHandler, name::String, presc::Int) = prescedence!(rpc, _check_pass_name(rpc, name, true), presc)
prescedence!(name::String, presc::Int) = prescedence!(PASS_HANDLER, name, presc)

function prescedence!(rpc::PassHandler, pass_idx::Int, presc::Int)
    pass = rpc.passes[pass_idx]
    presc = clamp(presc, 1, length(rpc.passes))
    deleteat!(rpc.passes, pass_idx)
    insert!(rpc.passes, presc, pass)
    return rpc
end
prescedence!(pass_idx::Int, presc::Int) = prescedence!(PASS_HANDLER, pass_idx, presc)


text:
# ------------------ #
# Iteration managers #
# ------------------ #

abstract type IterationManager end

struct OneStepGMM <: IterationManager
    k::RobustVariance
    demean::Bool
end

struct TwoStepGMM <: IterationManager
    k::RobustVariance
    demean::Bool
end

struct IterativeGMM <: IterationManager
    k::RobustVariance
    demean::Bool
    tol::Float64
    maxiter::Int
end

# kwarg constructors with default values
OneStepGMM(;k::RobustVariance=HC0(), demean::Bool = false) = OneStepGMM(k, demean)
TwoStepGMM(;k::RobustVariance=HC0(), demean::Bool = false) = TwoStepGMM(k, demean)

OneStepGMM(k::RobustVariance) = OneStepGMM(k, false)
TwoStepGMM(k::RobustVariance) = TwoStepGMM(k, false)

function IterativeGMM(;k::RobustVariance=HC0(), demean::Bool = false, tol::Float64=1e-12, maxiter::Int=500)
    IterativeGMM(k, demean, tol, maxiter)
end

mutable struct IterationState
    n::Array{Int, 1}
    change::Array{Float64, 1}
    prev::Array{Float64, 1}  # previous value
end

finished(::OneStepGMM, ist::IterationState) = ist.n[1] > 1
finished(::TwoStepGMM, ist::IterationState) = ist.n[1] > 2
function finished(mgr::IterativeGMM, ist::IterationState)
    ist.n[1] > mgr.maxiter[1] || abs(ist.change[1]) <= mgr.tol
end


text:
"""
    dataset(dataset_name::AbstractString)

This is a convenience function whose only purpose is to load example datasets provided with this package.

### Arguments
* `dataset_name`: A string specifying the dataset to be loaded (e.g., "zaki_data", "adult", etc).

The returned value will be a dataframe.
"""
function dataset(dataset_name::AbstractString)
    filename = joinpath(dirname(@__FILE__), "..", "data", "$dataset_name.csv")

    if !isfile(filename)
        error(@sprintf "Unable to locate file %s \n" filename)
    else
        return readtable(filename)
    end
end

function assemble_string_mat(n::Int, p::Int)
    res = Array{String, 2}(n, p)
    empty_row = repeat([""], inner = n, outer = 1)
    for j = 1:p
        res[:, j] = empty_row
    end
    res
end

function transactions(v::Array{String,1}, sep = ",")
    n = length(v)
    num_cols = 0
    for i = 1:n
        transact = convert(Array{String,1}, split(v[i], sep))
        num_cols = (num_cols > length(transact)) ? num_cols : length(transact)
    end

    transact_mat = assemble_string_mat(n, num_cols)

    for i = 1:n
        transact = convert(Array{String,1}, split(v[i], sep))
        last_idx = length(transact)
        transact = map(strip, transact)
        sort!(transact)

        transact_mat[i, 1:last_idx] = transact
    end
    return DataFrame(transact_mat)
end

function transactions(v::Array{Array{String,1},1})
    n = length(v)
    num_cols = 0
    for i = 1:n
        num_cols = (num_cols > length(v[i])) ? num_cols : length(v[i])
    end

    transact_mat = assemble_string_mat(n, num_cols)

    for i = 1:n
        last_idx = length(v[i])
        transact = map(strip, v[i])
        sort!(transact)
        transact_mat[i, 1:last_idx] = transact
    end
    return DataFrame(transact_mat)
end

W = ["this, is, a, transaction", "and this, is, also"]

transactions(W)


text:
using DelimitedFiles
using WordNet

const special_characters = ['[', '!', '&', ''',
                            '(', ')', '*', ',',
                            '.', '/', ':', '$',
                            ';', '?', '@', '[',
                            '\\', ']', '^', '_',
                            '`', '{', '|', '}',
                            '·'
                            ]

const spaces = ['-', '\t', '\n']

const alphanumeric_characters = r"[^A-Za-z0-9 ]+"

function substitute_word(sentence, word)
    return replace(sentence, word => "")
end

function easy_data_augmentation(input_file, output_file, alpha_sr=0.1,
    alpha_ri=0.1, alpha_rs=0.1, p_rd=0.1, num_aug=9)

    f = open(input_file)
    lines = readlines(f)

    augmented_sentences = []
    num_aug_sentences_per_technique = floor(num_aug/4)+1

    # for each line in the input file, we generate agumented sentences
    for line in lines
        label, sentence = split(line, "\t")
        #println("$label $sentence")
        aug_sentences = augment_sentence(sentence)
        for aug_sentence in aug_sentences
            push!(augmented_sentences, [label, aug_sentence])
        end
     end

    writedlm(output_file, augmented_sentences)

    return output_file
end

function augment_sentence(sentence)

    sentence = clean_text_sentence(sentence)
    words = split(sentence, " ")

    return words

end

function clean_text_sentence(sentence)

    sentence = lowercase.(sentence)

    # delete special characters
    sentence = replace.(sentence, special_characters => "")
    # add spaces for tab and return
    sentence = replace.(sentence, spaces => " ")
    # keep alphanumeric characters
    #sentence = match.(alphanumeric_characters, sentence)

    # remove extra white spaces
    sentence = replace.(sentence, r" +" => " ")

    # remove trailing and ending white space
    sentence = lstrip.(sentence)
    sentence = rstrip.(sentence)

    return sentence
end

function get_synonyms(word)
    db = DB()
    lemma = db['a', "glad"]
    ss = synsets(db, lemma)

    return ss
end


text:
# structure to hold fractal data
@doc raw"""
    FractalData(xmin::Union{Float64,BigFloat},
        xmax::Union{Float64,BigFloat},
        ymin::Union{Float64,BigFloat},
        ymax::Union{Float64,BigFloat};
        width::Int = w_lr,
        height::Int = h_lr,
        fractal = :none,
        maxIter = 1500,
        colormap = cgrad(:inferno),
        background_color = :white,
        scale_function::Function = x -> x)

Structure to contain the informations necessary to compute a Mandelbrot fractal.
"""
mutable struct FractalData
    xmin::Union{Float64,BigFloat}
    xmax::Union{Float64,BigFloat}
    ymin::Union{Float64,BigFloat}
    ymax::Union{Float64,BigFloat}
    width::Int
    height::Int
    fractal::Matrix{Float64}
    maxIter::Int
    colormap::ColorGradient
    background_color
    scale_function::Function

    function FractalData(
        xmin::Union{Float64,BigFloat},
        xmax::Union{Float64,BigFloat},
        ymin::Union{Float64,BigFloat},
        ymax::Union{Float64,BigFloat};
        width::Int = w_lr,
        height::Int = h_lr,
        fractal = :none,
        maxIter = 1500,
        colormap = cgrad(:inferno),
        background_color = :white,
        scale_function::Function = x -> x,
    ) #where {T<:Real}
        img = zeros(Float64, height, width)
        if fractal != :none
            img = fractal
        end
        new(
            xmin,
            xmax,
            ymin,
            ymax,
            width,
            height,
            img,
            maxIter,
            colormap,
            background_color,
            scale_function,
        )
    end
end


text:
import J4Org: tokenized, untokenize
import J4Org: is_opening_parenthesis, is_closing_parenthesis
import J4Org: is_opening_brace, is_closing_brace
import J4Org: is_opening_square, is_closing_square
import J4Org: is_abstract
import J4Org: is_enum
import J4Org: is_const
import J4Org: is_macro
import J4Org: is_global
import J4Org: is_local
import J4Org: is_type
import J4Org: skip_identifier, skip_comma_separated_identifiers
import J4Org: skip_where_block, skip_declaration_block

@testset "openclose" begin

        @test is_opening_parenthesis(tokenized("("),1)
        @test is_closing_parenthesis(tokenized(")"),1)
        @test is_opening_brace(tokenized("{"),1)
        @test is_closing_brace(tokenized("}"),1)
        @test is_opening_square(tokenized("["),1)
        @test is_closing_square(tokenized("]"),1)

end;

@testset "skip_identifier" begin
    identifier=Ref{String}("")
    t=tokenized("A    qslkdjlk")
    @test untokenize(t[1:skip_identifier(t,1,identifier=identifier)-1]) == "A"
    @test identifier[] == "A"
    t=tokenized("A .  B      \n")
    @test untokenize(t[1:skip_identifier(t,1,identifier=identifier)-1]) == "A .  B"
    @test identifier[] == "A.B"
    t=tokenized("A. B {   A {  X}}  ")
    @test untokenize(t[1:skip_identifier(t,1,identifier=identifier)-1]) == "A. B {   A {  X}}"
    @test identifier[] == "A.B"
end;

@testset "skip_comma_separated_identifiers" begin
    t=tokenized("  A       ")
    @test untokenize(t[1:skip_comma_separated_identifiers(t,1)-1]) == "  A"
    t=tokenized("A   ,    ")
    @test untokenize(t[1:skip_comma_separated_identifiers(t,1)-1]) == "A"
    t=tokenized("A, B     ")
    @test untokenize(t[1:skip_comma_separated_identifiers(t,1)-1]) == "A, B"
    t=tokenized("A .  B      \n")
    @test untokenize(t[1:skip_comma_separated_identifiers(t,1)-1]) == "A .  B"
    t=tokenized("A, B {   A {  X}} ,  C  ")
    @test untokenize(t[1:skip_comma_separated_identifiers(t,1)-1]) == "A, B {   A {  X}} ,  C"
end;

@testset "skip_where_block" begin
    t=tokenized(" kjsq sqd       ")
    @test untokenize(t[1:skip_where_block(t,1)-1]) == ""
    t=tokenized("  \nwhere A dfg     ")
    @test untokenize(t[1:skip_where_block(t,1)-1]) == "  \nwhere A"

end;

@testset "skip_declaration_block" begin
    t=tokenized(" ::kjsq sqd       ")
    @test untokenize(t[1:skip_declaration_block(t,1)-1]) == " ::kjsq"
    t=tokenized("  \nwhere A dfg     ")
    @test untokenize(t[1:skip_declaration_block(t,1)-1]) == ""
    t=tokenized("  \n::A{B}  ::Int   ")
    @test untokenize(t[1:skip_declaration_block(t,1)-1]) == "  \n::A{B}"

end;

@testset "is_abstract_and_is_type" begin
    t=tokenized("abstract type something")
    @test is_abstract(t,1)
    @test is_type(t,3)
end;

@testset "is_enum" begin
    t=tokenized("@enum Alpha A B")
    @test is_enum(t,1)
end;

@testset "is_const_global_local" begin
    t=tokenized("const global A=0")
    @test is_const(t,1)
    @test is_global(t,3)
    t=tokenized("const local A=0")
    @test is_const(t,1)
    @test is_local(t,3)
end;

@testset "is_macro" begin
    t=tokenized("macro Alpha(A, B)")
    @test is_macro(t,1)
end;


text:
using LinearAlgebra
using CVChannel
using Convex
using Test
using DelimitedFiles

"""
This code first verifies the non-multiplicativity of the Werner-Holevo over the PPT
cone when tensored with the identity. This is different than over the separable cone.
We verify this using an a linear program (LP) that we scan over.
"""
#This script looks at multiplicativity of WH with identity over PPT cone
println("\nHere we initialize the identity channel and the solver. (One moment please...)")
identChan(X) = X
eaCVDual(choi(identChan,2,2),2,2)

println("\nNow we look at tensoring the Werner-Holevo channel with the identity.")
println("\nWhat makes this interesting is it is multiplcative over the separable")
println("cone, but over the PPT it is non-multiplicative. This exemplifies a separation.\n\n")

function nonMultWHID()
    println("\nNow we look at the Werner-Holevo channels tensored with the identity")
    println("\nfor dimension [2,8] for both channels for values of λ in [0.4,0.5,...,1]")

    table_vals = zeros(343,7)
    λ_vals = 0:0.1:0.6
    d1_range = 2:8
    d2_range = 2:8
    λ_ctr = 1
    table_ctr = 1
    for λ in λ_vals
        println("\nNow evaluating for Werner-Holevo channel with parameter = ", round(λ, digits = 2))
        #Note the LP is parameterized backwards with respect to how our SDP is
        for d1 in d1_range
            println("Now evaluating for d = ", d1, "...")
            #Here we get the communication value of the Werner-Holevo channel itself
            A,B,g,a = generalWHLPConstraints(1,3,(1-λ)*ones(1))
            cv_WH, v1 =wernerHolevoCVPPT(1,3,A,B,g,a)

            for d2 in d2_range
                cv = WHIDLP(d1,d2,λ)
                table_vals[table_ctr,:] = [
                    λ,            #parameter
                    d1,             #WH dimension
                    d2,             #identity channel dimension
                    cv_WH,          #comm val of WH
                    cv_WH*d2,       #cv(𝒩_WH ⊗ id)
                    cv,             #cv^{PPT}(𝒩_WH ⊗ id)
                    cv - cv_WH*d2   #cv^{PPT} - cv
                ];
                table_ctr = table_ctr + 1;
            end
        end
        λ_ctr = λ_ctr + 1
    end

    header = ["param" "WH Dim" "id Dim" "cv(WH)" "cv" "cv_ppt" "diff"]
    data_to_save = vcat(header,table_vals)
    info_vec = Vector{Union{Nothing,String}}(nothing, size(data_to_save)[1])
    info_vec[1] = "INFO:"
    info_vec[2] = "Generated by werner-holevo-channel-with-identity.jl"
    info_vec[3] = "WH dim range: " * string(d1_range)
    info_vec[4] = "id dim range: " * string(d2_range)
    info_vec[5] = "WH parameter range: " * string(λ_vals)
    data_to_save = hcat(data_to_save,info_vec)

    print("\nPlease name the file you'd like to write the results to: \n")
    file_name = readline()
    file_to_open = string(file_name,".csv")
    writedlm(file_to_open, data_to_save, ',')
    if all(table_vals -> table_vals < 1e-4 , table_vals[2:end,7])
        print("\nSomething went awry. Please check your results.")
        return false
    else
        #Here we construct the highlights of the data
        println("Here are the highlights.")
        println("There is no multiplicativity for qubit to qubit, but qutrit to qutrit there is.")
        qubit_to_qutrit = vcat(header,table_vals[6:9,:])
        show(stdout, "text/plain", qubit_to_qutrit)
        println("For p >= 0.5, there is multiplicativity. This is when Werner-Holevo becomes PPT.")
        p_geq_half = vcat(header,
                        [
                            table_vals[49,:]';table_vals[98,:]';table_vals[147,:]';
                            table_vals[196,:]';table_vals[245,:]';table_vals[294,:]';
                            table_vals[343,:]'
                        ])
        show(stdout, "text/plain", p_geq_half)
        println("\nFor the full data, please see the saved results.")
        return true
    end
    end

@testset "non-multiplicativity" begin
    @test nonMultWHID()
end

max_forks_repo_name: ChitambarLab/cv-channel


text:
module MessageUtils

import Base.fetch, Base.put!, Base.take!, Base.isready, Base.wait

include("core.jl")
include("spaces.jl")
include("ctasks.jl")

type DNSHandle
    h::SyncObjRef
    DNSHandle() = new()
end

const dns = DNSHandle()
const isv4 = !isless(Base.VERSION, v"0.4.0-")

# Globally available dictionary
function dnsref()
    if !isdefined (dns, :h)
        if (myid() == 1)
            dns.h = kvspace(;sz=-1)
        else
            dns.h = remotecall_fetch(1, ()->MessageUtils.dnsref())
        end
    end
    return dns.h
end

end


text:
########

"""
    Fields

This `baremodule` allows scoped enum-like access to electric field components `Ex`, `Ey`,
and `Ez`.

# Examples

```jldoctest; setup=:(using LongwaveModePropagator.Fields)
julia> Fields.Ex
Ex::Field = 0
julia> Fields.Ey
Ey::Field = 1
```
"""
baremodule Fields
using Base: @enum
@enum Field Ex Ey Ez
end

########

"""
    AbstractSampler

Abstract supertype for sampling fields in the waveguide.

Subtypes of AbstractSampler have a position in the guide and a `Fields.Field`.
"""
abstract type AbstractSampler{T} end

function distancechecks(d, stype; earthradius=LMPParams().earthradius)
    !issorted(d) && error("$stype distance must be sorted")
    minimum(d) < 0 && error("$stype distance must be positive")
    maximum(d) > (π*earthradius - 500e3) && @warn "antipode focusing effects not modeled"

    return true
end

"""
    Sampler{T} <: AbstractSampler{T}

`Sampler` types sample (measure) the electromagnetic field in the waveguide.

# Fields

- `distance::T`: ground distance from the transmitter in meters.
- `fieldcomponent::Fields.Field`: field component measured by the `Sampler`.
- `altitude::Float64`: height above the ground in meters.
"""
struct Sampler{T} <: AbstractSampler{T}
    distance::T
    fieldcomponent::Fields.Field
    altitude::Float64

    Sampler(d::T, fc, a) where T = distancechecks(d, Sampler) && new{T}(d, fc, a)
end
distance(s::Sampler) = s.distance
distance(s::Sampler,t::Transmitter) = s.distance
fieldcomponent(s::Sampler) = s.fieldcomponent
altitude(s::Sampler) = s.altitude

"""
    GroundSampler{T} <: AbstractSampler{T}

`GroundSamplers` are `Sampler` types with an altitude of zero.

# Fields

- `distance::T`: ground distance from the transmitter in meters.
- `fieldcomponent::Fields.Field`: field component measured by the `GroundSampler`.
"""
struct GroundSampler{T} <: AbstractSampler{T}
    distance::T  # T is usually <: AbstractVector but could be a scalar
    fieldcomponent::Fields.Field

    GroundSampler(d::T, fc) where T = distancechecks(d, GroundSampler) && new{T}(d, fc)
end
distance(g::GroundSampler) = g.distance
distance(g::GroundSampler,t::Transmitter) = g.distance
fieldcomponent(g::GroundSampler) = g.fieldcomponent
altitude(g::GroundSampler) = 0.0

"""
    Receiver{<:Antenna}

Represent a physical receiver.

A default `Receiver{VerticalDipole}` is returned with `Receiver()`.

# Fields

- `name::String = ""`: receiver name.
- `latitude::Float64 = 0.0`: geographic latitude in degrees.
- `longitude::Float64 = 0.0`: geographic longitude in degrees.
- `altitude::Float64 = 0.0`: receiver altitude in meters above the ground.
- `antenna::Antenna = VerticalDipole()`: receiver antenna.
"""
struct Receiver{A<:Antenna}
    name::String
    latitude::Float64
    longitude::Float64
    altitude::Float64
    antenna::A
end

Receiver() = Receiver{VerticalDipole}("", 0.0, 0.0, 0.0, VerticalDipole())

altitude(r::Receiver) = r.altitude
distance(r::Receiver,t::Transmitter) = nothing   # TODO proj4 stuff
fieldcomponent(r::Receiver) = fieldcomponent(r.antenna)


text:
import Base: convert, copy, show, getindex, lastindex, isempty,
             size, length, eachindex, ==, isequal, hash, vcat, hcat, merge, map

abstract type AbstractTrajectory end

struct TrjArray{T, U} <: AbstractTrajectory
    #DONE: support for x, y, z = nothing, nothing, nothing
    #DONE: natom, nframe
    #TODO: revert to parametric type, fix slowness of constructor
    #TODO: simple and easy constructions for other functions which outputs a new trjarray (such as centerofmass)
    #TODO: should allow one-dimensional array (vector) for x, y, z constructor input
    xyz::AbstractArray{T}
    boxsize::AbstractArray{T}
    chainname::AbstractArray{String}
    chainid::AbstractArray{U}
    resname::AbstractArray{String}
    resid::AbstractArray{U}
    atomname::AbstractArray{String}
    atomtype::AbstractArray{String}
    atomid::AbstractArray{U}
    mass::AbstractArray{T}
    radius::AbstractArray{T}
    charge::AbstractArray{T}
    sasa::AbstractArray{T}
    list_bond::AbstractArray{U}
    list_angle::AbstractArray{U}
    list_dihedral::AbstractArray{U}
    list_improper::AbstractArray{U}
    list_cmap::AbstractArray{U}
    natom::Int64
    nframe::Int64

    function TrjArray{T, U}(xyz, boxsize, chainname, chainid, resname, resid, atomname, atomtype, atomid, mass, radius, charge, sasa,
                      list_bond, list_angle, list_dihedral, list_improper, list_cmap) where {T, U}
        # nrow, ncol = (size(trj, 1), size(trj, 2))
        # natom = Int64(ncol/3)
        # ischecked && return new(trj, atomname, atomid, meta)
        # natom != length(chainname) && throw(DimensionMismatch("chainname must match width of trajectory"))
        # natom != length(chainid) && throw(DimensionMismatch("chainid must match width of trajectory"))

        # check size and define nframe
        nframe = 0
        if !isempty(xyz)
            nframe = size(xyz, 1)
            if !isempty(boxsize)
                @assert nframe == size(boxsize, 1)
                @assert 3 == size(boxsize, 2)
            end
        end
        nframe = U(nframe)

        # check size and define natom
        natom = 0
        vec_collection = [chainname, chainid, resname, resid, atomname, atomtype, atomid, mass, radius, charge, sasa]
        if !isempty(xyz)
            natom = U(size(xyz, 2)/3)
        else
            for vec in vec_collection
                if !isempty(vec)
                    natom = length(vec)
                    break
                end
            end
        end
        if !isempty(xyz)
            @assert (natom*3) == size(xyz, 2)
        end
        for vec in vec_collection
            if !isempty(vec)
                @assert natom == length(vec)
            end
        end
        natom = U(natom)

        return new(xyz, boxsize, chainname, chainid, resname, resid, atomname, atomtype, atomid, mass, radius, charge, sasa,
                   list_bond, list_angle, list_dihedral, list_improper, list_cmap, natom, nframe)
    end
end

###### outer constructors ########
function TrjArray{T, U}(;xyz = Matrix{T}(undef, 0, 0),
                  boxsize = Matrix{T}(undef, 0, 0),
                  chainname = Vector{String}(undef, 0), chainid = Vector{U}(undef, 0),
                  resname = Vector{String}(undef, 0), resid = Vector{U}(undef, 0),
                  atomname = Vector{String}(undef, 0), atomtype = Vector{String}(undef, 0), atomid = Vector{U}(undef, 0),
                  mass = Vector{T}(undef, 0), radius = Vector{T}(undef, 0), charge = Vector{T}(undef, 0), sasa = Vector{T}(undef, 0),
                  list_bond = Matrix{U}(undef, 0, 0), list_angle = Matrix{U}(undef, 0, 0),
                  list_dihedral = Matrix{U}(undef, 0, 0), list_improper = Matrix{U}(undef, 0, 0),
                  list_cmap = Matrix{U}(undef, 0, 0)) where {T, U}
    TrjArray{T, U}(xyz, boxsize, chainname, chainid, resname, resid, atomname, atomtype, atomid, mass, radius, charge, sasa,
             list_bond, list_angle, list_dihedral, list_improper, list_cmap)
end

TrjArray(ta::TrjArray{T, U}; xyz = nothing,
    boxsize = nothing,
    chainname = nothing, chainid = nothing,
    resname = nothing, resid = nothing,
    atomname = nothing, atomtype = nothing, atomid = nothing,
    mass = nothing, radius = nothing, charge = nothing, sasa = nothing,
    list_bond = nothing, list_angle = nothing,
    list_dihedral = nothing, list_improper = nothing,
    list_cmap = nothing) where {T, U} = TrjArray{T, U}(
        xyz = isnothing(xyz) ? ta.xyz : xyz,
        boxsize = isnothing(boxsize) ? ta.boxsize : boxsize,
        chainname = isnothing(chainname) ? ta.chainname : chainname,
        chainid = isnothing(chainid) ? ta.chainid : chainid,
        resname = isnothing(resname) ? ta.resname : resname,
        resid = isnothing(resid) ? ta.resid : resid,
        atomname = isnothing(atomname) ? ta.atomname : atomname,
        atomtype = isnothing(atomtype) ? ta.atomtype : atomtype,
        atomid = isnothing(atomid) ? ta.atomid : atomid,
        mass = isnothing(mass) ? ta.mass : mass,
        radius = isnothing(radius) ? ta.radius : radius,
        charge = isnothing(charge) ? ta.charge : charge,
        sasa = isnothing(sasa) ? ta.sasa : sasa,
        list_bond = isnothing(list_bond) ? ta.list_bond : list_bond,
        list_angle = isnothing(list_angle) ? ta.list_angle : list_angle,
        list_dihedral = isnothing(list_dihedral) ? ta.list_dihedral : list_dihedral,
        list_improper = isnothing(list_improper) ? ta.list_improper : list_improper,
        list_cmap = isnothing(list_cmap) ? ta.list_cmap : list_cmap)

TrjArray(xyz::AbstractArray{T}, boxsize::AbstractArray{T}, ta::TrjArray{T, U}) where {T, U} =
       TrjArray{T, U}(xyz = xyz, boxsize = boxsize,
                      chainname = ta.chainname, chainid = ta.chainid,
                      resname = ta.resname, resid = ta.resid,
                      atomname = ta.atomname, atomtype = ta.atomtype, atomid = ta.atomid,
                      mass = ta.mass, radius = ta.radius, charge = ta.charge, sasa = ta.sasa,
                      list_bond = ta.list_bond, list_angle = ta.list_angle,
                      list_dihedral = ta.list_dihedral, list_improper = ta.list_improper,
                      list_cmap = ta.list_cmap)

TrjArray(xyz::AbstractArray{T}, ta::TrjArray{T, U}) where {T, U} =
       TrjArray{T, U}(xyz = xyz, boxsize = ta.boxsize,
                      chainname = ta.chainname, chainid = ta.chainid,
                      resname = ta.resname, resid = ta.resid,
                      atomname = ta.atomname, atomtype = ta.atomtype, atomid = ta.atomid,
                      mass = ta.mass, radius = ta.radius, charge = ta.charge, sasa = ta.sasa,
                      list_bond = ta.list_bond, list_angle = ta.list_angle,
                      list_dihedral = ta.list_dihedral, list_improper = ta.list_improper,
                      list_cmap = ta.list_cmap)

TrjArray(x...; y...) = TrjArray{Float64,Int64}(x...; y...)
TrjArray() = TrjArray{Float64, Int64}()

###### size, length #################
size(ta::TrjArray) = (ta.nframe, ta.natom)

size(ta::TrjArray, dim::Int) = (dim == 1) ? ta.nframe : (dim == 2) ? ta.natom : 1

length(ta::TrjArray) = ta.nframe

###### getindex #################
# all element indexing
getindex(ta::TrjArray, ::Colon) = ta
getindex(ta::TrjArray, ::Colon, ::Colon) = ta

# single row
function getindex(ta::TrjArray{T, U}, n::Int) where {T, U}
    if iszero(n)
        return TrjArray(ta, xyz = Array{T, 2}(undef, 0, 0), boxsize = Array{T, 2}(undef, 0, 0)) ## TODO need to fix here
    elseif !isempty(ta.boxsize)
        return TrjArray(ta, xyz = ta.xyz[n:n, :], boxsize = ta.boxsize[n:n, :])
    else
        return TrjArray(ta, xyz = ta.xyz[n:n, :])
    end
end
getindex(ta::TrjArray{T, U}, n::Int, ::Colon) where {T, U} = getindex(ta, n)

# range of rows
function getindex(ta::TrjArray{T, U}, r::UnitRange{Int}) where {T, U}
    if !isempty(ta.boxsize)
        TrjArray(ta, xyz = ta.xyz[r, :], boxsize = ta.boxsize[r, :])
    else
        TrjArray(ta, xyz = ta.xyz[r, :])
    end
end
getindex(ta::TrjArray{T, U}, r::UnitRange{Int}, ::Colon) where {T, U} = getindex(ta, r)

# array of rows (integer)
function getindex(ta::TrjArray{T, U}, a::AbstractVector{S}) where {T, U, S <: Integer}
    if !isempty(ta.boxsize)
        TrjArray(ta, xyz = ta.xyz[a, :], boxsize = ta.boxsize[a, :])
    else
        TrjArray(ta, xyz = ta.xyz[a, :])
    end
end
getindex(ta::TrjArray{T, U}, a::AbstractVector{S}, ::Colon) where {T, U, S <: Integer} = getindex(ta, a)

# array of rows (bool)
#getindex(ta::TrjArray, a::AbstractVector{S}) where {S <: Bool} = TrjArray(ta.x[a, :], ta.y[a, :], ta.z[a, :], ta)
#getindex(ta::TrjArray, a::AbstractVector{S}, ::Colon) where {S <: Bool} = getindex(ta, a)
function getindex(ta::TrjArray{T, U}, a::AbstractVector{Bool}) where {T, U}
    if !isempty(ta.boxsize)
        TrjArray(ta, xyz = ta.xyz[a, :], boxsize = ta.boxsize[a, :])
    else
        TrjArray(ta, xyz = ta.xyz[a, :])
    end
end
getindex(ta::TrjArray{T, U}, a::AbstractVector{Bool}, ::Colon) where {T, U} = getindex(ta, a)

# define function for updating list_bond, list_angle, list_dihedral, list_improper, list_cmap
function reindex_list(natom, list_some, index)
  if isempty(list_some) || isempty(index)
    return Matrix{eltype(list_some)}(undef, 0, 0)
  end
  if typeof(index) <: AbstractVector{Bool}
    index_logical = index
    reindex = zeros(eltype(list_some), natom)
    reindex[index] .= collect(1:sum(index))
  else
    index_logical = falses(natom)
    index_logical[index] .= true
    reindex = zeros(eltype(list_some), natom)
    reindex[index] .= collect(1:length(index))
  end
  nlist = size(list_some, 1)
  list_some_new = similar(list_some)
  icount = 0
  for i = 1:nlist
    if all(index_logical[list_some[i, :]])
      icount += 1
      list_some_new[icount, :] .= reindex[list_some[i, :]]
    end
  end
  if icount == 0
    return Matrix{eltype(list_some)}(undef, 0, 0)
  else
    return list_some_new[1:icount, :]
  end
end

# single column
to3(r::Int) = [3*(r-1)+1, 3*(r-1)+2, 3*(r-1)+3]

getindex(ta::TrjArray{T, U}, ::Colon, r::Int) where {T, U} = TrjArray{T, U}(
             xyz = isempty(ta.xyz) ? ta.xyz : ta.xyz[:, to3(r)],
             boxsize = ta.boxsize,
             chainname = isempty(ta.chainname) ? ta.chainname : ta.chainname[r:r],
             chainid = isempty(ta.chainid) ? ta.chainid : ta.chainid[r:r],
             resname = isempty(ta.resname) ? ta.resname : ta.resname[r:r],
             resid = isempty(ta.resid) ? ta.resid : ta.resid[r:r],
             atomname = isempty(ta.atomname) ? ta.atomname : ta.atomname[r:r],
             atomtype = isempty(ta.atomtype) ? ta.atomtype : ta.atomtype[r:r],
             #atomid = isempty(ta.atomid) ? ta.atomid : ta.atomid[r:r],
             atomid = isempty(ta.atomid) ? ta.atomid : collect(Int64, 1:1),
             mass = isempty(ta.mass) ? ta.mass : ta.mass[r:r],
             radius = isempty(ta.radius) ? ta.radius : ta.radius[r:r],
             charge = isempty(ta.charge) ? ta.charge : ta.charge[r:r],
             sasa = isempty(ta.sasa) ? ta.sasa : ta.sasa[r:r],
             list_bond = isempty(ta.list_bond) ? ta.list_bond : reindex_list(ta.natom, ta.list_bond, r:r),
             list_angle = isempty(ta.list_angle) ? ta.list_angle : reindex_list(ta.natom, ta.list_angle, r:r),
             list_dihedral = isempty(ta.list_dihedral) ? ta.list_dihedral : reindex_list(ta.natom, ta.list_dihedral, r:r),
             list_improper = isempty(ta.list_improper) ? ta.list_improper : reindex_list(ta.natom, ta.list_improper, r:r),
             list_cmap = isempty(ta.list_cmap) ? ta.list_cmap : reindex_list(ta.natom, ta.list_cmap, r:r))

# range of columns
function to3(r::UnitRange{Int})
    n = length(r)
    r3 = similar(r, 3*n)
    r3[1:3:end] .= 3 .* (r .- 1) .+ 1
    r3[2:3:end] .= 3 .* (r .- 1) .+ 2
    r3[3:3:end] .= 3 .* (r .- 1) .+ 3
    return r3
end

getindex(ta::TrjArray{T, U}, ::Colon, r::UnitRange{Int}) where {T, U} = TrjArray{T, U}(
             xyz = isempty(ta.xyz) ? ta.xyz : ta.xyz[:, to3(r)],
             boxsize = ta.boxsize,
             chainname = isempty(ta.chainname) ? ta.chainname : ta.chainname[r],
             chainid = isempty(ta.chainid) ? ta.chainid : ta.chainid[r],
             resname = isempty(ta.resname) ? ta.resname : ta.resname[r],
             resid = isempty(ta.resid) ? ta.resid : ta.resid[r],
             atomname = isempty(ta.atomname) ? ta.atomname : ta.atomname[r],
             atomtype = isempty(ta.atomtype) ? ta.atomtype : ta.atomtype[r],
             #atomid = isempty(ta.atomid) ? ta.atomid : ta.atomid[r],
             atomid = isempty(ta.atomid) ? ta.atomid : collect(Int64, 1:length(r)),
             mass = isempty(ta.mass) ? ta.mass : ta.mass[r],
             radius = isempty(ta.radius) ? ta.radius : ta.radius[r],
             charge = isempty(ta.charge) ? ta.charge : ta.charge[r],
             sasa = isempty(ta.sasa) ? ta.sasa : ta.sasa[r],
             list_bond = isempty(ta.list_bond) ? ta.list_bond : reindex_list(ta.natom, ta.list_bond, r),
             list_angle = isempty(ta.list_angle) ? ta.list_angle : reindex_list(ta.natom, ta.list_angle, r),
             list_dihedral = isempty(ta.list_dihedral) ? ta.list_dihedral : reindex_list(ta.natom, ta.list_dihedral, r),
             list_improper = isempty(ta.list_improper) ? ta.list_improper : reindex_list(ta.natom, ta.list_improper, r),
             list_cmap = isempty(ta.list_cmap) ? ta.list_cmap : reindex_list(ta.natom, ta.list_cmap, r))

# array of columns (integer)
# range of columns
function to3(r::AbstractVector{S}) where {S <: Integer}
    n = length(r)
    r3 = similar(r, 3*n)
    r3[1:3:end] .= 3 .* (r .- 1) .+ 1
    r3[2:3:end] .= 3 .* (r .- 1) .+ 2
    r3[3:3:end] .= 3 .* (r .- 1) .+ 3
    return r3
end

getindex(ta::TrjArray{T, U}, ::Colon, r::AbstractVector{S}) where {T, U, S <: Integer} = TrjArray{T, U}(
             xyz = isempty(ta.xyz) ? ta.xyz : ta.xyz[:, to3(r)],
             boxsize = ta.boxsize,
             chainname = isempty(ta.chainname) ? ta.chainname : ta.chainname[r],
             chainid = isempty(ta.chainid) ? ta.chainid : ta.chainid[r],
             resname = isempty(ta.resname) ? ta.resname : ta.resname[r],
             resid = isempty(ta.resid) ? ta.resid : ta.resid[r],
             atomname = isempty(ta.atomname) ? ta.atomname : ta.atomname[r],
             atomtype = isempty(ta.atomtype) ? ta.atomtype : ta.atomtype[r],
             #atomid = isempty(ta.atomid) ? ta.atomid : ta.atomid[r],
             atomid = isempty(ta.atomid) ? ta.atomid : collect(Int64, 1:length(r)),
             mass = isempty(ta.mass) ? ta.mass : ta.mass[r],
             radius = isempty(ta.radius) ? ta.radius : ta.radius[r],
             charge = isempty(ta.charge) ? ta.charge : ta.charge[r],
             sasa = isempty(ta.sasa) ? ta.sasa : ta.sasa[r],
             list_bond = isempty(ta.list_bond) ? ta.list_bond : reindex_list(ta.natom, ta.list_bond, r),
             list_angle = isempty(ta.list_angle) ? ta.list_angle : reindex_list(ta.natom, ta.list_angle, r),
             list_dihedral = isempty(ta.list_dihedral) ? ta.list_dihedral : reindex_list(ta.natom, ta.list_dihedral, r),
             list_improper = isempty(ta.list_improper) ? ta.list_improper : reindex_list(ta.natom, ta.list_improper, r),
             list_cmap = isempty(ta.list_cmap) ? ta.list_cmap : reindex_list(ta.natom, ta.list_cmap, r))

# array of rows (bool)
function to3(r::AbstractVector{Bool})
    n = length(r)
    r3 = similar(r, 3*n)
    r3[1:3:end] .= r
    r3[2:3:end] .= r
    r3[3:3:end] .= r
    return r3
end

getindex(ta::TrjArray{T, U}, ::Colon, r::AbstractVector{Bool}) where {T, U} = TrjArray{T, U}(
             xyz = isempty(ta.xyz) ? ta.xyz : ta.xyz[:, to3(r)],
             boxsize = ta.boxsize,
             chainname = isempty(ta.chainname) ? ta.chainname : ta.chainname[r],
             chainid = isempty(ta.chainid) ? ta.chainid : ta.chainid[r],
             resname = isempty(ta.resname) ? ta.resname : ta.resname[r],
             resid = isempty(ta.resid) ? ta.resid : ta.resid[r],
             atomname = isempty(ta.atomname) ? ta.atomname : ta.atomname[r],
             atomtype = isempty(ta.atomtype) ? ta.atomtype : ta.atomtype[r],
             #atomid = isempty(ta.atomid) ? ta.atomid : ta.atomid[r],
             atomid = isempty(ta.atomid) ? ta.atomid : collect(Int64, 1:sum(r)),
             mass = isempty(ta.mass) ? ta.mass : ta.mass[r],
             radius = isempty(ta.radius) ? ta.radius : ta.radius[r],
             charge = isempty(ta.charge) ? ta.charge : ta.charge[r],
             sasa = isempty(ta.sasa) ? ta.sasa : ta.sasa[r],
             list_bond = isempty(ta.list_bond) ? ta.list_bond : reindex_list(ta.natom, ta.list_bond, r),
             list_angle = isempty(ta.list_angle) ? ta.list_angle : reindex_list(ta.natom, ta.list_angle, r),
             list_dihedral = isempty(ta.list_dihedral) ? ta.list_dihedral : reindex_list(ta.natom, ta.list_dihedral, r),
             list_improper = isempty(ta.list_improper) ? ta.list_improper : reindex_list(ta.natom, ta.list_improper, r),
             list_cmap = isempty(ta.list_cmap) ? ta.list_cmap : reindex_list(ta.natom, ta.list_cmap, r))

# combinations
getindex(ta::TrjArray{T, U}, rows, cols) where {T, U} = ta[rows, :][:, cols]

# end index
lastindex(ta::TrjArray, dims::Int = 1) = (dims == 1) ? ta.nframe : (dims == 2) ? ta.natom : 1

###### atom selection #################
function unfold(A)
    V = []
    if typeof(A) <: AbstractString
        push!(V, A)
    else
        for x in A
            if x === A
                push!(V, x)
            else
                append!(V, unfold(x))
            end
        end
    end
    V
end

function match_query(some_array, query)
    natom = length(some_array)
    #index = fill(false, natom)
    index = falses(natom)
    if isempty(some_array)
        return index
    elseif typeof(some_array[1]) == String
        query = split(query)
    else
        query = Meta.eval(Meta.parse(replace("[" * query * "]", r"(\d)\s" => s"\1, ")))
    end
    query = unique(unfold(query))
    for q in query
        index = index .| (some_array .== q)
    end
    index
end

function replace_ex!(ex::Expr, ta::TrjArray)
    chainid = ta.chainid
    chainname = ta.chainname
    resid = ta.resid
    resname = ta.resname
    atomid = ta.atomid
    atomname = ta.atomname
    atomtype = ta.atomtype
    for (i, arg) in enumerate(ex.args)
        if arg == :(match_query)
            ex.args[i] = :($match_query)
        elseif arg == :(chainid)
            ex.args[i] = :($chainid)
        elseif arg == :(chainname)
            ex.args[i] = :($chainname)
        elseif arg == :(resid)
            ex.args[i] = :($resid)
        elseif arg == :(resname)
            ex.args[i] = :($resname)
        elseif arg == :(atomid)
            ex.args[i] = :($atomid)
        elseif arg == :(atomname)
            ex.args[i] = :($atomname)
        elseif arg == :(atomtype)
            ex.args[i] = :($atomtype)
        end
        if isa(arg, Expr)
            replace_ex!(arg, ta)
        end
    end
    ex
end

function select_atom(ta::TrjArray, s::AbstractString)
    #s = strip(s)

    # alias
    s = replace(s, "waters" => "water")
    s = replace(s, "solvents" => "solvent")
    s = replace(s, "proteins" => "protein")

    """
    this code is licensed under the GNU LGPL version 2.1, or at your option a later version of the license.  (Copyright (c) R.T McGibbon et al.), see LICENSE.md
    """
    s = replace(s, "water" => "resname H2O SOL WAT HHO OHH HOH OH2 TIP TIP2 TIP3 TIP4")

    """
    this code is licensed under the GNU LGPL version 2.1, or at your option a later version of the license.  (Copyright (c) R.T McGibbon et al.), see LICENSE.md
    """
    s = replace(s, "hydrogen" => "atomname 1H 2H 3H H1 H2 H3 H4 H5 H HA 1HA 2HA HB 1HB " *
    "2HB 3HB HG 1HG 2HG 1HG1 2HG1 3HG1 1HG2 2HG2 3HG2 1HD 2HD 1HD1 2HD1 3HD1 1HD2 2HD2 " *
    "3HD2 1HH1 2HH1 1HH2 2HH2 HE 1HE 2HE 3HE 1HE1 2HE1 1HE2 2HE2 HD1 HD2 HE1 HNZ1 HNZ2 " *
    "HNZ3 HG1 HH HH1 HH2 ")

    """
    this code is licensed under the GNU LGPL version 2.1, or at your option a later version of the license.  (Copyright (c) R.T McGibbon et al.), see LICENSE.md
    """
    s = replace(s, "solvent" => "resname 118 119 1AL 1CU 2FK 2HP 2OF 3CO 3MT 3NI 3OF 4MO " *
    "543 6MO ACT AG AL ALF ATH AU AU3 AUC AZI Ag BA BAR BCT BEF BF4 BO4 BR BS3 BSY Be CA " *
    "CA+2 Ca+2 CAC CAD CAL CD CD1 CD3 CD5 CE CES CHT CL CL- CLA Cl- CO CO3 CO5 CON CR CS " *
    "CSB CU CU1 CU3 CUA CUZ CYN Cl- Cr DME DMI DSC DTI DY E4N EDR EMC ER3 EU EU3 F FE FE2 " *
    "FPO GA GD3 GEP HAI HG HGC HOH IN IOD ION IR IR3 IRI IUM K K+ KO4 LA LCO LCP LI LIT LU " *
    "MAC MG MH2 MH3 MLI MMC MN MN3 MN5 MN6 MO1 MO2 MO3 MO4 MO5 MO6 MOO MOS MOW MW1 MW2 MW3 " *
    "NA NA+2 NA2 NA5 NA6 NAO NAW Na+2 NET NH4 NI NI1 NI2 NI3 NO2 NO3 NRU Na+ O4M OAA OC1 OC2 " *
    "OC3 OC4 OC5 OC6 OC7 OC8 OCL OCM OCN OCO OF1 OF2 OF3 OH OS OS4 OXL PB PBM PD PER PI PO3 " *
    "PO4 POT PR PT PT4 PTN RB RH3 RHD RU RUB Ra SB SCN SE4 SEK SM SMO SO3 SO4 SOD SR Sm Sn " *
    "T1A TB TBA TCN TEA THE TL TMA TRA UNX V V2+ VN3 VO4 W WO5 Y1 YB YB2 YH YT3 ZN ZN2 ZN3 ZNA ZNO ZO3")

    """
    this code is licensed under the GNU LGPL version 2.1, or at your option a later version of the license.  (Copyright (c) R.T McGibbon et al.), see LICENSE.md
    """
    s = replace(s, "protein" => "resname ACE NME 00C 01W 02K 02L 03Y 07O 08P 0A0 0A1 0A2 " *
    "0A8 0AA 0AB 0AC 0AF 0AG 0AH 0AK 0BN 0CS 0E5 0EA 0FL 0NC 0WZ 0Y8 143 193 1OP 1PA 1PI " *
    "1TQ 1TY 1X6 200 23F 23S 26B 2AD 2AG 2AO 2AS 2CO 2DO 2FM 2HF 2KK 2KP 2LU 2ML 2MR 2MT " *
    "2OR 2PI 2QZ 2R3 2SI 2TL 2TY 2VA 2XA 32S 32T 33X 3AH 3AR 3CF 3GA 3MD 3NF 3QN 3TY 3XH " *
    "4BF 4CF 4CY 4DP 4FB 4FW 4HT 4IN 4MM 4PH 4U7 56A 5AB 5CS 5CW 5HP 6CL 6CW 6GL 6HN 7JA " *
    "9NE 9NF 9NR 9NV A5N A66 AA3 AA4 AAR AB7 ABA ACB ACL ADD AEA AEI AFA AGM AGT AHB AHH " *
    "AHO AHP AHS AHT AIB AKL AKZ ALA ALC ALM ALN ALO ALS ALT ALV ALY AN8 APE APH API APK " *
    "APM APP AR2 AR4 AR7 ARG ARM ARO ARV AS2 AS9 ASA ASB ASI ASK ASL ASM ASN ASP ASQ ASX " *
    "AVN AYA AZK AZS AZY B1F B2A B2F B2I B2V B3A B3D B3E B3K B3L B3M B3Q B3S B3T B3U B3X " *
    "B3Y BB6 BB7 BB8 BB9 BBC BCS BE2 BFD BG1 BH2 BHD BIF BIL BIU BJH BL2 BLE BLY BMT BNN " *
    "BNO BOR BPE BSE BTA BTC BTR BUC BUG C1X C22 C3Y C4R C5C C66 C6C CAF CAL CAS CAV CAY " *
    "CCL CCS CDE CDV CEA CGA CGU CHF CHG CHP CHS CIR CLE CLG CLH CME CMH CML CMT CPC CPI " *
    "CR5 CS0 CS1 CS3 CS4 CSA CSB CSD CSE CSJ CSO CSP CSR CSS CSU CSW CSX CSZ CTE CTH CUC " *
    "CWR CXM CY0 CY1 CY3 CY4 CYA CYD CYF CYG CYJ CYM CYQ CYR CYS CZ2 CZZ D11 D3P D4P DA2 " *
    "DAB DAH DAL DAR DAS DBB DBS DBU DBY DBZ DC2 DCL DCY DDE DFI DFO DGH DGL DGN DHA DHI " *
    "DHL DHN DHP DHV DI7 DIL DIR DIV DLE DLS DLY DM0 DMH DMK DMT DNE DNL DNP DNS DOA DOH " *
    "DON DPL DPN DPP DPQ DPR DSE DSG DSN DSP DTH DTR DTY DVA DYS ECC EFC EHP ESB ESC EXY " *
    "EYS F2F FAK FB5 FB6 FCL FGA FGL FGP FH7 FHL FHO FLA FLE FLT FME FOE FP9 FRD FT6 FTR " *
    "FTY FVA FZN GAU GCM GFT GGL GHG GHP GL3 GLH GLJ GLK GLM GLN GLQ GLU GLX GLY GLZ GMA " *
    "GND GPL GSC GSU GT9 GVL H14 H5M HAC HAR HBN HCS HFA HGL HHI HIA HIC HIP HIQ HIS HL2 " *
    "HLU HMR HPC HPE HPH HPQ HQA HRG HRP HS8 HS9 HSE HSL HSO HTI HTN HTR HV5 HVA HY3 HYP " *
    "HZP I2M I58 IAM IAR IAS IEL IGL IIL ILE ILG ILX IML IOY IPG IT1 IYR IYT IZO JJJ JJK " *
    "JJL K1R KCX KGC KNB KOR KPI KST KYN KYQ L2A LA2 LAA LAL LBY LCK LCX LCZ LDH LED LEF " *
    "LEH LEI LEM LEN LET LEU LEX LHC LLP LLY LME LMF LMQ LP6 LPD LPG LPL LPS LSO LTA LTR " *
    "LVG LVN LYF LYK LYM LYN LYR LYS LYX LYZ M0H M2L M2S M30 M3L MA MAA MAI MBQ MC1 MCG " *
    "MCL MCS MD3 MD6 MDF MDH MEA MED MEG MEN MEQ MET MEU MF3 MGG MGN MGY MHL MHO MHS MIS " *
    "MK8 ML3 MLE MLL MLY MLZ MME MMO MND MNL MNV MOD MP8 MPH MPJ MPQ MSA MSE MSL MSO MSP " *
    "MT2 MTY MVA N10 N2C N7P N80 N8P NA8 NAL NAM NB8 NBQ NC1 NCB NCY NDF NEM NEP NFA NHL " *
    "NIY NLE NLN NLO NLP NLQ NMC NMM NNH NPH NPI NSK NTR NTY NVA NYS NZH O12 OAR OAS OBF " *
    "OBS OCS OCY OHI OHS OIC OLE OLT OLZ OMT ONH ONL OPR ORN ORQ OSE OTB OTH OXX P1L P2Y " *
    "PAQ PAS PAT PAU PBB PBF PCA PCC PCE PCS PDL PEC PF5 PFF PFX PG1 PG9 PGL PGY PH6 PHA " *
    "PHD PHE PHI PHL PHM PIV PLE PM3 POM PPN PR3 PR9 PRO PRS PSA PSH PTA PTH PTM PTR PVH " *
    "PVL PYA PYL PYX QCS QMM QPA QPH R1A R4K RE0 RE3 RON RVX RZ4 S1H S2C S2D S2P SAC SAH " *
    "SAR SBL SCH SCS SCY SD2 SDP SE7 SEB SEC SEG SEL SEM SEN SEP SER SET SGB SHC SHP SHR " *
    "SIB SLR SLZ SMC SME SMF SNC SNN SOC SOY SRZ STY SUB SUN SVA SVV SVW SVX SVY SVZ SYS " *
    "T11 T66 TA4 TAV TBG TBM TCQ TCR TDD TFQ TH6 THC THO THR THZ TIH TMB TMD TNB TNR TOQ " *
    "TPH TPL TPO TPQ TQI TQQ TRF TRG TRN TRO TRP TRQ TRW TRX TRY TST TTQ TTS TXY TY1 TY2 " *
    "TY3 TY5 TYB TYI TYJ TYN TYO TYQ TYR TYS TYT TYW TYX TYY TZB TZO UMA UN1 UN2 UNK VAD " *
    "VAF VAL VB1 VDL VLL VLM VMS VOL WLU WPA WRP WVL X2W XCN XCP XDT XPL XPR XSN XX1 YCM " *
    "YOF YTH Z01 ZAL ZCL ZFB ZU0 ZZJ")

    # attributes for selection
    s = replace(s, "chainid" => "match_query(chainid, \" ")
    s = replace(s, "chainname" => "match_query(chainname, \" ")
    s = replace(s, "resid" => "match_query(resid, \" ")
    s = replace(s, "resname" => "match_query(resname, \" ")
    s = replace(s, "atomid" => "match_query(atomid, \" ")
    s = replace(s, "atomname" => "match_query(atomname, \" ")
    s = replace(s, "atomtype" => "match_query(atomtype, \" ")
    s = replace(s, r"(^|(\s+))name(\s+)" => "match_query(atomname, \" ")
    s = replace(s, r"(^|(\s+))type(\s+)" => "match_query(atomtype, \" ")
    s = replace(s, r"(^|(\s+))chain(\s+)" => "match_query(chainname, \" ")

    # parentheses (only 1-depth) and logical operators (and, or , not)
    s = replace(s, r"\)(\s+)and" => "\" ) ) .& ")
    s = replace(s, r"([^\)])(\s+)and" => s"\1 \" ) .& ")
    s = replace(s, r"\)(\s+)or" => "\" ) ) .| ")
    s = replace(s, r"([^\)])(\s+)or" => s"\1 \" ) .| ")
    s = replace(s, r"(^|(\s+))not(\s+)" => " .! ")

    s = strip(s)
    if s[end] == ')'
        s = s[1:end-1] * " \" ) )"
    elseif s[end-length("all")+1:end] != "all" && s[end-length("backbone")+1:end] != "backbone"
        s = s * " \" )"
    end

    #resname = ta.resname
    #println(s)
    ex = Meta.parse(s)
    #Meta.dump(ex)
    replace_ex!(ex, ta)
    #Meta.dump(ex)
    #println(s)
    index = Meta.eval(ex)
    #println(index')
    findall(index)
end

function getindex(ta::TrjArray, s::AbstractString)
    index = select_atom(ta, s)
    ta[:, index]
end

# combinations
getindex(ta::TrjArray, ::Colon, s::AbstractString) = getindex(ta, s)
getindex(ta::TrjArray, rows, s::AbstractString) = ta[rows, :][:, s]

###### equality #################
==(x::TrjArray, y::TrjArray) = all(f -> getfield(x, f) == getfield(y, f), fieldnames(TrjArray))
isequal(x::TrjArray, y::TrjArray) = all(f -> isequal(getfield(x, f), getfield(y, f)), fieldnames(TrjArray))

###### copy ###############
copy(ta::TrjArray)::TrjArray =
    TrjArray(ta.xyz, ta.boxsize,
             ta.chainname, ta.chainid,
             ta.resname, ta.resid,
             ta.atomname, ta.atomtype, ta.atomid,
             ta.mass, ta.radius, ta.charge, ta.sasa,
             ta.list_bond, ta.list_angle, ta.list_dihedral, ta.list_improper, ta.list_cmap)

###### accessors to field values #################

###### vcat, hcat, merge #################
function vcat(ta_collection::TrjArray...)
    T = eltype(ta_collection[1].xyz)

    # check the number of atoms
    natom = ta_collection[1].natom
    for i = 2:length(ta_collection)
        if natom != ta_collection[i].natom
            throw(ArgumentError("number of atoms doesn't match"))
        end
    end
    xyz = ta_collection[1].xyz

    # check boxsize
    if isempty(ta_collection[1].boxsize)
        is_box = false
        boxsize = Matrix{T}(undef, 0, 0)
    else
        is_box = true
        boxsize = ta_collection[1].boxsize
    end

    for i = 2:length(ta_collection)
        if !isempty(ta_collection[i].xyz)
            if isempty(xyz)
                xyz = ta_collection[i].xyz
            else
                xyz = [xyz; ta_collection[i].xyz]
            end
        end
        if is_box
            if !isempty(ta_collection[i].boxsize)
                boxsize = [boxsize; ta_collection[i].boxsize]
            else
                @printf "Warning: boxsize information discarded in some trajectories\n"
                is_box = false
                boxsize = Matrix{T}(undef, 0, 0)
            end
        else
            if !isempty(ta_collection[i].boxsize)
                @printf "Warning: boxsize information discarded in some trajectories\n"
            end
        end
    end
    TrjArray(ta_collection[1], xyz=xyz, boxsize=boxsize)
end

function hcat(ta_collection::TrjArray...)
    natom = 0
    isbox_empty_any = false

    nframe = ta_collection[1].nframe
    natom = ta_collection[1].natom
    xyz = ta_collection[1].xyz
    boxsize = []
    chainname = ta_collection[1].chainname
    chainid = ta_collection[1].chainid
    resname = ta_collection[1].resname
    resid = ta_collection[1].resid
    atomname = ta_collection[1].atomname
    atomtype = ta_collection[1].atomtype
    atomid = ta_collection[1].atomid
    mass = ta_collection[1].mass
    radius = ta_collection[1].radius
    charge = ta_collection[1].charge
    sasa = ta_collection[1].sasa
    list_bond = ta_collection[1].list_bond
    list_angle = ta_collection[1].list_angle
    list_dihedral = ta_collection[1].list_dihedral
    list_improper = ta_collection[1].list_improper
    list_cmap = ta_collection[1].list_cmap

    for i = 2:length(ta_collection)
        @assert nframe == ta_collection[i].nframe

        if !isempty(ta_collection[i].xyz)
            xyz = [xyz ta_collection[i].xyz]
        else
            xyz = []
        end

        if !isempty(ta_collection[i].chainname)
            chainname = [chainname; ta_collection[i].chainname]
        else
            chainname = []
        end

        if !isempty(ta_collection[i].chainid)
            chainid = [chainid; ta_collection[i].chainid]
        else
            chainid = []
        end

        if !isempty(ta_collection[i].resname)
            resname = [resname; ta_collection[i].resname]
        else
            resname = []
        end

        if !isempty(ta_collection[i].resid)
            resid = [resid; ta_collection[i].resid]
        else
            resid = []
        end

        if !isempty(ta_collection[i].atomname)
            atomname = [atomname; ta_collection[i].atomname]
        else
            atomname = []
        end

        if !isempty(ta_collection[i].atomtype)
            atomtype = [atomtype; ta_collection[i].atomtype]
        else
            atomtype = []
        end

        if !isempty(ta_collection[i].atomid)
            atomid = [atomid; ta_collection[i].atomid .+ natom]
        else
            atomid = []
        end

        natom += ta_collection[i].natom
    end
    #@show size(xyz)
    TrjArray(xyz=xyz, boxsize=boxsize,
             chainname=chainname, chainid=chainid,
             resname=resname, resid=resid,
             atomname=atomname, atomtype=atomtype, atomid=atomid)
end

###### end keyword #################
# endof(ta::TrjArray) = isempty(ta.x) ? nothing : size(ta.x, 1)
# eachindex(ta::TrjArray) = Base.OneTo(size(ta.x, 1))

###### iterator #################
Base.iterate(ta::TrjArray, state=1) = state > ta.nframe ? nothing : (ta[state], state + 1)

###### conversion #################
function convert(::Type{T}, ta::TrjArray) where {T<:AbstractArray}
    c = Matrix{eltype(T)}(undef, ta.nframe, 3*ta.natom)
    c .= ta.xyz
    #for iatom = 1:ta.natom
    #    c[1:ta.nframe, (3*(iatom-1)+1):(3*(iatom-1)+1)] .= eltype(T).(ta.x[1:ta.nframe, iatom:iatom])
    #    c[1:ta.nframe, (3*(iatom-1)+2):(3*(iatom-1)+2)] .= eltype(T).(ta.y[1:ta.nframe, iatom:iatom])
    #    c[1:ta.nframe, (3*(iatom-1)+3):(3*(iatom-1)+3)] .= eltype(T).(ta.z[1:ta.nframe, iatom:iatom])
    #end
    return c
end

###### broadcast #################

###### show #####################
function alignment_xyz(io::IO, X::AbstractVecOrMat,
        rows::AbstractVector, cols::AbstractVector,
        cols_if_complete::Integer, cols_otherwise::Integer, sep::Integer)
    a = Tuple{Int, Int}[]
    for j in cols
        l = 14
        r = 13
        push!(a, (l, r))
        if length(a) > 1 && sum(map(sum,a)) + sep*length(a) >= cols_if_complete
            pop!(a)
            break
        end
    end
    if 1 < length(a) < length(axes(X,2))
        while sum(map(sum,a)) + sep*length(a) >= cols_otherwise
            pop!(a)
        end
    end
    return a
end

function print_matrix_xyz(io::IO,
        X::AbstractVecOrMat, Y::AbstractVecOrMat, Z::AbstractVecOrMat,
        columnwidth::Vector,
        i::Integer, cols::AbstractVector, sep::AbstractString)
    for (k, j) = enumerate(cols)
        k > length(columnwidth) && break
        # if X[i,j] < 10^6 && X[i,j] > -10^5
        #     print_x = Printf.@sprintf " %8.2f" X[i,j]
        # else
        #     print_x = Printf.@sprintf " %8.2e" X[i,j]
        # end
        # TODO: treatment for very large or small values
        print_x = Printf.@sprintf " %8.2f" X[i,j]
        print_y = Printf.@sprintf " %8.2f" Y[i,j]
        print_z = Printf.@sprintf " %8.2f" Z[i,j]
        print(io, print_x, print_y, print_z, sep) # 27 characters + sep
        #if k < length(columnwidth); print(io, sep); end
    end
end

function string_column(x, y, j)
    if isempty(x)
        string_x = ""
    else
        string_x = string(x[j])
    end
    if isempty(y)
        string_y = ""
    else
        string_y = string(y[j])
    end
    s = " " * string_x * string_y
    if length(s) > 24
        s = " " * s[1:24] * "~" * " "
    else
        s = rpad(s, 27)
    end
    s
end

function print_column(io::IO,
                      x, y, columnwidth::Vector, cols::AbstractVector,
                      sep::AbstractString)
    for (k, j) = enumerate(cols)
        k > length(columnwidth) && break
        s = string_column(x, y, j)
        print(io, s, sep) # 27 characters + sep
    end
end

function print_matrix_vdots(io::IO, vdots::AbstractString,
        A::Vector, sep::AbstractString)
    for k = 1:length(A)
        w = A[k][1] + A[k][2]
        l = repeat(" ", max(0, A[k][1]-length(vdots)))
        r = repeat(" ", max(0, w-length(vdots)-length(l)))
        print(io, l, vdots, r)
        if k <= length(A); print(io, sep); end
    end
end

function show(io::IO, ta::TrjArray)
    # for jupyter notebook
    #ENV["COLUMNS"] = 150
    #ENV["LINES"] = 30

    #TODO: fix double empty lines when nframe=0
    pre = "|"  # pre-matrix string
    sep = " |" # separator between elements
    hdots = "  \u2026  "
    vdots = "\u22ee"
    ddots = "  \u22f1  "
    hmod = 5

    # summary line
    @printf(io, "%dx%d %s\n", ta.nframe, ta.natom, typeof(ta))

    X = ta.xyz[:, 1:3:end]
    Y = ta.xyz[:, 2:3:end]
    Z = ta.xyz[:, 3:3:end]

    sz = displaysize(io)
    screenheight, screenwidth = sz[1] - 4, sz[2]
    screenwidth -= length(pre)

    if !isempty(ta.chainid) || !isempty(ta.chainname)
        screenheight -= 1
    end

    if !isempty(ta.resid) || !isempty(ta.resname)
        screenheight -= 1
    end

    if !isempty(ta.atomid) || !isempty(ta.atomname)
        screenheight -= 1
    end

    @assert textwidth(hdots) == textwidth(ddots)

    #rowsA, colsA = UnitRange(axes(X,1)), UnitRange(axes(X,2))
    rowsA, colsA = 1:ta.nframe, 1:ta.natom
    m, n = length(rowsA), length(colsA)

    # To figure out alignments, only need to look at as many rows as could
    # fit down screen. If screen has at least as many rows as A, look at A.
    # If not, then we only need to look at the first and last chunks of A,
    # each half a screen height in size.
    halfheight = div(screenheight,2)
    if m > screenheight
        rowsA = [rowsA[(0:halfheight-1) .+ firstindex(rowsA)]; rowsA[(end-div(screenheight-1,2)+1):end]]
    end
    # Similarly for columns, only necessary to get alignments for as many
    # columns as could conceivably fit across the screen
    maxpossiblecols = div(screenwidth, 3+length(sep))
    if n > maxpossiblecols
        colsA = [colsA[(1:maxpossiblecols)]; colsA[(end-maxpossiblecols+1):end]]
    end
    columnwidth = alignment_xyz(io, X, rowsA, colsA, screenwidth, screenwidth, length(sep))

    # Nine-slicing is accomplished using print_matrix_row repeatedly
    if n <= length(columnwidth) # rows and cols fit so just print whole matrix in one piece
        if !isempty(ta.chainid) || !isempty(ta.chainname)
            print(io, pre)
            print_column(io, ta.chainid, ta.chainname, columnwidth, colsA, sep)
            println(io)
        end
        if !isempty(ta.resid) || !isempty(ta.resname)
            print(io, pre)
            print_column(io, ta.resid, ta.resname, columnwidth, colsA, sep)
            println(io)
        end
        if !isempty(ta.atomid) || !isempty(ta.atomname)
            print(io, pre)
            print_column(io, ta.atomid, ta.atomname, columnwidth, colsA, sep)
            println(io)
        end
    else # rows fit down screen but cols don't, so need horizontal ellipsis
        c = div(screenwidth-length(hdots)+1,2)+1  # what goes to right of ellipsis
        Rcolumnwidth = reverse(alignment_xyz(io, X, rowsA, reverse(colsA), c, c, length(sep))) # alignments for right
        c = screenwidth - sum(map(sum,Rcolumnwidth)) - (length(Rcolumnwidth)-1)*length(sep) - length(hdots)
        Lcolumwidth = alignment_xyz(io, X, rowsA, colsA, c, c, length(sep)) # alignments for left of ellipsis
        if !isempty(ta.chainid) || !isempty(ta.chainname)
            print(io, pre)
            #print_matrix_xyz(io, X, Y, Z, Lcolumwidth, i, colsA[1:length(Lcolumwidth)], sep)
            print_column(io, ta.chainid, ta.chainname, Lcolumwidth, colsA[1:length(Lcolumwidth)], sep)
            print(io, hdots)
            #print_matrix_xyz(io, X, Y, Z, Rcolumnwidth, i, (n - length(Rcolumnwidth)) .+ colsA, sep)
            print_column(io, ta.chainid, ta.chainname, Rcolumnwidth, (n - length(Rcolumnwidth)) .+ colsA, sep)
            println(io)
        end
        if !isempty(ta.resid) || !isempty(ta.resname)
            print(io, pre)
            #print_matrix_xyz(io, X, Y, Z, Lcolumwidth, i, colsA[1:length(Lcolumwidth)], sep)
            print_column(io, ta.resid, ta.resname, Lcolumwidth, colsA[1:length(Lcolumwidth)], sep)
            print(io, hdots)
            #print_matrix_xyz(io, X, Y, Z, Rcolumnwidth, i, (n - length(Rcolumnwidth)) .+ colsA, sep)
            print_column(io, ta.resid, ta.resname, Rcolumnwidth, (n - length(Rcolumnwidth)) .+ colsA, sep)
            println(io)
        end
        if !isempty(ta.atomid) || !isempty(ta.atomname)
            print(io, pre)
            #print_matrix_xyz(io, X, Y, Z, Lcolumwidth, i, colsA[1:length(Lcolumwidth)], sep)
            print_column(io, ta.atomid, ta.atomname, Lcolumwidth, colsA[1:length(Lcolumwidth)], sep)
            print(io, hdots)
            #print_matrix_xyz(io, X, Y, Z, Rcolumnwidth, i, (n - length(Rcolumnwidth)) .+ colsA, sep)
            print_column(io, ta.atomid, ta.atomname, Rcolumnwidth, (n - length(Rcolumnwidth)) .+ colsA, sep)
            println(io)
        end
    end

    if m <= screenheight # rows fit vertically on screen
        if n <= length(columnwidth) # rows and cols fit so just print whole matrix in one piece
            for i in rowsA
                print(io, pre)
                print_matrix_xyz(io, X, Y, Z, columnwidth, i, colsA, sep)
                if i != last(rowsA); println(io); end
            end
        else # rows fit down screen but cols don't, so need horizontal ellipsis
            c = div(screenwidth-length(hdots)+1,2)+1  # what goes to right of ellipsis
            Rcolumnwidth = reverse(alignment_xyz(io, X, rowsA, reverse(colsA), c, c, length(sep))) # alignments for right
            c = screenwidth - sum(map(sum,Rcolumnwidth)) - (length(Rcolumnwidth)-1)*length(sep) - length(hdots)
            Lcolumwidth = alignment_xyz(io, X, rowsA, colsA, c, c, length(sep)) # alignments for left of ellipsis
            for i in rowsA
                print(io, pre)
                print_matrix_xyz(io, X, Y, Z, Lcolumwidth, i, colsA[1:length(Lcolumwidth)], sep)
                print(io, (i - first(rowsA)) % hmod == 0 ? hdots : repeat(" ", length(hdots)))
                print_matrix_xyz(io, X, Y, Z, Rcolumnwidth, i, (n - length(Rcolumnwidth)) .+ colsA, sep)
                if i != last(rowsA); println(io); end
            end
        end
    else # rows don't fit so will need vertical ellipsis
        if n <= length(columnwidth) # rows don't fit, cols do, so only vertical ellipsis
            for i in rowsA
                print(io, pre)
                print_matrix_xyz(io, X, Y, Z, columnwidth, i, colsA, sep)
                if i != rowsA[end] || i == rowsA[halfheight]; println(io); end
                if i == rowsA[halfheight]
                    print(io, pre)
                    print_matrix_vdots(io, vdots, columnwidth, sep)
                    println(io)
                end
            end
        else # neither rows nor cols fit, so use all 3 kinds of dots
            c = div(screenwidth-length(hdots)+1,2)+1
            Rcolumnwidth = reverse(alignment_xyz(io, X, rowsA, reverse(colsA), c, c, length(sep)))
            c = screenwidth - sum(map(sum,Rcolumnwidth)) - (length(Rcolumnwidth)-1)*length(sep) - length(hdots)
            Lcolumwidth = alignment_xyz(io, X, rowsA, colsA, c, c, length(sep))
            for i in rowsA
                print(io, pre)
                print_matrix_xyz(io, X, Y, Z, Lcolumwidth, i, colsA[1:length(Lcolumwidth)], sep)
                print(io, (i - first(rowsA)) % hmod == 0 ? hdots : repeat(" ", length(hdots)))
                print_matrix_xyz(io, X, Y, Z, Rcolumnwidth, i, (n-length(Rcolumnwidth)).+colsA, sep)
                if i != rowsA[end] || i == rowsA[halfheight]; println(io); end
                if i == rowsA[halfheight]
                    print(io, pre)
                    print_matrix_vdots(io, vdots, Lcolumwidth, sep)
                    print(io, ddots)
                    print_matrix_vdots(io, vdots, Rcolumnwidth, sep)
                    println(io)
                end
            end
        end
    end
end


text:
using StandardYoungTableaux
using Test

function sameVectors(v1, v2)
  return (length(v1) == length(v2)) && all(v1 .== v2)
end

function sameSYTx(syt1, syt2)
  entries1 = vcat(syt1.tableau...)
  entries2 = vcat(syt2.tableau...)
  return sameVectors(syt1.shape.partition, syt2.shape.partition) &&
    sameVectors(entries1, entries2)
end

@testset "dual SYT" begin
  syt = StandardYoungTableau([[1,2,6], [3,5], [4]])
  dsyt = dualSYT(syt)
  ddsyt = dualSYT(dsyt)
  @test sameSYTx(syt, ddsyt)
end

@testset "random SYT" begin
  lambda = IPartition([3,2,1])
  syt = randomSYT(lambda)
  @test sameVectors(lambda.partition, syt.shape.partition)
end

@testset "enumeration" begin
  lambda = IPartition([5,2,1])
  sytx = allSYTx(lambda)
  @test length(sytx) == countSYTx(lambda)
  @test sameSYTx(sytx[1], firstSYT(lambda))
  @test sameSYTx(sytx[2], nextSYT(sytx[1]))
  @test isnothing(nextSYT(last(sytx)))
end

@testset "growth process" begin
  syt = StandardYoungTableau([[1,2,6], [3,5], [4]])
  path = SYT2YoungPath(syt)
  @test sameSYTx(syt, YoungPath2SYT(path))
end

@testset "ballot" begin
  b = [1, 1, 2, 3, 2, 1]
  syt = ballot2SYT(b)
  @test sameVectors(b, SYT2ballot(syt))
  @test sameSYTx(syt, StandardYoungTableau([[1,2,6], [3,5], [4]]))
end

@testset "RS" begin
  sigma = [1, 3, 6, 4, 7, 5, 2]
  PQ = RS(sigma)
  QP = RS(invperm(sigma))
  @test sameSYTx(PQ.P, QP.Q)
  @test sameSYTx(PQ.Q, QP.P)
end


text:
struct ElementaryPathInstance{T} <: CombinatorialInstance
  graph::AbstractGraph{T}
  costs::Dict{Edge{T}, Float64}
  src::T
  dst::T
end

graph(i::ElementaryPathInstance{T}) where T = i.graph
src(i::ElementaryPathInstance{T}) where T = i.src
dst(i::ElementaryPathInstance{T}) where T = i.dst

# dimension(i::ElementaryPathInstance{T}) where T = ne(graph(i))
cost(i::ElementaryPathInstance{T}, u::T, v::T) where T = i.costs[Edge(u, v)]

struct ElementaryPathSolution{T} <: CombinatorialInstance
  instance::ElementaryPathInstance{T}
  path::Vector{Edge{T}}
  states::Dict{T, Float64}
  solutions::Dict{T, Vector{Edge{T}}}
end


text:
import GPLinearODEMaker.powers_of_negative_one

"""
pp_kernel function created by kernel_coder(). Requires 1 hyperparameters. Likely created using pp_kernel_base() as an input.
Use with include("src/kernels/pp_kernel.jl").
hyperparameters == ["λ"]
"""
function pp_kernel(
    hyperparameters::Vector{<:Real},
    δ::Real,
    dorder::Vector{<:Integer};
    shift_ind::Integer=0)

    include_shift = shift_ind!=0
    @assert length(hyperparameters)==1+Int(include_shift) "hyperparameters is the wrong length"
    dorder_len = 3 + Int(include_shift)
    @assert length(dorder)==dorder_len "dorder is the wrong length"
    @assert maximum(dorder) < 3 "No more than two derivatives for each time or hyperparameter can be calculated"

    dorder2 = dorder[2]
    dorder[2] += dorder[1]

    if include_shift
        dorder[2] += dorder[shift_ind+2]
        δ += hyperparameters[shift_ind]
        hyperparameters_view = view(hyperparameters, 1:2 .!= shift_ind)
        dorder_view = view(dorder, 2:dorder_len)
        dorder_view = view(dorder_view, 1:(dorder_len-1) .!= (shift_ind+1))
    else
        hyperparameters_view = hyperparameters
        dorder_view = view(dorder, 2:dorder_len)
    end

    λ = hyperparameters_view[1]

    if abs(δ) > λ
        dorder[2] = dorder2
        return 0
    end

    dabs_δ = powers_of_negative_one(δ < 0)  # store derivative of abs()
    abs_δ = abs(δ)

    if dorder_view==[6, 2]
        func = -403200*abs_δ/λ^9 + 1209600*(1 - abs_δ/λ)/λ^8 - 21600*(5*dabs_δ/λ + 16*abs_δ*dabs_δ/λ^2)/λ^7 + 7200*(-5*dabs_δ/λ^2 - 32*abs_δ*dabs_δ/λ^3)/λ^6 - 720*(10*dabs_δ/λ^3 + 96*abs_δ*dabs_δ/λ^4)/λ^5
    end

    if dorder_view==[5, 2]
        func = -19200*abs_δ^2/λ^9 - 288000*(1 - abs_δ/λ)^2/λ^7 - 120*(48*abs_δ^2/λ^4 + 10*abs_δ/λ^3)/λ^5 + 1200*(-16*abs_δ^2/λ^3 - 5*abs_δ/λ^2)/λ^6 - 3600*(1 + 8*abs_δ^2/λ^2 + 5*abs_δ/λ)/λ^7 + 230400*abs_δ*(1 - abs_δ/λ)/λ^8 - 6000*abs_δ*(5*dabs_δ/λ + 16*abs_δ*dabs_δ/λ^2)/λ^7 + 1200*abs_δ*(-5*dabs_δ/λ^2 - 32*abs_δ*dabs_δ/λ^3)/λ^6 + 12000*(1 - abs_δ/λ)*(5*dabs_δ/λ + 16*abs_δ*dabs_δ/λ^2)/λ^6 - 4800*(1 - abs_δ/λ)*(-5*dabs_δ/λ^2 - 32*abs_δ*dabs_δ/λ^3)/λ^5 + 600*(1 - abs_δ/λ)*(10*dabs_δ/λ^3 + 96*abs_δ*dabs_δ/λ^4)/λ^4
    end

    if dorder_view==[4, 2]
        func = 38400*(1 - abs_δ/λ)^3/λ^6 + 11520*abs_δ^2*(1 - abs_δ/λ)/λ^8 - 57600*abs_δ*(1 - abs_δ/λ)^2/λ^7 - 480*abs_δ^2*(5*dabs_δ/λ + 16*abs_δ*dabs_δ/λ^2)/λ^7 - 2880*(1 - abs_δ/λ)^2*(5*dabs_δ/λ + 16*abs_δ*dabs_δ/λ^2)/λ^5 + 1440*(1 - abs_δ/λ)^2*(-5*dabs_δ/λ^2 - 32*abs_δ*dabs_δ/λ^3)/λ^4 - 240*(1 - abs_δ/λ)^2*(10*dabs_δ/λ^3 + 96*abs_δ*dabs_δ/λ^4)/λ^3 + 120*(48*abs_δ^2/λ^4 + 10*abs_δ/λ^3)*(1 - abs_δ/λ)/λ^4 + 240*(-16*abs_δ^2/λ^3 - 5*abs_δ/λ^2)*abs_δ/λ^6 - 960*(-16*abs_δ^2/λ^3 - 5*abs_δ/λ^2)*(1 - abs_δ/λ)/λ^5 - 1200*(1 + 8*abs_δ^2/λ^2 + 5*abs_δ/λ)*abs_δ/λ^7 + 2400*(1 + 8*abs_δ^2/λ^2 + 5*abs_δ/λ)*(1 - abs_δ/λ)/λ^6 + 3840*abs_δ*(1 - abs_δ/λ)*(5*dabs_δ/λ + 16*abs_δ*dabs_δ/λ^2)/λ^6 - 960*abs_δ*(1 - abs_δ/λ)*(-5*dabs_δ/λ^2 - 32*abs_δ*dabs_δ/λ^3)/λ^5
    end

    if dorder_view==[3, 2]
        func = -2880*(1 - abs_δ/λ)^4/λ^5 - 2880*abs_δ^2*(1 - abs_δ/λ)^2/λ^7 + 7680*abs_δ*(1 - abs_δ/λ)^3/λ^6 + 360*(1 - abs_δ/λ)^3*(5*dabs_δ/λ + 16*abs_δ*dabs_δ/λ^2)/λ^4 - 240*(1 - abs_δ/λ)^3*(-5*dabs_δ/λ^2 - 32*abs_δ*dabs_δ/λ^3)/λ^3 + 60*(1 - abs_δ/λ)^3*(10*dabs_δ/λ^3 + 96*abs_δ*dabs_δ/λ^4)/λ^2 - 60*(48*abs_δ^2/λ^4 + 10*abs_δ/λ^3)*(1 - abs_δ/λ)^2/λ^3 + 360*(-16*abs_δ^2/λ^3 - 5*abs_δ/λ^2)*(1 - abs_δ/λ)^2/λ^4 - 120*(1 + 8*abs_δ^2/λ^2 + 5*abs_δ/λ)*abs_δ^2/λ^7 - 720*(1 + 8*abs_δ^2/λ^2 + 5*abs_δ/λ)*(1 - abs_δ/λ)^2/λ^5 + 360*abs_δ^2*(1 - abs_δ/λ)*(5*dabs_δ/λ + 16*abs_δ*dabs_δ/λ^2)/λ^6 - 1080*abs_δ*(1 - abs_δ/λ)^2*(5*dabs_δ/λ + 16*abs_δ*dabs_δ/λ^2)/λ^5 + 360*abs_δ*(1 - abs_δ/λ)^2*(-5*dabs_δ/λ^2 - 32*abs_δ*dabs_δ/λ^3)/λ^4 - 240*(-16*abs_δ^2/λ^3 - 5*abs_δ/λ^2)*abs_δ*(1 - abs_δ/λ)/λ^5 + 960*(1 + 8*abs_δ^2/λ^2 + 5*abs_δ/λ)*abs_δ*(1 - abs_δ/λ)/λ^6
    end

    if dorder_view==[2, 2]
        func = 96*(1 - abs_δ/λ)^5/λ^4 + 320*abs_δ^2*(1 - abs_δ/λ)^3/λ^6 - 480*abs_δ*(1 - abs_δ/λ)^4/λ^5 + 20*(48*abs_δ^2/λ^4 + 10*abs_δ/λ^3)*(1 - abs_δ/λ)^3/λ^2 - 80*(-16*abs_δ^2/λ^3 - 5*abs_δ/λ^2)*(1 - abs_δ/λ)^3/λ^3 + 120*(1 + 8*abs_δ^2/λ^2 + 5*abs_δ/λ)*(1 - abs_δ/λ)^3/λ^4 - 20*dabs_δ*(1 - abs_δ/λ)^4*(5*dabs_δ/λ + 16*abs_δ*dabs_δ/λ^2)/λ^3 + 20*dabs_δ*(1 - abs_δ/λ)^4*(-5*dabs_δ/λ^2 - 32*abs_δ*dabs_δ/λ^3)/λ^2 - 10*dabs_δ*(1 - abs_δ/λ)^4*(10*dabs_δ/λ^3 + 96*abs_δ*dabs_δ/λ^4)/λ + 120*(-16*abs_δ^2/λ^3 - 5*abs_δ/λ^2)*abs_δ*(1 - abs_δ/λ)^2/λ^4 + 120*(1 + 8*abs_δ^2/λ^2 + 5*abs_δ/λ)*abs_δ^2*(1 - abs_δ/λ)/λ^6 - 360*(1 + 8*abs_δ^2/λ^2 + 5*abs_δ/λ)*abs_δ*(1 - abs_δ/λ)^2/λ^5 - 120*abs_δ^2*dabs_δ*(1 - abs_δ/λ)^2*(5*dabs_δ/λ + 16*abs_δ*dabs_δ/λ^2)/λ^5 + 160*abs_δ*dabs_δ*(1 - abs_δ/λ)^3*(5*dabs_δ/λ + 16*abs_δ*dabs_δ/λ^2)/λ^4 - 80*abs_δ*dabs_δ*(1 - abs_δ/λ)^3*(-5*dabs_δ/λ^2 - 32*abs_δ*dabs_δ/λ^3)/λ^3
    end

    if dorder_view==[1, 2]
        func = (1 - abs_δ/λ)^5*(10*dabs_δ/λ^3 + 96*abs_δ*dabs_δ/λ^4) + 20*abs_δ^2*(1 - abs_δ/λ)^3*(5*dabs_δ/λ + 16*abs_δ*dabs_δ/λ^2)/λ^4 - 10*abs_δ*(1 - abs_δ/λ)^4*(5*dabs_δ/λ + 16*abs_δ*dabs_δ/λ^2)/λ^3 + 10*abs_δ*(1 - abs_δ/λ)^4*(-5*dabs_δ/λ^2 - 32*abs_δ*dabs_δ/λ^3)/λ^2 - 5*(48*abs_δ^2/λ^4 + 10*abs_δ/λ^3)*dabs_δ*(1 - abs_δ/λ)^4/λ + 10*(-16*abs_δ^2/λ^3 - 5*abs_δ/λ^2)*dabs_δ*(1 - abs_δ/λ)^4/λ^2 - 10*(1 + 8*abs_δ^2/λ^2 + 5*abs_δ/λ)*dabs_δ*(1 - abs_δ/λ)^4/λ^3 - 40*(-16*abs_δ^2/λ^3 - 5*abs_δ/λ^2)*abs_δ*dabs_δ*(1 - abs_δ/λ)^3/λ^3 - 60*(1 + 8*abs_δ^2/λ^2 + 5*abs_δ/λ)*abs_δ^2*dabs_δ*(1 - abs_δ/λ)^2/λ^5 + 80*(1 + 8*abs_δ^2/λ^2 + 5*abs_δ/λ)*abs_δ*dabs_δ*(1 - abs_δ/λ)^3/λ^4
    end

    if dorder_view==[0, 2]
        func = (48*abs_δ^2/λ^4 + 10*abs_δ/λ^3)*(1 - abs_δ/λ)^5 + 10*(-16*abs_δ^2/λ^3 - 5*abs_δ/λ^2)*abs_δ*(1 - abs_δ/λ)^4/λ^2 + 20*(1 + 8*abs_δ^2/λ^2 + 5*abs_δ/λ)*abs_δ^2*(1 - abs_δ/λ)^3/λ^4 - 10*(1 + 8*abs_δ^2/λ^2 + 5*abs_δ/λ)*abs_δ*(1 - abs_δ/λ)^4/λ^3
    end

    if dorder_view==[6, 1]
        func = 28800*abs_δ/λ^8 - 172800*(1 - abs_δ/λ)/λ^7 + 3600*(5*dabs_δ/λ + 16*abs_δ*dabs_δ/λ^2)/λ^6 - 720*(-5*dabs_δ/λ^2 - 32*abs_δ*dabs_δ/λ^3)/λ^5
    end

    if dorder_view==[5, 1]
        func = 48000*(1 - abs_δ/λ)^2/λ^6 - 120*(-16*abs_δ^2/λ^3 - 5*abs_δ/λ^2)/λ^5 + 600*(1 + 8*abs_δ^2/λ^2 + 5*abs_δ/λ)/λ^6 - 19200*abs_δ*(1 - abs_δ/λ)/λ^7 + 600*abs_δ*(5*dabs_δ/λ + 16*abs_δ*dabs_δ/λ^2)/λ^6 - 2400*(1 - abs_δ/λ)*(5*dabs_δ/λ + 16*abs_δ*dabs_δ/λ^2)/λ^5 + 600*(1 - abs_δ/λ)*(-5*dabs_δ/λ^2 - 32*abs_δ*dabs_δ/λ^3)/λ^4
    end

    if dorder_view==[4, 1]
        func = -7680*(1 - abs_δ/λ)^3/λ^5 + 5760*abs_δ*(1 - abs_δ/λ)^2/λ^6 + 720*(1 - abs_δ/λ)^2*(5*dabs_δ/λ + 16*abs_δ*dabs_δ/λ^2)/λ^4 - 240*(1 - abs_δ/λ)^2*(-5*dabs_δ/λ^2 - 32*abs_δ*dabs_δ/λ^3)/λ^3 + 120*(-16*abs_δ^2/λ^3 - 5*abs_δ/λ^2)*(1 - abs_δ/λ)/λ^4 + 120*(1 + 8*abs_δ^2/λ^2 + 5*abs_δ/λ)*abs_δ/λ^6 - 480*(1 + 8*abs_δ^2/λ^2 + 5*abs_δ/λ)*(1 - abs_δ/λ)/λ^5 - 480*abs_δ*(1 - abs_δ/λ)*(5*dabs_δ/λ + 16*abs_δ*dabs_δ/λ^2)/λ^5
    end

    if dorder_view==[3, 1]
        func = 720*(1 - abs_δ/λ)^4/λ^4 - 960*abs_δ*(1 - abs_δ/λ)^3/λ^5 - 120*(1 - abs_δ/λ)^3*(5*dabs_δ/λ + 16*abs_δ*dabs_δ/λ^2)/λ^3 + 60*(1 - abs_δ/λ)^3*(-5*dabs_δ/λ^2 - 32*abs_δ*dabs_δ/λ^3)/λ^2 - 60*(-16*abs_δ^2/λ^3 - 5*abs_δ/λ^2)*(1 - abs_δ/λ)^2/λ^3 + 180*(1 + 8*abs_δ^2/λ^2 + 5*abs_δ/λ)*(1 - abs_δ/λ)^2/λ^4 + 180*abs_δ*(1 - abs_δ/λ)^2*(5*dabs_δ/λ + 16*abs_δ*dabs_δ/λ^2)/λ^4 - 120*(1 + 8*abs_δ^2/λ^2 + 5*abs_δ/λ)*abs_δ*(1 - abs_δ/λ)/λ^5
    end

    if dorder_view==[2, 1]
        func = -32*(1 - abs_δ/λ)^5/λ^3 + 80*abs_δ*(1 - abs_δ/λ)^4/λ^4 + 20*(-16*abs_δ^2/λ^3 - 5*abs_δ/λ^2)*(1 - abs_δ/λ)^3/λ^2 - 40*(1 + 8*abs_δ^2/λ^2 + 5*abs_δ/λ)*(1 - abs_δ/λ)^3/λ^3 + 10*dabs_δ*(1 - abs_δ/λ)^4*(5*dabs_δ/λ + 16*abs_δ*dabs_δ/λ^2)/λ^2 - 10*dabs_δ*(1 - abs_δ/λ)^4*(-5*dabs_δ/λ^2 - 32*abs_δ*dabs_δ/λ^3)/λ + 60*(1 + 8*abs_δ^2/λ^2 + 5*abs_δ/λ)*abs_δ*(1 - abs_δ/λ)^2/λ^4 - 40*abs_δ*dabs_δ*(1 - abs_δ/λ)^3*(5*dabs_δ/λ + 16*abs_δ*dabs_δ/λ^2)/λ^3
    end

    if dorder_view==[1, 1]
        func = (1 - abs_δ/λ)^5*(-5*dabs_δ/λ^2 - 32*abs_δ*dabs_δ/λ^3) + 5*abs_δ*(1 - abs_δ/λ)^4*(5*dabs_δ/λ + 16*abs_δ*dabs_δ/λ^2)/λ^2 - 5*(-16*abs_δ^2/λ^3 - 5*abs_δ/λ^2)*dabs_δ*(1 - abs_δ/λ)^4/λ + 5*(1 + 8*abs_δ^2/λ^2 + 5*abs_δ/λ)*dabs_δ*(1 - abs_δ/λ)^4/λ^2 - 20*(1 + 8*abs_δ^2/λ^2 + 5*abs_δ/λ)*abs_δ*dabs_δ*(1 - abs_δ/λ)^3/λ^3
    end

    if dorder_view==[0, 1]
        func = (-16*abs_δ^2/λ^3 - 5*abs_δ/λ^2)*(1 - abs_δ/λ)^5 + 5*(1 + 8*abs_δ^2/λ^2 + 5*abs_δ/λ)*abs_δ*(1 - abs_δ/λ)^4/λ^2
    end

    if dorder_view==[6, 0]
        func = 28800*(1 - abs_δ/λ)/λ^6 - 720*(5*dabs_δ/λ + 16*abs_δ*dabs_δ/λ^2)/λ^5
    end

    if dorder_view==[5, 0]
        func = -9600*(1 - abs_δ/λ)^2/λ^5 - 120*(1 + 8*abs_δ^2/λ^2 + 5*abs_δ/λ)/λ^5 + 600*(1 - abs_δ/λ)*(5*dabs_δ/λ + 16*abs_δ*dabs_δ/λ^2)/λ^4
    end

    if dorder_view==[4, 0]
        func = 1920*(1 - abs_δ/λ)^3/λ^4 - 240*(1 - abs_δ/λ)^2*(5*dabs_δ/λ + 16*abs_δ*dabs_δ/λ^2)/λ^3 + 120*(1 + 8*abs_δ^2/λ^2 + 5*abs_δ/λ)*(1 - abs_δ/λ)/λ^4
    end

    if dorder_view==[3, 0]
        func = -240*(1 - abs_δ/λ)^4/λ^3 + 60*(1 - abs_δ/λ)^3*(5*dabs_δ/λ + 16*abs_δ*dabs_δ/λ^2)/λ^2 - 60*(1 + 8*abs_δ^2/λ^2 + 5*abs_δ/λ)*(1 - abs_δ/λ)^2/λ^3
    end

    if dorder_view==[2, 0]
        func = 16*(1 - abs_δ/λ)^5/λ^2 + 20*(1 + 8*abs_δ^2/λ^2 + 5*abs_δ/λ)*(1 - abs_δ/λ)^3/λ^2 - 10*dabs_δ*(1 - abs_δ/λ)^4*(5*dabs_δ/λ + 16*abs_δ*dabs_δ/λ^2)/λ
    end

    if dorder_view==[1, 0]
        func = (1 - abs_δ/λ)^5*(5*dabs_δ/λ + 16*abs_δ*dabs_δ/λ^2) - 5*(1 + 8*abs_δ^2/λ^2 + 5*abs_δ/λ)*dabs_δ*(1 - abs_δ/λ)^4/λ
    end

    if dorder_view==[0, 0]
        func = (1 + 8*abs_δ^2/λ^2 + 5*abs_δ/λ)*(1 - abs_δ/λ)^5
    end

    dorder[2] = dorder2  # resetting dorder[2]
    return powers_of_negative_one(dorder2) * float(func)  # correcting for amount of t2 derivatives

end

return pp_kernel, 1  # the function handle and the number of kernel hyperparameters