text:
# This file is auto-generated by AWSMetadata.jl
using AWS
using AWS.AWSServices: cognito_identity
using AWS.Compat
using AWS.UUIDs

"""
    create_identity_pool(allow_unauthenticated_identities, identity_pool_name)
    create_identity_pool(allow_unauthenticated_identities, identity_pool_name, params::Dict{String,<:Any})

Creates a new identity pool. The identity pool is a store of user identity information that
is specific to your AWS account. The keys for SupportedLoginProviders are as follows:
Facebook: graph.facebook.com    Google: accounts.google.com    Amazon: www.amazon.com
Twitter: api.twitter.com    Digits: www.digits.com    You must use AWS Developer
credentials to call this API.

# Arguments
- `allow_unauthenticated_identities`: TRUE if the identity pool supports unauthenticated
  logins.
- `identity_pool_name`: A string that you provide.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"AllowClassicFlow"`: Enables or disables the Basic (Classic) authentication flow. For
  more information, see Identity Pools (Federated Identities) Authentication Flow in the
  Amazon Cognito Developer Guide.
- `"CognitoIdentityProviders"`: An array of Amazon Cognito user pools and their client IDs.
- `"DeveloperProviderName"`: The \"domain\" by which Cognito will refer to your users. This
  name acts as a placeholder that allows your backend and the Cognito service to communicate
  about the developer provider. For the DeveloperProviderName, you can use letters as well as
  period (.), underscore (_), and dash (-). Once you have set a developer provider name, you
  cannot change it. Please take care in setting this parameter.
- `"IdentityPoolTags"`: Tags to assign to the identity pool. A tag is a label that you can
  apply to identity pools to categorize and manage them in different ways, such as by
  purpose, owner, environment, or other criteria.
- `"OpenIdConnectProviderARNs"`: The Amazon Resource Names (ARN) of the OpenID Connect
  providers.
- `"SamlProviderARNs"`: An array of Amazon Resource Names (ARNs) of the SAML provider for
  your identity pool.
- `"SupportedLoginProviders"`: Optional key:value pairs mapping provider names to provider
  app IDs.
"""
function create_identity_pool(
    AllowUnauthenticatedIdentities,
    IdentityPoolName;
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return cognito_identity(
        "CreateIdentityPool",
        Dict{String,Any}(
            "AllowUnauthenticatedIdentities" => AllowUnauthenticatedIdentities,
            "IdentityPoolName" => IdentityPoolName,
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function create_identity_pool(
    AllowUnauthenticatedIdentities,
    IdentityPoolName,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return cognito_identity(
        "CreateIdentityPool",
        Dict{String,Any}(
            mergewith(
                _merge,
                Dict{String,Any}(
                    "AllowUnauthenticatedIdentities" => AllowUnauthenticatedIdentities,
                    "IdentityPoolName" => IdentityPoolName,
                ),
                params,
            ),
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    delete_identities(identity_ids_to_delete)
    delete_identities(identity_ids_to_delete, params::Dict{String,<:Any})

Deletes identities from an identity pool. You can specify a list of 1-60 identities that
you want to delete. You must use AWS Developer credentials to call this API.

# Arguments
- `identity_ids_to_delete`: A list of 1-60 identities that you want to delete.

"""
function delete_identities(
    IdentityIdsToDelete; aws_config::AbstractAWSConfig=global_aws_config()
)
    return cognito_identity(
        "DeleteIdentities",
        Dict{String,Any}("IdentityIdsToDelete" => IdentityIdsToDelete);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function delete_identities(
    IdentityIdsToDelete,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return cognito_identity(
        "DeleteIdentities",
        Dict{String,Any}(
            mergewith(
                _merge,
                Dict{String,Any}("IdentityIdsToDelete" => IdentityIdsToDelete),
                params,
            ),
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    delete_identity_pool(identity_pool_id)
    delete_identity_pool(identity_pool_id, params::Dict{String,<:Any})

Deletes an identity pool. Once a pool is deleted, users will not be able to authenticate
with the pool. You must use AWS Developer credentials to call this API.

# Arguments
- `identity_pool_id`: An identity pool ID in the format REGION:GUID.

"""
function delete_identity_pool(
    IdentityPoolId; aws_config::AbstractAWSConfig=global_aws_config()
)
    return cognito_identity(
        "DeleteIdentityPool",
        Dict{String,Any}("IdentityPoolId" => IdentityPoolId);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function delete_identity_pool(
    IdentityPoolId,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return cognito_identity(
        "DeleteIdentityPool",
        Dict{String,Any}(
            mergewith(_merge, Dict{String,Any}("IdentityPoolId" => IdentityPoolId), params)
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    describe_identity(identity_id)
    describe_identity(identity_id, params::Dict{String,<:Any})

Returns metadata related to the given identity, including when the identity was created and
any associated linked logins. You must use AWS Developer credentials to call this API.

# Arguments
- `identity_id`: A unique identifier in the format REGION:GUID.

"""
function describe_identity(IdentityId; aws_config::AbstractAWSConfig=global_aws_config())
    return cognito_identity(
        "DescribeIdentity",
        Dict{String,Any}("IdentityId" => IdentityId);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function describe_identity(
    IdentityId,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return cognito_identity(
        "DescribeIdentity",
        Dict{String,Any}(
            mergewith(_merge, Dict{String,Any}("IdentityId" => IdentityId), params)
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    describe_identity_pool(identity_pool_id)
    describe_identity_pool(identity_pool_id, params::Dict{String,<:Any})

Gets details about a particular identity pool, including the pool name, ID description,
creation date, and current number of users. You must use AWS Developer credentials to call
this API.

# Arguments
- `identity_pool_id`: An identity pool ID in the format REGION:GUID.

"""
function describe_identity_pool(
    IdentityPoolId; aws_config::AbstractAWSConfig=global_aws_config()
)
    return cognito_identity(
        "DescribeIdentityPool",
        Dict{String,Any}("IdentityPoolId" => IdentityPoolId);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function describe_identity_pool(
    IdentityPoolId,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return cognito_identity(
        "DescribeIdentityPool",
        Dict{String,Any}(
            mergewith(_merge, Dict{String,Any}("IdentityPoolId" => IdentityPoolId), params)
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    get_credentials_for_identity(identity_id)
    get_credentials_for_identity(identity_id, params::Dict{String,<:Any})

Returns credentials for the provided identity ID. Any provided logins will be validated
against supported login providers. If the token is for cognito-identity.amazonaws.com, it
will be passed through to AWS Security Token Service with the appropriate role for the
token. This is a public API. You do not need any credentials to call this API.

# Arguments
- `identity_id`: A unique identifier in the format REGION:GUID.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"CustomRoleArn"`: The Amazon Resource Name (ARN) of the role to be assumed when multiple
  roles were received in the token from the identity provider. For example, a SAML-based
  identity provider. This parameter is optional for identity providers that do not support
  role customization.
- `"Logins"`: A set of optional name-value pairs that map provider names to provider
  tokens. The name-value pair will follow the syntax \"provider_name\":
  \"provider_user_identifier\". Logins should not be specified when trying to get credentials
  for an unauthenticated identity. The Logins parameter is required when using identities
  associated with external identity providers such as Facebook. For examples of Logins maps,
  see the code examples in the External Identity Providers section of the Amazon Cognito
  Developer Guide.
"""
function get_credentials_for_identity(
    IdentityId; aws_config::AbstractAWSConfig=global_aws_config()
)
    return cognito_identity(
        "GetCredentialsForIdentity",
        Dict{String,Any}("IdentityId" => IdentityId);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function get_credentials_for_identity(
    IdentityId,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return cognito_identity(
        "GetCredentialsForIdentity",
        Dict{String,Any}(
            mergewith(_merge, Dict{String,Any}("IdentityId" => IdentityId), params)
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    get_id(identity_pool_id)
    get_id(identity_pool_id, params::Dict{String,<:Any})

Generates (or retrieves) a Cognito ID. Supplying multiple logins will create an implicit
linked account. This is a public API. You do not need any credentials to call this API.

# Arguments
- `identity_pool_id`: An identity pool ID in the format REGION:GUID.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"AccountId"`: A standard AWS account ID (9+ digits).
- `"Logins"`: A set of optional name-value pairs that map provider names to provider
  tokens. The available provider names for Logins are as follows:   Facebook:
  graph.facebook.com    Amazon Cognito user pool:
  cognito-idp.<region>.amazonaws.com/<YOUR_USER_POOL_ID>, for example,
  cognito-idp.us-east-1.amazonaws.com/us-east-1_123456789.    Google: accounts.google.com
  Amazon: www.amazon.com    Twitter: api.twitter.com    Digits: www.digits.com
"""
function get_id(IdentityPoolId; aws_config::AbstractAWSConfig=global_aws_config())
    return cognito_identity(
        "GetId",
        Dict{String,Any}("IdentityPoolId" => IdentityPoolId);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function get_id(
    IdentityPoolId,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return cognito_identity(
        "GetId",
        Dict{String,Any}(
            mergewith(_merge, Dict{String,Any}("IdentityPoolId" => IdentityPoolId), params)
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    get_identity_pool_roles(identity_pool_id)
    get_identity_pool_roles(identity_pool_id, params::Dict{String,<:Any})

Gets the roles for an identity pool. You must use AWS Developer credentials to call this
API.

# Arguments
- `identity_pool_id`: An identity pool ID in the format REGION:GUID.

"""
function get_identity_pool_roles(
    IdentityPoolId; aws_config::AbstractAWSConfig=global_aws_config()
)
    return cognito_identity(
        "GetIdentityPoolRoles",
        Dict{String,Any}("IdentityPoolId" => IdentityPoolId);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function get_identity_pool_roles(
    IdentityPoolId,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return cognito_identity(
        "GetIdentityPoolRoles",
        Dict{String,Any}(
            mergewith(_merge, Dict{String,Any}("IdentityPoolId" => IdentityPoolId), params)
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    get_open_id_token(identity_id)
    get_open_id_token(identity_id, params::Dict{String,<:Any})

Gets an OpenID token, using a known Cognito ID. This known Cognito ID is returned by GetId.
You can optionally add additional logins for the identity. Supplying multiple logins
creates an implicit link. The OpenID token is valid for 10 minutes. This is a public API.
You do not need any credentials to call this API.

# Arguments
- `identity_id`: A unique identifier in the format REGION:GUID.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"Logins"`: A set of optional name-value pairs that map provider names to provider
  tokens. When using graph.facebook.com and www.amazon.com, supply the access_token returned
  from the provider's authflow. For accounts.google.com, an Amazon Cognito user pool
  provider, or any other OpenID Connect provider, always include the id_token.
"""
function get_open_id_token(IdentityId; aws_config::AbstractAWSConfig=global_aws_config())
    return cognito_identity(
        "GetOpenIdToken",
        Dict{String,Any}("IdentityId" => IdentityId);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function get_open_id_token(
    IdentityId,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return cognito_identity(
        "GetOpenIdToken",
        Dict{String,Any}(
            mergewith(_merge, Dict{String,Any}("IdentityId" => IdentityId), params)
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    get_open_id_token_for_developer_identity(identity_pool_id, logins)
    get_open_id_token_for_developer_identity(identity_pool_id, logins, params::Dict{String,<:Any})

Registers (or retrieves) a Cognito IdentityId and an OpenID Connect token for a user
authenticated by your backend authentication process. Supplying multiple logins will create
an implicit linked account. You can only specify one developer provider as part of the
Logins map, which is linked to the identity pool. The developer provider is the \"domain\"
by which Cognito will refer to your users. You can use GetOpenIdTokenForDeveloperIdentity
to create a new identity and to link new logins (that is, user credentials issued by a
public provider or developer provider) to an existing identity. When you want to create a
new identity, the IdentityId should be null. When you want to associate a new login with an
existing authenticated/unauthenticated identity, you can do so by providing the existing
IdentityId. This API will create the identity in the specified IdentityPoolId. You must use
AWS Developer credentials to call this API.

# Arguments
- `identity_pool_id`: An identity pool ID in the format REGION:GUID.
- `logins`: A set of optional name-value pairs that map provider names to provider tokens.
  Each name-value pair represents a user from a public provider or developer provider. If the
  user is from a developer provider, the name-value pair will follow the syntax
  \"developer_provider_name\": \"developer_user_identifier\". The developer provider is the
  \"domain\" by which Cognito will refer to your users; you provided this domain while
  creating/updating the identity pool. The developer user identifier is an identifier from
  your backend that uniquely identifies a user. When you create an identity pool, you can
  specify the supported logins.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"IdentityId"`: A unique identifier in the format REGION:GUID.
- `"PrincipalTags"`: Use this operation to configure attribute mappings for custom
  providers.
- `"TokenDuration"`: The expiration time of the token, in seconds. You can specify a custom
  expiration time for the token so that you can cache it. If you don't provide an expiration
  time, the token is valid for 15 minutes. You can exchange the token with Amazon STS for
  temporary AWS credentials, which are valid for a maximum of one hour. The maximum token
  duration you can set is 24 hours. You should take care in setting the expiration time for a
  token, as there are significant security implications: an attacker could use a leaked token
  to access your AWS resources for the token's duration.  Please provide for a small grace
  period, usually no more than 5 minutes, to account for clock skew.
"""
function get_open_id_token_for_developer_identity(
    IdentityPoolId, Logins; aws_config::AbstractAWSConfig=global_aws_config()
)
    return cognito_identity(
        "GetOpenIdTokenForDeveloperIdentity",
        Dict{String,Any}("IdentityPoolId" => IdentityPoolId, "Logins" => Logins);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function get_open_id_token_for_developer_identity(
    IdentityPoolId,
    Logins,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return cognito_identity(
        "GetOpenIdTokenForDeveloperIdentity",
        Dict{String,Any}(
            mergewith(
                _merge,
                Dict{String,Any}("IdentityPoolId" => IdentityPoolId, "Logins" => Logins),
                params,
            ),
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    get_principal_tag_attribute_map(identity_pool_id, identity_provider_name)
    get_principal_tag_attribute_map(identity_pool_id, identity_provider_name, params::Dict{String,<:Any})

Use GetPrincipalTagAttributeMap to list all mappings between PrincipalTags and user
attributes.

# Arguments
- `identity_pool_id`: You can use this operation to get the ID of the Identity Pool you
  setup attribute mappings for.
- `identity_provider_name`: You can use this operation to get the provider name.

"""
function get_principal_tag_attribute_map(
    IdentityPoolId, IdentityProviderName; aws_config::AbstractAWSConfig=global_aws_config()
)
    return cognito_identity(
        "GetPrincipalTagAttributeMap",
        Dict{String,Any}(
            "IdentityPoolId" => IdentityPoolId,
            "IdentityProviderName" => IdentityProviderName,
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function get_principal_tag_attribute_map(
    IdentityPoolId,
    IdentityProviderName,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return cognito_identity(
        "GetPrincipalTagAttributeMap",
        Dict{String,Any}(
            mergewith(
                _merge,
                Dict{String,Any}(
                    "IdentityPoolId" => IdentityPoolId,
                    "IdentityProviderName" => IdentityProviderName,
                ),
                params,
            ),
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    list_identities(identity_pool_id, max_results)
    list_identities(identity_pool_id, max_results, params::Dict{String,<:Any})

Lists the identities in an identity pool. You must use AWS Developer credentials to call
this API.

# Arguments
- `identity_pool_id`: An identity pool ID in the format REGION:GUID.
- `max_results`: The maximum number of identities to return.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"HideDisabled"`: An optional boolean parameter that allows you to hide disabled
  identities. If omitted, the ListIdentities API will include disabled identities in the
  response.
- `"NextToken"`: A pagination token.
"""
function list_identities(
    IdentityPoolId, MaxResults; aws_config::AbstractAWSConfig=global_aws_config()
)
    return cognito_identity(
        "ListIdentities",
        Dict{String,Any}("IdentityPoolId" => IdentityPoolId, "MaxResults" => MaxResults);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function list_identities(
    IdentityPoolId,
    MaxResults,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return cognito_identity(
        "ListIdentities",
        Dict{String,Any}(
            mergewith(
                _merge,
                Dict{String,Any}(
                    "IdentityPoolId" => IdentityPoolId, "MaxResults" => MaxResults
                ),
                params,
            ),
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    list_identity_pools(max_results)
    list_identity_pools(max_results, params::Dict{String,<:Any})

Lists all of the Cognito identity pools registered for your account. You must use AWS
Developer credentials to call this API.

# Arguments
- `max_results`: The maximum number of identities to return.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"NextToken"`: A pagination token.
"""
function list_identity_pools(MaxResults; aws_config::AbstractAWSConfig=global_aws_config())
    return cognito_identity(
        "ListIdentityPools",
        Dict{String,Any}("MaxResults" => MaxResults);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function list_identity_pools(
    MaxResults,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return cognito_identity(
        "ListIdentityPools",
        Dict{String,Any}(
            mergewith(_merge, Dict{String,Any}("MaxResults" => MaxResults), params)
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    list_tags_for_resource(resource_arn)
    list_tags_for_resource(resource_arn, params::Dict{String,<:Any})

Lists the tags that are assigned to an Amazon Cognito identity pool. A tag is a label that
you can apply to identity pools to categorize and manage them in different ways, such as by
purpose, owner, environment, or other criteria. You can use this action up to 10 times per
second, per account.

# Arguments
- `resource_arn`: The Amazon Resource Name (ARN) of the identity pool that the tags are
  assigned to.

"""
function list_tags_for_resource(
    ResourceArn; aws_config::AbstractAWSConfig=global_aws_config()
)
    return cognito_identity(
        "ListTagsForResource",
        Dict{String,Any}("ResourceArn" => ResourceArn);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function list_tags_for_resource(
    ResourceArn,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return cognito_identity(
        "ListTagsForResource",
        Dict{String,Any}(
            mergewith(_merge, Dict{String,Any}("ResourceArn" => ResourceArn), params)
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    lookup_developer_identity(identity_pool_id)
    lookup_developer_identity(identity_pool_id, params::Dict{String,<:Any})

Retrieves the IdentityID associated with a DeveloperUserIdentifier or the list of
DeveloperUserIdentifier values associated with an IdentityId for an existing identity.
Either IdentityID or DeveloperUserIdentifier must not be null. If you supply only one of
these values, the other value will be searched in the database and returned as a part of
the response. If you supply both, DeveloperUserIdentifier will be matched against
IdentityID. If the values are verified against the database, the response returns both
values and is the same as the request. Otherwise a ResourceConflictException is thrown.
LookupDeveloperIdentity is intended for low-throughput control plane operations: for
example, to enable customer service to locate an identity ID by username. If you are using
it for higher-volume operations such as user authentication, your requests are likely to be
throttled. GetOpenIdTokenForDeveloperIdentity is a better option for higher-volume
operations for user authentication. You must use AWS Developer credentials to call this API.

# Arguments
- `identity_pool_id`: An identity pool ID in the format REGION:GUID.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"DeveloperUserIdentifier"`: A unique ID used by your backend authentication process to
  identify a user. Typically, a developer identity provider would issue many developer user
  identifiers, in keeping with the number of users.
- `"IdentityId"`: A unique identifier in the format REGION:GUID.
- `"MaxResults"`: The maximum number of identities to return.
- `"NextToken"`: A pagination token. The first call you make will have NextToken set to
  null. After that the service will return NextToken values as needed. For example, let's say
  you make a request with MaxResults set to 10, and there are 20 matches in the database. The
  service will return a pagination token as a part of the response. This token can be used to
  call the API again and get results starting from the 11th match.
"""
function lookup_developer_identity(
    IdentityPoolId; aws_config::AbstractAWSConfig=global_aws_config()
)
    return cognito_identity(
        "LookupDeveloperIdentity",
        Dict{String,Any}("IdentityPoolId" => IdentityPoolId);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function lookup_developer_identity(
    IdentityPoolId,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return cognito_identity(
        "LookupDeveloperIdentity",
        Dict{String,Any}(
            mergewith(_merge, Dict{String,Any}("IdentityPoolId" => IdentityPoolId), params)
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    merge_developer_identities(destination_user_identifier, developer_provider_name, identity_pool_id, source_user_identifier)
    merge_developer_identities(destination_user_identifier, developer_provider_name, identity_pool_id, source_user_identifier, params::Dict{String,<:Any})

Merges two users having different IdentityIds, existing in the same identity pool, and
identified by the same developer provider. You can use this action to request that discrete
users be merged and identified as a single user in the Cognito environment. Cognito
associates the given source user (SourceUserIdentifier) with the IdentityId of the
DestinationUserIdentifier. Only developer-authenticated users can be merged. If the users
to be merged are associated with the same public provider, but as two different users, an
exception will be thrown. The number of linked logins is limited to 20. So, the number of
linked logins for the source user, SourceUserIdentifier, and the destination user,
DestinationUserIdentifier, together should not be larger than 20. Otherwise, an exception
will be thrown. You must use AWS Developer credentials to call this API.

# Arguments
- `destination_user_identifier`: User identifier for the destination user. The value should
  be a DeveloperUserIdentifier.
- `developer_provider_name`: The \"domain\" by which Cognito will refer to your users. This
  is a (pseudo) domain name that you provide while creating an identity pool. This name acts
  as a placeholder that allows your backend and the Cognito service to communicate about the
  developer provider. For the DeveloperProviderName, you can use letters as well as period
  (.), underscore (_), and dash (-).
- `identity_pool_id`: An identity pool ID in the format REGION:GUID.
- `source_user_identifier`: User identifier for the source user. The value should be a
  DeveloperUserIdentifier.

"""
function merge_developer_identities(
    DestinationUserIdentifier,
    DeveloperProviderName,
    IdentityPoolId,
    SourceUserIdentifier;
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return cognito_identity(
        "MergeDeveloperIdentities",
        Dict{String,Any}(
            "DestinationUserIdentifier" => DestinationUserIdentifier,
            "DeveloperProviderName" => DeveloperProviderName,
            "IdentityPoolId" => IdentityPoolId,
            "SourceUserIdentifier" => SourceUserIdentifier,
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function merge_developer_identities(
    DestinationUserIdentifier,
    DeveloperProviderName,
    IdentityPoolId,
    SourceUserIdentifier,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return cognito_identity(
        "MergeDeveloperIdentities",
        Dict{String,Any}(
            mergewith(
                _merge,
                Dict{String,Any}(
                    "DestinationUserIdentifier" => DestinationUserIdentifier,
                    "DeveloperProviderName" => DeveloperProviderName,
                    "IdentityPoolId" => IdentityPoolId,
                    "SourceUserIdentifier" => SourceUserIdentifier,
                ),
                params,
            ),
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    set_identity_pool_roles(identity_pool_id, roles)
    set_identity_pool_roles(identity_pool_id, roles, params::Dict{String,<:Any})

Sets the roles for an identity pool. These roles are used when making calls to
GetCredentialsForIdentity action. You must use AWS Developer credentials to call this API.

# Arguments
- `identity_pool_id`: An identity pool ID in the format REGION:GUID.
- `roles`: The map of roles associated with this pool. For a given role, the key will be
  either \"authenticated\" or \"unauthenticated\" and the value will be the Role ARN.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"RoleMappings"`: How users for a specific identity provider are to mapped to roles. This
  is a string to RoleMapping object map. The string identifies the identity provider, for
  example, \"graph.facebook.com\" or
  \"cognito-idp.us-east-1.amazonaws.com/us-east-1_abcdefghi:app_client_id\". Up to 25 rules
  can be specified per identity provider.
"""
function set_identity_pool_roles(
    IdentityPoolId, Roles; aws_config::AbstractAWSConfig=global_aws_config()
)
    return cognito_identity(
        "SetIdentityPoolRoles",
        Dict{String,Any}("IdentityPoolId" => IdentityPoolId, "Roles" => Roles);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function set_identity_pool_roles(
    IdentityPoolId,
    Roles,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return cognito_identity(
        "SetIdentityPoolRoles",
        Dict{String,Any}(
            mergewith(
                _merge,
                Dict{String,Any}("IdentityPoolId" => IdentityPoolId, "Roles" => Roles),
                params,
            ),
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    set_principal_tag_attribute_map(identity_pool_id, identity_provider_name)
    set_principal_tag_attribute_map(identity_pool_id, identity_provider_name, params::Dict{String,<:Any})

You can use this operation to use default (username and clientID) attribute or custom
attribute mappings.

# Arguments
- `identity_pool_id`: The ID of the Identity Pool you want to set attribute mappings for.
- `identity_provider_name`: The provider name you want to use for attribute mappings.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"PrincipalTags"`: You can use this operation to add principal tags.
- `"UseDefaults"`: You can use this operation to use default (username and clientID)
  attribute mappings.
"""
function set_principal_tag_attribute_map(
    IdentityPoolId, IdentityProviderName; aws_config::AbstractAWSConfig=global_aws_config()
)
    return cognito_identity(
        "SetPrincipalTagAttributeMap",
        Dict{String,Any}(
            "IdentityPoolId" => IdentityPoolId,
            "IdentityProviderName" => IdentityProviderName,
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function set_principal_tag_attribute_map(
    IdentityPoolId,
    IdentityProviderName,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return cognito_identity(
        "SetPrincipalTagAttributeMap",
        Dict{String,Any}(
            mergewith(
                _merge,
                Dict{String,Any}(
                    "IdentityPoolId" => IdentityPoolId,
                    "IdentityProviderName" => IdentityProviderName,
                ),
                params,
            ),
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    tag_resource(resource_arn, tags)
    tag_resource(resource_arn, tags, params::Dict{String,<:Any})

Assigns a set of tags to the specified Amazon Cognito identity pool. A tag is a label that
you can use to categorize and manage identity pools in different ways, such as by purpose,
owner, environment, or other criteria. Each tag consists of a key and value, both of which
you define. A key is a general category for more specific values. For example, if you have
two versions of an identity pool, one for testing and another for production, you might
assign an Environment tag key to both identity pools. The value of this key might be Test
for one identity pool and Production for the other. Tags are useful for cost tracking and
access control. You can activate your tags so that they appear on the Billing and Cost
Management console, where you can track the costs associated with your identity pools. In
an IAM policy, you can constrain permissions for identity pools based on specific tags or
tag values. You can use this action up to 5 times per second, per account. An identity pool
can have as many as 50 tags.

# Arguments
- `resource_arn`: The Amazon Resource Name (ARN) of the identity pool.
- `tags`: The tags to assign to the identity pool.

"""
function tag_resource(ResourceArn, Tags; aws_config::AbstractAWSConfig=global_aws_config())
    return cognito_identity(
        "TagResource",
        Dict{String,Any}("ResourceArn" => ResourceArn, "Tags" => Tags);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function tag_resource(
    ResourceArn,
    Tags,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return cognito_identity(
        "TagResource",
        Dict{String,Any}(
            mergewith(
                _merge,
                Dict{String,Any}("ResourceArn" => ResourceArn, "Tags" => Tags),
                params,
            ),
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    unlink_developer_identity(developer_provider_name, developer_user_identifier, identity_id, identity_pool_id)
    unlink_developer_identity(developer_provider_name, developer_user_identifier, identity_id, identity_pool_id, params::Dict{String,<:Any})

Unlinks a DeveloperUserIdentifier from an existing identity. Unlinked developer users will
be considered new identities next time they are seen. If, for a given Cognito identity, you
remove all federated identities as well as the developer user identifier, the Cognito
identity becomes inaccessible. You must use AWS Developer credentials to call this API.

# Arguments
- `developer_provider_name`: The \"domain\" by which Cognito will refer to your users.
- `developer_user_identifier`: A unique ID used by your backend authentication process to
  identify a user.
- `identity_id`: A unique identifier in the format REGION:GUID.
- `identity_pool_id`: An identity pool ID in the format REGION:GUID.

"""
function unlink_developer_identity(
    DeveloperProviderName,
    DeveloperUserIdentifier,
    IdentityId,
    IdentityPoolId;
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return cognito_identity(
        "UnlinkDeveloperIdentity",
        Dict{String,Any}(
            "DeveloperProviderName" => DeveloperProviderName,
            "DeveloperUserIdentifier" => DeveloperUserIdentifier,
            "IdentityId" => IdentityId,
            "IdentityPoolId" => IdentityPoolId,
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function unlink_developer_identity(
    DeveloperProviderName,
    DeveloperUserIdentifier,
    IdentityId,
    IdentityPoolId,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return cognito_identity(
        "UnlinkDeveloperIdentity",
        Dict{String,Any}(
            mergewith(
                _merge,
                Dict{String,Any}(
                    "DeveloperProviderName" => DeveloperProviderName,
                    "DeveloperUserIdentifier" => DeveloperUserIdentifier,
                    "IdentityId" => IdentityId,
                    "IdentityPoolId" => IdentityPoolId,
                ),
                params,
            ),
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    unlink_identity(identity_id, logins, logins_to_remove)
    unlink_identity(identity_id, logins, logins_to_remove, params::Dict{String,<:Any})

Unlinks a federated identity from an existing account. Unlinked logins will be considered
new identities next time they are seen. Removing the last linked login will make this
identity inaccessible. This is a public API. You do not need any credentials to call this
API.

# Arguments
- `identity_id`: A unique identifier in the format REGION:GUID.
- `logins`: A set of optional name-value pairs that map provider names to provider tokens.
- `logins_to_remove`: Provider names to unlink from this identity.

"""
function unlink_identity(
    IdentityId, Logins, LoginsToRemove; aws_config::AbstractAWSConfig=global_aws_config()
)
    return cognito_identity(
        "UnlinkIdentity",
        Dict{String,Any}(
            "IdentityId" => IdentityId,
            "Logins" => Logins,
            "LoginsToRemove" => LoginsToRemove,
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function unlink_identity(
    IdentityId,
    Logins,
    LoginsToRemove,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return cognito_identity(
        "UnlinkIdentity",
        Dict{String,Any}(
            mergewith(
                _merge,
                Dict{String,Any}(
                    "IdentityId" => IdentityId,
                    "Logins" => Logins,
                    "LoginsToRemove" => LoginsToRemove,
                ),
                params,
            ),
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    untag_resource(resource_arn, tag_keys)
    untag_resource(resource_arn, tag_keys, params::Dict{String,<:Any})

Removes the specified tags from the specified Amazon Cognito identity pool. You can use
this action up to 5 times per second, per account

# Arguments
- `resource_arn`: The Amazon Resource Name (ARN) of the identity pool.
- `tag_keys`: The keys of the tags to remove from the user pool.

"""
function untag_resource(
    ResourceArn, TagKeys; aws_config::AbstractAWSConfig=global_aws_config()
)
    return cognito_identity(
        "UntagResource",
        Dict{String,Any}("ResourceArn" => ResourceArn, "TagKeys" => TagKeys);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function untag_resource(
    ResourceArn,
    TagKeys,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return cognito_identity(
        "UntagResource",
        Dict{String,Any}(
            mergewith(
                _merge,
                Dict{String,Any}("ResourceArn" => ResourceArn, "TagKeys" => TagKeys),
                params,
            ),
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    update_identity_pool(allow_unauthenticated_identities, identity_pool_id, identity_pool_name)
    update_identity_pool(allow_unauthenticated_identities, identity_pool_id, identity_pool_name, params::Dict{String,<:Any})

Updates an identity pool. You must use AWS Developer credentials to call this API.

# Arguments
- `allow_unauthenticated_identities`: TRUE if the identity pool supports unauthenticated
  logins.
- `identity_pool_id`: An identity pool ID in the format REGION:GUID.
- `identity_pool_name`: A string that you provide.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"AllowClassicFlow"`: Enables or disables the Basic (Classic) authentication flow. For
  more information, see Identity Pools (Federated Identities) Authentication Flow in the
  Amazon Cognito Developer Guide.
- `"CognitoIdentityProviders"`: A list representing an Amazon Cognito user pool and its
  client ID.
- `"DeveloperProviderName"`: The \"domain\" by which Cognito will refer to your users.
- `"IdentityPoolTags"`: The tags that are assigned to the identity pool. A tag is a label
  that you can apply to identity pools to categorize and manage them in different ways, such
  as by purpose, owner, environment, or other criteria.
- `"OpenIdConnectProviderARNs"`: The ARNs of the OpenID Connect providers.
- `"SamlProviderARNs"`: An array of Amazon Resource Names (ARNs) of the SAML provider for
  your identity pool.
- `"SupportedLoginProviders"`: Optional key:value pairs mapping provider names to provider
  app IDs.
"""
function update_identity_pool(
    AllowUnauthenticatedIdentities,
    IdentityPoolId,
    IdentityPoolName;
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return cognito_identity(
        "UpdateIdentityPool",
        Dict{String,Any}(
            "AllowUnauthenticatedIdentities" => AllowUnauthenticatedIdentities,
            "IdentityPoolId" => IdentityPoolId,
            "IdentityPoolName" => IdentityPoolName,
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function update_identity_pool(
    AllowUnauthenticatedIdentities,
    IdentityPoolId,
    IdentityPoolName,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return cognito_identity(
        "UpdateIdentityPool",
        Dict{String,Any}(
            mergewith(
                _merge,
                Dict{String,Any}(
                    "AllowUnauthenticatedIdentities" => AllowUnauthenticatedIdentities,
                    "IdentityPoolId" => IdentityPoolId,
                    "IdentityPoolName" => IdentityPoolName,
                ),
                params,
            ),
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end


text:
# module for representing agent
# decide control order to robot
# get action based on policy
# by q learning

include(joinpath(split(@__FILE__, "src")[1], "src/decision_making/reinforcement_learning/state_info.jl"))

mutable struct QAgent
  speed
  yaw_rate
  delta_time
  estimator
  prev_spd
  prev_yr
  puddle_coef
  puddle_depth
  total_reward
  in_goal
  final_value
  goal
  pose_min
  pose_max
  widths
  index_nums
  indexes
  actions
  policy_data
  state_space

  # for reinforcement learning
  alpha # step size parameter
  s # state
  a # action
  update_end # end flag
  stuck_timer

  # init
  function QAgent(;delta_time::Float64=0.1,
                  estimator=nothing,
                  puddle_coef=100,
                  alpha=0.5,
                  widths=[0.2, 0.2, pi/18],
                  lower_left=[-4.0, -4.0],
                  upper_right=[4.0, 4.0],
                  dev_borders=[0.1, 0.2, 0.4, 0.8])
    self = new()
    self.speed = 0.0
    self.yaw_rate = 0.0
    self.delta_time = delta_time
    self.estimator = estimator
    self.prev_spd = 0.0
    self.prev_yr = 0.0
    self.puddle_coef = puddle_coef
    self.puddle_depth = 0.0
    self.total_reward = 0.0
    self.in_goal = false
    self.final_value = 0.0
    self.goal = nothing

    self.pose_min = [lower_left[1], lower_left[2], 0.0]
    self.pose_max = [upper_right[1], upper_right[2], 2*pi]
    self.widths = widths
    self.index_nums = round.(Int64, (self.pose_max - self.pose_min)./self.widths)
    self.policy_data = init_policy(self)

    nx, ny, nt = self.index_nums[1], self.index_nums[2], self.index_nums[3]
    self.indexes = vec(collect(Base.product(0:nx-1, 0:ny-1, 0:nt-1)))
    actions_set = Set([Tuple(self.policy_data[i[1]+1, i[2]+1, i[3]+1, :]) for i in self.indexes])
    self.actions = [a for a in actions_set]
    self.state_space = set_action_value_function(self)

    self.alpha = alpha
    self.s = nothing
    self.a = nothing
    self.update_end = false
    self.stuck_timer = 0.0
    return self
  end
end

function set_action_value_function(self::QAgent)
  state_space = Dict()

  txt_path = "src/decision_making/reinforcement_learning/value.txt"
  open(joinpath(split(@__FILE__, "src")[1], txt_path), "r") do fp
    for line in eachline(fp)
      d = split(line) # [i_x, i_y, i_theta, value]
      index, value = Tuple([parse(Int64, d[1])+1, parse(Int64, d[2])+1, parse(Int64, d[3])+1]), parse(Float64, d[4])
      state_space[index] = StateInfo(length(self.actions))

      for (i, a) in enumerate(self.actions)
        if Tuple(self.policy_data[index[1], index[2], index[3], :]) == a
          state_space[index].q[i] = value
        else
          state_space[index].q[i] = value - 0.1
        end
      end
    end
  end

  return state_space
end

function init_policy(self::QAgent)
  tmp = zeros(Tuple([self.index_nums[1], self.index_nums[2], self.index_nums[3], 2]))

  txt_path = "src/decision_making/reinforcement_learning/policy.txt"
  open(joinpath(split(@__FILE__, "src")[1], txt_path), "r") do fp
    for line in eachline(fp)
      d = split(line) # [i_x, i_y, i_theta, speed, yaw_rate]
      tmp[parse(Int64, d[1])+1, parse(Int64, d[2])+1, parse(Int64, d[3])+1, :] .= [parse(Float64, d[4]), parse(Float64, d[5])]
    end
  end

  return tmp
end

function reward_per_sec(self::QAgent)
  return (-1.0 - self.puddle_depth*self.puddle_coef)
end

function to_index(self::QAgent, pose)
  index = Int64.(floor.((pose - self.pose_min)./self.widths))

  # normalize direction index
  index[3] = (index[3] + self.index_nums[3]*1000)%self.index_nums[3]

  for i in 1:2
    if index[i] < 0
      index[i] = 0
    elseif index[i] >= self.index_nums[i]
      index[i] = self.index_nums[i] - 1
    end
  end

  return index
end

function policy(self::QAgent, pose)
  index = to_index(self, pose)

  s = Tuple(index .+ [1, 1, 1])

  a = _pi(self.state_space[s])

  return s, a
end

function q_update(self::QAgent, r, s_)
  if self.s === nothing
    return # previous state is none
  end

  # q before update
  q = self.state_space[self.s].q[self.a]

  # q after state transition
  if self.in_goal == true
    q_ = self.final_value
  else
    q_ = max_q(self.state_space[s_])
  end

  # update q
  self.state_space[self.s].q[self.a] = (1 - self.alpha)*q + self.alpha*(r + q_)
end

function draw_decision!(self::QAgent, observation)
  # finished learning
  if self.update_end == true
    return 0.0, 0.0 # stop
  end

  # reached at goal
  if self.in_goal == true
    self.update_end = true
  end

  if self.estimator !== nothing
    # state estimation
    motion_update(self.estimator, self.prev_spd, self.prev_yr, self.delta_time)
    observation_update(self.estimator, observation)

    # decide next action
    s_, a_ = policy(self, self.estimator.estimated_pose)

    # calculate reward for state transition
    r = self.delta_time * reward_per_sec(self)
    self.total_reward += r

    # q-learning
    q_update(self, r, s_) # update using reward and next state

    # save current state and action
    self.s, self.a = s_, a_

    # count up while stucking
    if self.actions[a_][1] == 0.0
      self.stuck_timer += self.delta_time
    else
      self.stuck_timer = 0.0
    end

    # prevent stuck
    if self.stuck_timer > 10.0
      self.speed, self.yaw_rate = 1.0, 0.0
    else
      self.speed, self.yaw_rate = self.actions[a_][1], self.actions[a_][2]
    end

    self.prev_spd, self.prev_yr = self.speed, self.yaw_rate

    draw!(self.estimator)
    x = self.estimator.estimated_pose[1]
    y = self.estimator.estimated_pose[2]
    annotate!(x+1.0, y-0.5, text("reward/sec:$(reward_per_sec(self))", :left, 8))
    annotate!(x+1.0, y-1.0, text("total reward:$(round(self.total_reward, digits=1))", :left, 8))
  end

  return self.speed, self.yaw_rate
end

ext: jl

max_stars_repo_name: ShisatoYano/JuliaAutonomy

max_issues_repo_name: ShisatoYano/JuliaAutonomy

max_forks_repo_name: ShisatoYano/JuliaAutonomy


text:
using CSV
using DataStructures: SortedDict, SortedSet
using Random
using DataAPI
using StringEncodings
using StaticArrays: SVector
using CoordinateTransformations: AbstractAffineMap

Base.convert(::Type{Symbol}, elm::Element) = Symbol(uppercase(elm.symbol))

"""
Handles RJ Lee-style Zeppelin particle data sets.
"""
struct Zeppelin
    headerfile::String
    header::SortedDict{String,String}
    data::DataFrame
    classnames::Vector{String}

    function Zeppelin( #
        headerfile::String,
        header::AbstractDict{String,String},
        data::DataFrame,
        classnames::AbstractVector{String}
    )
        function _massagehdz(header)
            res = SortedDict(header)
            for hi in keys(header)
                if  hi in ( "PARTICLE_PARAMETERS", "PARAMETERS", "HEADER_FMT" ) || #
                    startswith(hi, "ELEM") || startswith(hi, "CLASS")
                    delete!(res, hi)
                end
            end
            return res
        end
        # Mostly converts to pretty printing
        function _massagepxz(zep)
            zd = zep.data
            # Find all the class name mappings
            for col in ("CLASS", "VERIFIEDCLASS")
                ic = findfirst(nm->nm == col, names(zd))
                if (!isnothing(ic)) && (eltype(zd[:,ic]) <: Integer)
                    old = zd[:, ic]
                    select!(zd, Not(ic))
                    insertcols!(zd, ic, col => map(ncl->ZepClass(zep, ncl), old))
                end
            end
            for col in ( "FIRSTELM", "SECONDELM", "THIRDELM", "FOURTHELM" )
                ic = findfirst(nm->nm==col, names(zd))
                if !isnothing(ic) && (eltype(zd[:,ic]) <: Integer)
                    old = zd[:, ic]
                    select!(zd, Not(ic))
                    insertcols!(zd, ic, col=>map(z->z in 1:100 ? elements[z] : missing, old))
                end
            end
            for col in ( "XDAC", "YDAC", "TYPE_4ET_", "TYPE4ET" )
                ic = findfirst(nm->nm==col, names(zd))
                if !isnothing(ic)
                    select!(zd, Not(ic))
                end
            end
        end
        zep = new(
            endswith(headerfile, r"\.[h|H][d|D][z|Z]$") ? headerfile : "$headerfile.hdz",
            _massagehdz(header),
            copy(data),
            classnames
        )
        _massagepxz(zep)
        return zep
    end

    Zeppelin( hdzfilename::String) = loadZep( hdzfilename)
end

function NeXLSpectrum.name(zep::Zeppelin)
    get(zep.header, "NAME", get(zep.header, "DESCRIPTION", splitpath(zep.headerfile)[end-1]))
end

struct ZepClass
    zep::Zeppelin
    index::Int
end
function Base.show(io::IO, zc::ZepClass)
    if zc.index == -1
        print(io, "-")
    elseif zc.index+1 in eachindex(zc.zep.classnames)
        print(io, zc.zep.classnames[zc.index+1])
    else
        print(io, "**UNCLASSIFIED**")
    end
end
Base.:(==)(zc1::ZepClass, zc2::ZepClass) = zc1.index==zc2.index
Base.:(==)(zc1::ZepClass, zc2::AbstractString) = repr(zc1)==zc2
Base.:(==)(zc1::AbstractString, zc2::ZepClass) = zc1==repr(zc2)
Base.String(zc::ZepClass) = repr(zc)

Base.get(z::Zeppelin, key::String, def=missing) = get(z.header, uppercase(key), def)
Base.getindex(z::Zeppelin, key::String) = getindex(z.header, uppercase(key))

Base.copy(z::Zeppelin) = Zeppelin(z.headerfile, copy(z.header), copy(z.data), copy(z.classnames))

function loadZep( hdzfilename::String)::Zeppelin
    remapcolumnnames = Dict(
        "PART#" => "NUMBER",
        "PARTNUM" => "NUMBER",
        "FIELD#" => "FIELD",
        "FIELDNUM" => "FIELD",
        "MAGFIELD#" => "MAGFIELD",
        "MAGFIELDNUM" => "MAGFIELD",
        "X_ABS" => "XABS",
        "Y_ABS" => "YABS",
        "X_DAC" => "XDAC",
        "Y_DAC" => "YDAC",
        "XCENT" => "XDAC",
        "YCENT" => "YDAC",
        "X_FERET" => "XFERET",
        "Y_FERET" => "YFERET",
        "DVG" => "DAVG",
        "DAVE" => "DAVG",
        "PERIM" => "PERIMETER",
        "ORIENT" => "ORIENTATION",
        "LIVE_TIME" => "LIVETIME",
        "FIT_QUAL" => "FITQUAL",
        "MAG_INDEX" => "MAGINDEX",
        "FIRST_ELEM" => "FIRSTELM",
        "SECOND_ELEM" => "SECONDELM",
        "THIRD_ELEM" => "THIRDELM",
        "FOURTH_ELEM" => "FOURTHELM",
        "ATOMICNUMBER1" => "FIRSTELM",
        "ATOMICNUMBER2" => "SECONDELM",
        "ATOMICNUMBER3" => "THIRDELM",
        "ATOMICNUMBER4" => "FOURTHELM",
        "FIRST_CONC" => "COUNTS1",
        "SECOND_CONC" => "COUNTS2",
        "THIRD_CONC" => "COUNTS3",
        "FOURTH_CONC" => "COUNTS4",
        "FIRST_PCT" => "FIRSTPCT",
        "SECOND_PCT" => "SECONDPCT",
        "THIRD_PCT" => "THIRDPCT",
        "FOURTH_PCT" => "FOURTHPCT",
        "PCT1" => "FIRSTPCT",
        "PCT2" => "SECONDPCT",
        "PCT3" => "THIRDPCT",
        "PCT4" => "FOURTHPCT",
        "TYPE(4ET)#" => "TYPE4ET",
        "TYPE(4ET)" => "TYPE4ET",
        "VOID_AREA" => "VOIDAREA",
        "RMS_VIDEO" => "RMSVIDEO",
        "FIT_QUAL" => "FITQUAL",
        "VERIFIED_CLASS" => "VERIFIEDCLASS",
        "EDGE_ROUGHNESS" => "EDGEROUGHNESS",
        "COMP_HASH" => "COMPHASH",
        "PSEM_CLASS" => "CLASS",
    )
    function _extractclassnames(header)
        # Find all the class name mappings
        cn = SortedDict(parse(Int, cn[6:end]) => header[cn] for cn in filter(c -> !isnothing(match(r"^CLASS\d+", c)), collect(keys(header))))
        return map(i -> get(cn, i, "CLASS$i"), 0:maximum(keys(cn)))
    end
    columnnames(cols) = uppercase.(map(cn -> get(remapcolumnnames, cn, cn), map(c -> c[1], cols)))
    header, columns, hdr = Dict{String,String}(), [], true
    open(hdzfilename, enc"WINDOWS-1252","r") do f
        for line in readlines(f)
            if hdr
                p = findfirst(c -> c == '=', line)
                if !isnothing(p)
                    (k, v) = line[1:p-1], line[p+1:end]
                    header[k] = v
                    hdr = !isequal(uppercase(k), "PARTICLE_PARAMETERS")
                end
            else
                push!(columns, string.(strip.(split(line, "\t"))))
            end
        end
    end
    pxz = CSV.File(
        replace(hdzfilename, r".[h|H][d|D][z|Z]$" => ".pxz"),
        header = columnnames(columns),
        delim='\t',
        normalizenames = true,
        missingstring="-",
        decimal='.'
    ) |> DataFrame
    return Zeppelin( hdzfilename, header, pxz, _extractclassnames(header))
end

function Base.show(io::IO, zep::Zeppelin)
    print(io, "Zeppelin[$(zep.headerfile),$(size(zep.data))]")
end

Base.size(z::Zeppelin) = size(z.data)
Base.size(z::Zeppelin, i::Int) = size(z.data, i)

classes(zep::Zeppelin) = levels(zep[:,"CLASS"])

NeXLCore.elms(zep::Zeppelin) = SortedSet(filter(elm -> uppercase(elm.symbol) in names(zep.data), PeriodicTable.elements[1:94]))

header(zep::Zeppelin) = SortedDict(zep.header)

"""
    asa(::Type{DataFrame}, zep::Zeppelin; rows=missing, columns=missing, sortcol=:None, rev=false)

Create a DataFrame containing the particle data.  Optionally specify which rows and columns to include and
whether to sort the resulting table.
"""
function NeXLUncertainties.asa(::Type{DataFrame}, zep::Zeppelin; rows=missing, sortcol=:None, columns=missing, rev=false)
    rs = ismissing(rows) ? eachparticle(zep) : intersect(eachparticle(zep), rows)
    cols = ismissing(columns) ? names(zep.data) : intersect(columns,names(zep.data))
    res = zep.data[rs, cols]
    return sortcol≠:None ? sort(res, sortcol, rev=rev) : res
end

eachparticle(zep::Zeppelin) = 1:size(zep.data, 1)

function DataAPI.describe(zeps::AbstractVector{Zeppelin}; dcol=:DAVG, nelms=2)
    function build(zep)
        df=describe(zep, dcol=dcol, nelms=nelms)
        insertcols!(df, 1, :Dataset=>[zep.header["DESCRIPTION"] for _ in 1:size(df,1)])
        return df
    end
    return vcat(map(z->build(z),zeps))
end

function DataAPI.describe(zep::Zeppelin; dcol=:DAVG, nelms=3)
    els = elms(zep)
    function sortedstats(rows)
        tmp = [ ( elm, summarystats(zep[rows,convert(Symbol, elm)])) for elm in els ]
        return sort!(tmp, lt=(v1,v2)->isless(v1[2].mean,v2[2].mean),rev=true)
    end
    cnelms = min(length(els), nelms)
    ds = summarystats(zep[:,dcol])
    clss = String["All"]
    szs = Int[size(zep.data,1)]
    minds = Float64[ ds.min ]
    medds = Float64[ ds.median ]
    maxds = Float64[ ds.max ]
    sbcm = StatsBase.countmap(zep[:,"CLASS"])
    for (cn, _) in sbcm
        rows = rowsclass(zep,String(cn))
        #if length(rows)>0
        push!(clss, repr(cn))
        push!(szs, length(rows))
        ds = summarystats(zep[rows,dcol])
        push!(minds, ds.min)
        push!(medds, ds.median)
        push!(maxds, ds.max)
        #end
    end
    dfres = DataFrame(Symbol("Class")=>clss, Symbol("Count")=>szs,Symbol("Min[$dcol]")=>minds, Symbol("Median[$dcol]")=>medds, Symbol("Max[$dcol]")=>maxds)
    # Add All elemental row
    ss = sortedstats(eachparticle(zep))
    elmdfs=DataFrame[ ]
    uem, uef = Union{String,Missing}, Union{Float64,Missing}
    for ne in 1:cnelms
        cols = Symbol.( [ "Elm[$ne]", "Min[$ne]", "Median[$ne]", "Max[$ne]" ])
        sst = ss[ne][2]
        push!(elmdfs, DataFrame(cols[1]=>uem[ symbol(ss[ne][1]) ], cols[2]=>uef[sst.min], cols[3]=>uef[sst.median], cols[4]=>uef[sst.max]))
    end
    for (cn, _) in sbcm
        try
            ss = sortedstats(rowsclass(zep, String(cn)))
            for ne in 1:cnelms
                sst = ss[ne][2]
                push!(elmdfs[ne], [ symbol(ss[ne][1]), sst.min, sst.median, sst.max ])
            end
        catch
            for ne in 1:cnelms
                push!(elmdfs[ne], [ missing, missing, missing, missing ])
            end
        end
    end
    for elmdf in elmdfs
        for col in names(elmdf)
            insertcols!(dfres, size(dfres,2)+1, col=>elmdf[:,col])
        end
    end
    return sort!(dfres, [:Count, :Class], rev=true)
end

"""
    zep[123] # where zep is a Zeppelin
    zep[1:2:8]

Returns the Spectrum (with images) associated with the particle at row or rows
"""
Base.getindex(zep::Zeppelin, row::Int) = spectrum(zep, row, true)
Base.getindex(zep::Zeppelin, rows) = map(row->spectrum(zep, row, true), rows)

Base.getindex(zep::Zeppelin, rows, cols) = Base.getindex(zep.data, rows, cols)

Base.lastindex(zep::Zeppelin, axis::Integer) = Base.lastindex(zep.data, axis)

# Replace the default in PeriodicTable because it is too verbose...
# Base.show(io::IO, elm::Element) = print(io, elm.symbol)

function filenumber(zep::Zeppelin, row::Int)::String
    tmp="$(zep.data[row,:NUMBER])"
    return repeat('0',max(0,5-length(tmp)))*tmp
end

"""
    spectrumfilename(zep::Zeppelin, row::Int, dir::AbstractString="MAG", ext::AbstractString=".tif")

Returns the name of the spectrum/image file for the particle in the specified row.
"""
function spectrumfilename(zep::Zeppelin, row::Int, dir::AbstractString = "MAG", ext::AbstractString = ".tif", relocated=false)
    mag = hasproperty(zep.data, :MAG) ? convert(Int, trunc(zep.data[row, :MAG])) : 0
    # First check if a spectrum file exists
    tmp=repr(zep.data[row, :NUMBER])
    if relocated && isdir(joinpath(dirname(zep.headerfile),"RELOCATED"))
        for n in 6:-1:4
            fn = joinpath(dirname(zep.headerfile), "RELOCATED", repeat('0',max(0,n-length(tmp)))*tmp*ext)
            if isfile(fn)
                return fn
            end
        end
    end
    for n in 6:-1:4
        fn = joinpath(dirname(zep.headerfile), "$(dir)$(mag)", repeat('0',max(0,n-length(tmp)))*tmp*ext)
        if isfile(fn)
            return fn
        end
    end
    # Default case
    return joinpath(dirname(zep.headerfile), "$(dir)$(mag)", repeat('0',max(0,5-length(tmp)))*tmp*ext)
end

"""
    ParticleClassifier

A type that implements:

    classify(zep::Zeppelin, sr::ParticleClassifier)::CategoricalArray{String}
"""
abstract type ParticleClassifier end

"""
    spectrum(zep::Zeppelin, row::Int, withImgs = true)::Union{Spectrum, missing}

Returns the Spectrum (with images) associated with the particle at row.  If withImgs
is true, the associated image or images are read.
"""
function spectrum(zep::Zeppelin, row::Int, withImgs = true, relocated=true)::Union{Spectrum,Missing}
    file, at = spectrumfilename(zep, row, "MAG", ".tif", relocated), missing
    if isfile(file)
        try
            at = loadspectrum(ASPEXTIFF, file; withImgs = withImgs)
        catch err
            showerror(stderr, err)
            @info "$(file) does not appear to be a valid ASPEX spectrum TIFF."
        end
        try
            at[:BeamEnergy] = beamenergy(zep, get(at, :BeamEnergy, 20.0e3))
            at[:ProbeCurrent] = get(at, :ProbeCurrent, probecurrent(zep, 1.0))
            at[:Signature] = filter(kv->kv[2]>0.0, Dict(elm => zep.data[row, convert(Symbol,elm)] for elm in elms(zep)))
            at[:Name] = "$(name(zep))[$(zep.data[row, :NUMBER]), $(zep.data[row, :CLASS])]"
        catch
            @info "Error adding properties to ASPEX TIFF file."
        end
    end
    return at
end

function iszeppelin(filename::String)
    open(filename, enc"WINDOWS-1252") do ios
        seekstart(ios)
        if isequal(uppercase(String(read(ios,11))),"PARAMETERS=")
            readline(ios) # read the rest of the line
            return isequal(uppercase(readline(ios)),"HEADER_FMT=ZEPP_1")
        end
    end
    return false
end

function writeZep(zep::Zeppelin,  hdzfilename::String)
    remapcolumnnames = Dict(
        "NUMBER"=> "PART#\t1\tINT16",
        "FIELD" => "FIELD#\t1\tINT16",
        "MAGFIELD" => "MAGFIELD#\t1\tINT16",
        "XABS" => "X_ABS\tmm\tFLOAT",
        "YABS" => "Y_ABS\tmm\tFLOAT",
        "XDAC" => "X_DAC\t1\tINT16",
        "YDAC" => "Y_DAC\t1\tINT16",
        "XFERET" => "X_FERET\tµm\tFLOAT",
        "YFERET" => "Y_FERET\tµm\tFLOAT",
        "DAVG" => "DAVE\tµm\tFLOAT",
        "DMAX" => "DMAX\tµm\tFLOAT",
        "DMIN" => "DMIN\tµm\tFLOAT",
        "DPERP" => "DPERP\tµm\tFLOAT",
        "ASPECT" =>"ASPECT\t1\tFLOAT",
        "AREA" => "AREA\tµm²\tFLOAT",
        "PERIMETER" => "PERIMETER\tµm\tFLOAT",
        "ORIENTATION" => "ORIENTATION\tdeg\tFLOAT",
        "LIVETIME" => "LIVE_TIME\ts\tFLOAT",
        "FITQUAL" => "FIT_QUAL\t1\tFLOAT",
        "MAG" => "MAG\t1\tINT16",
        "VIDEO" => "VIDEO\t1\tINT16",
        "IMPORTANCE" => "IMPORTANCE\t1\tINT16",
        "COUNTS" => "COUNTS\t1\tFLOAT",
        "MAGINDEX" => "MAG_INDEX\t1\tINT16",
        "FIRSTELM" => "FIRST_ELEM\t1\tINT16",
        "SECONDELM" => "SECOND_ELEM\t1\tINT16",
        "THIRDELM" => "THIRD_ELEM\t1\tINT16",
        "FOURTHELM" => "FOURTH_ELEM\t1\tINT16",
        "COUNTS1" => "FIRST_CONC\tcounts\tFLOAT",
        "COUNTS2" => "SECOND_CONC\tcounts\tFLOAT",
        "COUNTS3" => "THIRD_CONC\tcounts\tFLOAT",
        "COUNTS4" => "FOURTH_CONC\tcounts\tFLOAT",
        "FIRSTPCT" => "FIRST_PCT\t%\tFLOAT",
        "SECONDPCT" => "SECOND_PCT\t%\tFLOAT",
        "THIRDPCT" => "THIRD_PCT\t%\tFLOAT",
        "FOURTHPCT" => "FOURTH_PCT\t%\tFLOAT",
        "TYPE4ET" => "TYPE(4ET#)\t1\tLONG",
        "VOIDAREA" => "VOID_AREA\tµm²\tFLOAT",
        "RMSVIDEO" => "RMS_VIDEO\t1\tINT16",
        "FITQUAL" => "FIT_QUAL\t1\tFLOAT",
        "VERIFIEDCLASS" => "VERIFIED_CLASS\t1\tINT16",
        "EDGEROUGHNESS" => "EDGE_ROUGHNESS\t1\tFLOAT",
        "COMPHASH" => "COMP_HASH\t1\tLONG",
        "CLASS" => "PSEM_CLASS\t1\tINT16",
        "TYPE_4ET_" => "Type[4ET]\t1\tLONG",
    )
    els = elms(zep)
    merge!(remapcolumnnames, Dict( uppercase(elm.symbol) => "$(uppercase(elm.symbol))\t%(k)\tFLOAT" for elm in els))
    merge!(remapcolumnnames, Dict( "U_$(uppercase(elm.symbol))_"  => "U[$(uppercase(elm.symbol))]\t%(k)\tFLOAT" for elm in els))
    headeritems = copy(zep.header)
    # add back the element tags
    for (i,elm) in enumerate(els)
        headeritems["ELEM$(i-1)"] = "$(elm.symbol) $(z(elm)) 1"
    end
    headeritems["ELEMENTS"] = "$(length(els))"
    if "CLASS" in names(zep.data)
        for (i, cn) in enumerate(zep.classnames)
            headeritems["CLASS$(i-1)"]=cn
        end
    end
    headeritems["TOTAL_PARTICLES"] = "$(size(zep.data,1))"
    # write out the header
    colnames = names(zep.data)
    open(hdzfilename, enc"WINDOWS-1252", "w") do ios
        println(ios, "PARAMETERS=$(length(headeritems)+size(zep.data,2)+2)")
        println(ios, "HEADER_FMT=ZEPP_1")
        foreach(hk->println(ios,"$(hk)=$(headeritems[hk])"), sort(collect(keys(headeritems))))
        println(ios, "PARTICLE_PARAMETERS=$(size(zep.data,2))")
        foreach(colnames) do col
            println(ios, get(remapcolumnnames, col, "$col\t1\t"*uppercase(repr(eltype(zep.data[:,col])))))
        end
    end
    pxzfilename = replace( hdzfilename, r".[h|H][d|D][z|Z]"=>".pxz")
    zd = DataFrame(zep.data)
    # Replace prettified items with the index (either into CLASS# in header or z(elm))
    for (ic, col) in enumerate(names(zd))
        if col in ("CLASS", "VERIFIEDCLASS", "FIRSTELM", "SECONDELM", "THIRDELM", "FOURTHELM" )
            coldata=zd[:,col]
            select!(zd ,Not(ic))
            if col in ( "CLASS", "VERIFIEDCLASS" )
                insertcols!(zd, ic, col => [ zc.index for zc in coldata ]) # CLASS
            elseif col in ("FIRSTELM", "SECONDELM", "THIRDELM", "FOURTHELM" )
                insertcols!(zd, ic, col => [ ze isa Element ? z(ze) : 0 for ze in coldata ]) # Element
            end
        end
    end
    CSV.write(pxzfilename, zd, delim="\t", missingstring="-", header=false)
end

function beamenergy(zep::Zeppelin, def=missing)
    number(v) = parse(Float64, match(r"([+-]?[0-9]+[.]?[0-9]*)",v)[1])
    val = def
    try
        v = get(zep.header, "ACCELERATING_VOLTAGE", missing)
        val = !ismissing(v) ? number(v)*1.0e3 : def
    catch
        # Ignore it...
    end
    return val
end

function probecurrent(zep::Zeppelin, def=missing)
    number(v) = parse(Float64, match(r"([+-]?[0-9]+[.]?[0-9]*)",v)[1])
    val = def
    try
        v = get(zep.header, "PROBE_CURRENT", missing)
        val = !ismissing(v) ? number(v) : def
    catch
        # Ignore it...
    end
    return val
end

function magdata(zep::Zeppelin, index::Int)
    h = split(zep.header["MAG_FMT"],isspace)
    v = split(zep.header["MAG$index"],isspace)
    return Dict( string(h[i])=>parse(Float64, v[i]) for i in 1:min(length(h),length(v)) )
end

"""
    randomsubset(zep::Zeppelin, maxRows::Int)

Creates an ordered random subselection of `maxRows` without replacement of the rows in the `zep` dataset.  If maxRows
is larger than the number of rows in `zep` then a UnitRange with all rows is returned.
"""
randomsubset(zep::Zeppelin, maxRows::Int) =
    return maxRows<size(zep.data,1) ? #
        sort(Random.shuffle(collect(eachparticle(zep)))[1:maxRows]) :
        eachparticle(zep)

"""
    residual(zep::Zeppelin, row::Int, withImgs=false)::Union{Spectrum,Missing}

Returns the residual spectrum for the particle at `row` or missing if it does not exist. Never returns images.
"""
function residual(zep::Zeppelin, row::Int, withImgs=false)::Union{Spectrum,Missing}
    res = missing
    try
        filename = spectrumfilename(zep, row, "Residual", ".msa")
        res = isfile(filename) ? loadspectrum(filename) : missing
    catch
        # Ignore errors, just return missing
    end
    return res
end

"""
    maxparticle(zep::Zeppelin, rows::Union{AbstractVector{Int},UnitRange{Int}})
    maxresidual(zep::Zeppelin, rows::Union{AbstractVector{Int},UnitRange{Int}})

Computes the maxparticle spectrum from the specified particles by `rows`.
"""
function maxparticle(zep::Zeppelin, rows::Union{AbstractVector{Int},UnitRange{Int}})
    mp, firstspec = missing, missing
    for spec in filter(s->!ismissing(s), map(r->spectrum(zep,r,false),rows))
        @assert spec isa Spectrum
        firstspec = ismissing(firstspec) ? spec : firstspec
        cx = NeXLSpectrum.counts(spec)
        mp = ismissing(mp) ? cx : max.(mp, cx)
    end
    # Now copy it out as a spectrum
    props = copy(firstspec.properties)
    props[:Name] = "MaxParticle"
    return Spectrum(firstspec.energy, mp, props)
end

"""
    maxresidual(zep::Zeppelin, rows::Union{AbstractVector{Int},UnitRange{Int}}=1:1000000)

Computes the max-residual spectrum from the specified particles by `rows`.  The residual must have been
previously computed in quantify(zep, ....)
"""
function maxresidual(zep::Zeppelin, rows::Union{AbstractVector{Int},UnitRange{Int}})
    mp, firstspec = missing, missing
    for spec in filter(s->!ismissing(s), map(r->residual(zep,r,false),rows))
        @assert spec isa Spectrum
        firstspec = ismissing(firstspec) ? spec : firstspec
        cx = NeXLSpectrum.counts(spec)
        mp = ismissing(mp) ? cx : max.(mp, cx)
    end
    # Now copy it out as a spectrum
    props = copy(firstspec.properties)
    props[:Name] = "MaxResidual"
    return Spectrum(firstspec.energy, mp, props)
end

"""
    rowsmax(zep::Zeppelin, col::Symbol; n::Int=10000000, classname::Union{Missing,AbstractString}=missing)

Returns the row indices associated with the `n` maximum values in the `col` column.

Examples:

    # Plot ten largest by :DAVG
    plot(zep, rowsmax(zep, :DAVG, 10))
    # data from 100 most Iron-rich sorted by DAVG
    rowsmax(DataFrame, zep, rowsmax(zep, :FE, 100), sortCol=:DAVG)
"""
function rowsmax(zep::Zeppelin, col::Symbol; n::Int=10000000, classname::Union{Missing,AbstractString}=missing)
    if ismissing(classname)
        d = collect(zip(1:250,zep.data[:,col]))
        s = sort(d, lt=(a1,a2)->isless(a1[2],a2[2]),rev=true)
        return getindex.(s[intersect(eachparticle(zep),1:n)],1)
    else
        cr = filter(r->zep.data[r,"CLASS"]==classname, eachparticle(zep))
        d = collect(zip(cr, zep.data[cr,col]))
        s = sort(d, lt=(a1,a2)->isless(a1[2],a2[2]),rev=true)
        return getindex.(s[intersect(eachindex(cr),1:n)],1)
    end
end
"""
    rowsmin(zep::Zeppelin, col::Symbol; n::Int=10000000, classname::Union{Missing,AbstractString}=missing)

Returns the row indices associated with the `n` minimum values in the `col` column.

Examples:

    # Plot ten smallest by :DAVG
    plot(zep, rowsmin(zep, :DAVG, 10))
    # data from 100 most Iron-rich sorted by DAVG
    rowsmin(DataFrame, zep, rowsmax(zep, :FE, 100), sortCol=:DAVG)
"""
function rowsmin(zep::Zeppelin, col::Symbol; n::Int=20, classname::Union{Missing,AbstractString}=missing)
    if ismissing(classname)
        d = collect(zip(eachparticle(zep),zep.data[:,col]))
        s = sort(d, lt=(a1,a2)->isless(a1[2],a2[2]),rev=false)
        return getindex.(s[intersect(eachparticle(zep),1:n)],1)
    else
        cr = filter(r->zep.data[r,"CLASS"]==classname, eachparticle(zep))
        d = collect(zip(cr, zep.data[cr,col]))
        s = sort(d, lt=(a1,a2)->isless(a1[2],a2[2]),rev=false)
        return getindex.(s[intersect(eachindex(cr),1:n)],1)
    end
end

"""
    rowsclass(zep::Zeppelin, classname::AbstractString; shuffle=false, n=1000000)

Returns the row indices associated with the specified `classname`.  If `shuffle` is true,
the row indices are shuffled so that a randomized n can be plucked off to plot or other.

Example:

    # Plot a randomized selection of 10 "Calcite" particles
    plot(zep, rowsclass(zep, "Calcite", shuffle=true, n=10), xmax=8.0e3)
"""
function rowsclass(zep::Zeppelin, classname::AbstractString; shuffle=false, n=1000000)
    res = filter(i->zep.data[i,"CLASS"]==classname, eachparticle(zep))
    return (shuffle ? Random.shuffle(res) : res)[intersect(eachindex(res),1:n)]
end

rowsclass(zep::Zeppelin, classnames::AbstractVector{<:AbstractString}; shuffle=false, n=1000000) =
    mapreduce(cn->rowsclass(zep, cn, shuffle=shuffle, n=n), union, classnames)

"""
    Base.filter(filt::Function, zep::Zeppelin)

Use a function of the form `filt(row)::Bool` to filter a Zeppelin dataset returning a new Zeppelin dataset
with only the rows for which the function evaluated true.

Example:

    gsr = filter(row->startswith( String(row["CLASS"]), "GSR."), zep)
"""
function Base.filter(filt::Function, zep::Zeppelin)::Zeppelin
    zd = filter(filt, zep.data; view=false)
    return Zeppelin(zep.headerfile, copy(zep.header), zd, copy(zep.classnames))
end

const MORPH_COLS = ( "NUMBER", "XABS", "YABS", "DAVG", "DMIN", "DMAX", "DPERP", "PERIMETER", "ORIENTATION", "AREA" )
const CLASS_COLS = ( "CLASS", "VERIFIEDCLASS", "IMPORTANCE" )
const COMP_COLS = ( "FIRSTELM", "FIRSTPCT", "SECONDELM", "SECONDPCT", "THIRDELM", "THIRDPCT", "FOURTHELM", "FOURTHPCT", "COUNTS" )
const ALL_ELMS = map(elm->uppercase(elm.symbol), elements[1:95])

const ALL_COMPOSITIONAL_COLUMNS = ( "FIRST", "FIRSTELM", "SECONDELM", "THIRDELM", "FOURTHELM", "FIRSTELM", "SECONDELM",
    "THIRDELM", "FOURTHELM", "COUNTS1", "COUNTS2", "COUNTS3", "COUNTS4", "FIRSTPCT", "SECONDPCT", "THIRDPCT",
    "FOURTHPCT", "TYPE4ET", "COUNTS", "FITQUAL", "COMPHASH" )
const ALL_CLASS_COLS = ( "CLASS", "VERIFIEDCLASS", "IMPORTANCE" )

"""
    translate(zep::Zeppelin, am::AbstractAffineMap)::Zeppelin

The result is a copy of `zep` with the columns `:XABS` and `:YABS` replaced with the DataFtranslated coordinates.
"""
function translate(zep::Zeppelin, am::AbstractAffineMap)::Zeppelin
    res=copy(zep)
    foreach(eachrow(res.data)) do r
        ( r.XABS, r.YABS ) = am(SVector(r.XABS, r.YABS))
    end
    return res
end

"""
    align(zep1::Zeppelin, zep2::Zeppelin; tol=0.001, finealign=true)

Aligns `zep2` to overlay with `zep1`.   `zep1` and `zep2` are assumed to be particle data sets collected
from the same sample but that may translated and rotated from one-another.   Not all the particles need to
correspond between `zep1` and `zep2` but many must.
The function returns a copy of `zep2` transformed to overlay `zep1`.
"""
function align(zep1::Zeppelin, zep2::Zeppelin; tol=0.001, finealign=true)
    ps1 = map(xy-> SA[ xy... ], zip(zep1[:, :XABS], zep1[:,:YABS]))
    ps2 = map(xy-> SA[ xy... ], zip(zep2[:, :XABS], zep2[:,:YABS]))
    ct1, ct2 = align(ps1, ps2, tol=tol, finealign=finealign)
    return translate(zep2, inv(ct1)∘ct2)
end
"""
    correspondences(zep1::Zeppelin, zep2::Zeppelin; tol=0.01, invert=false)

Identify particle correspondences between `zep1` and `zep2`.  Returns two new
`Zeppelin` data sets `zc1` and `zc2` in which corresponding particles are matched
by row index. So `zc1[1,:]` is likely to refer to the same particle as `zc2[1, :]`.
Unless invert is chosen, in which case only the particles with no corresponding
partners are returned.
"""
function correspondences(zep1::Zeppelin, zep2::Zeppelin; tol=0.01, invert=false)
    ps1 = map(xy-> SA[ xy... ], zip(zep1[:, :XABS], zep1[:,:YABS]))
    ps2 = map(xy-> SA[ xy... ], zip(zep2[:, :XABS], zep2[:,:YABS]))
    c1, c2 = correspondences(ps1, ps2, tol=tol, invert=invert)
    return (
        Zeppelin(zep1.headerfile, copy(zep1.header), zep1.data[c1, :], copy(zep1.classnames)),
        Zeppelin(zep2.headerfile, copy(zep2.header), zep2.data[c2, :], copy(zep2.classnames))
    )
end

"""
    identify(zeps::AbstractArray{Zeppelin}; tol=0.001, ctol=0.01, columns=())::DataFrame

Takes multiple Zeppelin data sets that represent the same particles and returns a `DataFrame`
that identifies the particles from one data set to the next.  The algorithm aligns the data sets
by determining the rotation and offset to bring them into registration.  Then it identifies all
the unique particles by position and tracks them from one data set to the next.  The number of
rows in the `DataFrame` is the number of unique particle positions identified (the `:XABS` and
`:YABS` colums).  The `:COUNT` column is the number of times a particle was found at this
position.  The `:APPEARS` column is the first particle data set in which it was found by index.
The first columns are the index at which the particle is found in the `Zeppelin` data set.

The columns argument allows you to extract `mean` and `std` for a property of a physical particle
within the data sets in which it was measured.  Like: `columns=(:DAVG, :AREA)`
"""
function identify(zeps::AbstractArray{Zeppelin}; tol=0.001, ctol=0.01, columns=())
    pss=map(zeps) do zep
        map(xy-> SA[ xy... ], zip(zep[:, :XABS], zep[:,:YABS]))
    end
    res = identify(pss; tol=tol, ctol=ctol)
    rename!(res, ("PS$i"=>NeXLSpectrum.name(zeps[i]) for i in eachindex(zeps))...)
    l = levels(res[:, :APPEARS])
    insertcols!(res, length(zeps)+2, :ZEPPELIN => map(i->name(zeps[l[i.ref]]), res[:,:APPEARS]))
    for col in columns
        cm=map(eachrow(res)) do r
            mean(skipmissing( [ismissing(r[i]) ? missing : z[r[i], col] for (i, z) in enumerate(zeps)]))
        end
        cs=map(eachrow(res)) do r
            std(skipmissing( [ismissing(r[i]) ? missing : z[r[i], col] for (i, z) in enumerate(zeps)]))
        end
        insertcols!(res, Symbol(col,"_mean")=>cm)
        insertcols!(res, Symbol(col,"_std")=>cs)
    end
    res
end

max_stars_repo_name: NicholasWMRitchie/NeXLParticle

max_issues_repo_name: NicholasWMRitchie/NeXLParticle

max_forks_repo_name: NicholasWMRitchie/NeXLParticle


text:
# To be used instead of _PMACDC.variable_branch_ne() - supports deduplication of variables
function variable_ne_branchdc_indicator(pm::_PM.AbstractPowerModel; nw::Int=_PM.nw_id_default, relax::Bool=false, report::Bool=true)
    first_n = first_id(pm, nw, :hour, :scenario)
    if nw == first_n
        if !relax
            Z_dc_branch_ne = _PM.var(pm, nw)[:branchdc_ne] = JuMP.@variable(pm.model, #branch_ne is also name in PowerModels, branchdc_ne is candidate branches
                [l in _PM.ids(pm, nw, :branchdc_ne)], base_name="$(nw)_branch_ne",
                binary = true,
                start = _PM.comp_start_value(_PM.ref(pm, nw, :branchdc_ne, l), "convdc_tnep_start",  0.0)
            )
        else
            Z_dc_branch_ne = _PM.var(pm, nw)[:branchdc_ne] = JuMP.@variable(pm.model, #branch_ne is also name in PowerModels, branchdc_ne is candidate branches
                [l in _PM.ids(pm, nw, :branchdc_ne)], base_name="$(nw)_branch_ne",
                lower_bound = 0,
                upper_bound = 1,
                start = _PM.comp_start_value(_PM.ref(pm, nw, :branchdc_ne, l), "convdc_tnep_start",  0.0)
            )
        end
    else
        Z_dc_branch_ne = _PM.var(pm, nw)[:branchdc_ne] = _PM.var(pm, first_n)[:branchdc_ne]
    end
    report && _PM.sol_component_value(pm, nw, :branchdc_ne, :isbuilt, _PM.ids(pm, nw, :branchdc_ne), Z_dc_branch_ne)
end

function variable_ne_branchdc_investment(pm::_PM.AbstractPowerModel; nw::Int=_PM.nw_id_default, relax::Bool=false, report::Bool=true)
    first_n = first_id(pm, nw, :hour, :scenario)
    if nw == first_n
        if !relax
            investment = _PM.var(pm, nw)[:branchdc_ne_investment] = JuMP.@variable(pm.model, #branch_ne is also name in PowerModels, branchdc_ne is candidate branches
                [l in _PM.ids(pm, nw, :branchdc_ne)], base_name="$(nw)_branch_ne_investment",
                binary = true,
                start = 0
            )
        else
            investment = _PM.var(pm, nw)[:branchdc_ne_investment] = JuMP.@variable(pm.model, #branch_ne is also name in PowerModels, branchdc_ne is candidate branches
                [l in _PM.ids(pm, nw, :branchdc_ne)], base_name="$(nw)_branch_ne_investment",
                lower_bound = 0,
                upper_bound = 1,
                start = 0
            )
        end
    else
        investment = _PM.var(pm, nw)[:branchdc_ne_investment] = _PM.var(pm, first_n)[:branchdc_ne_investment]
    end
    report && _PM.sol_component_value(pm, nw, :branchdc_ne, :investment, _PM.ids(pm, nw, :branchdc_ne), investment)
end


text:
using jInv.InverseSolve
using Test
using jInv.Mesh
using jInv.Utils

# setup forward mesh
domain = [0;5;0;5; 0;3]
n     = [12;13;10]
Minv  = getRegularMesh(domain,n)

modFun = (expMod,boundMod,fMod)
for k=1:length(modFun)
	# checkDerivative of $(modFun[k])
	function testModFun(m,v=[])
		mc,dm = modFun[k](m)
		if !isempty(v)
			dm = dm*v
			return mc,dm
		end
		return mc
	end
	derivativeDistModel, = checkDerivative(testModFun,rand(prod(n)),out=false)
	@test derivativeDistModel
end


text:
using Test
using MappedArrays
using HyperbolicDiffEq
@static if Int==Int64
    # NOTE: Only on 64 bit machines due to
    # - https://github.com/JuliaPlots/Plots.jl/issues/968
    # - https://github.com/JuliaPlots/Plots.jl/issues/963
    # Fixed for julia v0.7 (https://github.com/JuliaLang/julia/pull/22644)
    using Plots; unicodeplots()
end

const godunov = GodunovFlux()

# Burgers' equation
model = Burgers()

# Rarefaction wave
prob1 = RiemannProblem(model, 0., 1.)
print(devnull, prob1)
@test prob1(0-eps()) ≈ 0
@test prob1(0+eps()) ≈ 1
sol = solve(prob1)
print(devnull, sol)
@test sol(-1) ≈ 0
@test sol( 2) ≈ 1
ξ = range(0, 1, length=10)
@test all(sol.(ξ) .== ξ)
@test sol(2., -0.5) ≈ 0
@test sol(2., 1.) ≈ 0.5
@test sol(2., 2.) ≈ 1
@static if Int==Int64
    plot(sol)
end
@test flux(sol(0), sol.prob.model) ≈ godunov(sol.prob.uₗ, sol.prob.uᵣ, sol.prob.model)

# Stationary shock wave
prob2 = RiemannProblem(model, 1., -1., 1.)
print(devnull, prob2)
@test prob2(1-eps()) ≈ 1
@test prob2(1+eps()) ≈ -1
sol = solve(prob2)
print(devnull, sol)
@test sol(-1) ≈  1
@test sol( 1) ≈ -1
@static if Int==Int64
    plot(sol)
end
@test flux(sol(0), sol.prob.model) ≈ godunov(sol.prob.uₗ, sol.prob.uᵣ, sol.prob.model)

# Tuples of Riemann problems
prob3 = RiemannProblem(model, -1., -2., 2.f0)
prob4 = RiemannProblem(model, -2., 5., 7//2)

@test_throws ErrorException prob3 * prob1
prob1 * prob2
(prob1 * prob2) * prob3
prob1 * (prob2 * prob3)
(prob1 * prob2) * (prob3 * prob4)
prob = prob1 * prob2
print(devnull, prob)

sol = solve(prob)
print(devnull, sol)
@test sol(1., -1.) ≈ 0
@test sol(1., 0.) ≈ 0
@test sol(1., 0.5) ≈ 0.5
@test sol(1., 1-eps()) ≈ 1
@test sol(1., 1+eps()) ≈ -1
@test sol(1., 2.) ≈ -1
@static if Int==Int64
    plot(sol)
end

us = range(-1, 1, length=10)
for (uₗ,uᵣ) in Iterators.product(us,us)
  prob = RiemannProblem(model, uₗ, uᵣ)
  sol = solve(prob)
  @test isapprox(godunov(uₗ, uᵣ, model), flux(sol(0), model), atol=1.e-30)
end

################################################################################

# Buckley-Leverette equation
model = BuckleyLeverette()
us = range(0, 1, length=10)
for (uₗ,uᵣ) in Iterators.product(us,us)
  prob = RiemannProblem(model, uₗ, uᵣ)
  sol = solve(prob)
  @test godunov(uₗ, uᵣ, model) ≈ flux(sol(0), model)
end

################################################################################

# Cubic conservation law
model = Cubic()
us = range(-5, 5, length=100)
for (uₗ,uᵣ) in Iterators.product(us,us)
  prob = RiemannProblem(model, uₗ, uᵣ)
  sol = solve(prob)
  @test godunov(uₗ, uᵣ, model) ≈ flux(sol(0), model)
end

################################################################################

# Shallow water equations
model = ShallowWater()

# Dam Break
# Example 5.20 of Holden & Risebro, "Front Tracking for Hyperbolic Conservation Laws", 2002
uₗ = ShallowWaterVar1D(1., 0.)
uᵣ = ShallowWaterVar1D(0., 0.)
prob = RiemannProblem(model, uₗ, uᵣ)
sol = solve(prob)
x = range(-3, 3, length=50)
u = sol.(1., x)
h = mappedarray(u->u.h, u)
v = mappedarray(u->(u.h ≈ 0 ? zero(u.hv) : u.hv/u.h), u)
@test all(h[x .< -1] .≈ 1)
@test all(h[-1 .< x .< 2] ≈ (2 .- x[-1 .< x .< 2]).^2 ./ 9)
@test all(h[x .> 2] .≈ 0)
@test all(v[x .< -1] .≈ 0)
@test all(v[-1 .< x .< 2] ≈ 2 .* (1 .+ x[-1 .< x .< 2]) ./ 3)
@test all(v[x .> 2] .≈ 0)
@static if Int==Int64
    plot(sol)
end

uₗ, uᵣ = uᵣ, uₗ
prob = RiemannProblem(model, uₗ, uᵣ)
sol = solve(prob)
x = range(-3, 3, length=50)
u = sol.(1., x)
h = mappedarray(u->u.h, u)
v = mappedarray(u->(u.h ≈ 0 ? zero(u.hv) : u.hv/u.h), u)
@test all(h[x .< -2] .≈ 0)
@test all(h[-2 .< x .< 1] ≈ (2 .+ x[-2 .< x .< 1]).^2 ./ 9)
@test all(h[x .> 1] .≈ 1)
@test all(v[x .< -2] .≈ 0)
@test all(v[-2 .< x .< 1] ≈ -2 .* (1 .- x[-2 .< x .< 1]) ./ 3)
@test all(v[x .> 1] .≈ 0)
@static if Int==Int64
    plot(sol)
end

uₗ = ShallowWaterVar1D(1., 0.)
uᵣ = ShallowWaterVar1D(eps(), 0.)
prob = RiemannProblem(model, uₗ, uᵣ)
sol = solve(prob)
x = range(3, 3, length=50)
u = sol.(1., x)
h = mappedarray(u->u.h, u)
v = mappedarray(u->(u.h ≈ 0 ? zero(u.hv) : u.hv/u.h), u)
@test all(h[x .< -1] .≈ 1)
@test all(h[-1 .< x .< 2] ≈ (2 .- x[-1 .< x .< 2]).^2 ./ 9)
@test all(h[x .> 2] .≈ eps())
@test all(v[x .< -1] .≈ 0)
@test all(v[-1 .< x .< 2] ≈ 2 .* (1 .+ x[-1 .< x .< 2]) ./ 3)
@test all(v[x .> 2] .≈ 0)
@static if Int==Int64
    plot(sol)
end

uₗ, uᵣ = uᵣ, uₗ
prob = RiemannProblem(model, uₗ, uᵣ)
sol = solve(prob)
x = range(3, 3, length=50)
u = sol.(1., x)
h = mappedarray(u->u.h, u)
v = mappedarray(u->(u.h ≈ 0 ? zero(u.hv) : u.hv/u.h), u)
@test all(h[x .< -2] .≈ eps())
@test all(h[-2 .< x .< 1] ≈ (2 .+ x[-2 .< x .< 1]).^2 ./ 9)
@test all(h[x .> 1] .≈ 1)
@test all(v[x .< -2] .≈ 0)
@test all(v[-2 .< x .< 1] ≈ -2 .* (1 .- x[-2 .< x .< 1]) ./ 3)
@test all(v[x .> 1] .≈ 0)
@static if Int==Int64
    plot(sol)
end

# Moses' First Problem
# Example 5.21 of Holden & Risebro, "Front Tracking for Hyperbolic Conservation Laws", 2002
h₀ = 1.
v₀ = 2.5
uₗ = ShallowWaterVar1D(h₀, -v₀)
uᵣ = ShallowWaterVar1D(h₀,  v₀)
prob = RiemannProblem(model, uₗ, uᵣ)
sol = solve(prob)
x = range(-1-(v₀+sqrt(h₀)), 1+v₀+sqrt(h₀), length=50)
u = sol.(1., x)
h = mappedarray(u->u.h, u)
v = mappedarray(u->(u.h ≈ 0 ? zero(u.hv) : u.hv/u.h), u)
idx1 = x .< -(v₀+sqrt(h₀))
idx2 = -(v₀+sqrt(h₀)) .< x .< (2*sqrt(h₀)-v₀)
idx3 = (2*sqrt(h₀)-v₀) .< x .< (v₀-2*sqrt(h₀))
idx4 = (v₀-2*sqrt(h₀)) .< x .< (v₀+sqrt(h₀))
idx5 = (v₀+sqrt(h₀)) .< x
@test all(h[idx1] .≈ h₀)
@test all(h[idx2] .≈ (-v₀ .+ 2 .* sqrt(h₀) .- x[idx2]).^2 ./ 9)
@test all(h[idx3] .≈ 0)
@test all(h[idx4] .≈ (v₀ .- 2 .* sqrt(h₀) .- x[idx4]).^2 ./ 9)
@test all(h[idx5] .≈ h₀)
@test all(v[idx1] .≈ -v₀)
@test all(v[idx2] .≈ (-v₀ .+ 2 .* sqrt(h₀) .+ 2 .* x[idx2]) ./ 3)
@test all(v[idx3] .≈ 0)
@test all(v[idx4] .≈ (v₀ .- 2 .* sqrt(h₀) .+ 2 .* x[idx4]) ./ 3)
@test all(v[idx5] .≈ v₀)
@static if Int==Int64
    plot(sol)
end

################################################################################

# Euler equations
model = HyperbolicDiffEq.Euler()

# intermediate values
# Toro (2009), Riemann Solvers and Numerical Methods for Fluid Dynamics,
# Table 4.3
compute_pₘ_vₘ = (ϱₗ, vₗ, pₗ, ϱᵣ, vᵣ, pᵣ) -> begin
    γ = 1.4
    HyperbolicDiffEq.compute_pₘ_vₘ(ϱₗ, vₗ, pₗ, sqrt(γ*pₗ/ϱₗ), ϱᵣ, vᵣ, pᵣ, sqrt(γ*pᵣ/ϱᵣ), γ)
end
pₘ, vₘ = compute_pₘ_vₘ(1.0, 0.0, 1.0, 0.125, 0.0, 0.1)
@test isapprox(pₘ, 0.30313, atol=1.e-5)
@test isapprox(vₘ, 0.92745, atol=1.e-5)
pₘ, vₘ = compute_pₘ_vₘ(1.0, -2.0, 0.4, 1.0, 2.0, 0.4)
@test isapprox(pₘ, 0.00189, atol=1.e-5)
@test isapprox(vₘ, 0.00000, atol=1.e-5)
pₘ, vₘ = compute_pₘ_vₘ(1.0, 0.0, 1000.0, 1.0, 0.0, 0.01)
@test isapprox(pₘ, 460.894, atol=1.e-3)
@test isapprox(vₘ, 19.5975, atol=1.e-4)
pₘ, vₘ = compute_pₘ_vₘ(1.0, 0.0, 0.01, 1.0, 0.0, 100.0)
@test isapprox(pₘ, 46.0950, atol=1.e-4)
@test isapprox(vₘ, -6.19633, atol=1.e-5)
pₘ, vₘ = compute_pₘ_vₘ(5.99924, 19.5975, 460.894, 5.99242, -6.19633, 46.0950)
@test isapprox(pₘ, 1691.64, atol=1.e-2)
@test isapprox(vₘ, 8.68975, atol=3.e-5)

# Toro (2009), Riemann Solvers and Numerical Methods for Fluid Dynamics:
# Test 1 on page 129
uₗ = conserved_variables(1.0, 0.0, 1.0, model)
uᵣ = conserved_variables(0.125, 0.0, 0.1, model)
sol = solve(RiemannProblem(model, uₗ, uᵣ))
@static if Int==Int64
    plot(sol)
end

# Test 2 on page 129
uₗ = conserved_variables(1.0, -2.0, 0.4, model)
uᵣ = conserved_variables(1.0,  2.0, 0.4, model)
sol = solve(RiemannProblem(model, uₗ, uᵣ))
@static if Int==Int64
    plot(sol)
end

# Test 3 on page 129
uₗ = conserved_variables(1.0, 0.0, 1000.0, model)
uᵣ = conserved_variables(1.0, 0.0, 0.01, model)
sol = solve(RiemannProblem(model, uₗ, uᵣ))
@static if Int==Int64
    plot(sol)
end

# Test 4 on page 129
uₗ = conserved_variables(1.0, 0.0, 0.01, model)
uᵣ = conserved_variables(1.0, 0.0, 100.0, model)
sol = solve(RiemannProblem(model, uₗ, uᵣ))
@static if Int==Int64
    plot(sol)
end

# Test 5 on page 129
uₗ = conserved_variables(5.99924, 19.5975, 460.894, model)
uᵣ = conserved_variables(5.99242, -6.19633, 46.0950, model)
sol = solve(RiemannProblem(model, uₗ, uᵣ))
@static if Int==Int64
    plot(sol)
end


text:
# This code is part of QuantumCircuits.
#
# (C) Copyright Rafał Pracht 2022.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

module QBase

export QuantumCircuit, QuantumDevice, QuantumGate, QuantumObject,
       add!, tomatrix, setparameters!, simplify, measure!,
       standardGateError, bindparameters!, decompose

"Quantum object :)"
abstract type QuantumObject end

"Quantum Gate"
abstract type QuantumGate <: QuantumObject end

"Quantum Circuit object"
abstract type QuantumCircuit <: QuantumObject end

"Quantum davice, it may be simulator or real hardware."
abstract type QuantumDevice end

"The quantum gates"
add!(qc::QuantumCircuit, ::QuantumGate) = error("Please implement add! function used to adding gate to circuit for ", typeof(qc))

"Get the matrix version of the quantum object."
tomatrix(qc::QuantumObject) = error("Please implement tomatrix function for ", typeof(qc))
tomatrix(qc::QuantumObject, ::Any) = error("Please implement tomatrix with params function for ", typeof(qc))

"Method return the error of standard gate"
standardGateError(qc::QuantumObject) = error("Please implement standardGateError function for ", typeof(qc))
standardGateError(qc::QuantumObject, ::Any) = error("Please implement standardGateError with params function for ", typeof(qc))

"Set the parameters to the quantum object"
setparameters!(qc::QuantumObject, ::Any) = error("Please implement setparameters! function for ", typeof(qc))

"Bind the parameters to the quantum object"
bindparameters!(qc::QuantumObject) = error("Please implement bindparameters! function for ", typeof(qc))

"Simplify the quantum object"
simplify(qc::QuantumObject) = error("Please implement simplify function for ", typeof(qc))

decompose(qc::QuantumObject) = error("Please implement decompose function for ", typeof(qc))

Base.inv(qc::QuantumObject) = error("Please implement inv function for ", typeof(qc))

measure!(qc::QuantumObject) = error("Please implement measure! function for ", typeof(qc))

end  # module QBase


text:
using ITensors, LinearAlgebra, Test

@testset "ITensor Decompositions" begin
  @testset "truncate!" begin
    a = [0.1, 0.01, 1e-13]
    @test NDTensors.truncate!(a; use_absolute_cutoff=true, cutoff=1e-5) ==
          (1e-13, (0.01 + 1e-13) / 2)
    @test length(a) == 2

    # Negative definite spectrum treated by taking
    # square (if singular values) or absolute values
    a = [-0.12, -0.1]
    @test NDTensors.truncate!(a) == (0.0, 0.0)
    @test length(a) == 2

    a = [-0.1, -0.01, -1e-13]
    @test NDTensors.truncate!(a; use_absolute_cutoff=true, cutoff=1e-5) ==
          (1e-13, (0.01 + 1e-13) / 2)
    @test length(a) == 2
  end

  @testset "factorize" begin
    i = Index(2, "i")
    j = Index(2, "j")
    A = randomITensor(i, j)
    @test_throws ErrorException factorize(A, i; dir="left")
    @test_throws ErrorException factorize(A, i; ortho="fakedir")
  end

  @testset "factorize with eigen_perturbation" begin
    l = Index(4, "l")
    s1 = Index(2, "s1")
    s2 = Index(2, "s2")
    r = Index(4, "r")

    phi = randomITensor(l, s1, s2, r)

    drho = randomITensor(l', s1', l, s1)
    drho += swapprime(drho, 0, 1)
    drho .*= 1E-5

    U, B = factorize(phi, (l, s1); ortho="left", eigen_perturbation=drho)
    @test norm(U * B - phi) < 1E-5

    # Not allowed to use eigen_perturbation with which_decomp
    # other than "automatic" or "eigen":
    @test_throws ErrorException factorize(
      phi, (l, s1); ortho="left", eigen_perturbation=drho, which_decomp="svd"
    )
  end

  @testset "factorize with QR" begin
    l = Index(5, "l")
    s = Index(2, "s")
    r = Index(10, "r")
    A = randomITensor(l, s, r)
    Q, R, = factorize(A, l, s; which_decomp="qr")
    q = commonind(Q, R)
    @test A ≈ Q * R atol = 1e-13
    @test Q * dag(prime(Q, q)) ≈ δ(Float64, q, q') atol = 1e-13

    R, Q, = factorize(A, l, s; which_decomp="qr", ortho="right")
    q = commonind(Q, R)
    @test A ≈ Q * R atol = 1e-13
    @test Q * dag(prime(Q, q)) ≈ δ(Float64, q, q') atol = 1e-13
  end

  @testset "eigen" begin
    i = Index(2, "i")
    j = Index(2, "j")
    A = randomITensor(i, i')
    eigA = eigen(A)
    Dt, Ut = eigen(NDTensors.tensor(A))
    eigArr = eigen(array(A))
    @test diag(array(eigA.D), 0) ≈ eigArr.values
    @test diag(array(Dt), 0) == eigArr.values
  end

  @testset "exp function" begin
    At = rand(10, 10)
    k = Index(10, "k")
    A = itensor(At + transpose(At), k, k')
    @test Array(exp(Hermitian(NDTensors.tensor(A)))) ≈ exp(At + transpose(At))
  end

  @testset "Spectrum" begin
    i = Index(100, "i")
    j = Index(100, "j")

    U, S, V = svd(rand(100, 100))
    S ./= norm(S)
    A = itensor(U * ITensors.diagm(0 => S) * V', i, j)

    spec = svd(A, i).spec

    @test eigs(spec) ≈ S .^ 2
    @test truncerror(spec) == 0.0

    spec = svd(A, i; maxdim=length(S) - 3).spec
    @test truncerror(spec) ≈ sum(S[(end - 2):end] .^ 2)

    @test entropy(Spectrum([0.5; 0.5], 0.0)) == log(2)
    @test entropy(Spectrum([1.0], 0.0)) == 0.0
    @test entropy(Spectrum([0.0], 0.0)) == 0.0

    @test isnothing(eigs(Spectrum(nothing, 1.0)))
    @test_throws ErrorException entropy(Spectrum(nothing, 1.0))
    @test truncerror(Spectrum(nothing, 1.0)) == 1.0
  end

  @testset "Eigen QN flux regression test" begin
    cutoff = 1E-12
    N = 4
    s = siteinds("S=1", N; conserve_qns=true)
    A = randomITensor(QN("Sz", 2), s[1], s[2], s[3])

    R = A * dag(prime(A, s[1], s[2]))
    F = eigen(R, (s[1], s[2]), (s[1]', s[2]'))

    @test flux(F.Vt) == QN("Sz", 0)
  end
end

nothing