text:
#==============================================================================#
# CodeStar.jl
#
# This file is generated from:
# https://github.com/aws/aws-sdk-js/blob/master/apis/codestar-2017-04-19.normal.json
#==============================================================================#

__precompile__()

module CodeStar

using AWSCore

"""
    using AWSSDK.CodeStar.associate_team_member
    associate_team_member([::AWSConfig], arguments::Dict)
    associate_team_member([::AWSConfig]; projectId=, userArn=, projectRole=, <keyword arguments>)

    using AWSCore.Services.codestar
    codestar([::AWSConfig], "AssociateTeamMember", arguments::Dict)
    codestar([::AWSConfig], "AssociateTeamMember", projectId=, userArn=, projectRole=, <keyword arguments>)

# AssociateTeamMember Operation

Adds an IAM user to the team for an AWS CodeStar project.

# Arguments

## `projectId = ::String` -- *Required*
The ID of the project to which you will add the IAM user.

## `clientRequestToken = ::String`
A user- or system-generated token that identifies the entity that requested the team member association to the project. This token can be used to repeat the request.

## `userArn = ::String` -- *Required*
The Amazon Resource Name (ARN) for the IAM user you want to add to the AWS CodeStar project.

## `projectRole = ::String` -- *Required*
The AWS CodeStar project role that will apply to this user. This role determines what actions a user can take in an AWS CodeStar project.

## `remoteAccessAllowed = ::Bool`
Whether the team member is allowed to use an SSH public/private key pair to remotely access project resources, for example Amazon EC2 instances.

# Returns

`AssociateTeamMemberResult`

# Exceptions

`LimitExceededException`, `ProjectNotFoundException`, `TeamMemberAlreadyAssociatedException`, `ValidationException`, `InvalidServiceRoleException`, `ProjectConfigurationException` or `ConcurrentModificationException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/codestar-2017-04-19/AssociateTeamMember)
"""
@inline associate_team_member(aws::AWSConfig=default_aws_config(); args...) = associate_team_member(aws, args)

@inline associate_team_member(aws::AWSConfig, args) = AWSCore.Services.codestar(aws, "AssociateTeamMember", args)

@inline associate_team_member(args) = associate_team_member(default_aws_config(), args)

"""
    using AWSSDK.CodeStar.create_project
    create_project([::AWSConfig], arguments::Dict)
    create_project([::AWSConfig]; name=, id=, <keyword arguments>)

    using AWSCore.Services.codestar
    codestar([::AWSConfig], "CreateProject", arguments::Dict)
    codestar([::AWSConfig], "CreateProject", name=, id=, <keyword arguments>)

# CreateProject Operation

Reserved for future use. To create a project, use the AWS CodeStar console.

# Arguments

## `name = ::String` -- *Required*
Reserved for future use.

## `id = ::String` -- *Required*
Reserved for future use.

## `description = ::String`
Reserved for future use.

## `clientRequestToken = ::String`
Reserved for future use.

# Returns

`CreateProjectResult`

# Exceptions

`ProjectAlreadyExistsException`, `LimitExceededException`, `ValidationException`, `ProjectCreationFailedException`, `InvalidServiceRoleException`, `ProjectConfigurationException` or `ConcurrentModificationException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/codestar-2017-04-19/CreateProject)
"""
@inline create_project(aws::AWSConfig=default_aws_config(); args...) = create_project(aws, args)

@inline create_project(aws::AWSConfig, args) = AWSCore.Services.codestar(aws, "CreateProject", args)

@inline create_project(args) = create_project(default_aws_config(), args)

"""
    using AWSSDK.CodeStar.create_user_profile
    create_user_profile([::AWSConfig], arguments::Dict)
    create_user_profile([::AWSConfig]; userArn=, displayName=, emailAddress=, <keyword arguments>)

    using AWSCore.Services.codestar
    codestar([::AWSConfig], "CreateUserProfile", arguments::Dict)
    codestar([::AWSConfig], "CreateUserProfile", userArn=, displayName=, emailAddress=, <keyword arguments>)

# CreateUserProfile Operation

Creates a profile for a user that includes user preferences, such as the display name and email address assocciated with the user, in AWS CodeStar. The user profile is not project-specific. Information in the user profile is displayed wherever the user's information appears to other users in AWS CodeStar.

# Arguments

## `userArn = ::String` -- *Required*
The Amazon Resource Name (ARN) of the user in IAM.

## `displayName = ::String` -- *Required*
The name that will be displayed as the friendly name for the user in AWS CodeStar.

## `emailAddress = ::String` -- *Required*
The email address that will be displayed as part of the user's profile in AWS CodeStar.

## `sshPublicKey = ::String`
The SSH public key associated with the user in AWS CodeStar. If a project owner allows the user remote access to project resources, this public key will be used along with the user's private key for SSH access.

# Returns

`CreateUserProfileResult`

# Exceptions

`UserProfileAlreadyExistsException` or `ValidationException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/codestar-2017-04-19/CreateUserProfile)
"""
@inline create_user_profile(aws::AWSConfig=default_aws_config(); args...) = create_user_profile(aws, args)

@inline create_user_profile(aws::AWSConfig, args) = AWSCore.Services.codestar(aws, "CreateUserProfile", args)

@inline create_user_profile(args) = create_user_profile(default_aws_config(), args)

"""
    using AWSSDK.CodeStar.delete_project
    delete_project([::AWSConfig], arguments::Dict)
    delete_project([::AWSConfig]; id=, <keyword arguments>)

    using AWSCore.Services.codestar
    codestar([::AWSConfig], "DeleteProject", arguments::Dict)
    codestar([::AWSConfig], "DeleteProject", id=, <keyword arguments>)

# DeleteProject Operation

Deletes a project, including project resources. Does not delete users associated with the project, but does delete the IAM roles that allowed access to the project.

# Arguments

## `id = ::String` -- *Required*
The ID of the project to be deleted in AWS CodeStar.

## `clientRequestToken = ::String`
A user- or system-generated token that identifies the entity that requested project deletion. This token can be used to repeat the request.

## `deleteStack = ::Bool`
Whether to send a delete request for the primary stack in AWS CloudFormation originally used to generate the project and its resources. This option will delete all AWS resources for the project (except for any buckets in Amazon S3) as well as deleting the project itself. Recommended for most use cases.

# Returns

`DeleteProjectResult`

# Exceptions

`ConcurrentModificationException`, `ValidationException` or `InvalidServiceRoleException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/codestar-2017-04-19/DeleteProject)
"""
@inline delete_project(aws::AWSConfig=default_aws_config(); args...) = delete_project(aws, args)

@inline delete_project(aws::AWSConfig, args) = AWSCore.Services.codestar(aws, "DeleteProject", args)

@inline delete_project(args) = delete_project(default_aws_config(), args)

"""
    using AWSSDK.CodeStar.delete_user_profile
    delete_user_profile([::AWSConfig], arguments::Dict)
    delete_user_profile([::AWSConfig]; userArn=)

    using AWSCore.Services.codestar
    codestar([::AWSConfig], "DeleteUserProfile", arguments::Dict)
    codestar([::AWSConfig], "DeleteUserProfile", userArn=)

# DeleteUserProfile Operation

Deletes a user profile in AWS CodeStar, including all personal preference data associated with that profile, such as display name and email address. It does not delete the history of that user, for example the history of commits made by that user.

# Arguments

## `userArn = ::String` -- *Required*
The Amazon Resource Name (ARN) of the user to delete from AWS CodeStar.

# Returns

`DeleteUserProfileResult`

# Exceptions

`ValidationException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/codestar-2017-04-19/DeleteUserProfile)
"""
@inline delete_user_profile(aws::AWSConfig=default_aws_config(); args...) = delete_user_profile(aws, args)

@inline delete_user_profile(aws::AWSConfig, args) = AWSCore.Services.codestar(aws, "DeleteUserProfile", args)

@inline delete_user_profile(args) = delete_user_profile(default_aws_config(), args)

"""
    using AWSSDK.CodeStar.describe_project
    describe_project([::AWSConfig], arguments::Dict)
    describe_project([::AWSConfig]; id=)

    using AWSCore.Services.codestar
    codestar([::AWSConfig], "DescribeProject", arguments::Dict)
    codestar([::AWSConfig], "DescribeProject", id=)

# DescribeProject Operation

Describes a project and its resources.

# Arguments

## `id = ::String` -- *Required*
The ID of the project.

# Returns

`DescribeProjectResult`

# Exceptions

`ProjectNotFoundException`, `ValidationException`, `InvalidServiceRoleException`, `ProjectConfigurationException` or `ConcurrentModificationException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/codestar-2017-04-19/DescribeProject)
"""
@inline describe_project(aws::AWSConfig=default_aws_config(); args...) = describe_project(aws, args)

@inline describe_project(aws::AWSConfig, args) = AWSCore.Services.codestar(aws, "DescribeProject", args)

@inline describe_project(args) = describe_project(default_aws_config(), args)

"""
    using AWSSDK.CodeStar.describe_user_profile
    describe_user_profile([::AWSConfig], arguments::Dict)
    describe_user_profile([::AWSConfig]; userArn=)

    using AWSCore.Services.codestar
    codestar([::AWSConfig], "DescribeUserProfile", arguments::Dict)
    codestar([::AWSConfig], "DescribeUserProfile", userArn=)

# DescribeUserProfile Operation

Describes a user in AWS CodeStar and the user attributes across all projects.

# Arguments

## `userArn = ::String` -- *Required*
The Amazon Resource Name (ARN) of the user.

# Returns

`DescribeUserProfileResult`

# Exceptions

`UserProfileNotFoundException` or `ValidationException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/codestar-2017-04-19/DescribeUserProfile)
"""
@inline describe_user_profile(aws::AWSConfig=default_aws_config(); args...) = describe_user_profile(aws, args)

@inline describe_user_profile(aws::AWSConfig, args) = AWSCore.Services.codestar(aws, "DescribeUserProfile", args)

@inline describe_user_profile(args) = describe_user_profile(default_aws_config(), args)

"""
    using AWSSDK.CodeStar.disassociate_team_member
    disassociate_team_member([::AWSConfig], arguments::Dict)
    disassociate_team_member([::AWSConfig]; projectId=, userArn=)

    using AWSCore.Services.codestar
    codestar([::AWSConfig], "DisassociateTeamMember", arguments::Dict)
    codestar([::AWSConfig], "DisassociateTeamMember", projectId=, userArn=)

# DisassociateTeamMember Operation

Removes a user from a project. Removing a user from a project also removes the IAM policies from that user that allowed access to the project and its resources. Disassociating a team member does not remove that user's profile from AWS CodeStar. It does not remove the user from IAM.

# Arguments

## `projectId = ::String` -- *Required*
The ID of the AWS CodeStar project from which you want to remove a team member.

## `userArn = ::String` -- *Required*
The Amazon Resource Name (ARN) of the IAM user or group whom you want to remove from the project.

# Returns

`DisassociateTeamMemberResult`

# Exceptions

`ProjectNotFoundException`, `ValidationException`, `InvalidServiceRoleException` or `ConcurrentModificationException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/codestar-2017-04-19/DisassociateTeamMember)
"""
@inline disassociate_team_member(aws::AWSConfig=default_aws_config(); args...) = disassociate_team_member(aws, args)

@inline disassociate_team_member(aws::AWSConfig, args) = AWSCore.Services.codestar(aws, "DisassociateTeamMember", args)

@inline disassociate_team_member(args) = disassociate_team_member(default_aws_config(), args)

"""
    using AWSSDK.CodeStar.list_projects
    list_projects([::AWSConfig], arguments::Dict)
    list_projects([::AWSConfig]; <keyword arguments>)

    using AWSCore.Services.codestar
    codestar([::AWSConfig], "ListProjects", arguments::Dict)
    codestar([::AWSConfig], "ListProjects", <keyword arguments>)

# ListProjects Operation

Lists all projects in AWS CodeStar associated with your AWS account.

# Arguments

## `nextToken = ::String`
The continuation token to be used to return the next set of results, if the results cannot be returned in one response.

## `maxResults = ::Int`
The maximum amount of data that can be contained in a single set of results.

# Returns

`ListProjectsResult`

# Exceptions

`InvalidNextTokenException` or `ValidationException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/codestar-2017-04-19/ListProjects)
"""
@inline list_projects(aws::AWSConfig=default_aws_config(); args...) = list_projects(aws, args)

@inline list_projects(aws::AWSConfig, args) = AWSCore.Services.codestar(aws, "ListProjects", args)

@inline list_projects(args) = list_projects(default_aws_config(), args)

"""
    using AWSSDK.CodeStar.list_resources
    list_resources([::AWSConfig], arguments::Dict)
    list_resources([::AWSConfig]; projectId=, <keyword arguments>)

    using AWSCore.Services.codestar
    codestar([::AWSConfig], "ListResources", arguments::Dict)
    codestar([::AWSConfig], "ListResources", projectId=, <keyword arguments>)

# ListResources Operation

Lists resources associated with a project in AWS CodeStar.

# Arguments

## `projectId = ::String` -- *Required*
The ID of the project.

## `nextToken = ::String`
The continuation token for the next set of results, if the results cannot be returned in one response.

## `maxResults = ::Int`
The maximum amount of data that can be contained in a single set of results.

# Returns

`ListResourcesResult`

# Exceptions

`ProjectNotFoundException`, `InvalidNextTokenException` or `ValidationException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/codestar-2017-04-19/ListResources)
"""
@inline list_resources(aws::AWSConfig=default_aws_config(); args...) = list_resources(aws, args)

@inline list_resources(aws::AWSConfig, args) = AWSCore.Services.codestar(aws, "ListResources", args)

@inline list_resources(args) = list_resources(default_aws_config(), args)

"""
    using AWSSDK.CodeStar.list_tags_for_project
    list_tags_for_project([::AWSConfig], arguments::Dict)
    list_tags_for_project([::AWSConfig]; id=, <keyword arguments>)

    using AWSCore.Services.codestar
    codestar([::AWSConfig], "ListTagsForProject", arguments::Dict)
    codestar([::AWSConfig], "ListTagsForProject", id=, <keyword arguments>)

# ListTagsForProject Operation

Gets the tags for a project.

# Arguments

## `id = ::String` -- *Required*
The ID of the project to get tags for.

## `nextToken = ::String`
Reserved for future use.

## `maxResults = ::Int`
Reserved for future use.

# Returns

`ListTagsForProjectResult`

# Exceptions

`ProjectNotFoundException`, `ValidationException` or `InvalidNextTokenException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/codestar-2017-04-19/ListTagsForProject)
"""
@inline list_tags_for_project(aws::AWSConfig=default_aws_config(); args...) = list_tags_for_project(aws, args)

@inline list_tags_for_project(aws::AWSConfig, args) = AWSCore.Services.codestar(aws, "ListTagsForProject", args)

@inline list_tags_for_project(args) = list_tags_for_project(default_aws_config(), args)

"""
    using AWSSDK.CodeStar.list_team_members
    list_team_members([::AWSConfig], arguments::Dict)
    list_team_members([::AWSConfig]; projectId=, <keyword arguments>)

    using AWSCore.Services.codestar
    codestar([::AWSConfig], "ListTeamMembers", arguments::Dict)
    codestar([::AWSConfig], "ListTeamMembers", projectId=, <keyword arguments>)

# ListTeamMembers Operation

Lists all team members associated with a project.

# Arguments

## `projectId = ::String` -- *Required*
The ID of the project for which you want to list team members.

## `nextToken = ::String`
The continuation token for the next set of results, if the results cannot be returned in one response.

## `maxResults = ::Int`
The maximum number of team members you want returned in a response.

# Returns

`ListTeamMembersResult`

# Exceptions

`ProjectNotFoundException`, `InvalidNextTokenException` or `ValidationException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/codestar-2017-04-19/ListTeamMembers)
"""
@inline list_team_members(aws::AWSConfig=default_aws_config(); args...) = list_team_members(aws, args)

@inline list_team_members(aws::AWSConfig, args) = AWSCore.Services.codestar(aws, "ListTeamMembers", args)

@inline list_team_members(args) = list_team_members(default_aws_config(), args)

"""
    using AWSSDK.CodeStar.list_user_profiles
    list_user_profiles([::AWSConfig], arguments::Dict)
    list_user_profiles([::AWSConfig]; <keyword arguments>)

    using AWSCore.Services.codestar
    codestar([::AWSConfig], "ListUserProfiles", arguments::Dict)
    codestar([::AWSConfig], "ListUserProfiles", <keyword arguments>)

# ListUserProfiles Operation

Lists all the user profiles configured for your AWS account in AWS CodeStar.

# Arguments

## `nextToken = ::String`
The continuation token for the next set of results, if the results cannot be returned in one response.

## `maxResults = ::Int`
The maximum number of results to return in a response.

# Returns

`ListUserProfilesResult`

# Exceptions

`InvalidNextTokenException` or `ValidationException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/codestar-2017-04-19/ListUserProfiles)
"""
@inline list_user_profiles(aws::AWSConfig=default_aws_config(); args...) = list_user_profiles(aws, args)

@inline list_user_profiles(aws::AWSConfig, args) = AWSCore.Services.codestar(aws, "ListUserProfiles", args)

@inline list_user_profiles(args) = list_user_profiles(default_aws_config(), args)

"""
    using AWSSDK.CodeStar.tag_project
    tag_project([::AWSConfig], arguments::Dict)
    tag_project([::AWSConfig]; id=, tags=)

    using AWSCore.Services.codestar
    codestar([::AWSConfig], "TagProject", arguments::Dict)
    codestar([::AWSConfig], "TagProject", id=, tags=)

# TagProject Operation

Adds tags to a project.

# Arguments

## `id = ::String` -- *Required*
The ID of the project you want to add a tag to.

## `tags = ::Dict{String,String}` -- *Required*
The tags you want to add to the project.

# Returns

`TagProjectResult`

# Exceptions

`ProjectNotFoundException`, `ValidationException`, `LimitExceededException` or `ConcurrentModificationException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/codestar-2017-04-19/TagProject)
"""
@inline tag_project(aws::AWSConfig=default_aws_config(); args...) = tag_project(aws, args)

@inline tag_project(aws::AWSConfig, args) = AWSCore.Services.codestar(aws, "TagProject", args)

@inline tag_project(args) = tag_project(default_aws_config(), args)

"""
    using AWSSDK.CodeStar.untag_project
    untag_project([::AWSConfig], arguments::Dict)
    untag_project([::AWSConfig]; id=, tags=)

    using AWSCore.Services.codestar
    codestar([::AWSConfig], "UntagProject", arguments::Dict)
    codestar([::AWSConfig], "UntagProject", id=, tags=)

# UntagProject Operation

Removes tags from a project.

# Arguments

## `id = ::String` -- *Required*
The ID of the project to remove tags from.

## `tags = [::String, ...]` -- *Required*
The tags to remove from the project.

# Returns

`UntagProjectResult`

# Exceptions

`ProjectNotFoundException`, `ValidationException`, `LimitExceededException` or `ConcurrentModificationException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/codestar-2017-04-19/UntagProject)
"""
@inline untag_project(aws::AWSConfig=default_aws_config(); args...) = untag_project(aws, args)

@inline untag_project(aws::AWSConfig, args) = AWSCore.Services.codestar(aws, "UntagProject", args)

@inline untag_project(args) = untag_project(default_aws_config(), args)

"""
    using AWSSDK.CodeStar.update_project
    update_project([::AWSConfig], arguments::Dict)
    update_project([::AWSConfig]; id=, <keyword arguments>)

    using AWSCore.Services.codestar
    codestar([::AWSConfig], "UpdateProject", arguments::Dict)
    codestar([::AWSConfig], "UpdateProject", id=, <keyword arguments>)

# UpdateProject Operation

Updates a project in AWS CodeStar.

# Arguments

## `id = ::String` -- *Required*
The ID of the project you want to update.

## `name = ::String`
The name of the project you want to update.

## `description = ::String`
The description of the project, if any.

# Returns

`UpdateProjectResult`

# Exceptions

`ProjectNotFoundException` or `ValidationException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/codestar-2017-04-19/UpdateProject)
"""
@inline update_project(aws::AWSConfig=default_aws_config(); args...) = update_project(aws, args)

@inline update_project(aws::AWSConfig, args) = AWSCore.Services.codestar(aws, "UpdateProject", args)

@inline update_project(args) = update_project(default_aws_config(), args)

"""
    using AWSSDK.CodeStar.update_team_member
    update_team_member([::AWSConfig], arguments::Dict)
    update_team_member([::AWSConfig]; projectId=, userArn=, <keyword arguments>)

    using AWSCore.Services.codestar
    codestar([::AWSConfig], "UpdateTeamMember", arguments::Dict)
    codestar([::AWSConfig], "UpdateTeamMember", projectId=, userArn=, <keyword arguments>)

# UpdateTeamMember Operation

Updates a team member's attributes in an AWS CodeStar project. For example, you can change a team member's role in the project, or change whether they have remote access to project resources.

# Arguments

## `projectId = ::String` -- *Required*
The ID of the project.

## `userArn = ::String` -- *Required*
The Amazon Resource Name (ARN) of the user for whom you want to change team membership attributes.

## `projectRole = ::String`
The role assigned to the user in the project. Project roles have different levels of access. For more information, see [Working with Teams](http://docs.aws.amazon.com/codestar/latest/userguide/working-with-teams.html) in the *AWS CodeStar User Guide*.

## `remoteAccessAllowed = ::Bool`
Whether a team member is allowed to remotely access project resources using the SSH public key associated with the user's profile. Even if this is set to True, the user must associate a public key with their profile before the user can access resources.

# Returns

`UpdateTeamMemberResult`

# Exceptions

`LimitExceededException`, `ProjectNotFoundException`, `ValidationException`, `InvalidServiceRoleException`, `ProjectConfigurationException`, `ConcurrentModificationException` or `TeamMemberNotFoundException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/codestar-2017-04-19/UpdateTeamMember)
"""
@inline update_team_member(aws::AWSConfig=default_aws_config(); args...) = update_team_member(aws, args)

@inline update_team_member(aws::AWSConfig, args) = AWSCore.Services.codestar(aws, "UpdateTeamMember", args)

@inline update_team_member(args) = update_team_member(default_aws_config(), args)

"""
    using AWSSDK.CodeStar.update_user_profile
    update_user_profile([::AWSConfig], arguments::Dict)
    update_user_profile([::AWSConfig]; userArn=, <keyword arguments>)

    using AWSCore.Services.codestar
    codestar([::AWSConfig], "UpdateUserProfile", arguments::Dict)
    codestar([::AWSConfig], "UpdateUserProfile", userArn=, <keyword arguments>)

# UpdateUserProfile Operation

Updates a user's profile in AWS CodeStar. The user profile is not project-specific. Information in the user profile is displayed wherever the user's information appears to other users in AWS CodeStar.

# Arguments

## `userArn = ::String` -- *Required*
The name that will be displayed as the friendly name for the user in AWS CodeStar.

## `displayName = ::String`
The name that is displayed as the friendly name for the user in AWS CodeStar.

## `emailAddress = ::String`
The email address that is displayed as part of the user's profile in AWS CodeStar.

## `sshPublicKey = ::String`
The SSH public key associated with the user in AWS CodeStar. If a project owner allows the user remote access to project resources, this public key will be used along with the user's private key for SSH access.

# Returns

`UpdateUserProfileResult`

# Exceptions

`UserProfileNotFoundException` or `ValidationException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/codestar-2017-04-19/UpdateUserProfile)
"""
@inline update_user_profile(aws::AWSConfig=default_aws_config(); args...) = update_user_profile(aws, args)

@inline update_user_profile(aws::AWSConfig, args) = AWSCore.Services.codestar(aws, "UpdateUserProfile", args)

@inline update_user_profile(args) = update_user_profile(default_aws_config(), args)

end # module CodeStar

#==============================================================================#
# End of file
#==============================================================================#


text:
export quadratic_lattice, rational_span, has_ambient_space, hermitian_lattice

################################################################################
#
#  Types and constructors
#
################################################################################

abstract type AbsLat{S} end

mutable struct QuadLat{S, T, U} <: AbsLat{S}
  space::QuadSpace{S, T}
  pmat::U
  gram::T                        # gram matrix of the matrix part of pmat
  rational_span::QuadSpace{S, T}
  base_algebra::S
  @declare_other

  function QuadLat{S, T, U}() where {S, T, U}
    return new{S, T, U}()
  end

  function QuadLat(K::S, G::T, P::U) where {S, T, U}
    space = QuadSpace(K, G)
    z = new{S, T, U}(space, P)
    z.base_algebra = K
    return z
  end

  function QuadLat(K::S, G::T) where {S, T}
    n = nrows(G)
    M = pseudo_matrix(identity_matrix(K, n))
    return QuadLat(K, G, M)
  end
end

function quadratic_lattice(K::NumField, B::PMat; gram_ambient_space = nothing, gram = nothing)
  if gram_ambient_space === nothing && gram === nothing
    return QuadLat(K, identity_matrix(K, nrows(B)), pseudo_matrix(B))
  end
  if gram_ambient_space !== nothing && gram === nothing
    return QuadLat(K, gram_ambient_space, B)
  end
  if gram_ambient_space === nothing && gram !== nothing
    z = QuadLat{typeof(K), typeof(gram), typeof(B)}()
    z.pmat = P
    z.gram = gram
    z.base_algebra = K
    return z
  end
end

function quadratic_lattice(K::NumField, B::MatElem; gram_ambient_space = nothing, gram = nothing)
  if gram_ambient_space === nothing && gram === nothing
    return QuadLat(K, identity_matrix(K, nrows(B)), pseudo_matrix(B))
  end
  if gram_ambient_space !== nothing && gram === nothing
    return QuadLat(K, gram_ambient_space, pseudo_matrix(B))
  end
  if gram_ambient_space === nothing && gram !== nothing
    P = pseudo_matrix(B)
    z = QuadLat{typeof(K), typeof(B), typeof(P)}()
    z.pmat = P
    z.gram = gram
    z.base_algebra = K
    return z
  end
end

function has_ambient_space(L::AbsLat)
  return isdefined(L, :space)
end

function ambient_space(L::AbsLat)
  if has_ambient_space(L)
    return L.space
  else
    throw(error("Ambient quadratic space not defined"))
  end
end

mutable struct HermLat{S, T, U, V, W} <: AbsLat{S}
  space::HermSpace{S, T, U, W}
  pmat::V
  gram::U
  rational_span::HermSpace{S, T, U, W}
  base_algebra::S
  involution::W
  @declare_other

  function HermLat{S, T, U, V, W}() where {S, T, U, V, W}
    z = new{S, T, U, V, W}()
    return z
  end

  function HermLat(E::S, G::U, P::V) where {S, U, V}
    @assert degree(E) == 2
    A = automorphisms(E)
    a = gen(E)
    if A[1](a) == a
      involution = A[2]
    else
      involution = A[1]
    end

    K = base_field(E)

    space = HermSpace(E, G)

    z = new{S, typeof(K), U, V, typeof(involution)}(space, P)
    z.base_algebra = E
    z.involution = involution
    return z
  end

  function HermLat(E::S, G::U) where {S, U}
    n = nrows(G)
    M = pseudo_matrix(identity_matrix(E, n))
    return HermLat(E, G, M)
  end
end

function hermitian_lattice(K::NumField, B::MatElem; gram_ambient_space = nothing, gram = nothing)
  if gram_ambient_space === nothing && gram === nothing
    return HermLat(K, identity_matrix(K, nrows(B)), pseudo_matrix(B))
  end
  if gram_ambient_space !== nothing && gram === nothing
    return HermLat(K, gram_ambient_space, pseudo_matrix(B))
  end
  if gram_ambient_space === nothing && gram !== nothing
    @assert degree(K) == 2
    A = automorphisms(K)
    a = gen(K)
    if A[1](a) == a
      involution = A[2]
    else
      involution = A[1]
    end

    P = pseudo_matrix(B)
    z = HermLat{typeof(K), typeof(base_field(K)), typeof(B), typeof(P), morphism_type(typeof(K))}()
    z.pmat = P
    z.gram = gram
    z.involution = involution
    z.base_algebra = K
    return z
  end
end

function hermitian_lattice(K::NumField, B::PMat; gram_ambient_space = nothing, gram = nothing)
  if gram_ambient_space === nothing && gram === nothing
    return HermLat(K, identity_matrix(K, nrows(B)), B)
  end
  if gram_ambient_space !== nothing && gram === nothing
    return HermLat(K, gram_ambient_space, B)
  end
  if gram_ambient_space === nothing && gram !== nothing
    @assert degree(K) == 2
    A = automorphisms(K)
    a = gen(K)
    if A[1](a) == a
      involution = A[2]
    else
      involution = A[1]
    end

    z = HermLat{typeof(K), typeof(base_field(K)), typeof(gram), typeof(B), morphism_type(typeof(K))}()
    z.pmat = P
    z.gram = gram
    z.involution = involution
    z.base_algebra = K
    return z
  end
end

function rational_span(L::QuadLat)
  if isdefined(L, :rational_span)
    return L.rational_span
  else
    G = gram_matrix_of_basis(L)
    V = quadratic_space(base_field(L), G)
    L.rational_span = V
    return V
  end
end

function rational_span(L::HermLat)
  if isdefined(L, :rational_span)
    return L.rational_span
  else
    G = gram_matrix_of_basis(L)
    V = hermitian_space(base_field(L), G)
    L.rational_span = V
    return V
  end
end

function diagonal(L::AbsLat)
  D, _ = _gram_schmidt(gram_matrix_of_basis(L), involution(L))
  return diagonal(D)
end

pseudo_matrix(L::AbsLat) = L.pmat

coefficient_ideals(L::AbsLat) = coefficient_ideals(pseudo_matrix(L))

basis_matrix(L::AbsLat) = matrix(pseudo_matrix(L))

base_field(L::AbsLat) = L.base_algebra

base_algebra(L::AbsLat) = L.base_algebra

involution(L::QuadLat) = identity

involution(L::HermLat) = L.involution

rank(L::AbsLat) = rank(rational_span(L))

function gram_matrix_of_basis(L::AbsLat)
  if isdefined(L, :gram)
    return L.gram
  else
    return gram_matrix(ambient_space(L), L.pmat.matrix)
  end
end

function degree(L::AbsLat)
  return dim(ambient_space(L))
end

# Check if one really needs minimal
# Steinitz form is not pretty
function generators(L::AbsLat; minimal::Bool = true)
  St = _steinitz_form(pseudo_matrix(L), Val{false})
  d = nrows(St)
  n = degree(L)
  T = elem_type(base_algebra(L))
  v = Vector{T}[]
  for i in 1:(d - 1)
    @assert isprincipal(coefficient_ideals(St)[i])[1]
    push!(v, T[basis_matrix(L)[i, j] for j in 1:d])
  end

  I = numerator(coefficient_ideals(St)[d])
  den = denominator(coefficient_ideals(St)[d])
  if minimal && base_algebra(L) isa AnticNumberField
    b, a = isprincipal(I)
    if b
      push!(v, T[base_algebra(L)(a)//den * basis_matrix(L)[n, j] for j in 1:d])
    end
    return v
  end

  _assure_weakly_normal_presentation(I)
  push!(v, T[base_algebra(L)(I.gen_one)//den * basis_matrix(L)[n, j] for j in 1:d])
  push!(v, T[base_algebra(L)(I.gen_two)//den * basis_matrix(L)[n, j] for j in 1:d])

  return v
end

function discriminant(L::AbsLat)
  d = det(gram_matrix_of_basis(L))
  v = involution(L)
  C = coefficient_ideals(L)
  I = prod(J for J in C)
  return d * I * v(I)
end

################################################################################
#
#  Rational (local) equivalence
#
################################################################################

function hasse_invariant(L::QuadLat, p)
  return hasse_invariant(rational_span(L), p)
end

function hasse_invariant(L::HermLat, p)
  throw(error("The lattice must be quadratic"))
end

function witt_invariant(L::QuadLat, p::NfAbsOrdIdl)
  return hasse_invariant(rational_span(L), p)
end

function witt_invariant(L::QuadLat, p::InfPlc)
  return witt_invariant(rational_span(L), p)
end

function isrationally_equivalent(L::QuadLat, M::QuadLat, p::NfAbsOrdIdl)
  return isequivalent(rational_span(L), rational_span(M), p)
end

function isrationally_equivalent(L::QuadLat, M::QuadLat, p::InfPlc)
  return isequivalent(rational_span(L), rational_span(M), p)
end

function isrationally_equivalent(L::QuadLat, M::QuadLat)
  return isequivalent(rational_span(L), rational_span(M))
end

################################################################################
#
#  Definiteness
#
################################################################################

ispositive_definite(L::AbsLat) = ispositive_definite(rational_span(L))

isnegative_definite(L::AbsLat) = isnegative_definite(rational_span(L))

isdefinite(L::AbsLat) = isdefinite(rational_span(L))

function can_scale_totally_positive(L::AbsLat)
  a = _isdefinite(rational_span(L))
  if iszero(a)
    return false, a
  else
    return true, a
  end
end

################################################################################
#
#  Addition
#
################################################################################

# Some of these assertions can be relaxed, in particular in the scaling

function Base.:(+)(L::QuadLat, M::QuadLat)
  @assert has_ambient_space(L) && has_ambient_space(M)
  @assert ambient_space(L) === ambient_space(M)
  m = _sum_modules(pseudo_matrix(L), pseudo_matrix(M))
  return quadratic_lattice(base_algebra(L), m, gram_ambient_space = gram_matrix(ambient_space(L)))
end

function intersect(L::QuadLat, M::QuadLat)
  @assert has_ambient_space(L) && has_ambient_space(M)
  @assert ambient_space(L) === ambient_space(M)
  m = _intersect_modules(pseudo_matrix(L), pseudo_matrix(M))
  return quadratic_lattice(base_algebra(L), m, gram_ambient_space = gram_matrix(ambient_space(L)))
end

function Base.:(*)(a, L::QuadLat)
  @assert has_ambient_space(L)
  return quadratic_lattice(base_algebra(L), _module_scale_ideal(a, pseudo_matrix(L)), gram_ambient_space = gram_matrix(ambient_space(L)))
end

function Base.:(*)(L::QuadLat, a)
  @assert has_ambient_space(L)
  return quadratic_lattice(base_algebra(L), _module_scale_ideal(a, pseudo_matrix(L)), gram_ambient_space = gram_matrix(ambient_space(L)))
end

function Base.:(+)(L::HermLat, M::HermLat)
  @assert has_ambient_space(L) && has_ambient_space(M)
  @assert ambient_space(L) === ambient_space(M)
  m = _sum_modules(pseudo_matrix(L), pseudo_matrix(M))
  return hermitian_lattice(base_algebra(L), m, gram_ambient_space = gram_matrix(ambient_space(L)))
end

function intersect(L::HermLat, M::HermLat)
  @assert has_ambient_space(L) && has_ambient_space(M)
  @assert ambient_space(L) === ambient_space(M)
  m = _intersect_modules(pseudo_matrix(L), pseudo_matrix(M))
  return hermitian_lattice(base_algebra(L), m, gram_ambient_space = gram_matrix(ambient_space(L)))
end

function Base.:(*)(a, L::HermLat)
  @assert has_ambient_space(L)
  return hermitian_lattice(base_algebra(L), _module_scale_ideal(a, pseudo_matrix(L)), gram_ambient_space = gram_matrix(ambient_space(L)))
end

function Base.:(*)(L::HermLat, a)
  @assert has_ambient_space(L)
  return hermitian_lattice(base_algebra(L), _module_scale_ideal(a, pseudo_matrix(L)), gram_ambient_space = gram_matrix(ambient_space(L)))
end

################################################################################
#
#  Absolute basis
#
################################################################################

# TODO: Write this:
# Call absolute_basis on the ideals

@doc Markdown.doc"""
    absolute_basis(L::AbsLat) -> Vector

Return a $\mathbf{Z}$-basis of $L$.
"""
function absolute_basis(L::AbsLat)
end

@doc Markdown.doc"""
    absolute_basis_matrix(L::AbsLat) -> MatElem

Return a $\mathbf{Z}$-basis matrix of $L$.
"""
function absolute_basis_matrix(L::AbsLat)
end

################################################################################
#
#  Local basis matrix
#
################################################################################

function local_basis_matrix(L::AbsLat, p, type::Symbol = :any)
  if type == :any
    return _local_basis_matrix(pseudo_matrix(L), p)
  elseif type == :submodule
    return _local_basis_submodule_matrix(pseudo_matrix(L), p)
  elseif type == :supermodule
    return _local_basis_supermodule_matrix(pseudo_matrix(L), p)
  else
    throw(error("Invalid type keyword :$(type).\nMust be either :any, :submodule, or :supermodule"))
  end
end

################################################################################
#
#  Norm
#
################################################################################

# cache this
function norm(L::QuadLat)
  G = gram_matrix_of_basis(L)
  C = coefficient_ideals(L)
  return sum(G[i, i] * C[i]^2 for i in 1:length(C)) + nf(order(C[1]))(2) * scale(L)
end

# TODO: Be careful with the +, trace_ideal gives fmpq and then this must be gcd
function norm(L::HermLat)
  G = gram_matrix_of_basis(L)
  K = base_ring(G)
  C = coefficient_ideals(L)
  to_sum = sum(G[i, i] * C[i] * induce_image(C[i], involution(L)) for i in 1:length(C))
  to_sum = to_sum + K(reduce(gcd, [ideal_trace(C[i] * G[i, j] * induce_image(C[j], involution(L))) for j in 1:length(C) for i in 1:(j-1)]; init = fmpq(0)) )* order(to_sum)
  return to_sum
end

################################################################################
#
#  Scale
#
################################################################################

# cache this
function scale(L::QuadLat)
  G = gram_matrix_of_basis(L)
  C = coefficient_ideals(L)
  to_sum = [ G[i, j] * C[i] * involution(L)(C[j]) for j in 1:length(C) for i in 1:j]
  return sum(to_sum)
end

function scale(L::HermLat)
  G = gram_matrix_of_basis(L)
  C = coefficient_ideals(L)
  to_sum = [ G[i, j] * C[i] * involution(L)(C[j]) for j in 1:length(C) for i in 1:j ]
  d = length(to_sum)
  for i in 1:d
    push!(to_sum, involution(L)(to_sum[i]))
  end
  return sum(to_sum)
end

################################################################################
#
#  Rescale
#
################################################################################

function rescale(L::QuadLat, a)
  if isone(a)
    return L
  end
  K = fixed_field(L)
  b = K(a)
  return quadratic_lattice(base_field(L), pseudo_matrix(L), gram_ambient_space = b * gram_matrix(ambient_space(L)))
end

function rescale(L::HermLat, a)
  if isone(a)
    return L
  end
  K = fixed_field(L)
  b = base_field(L)(K(a))
  return hermitian_lattice(base_field(L), pseudo_matrix(L), gram_ambient_space = b * gram_matrix(ambient_space(L)))
end

fixed_field(L::AbsLat) = base_field(base_algebra(L))

################################################################################
#
#  Is integral?
#
################################################################################

function isintegral(L::AbsLat)
  return isintegral(scale(L))
end

function isintegral(I::NfOrdFracIdl)
  @assert ismaximal(order(I))
  simplify(I)
  return denominator(I) == 1
end

################################################################################
#
#  Dual
#
################################################################################

function dual(L::QuadLat)
  G, B = _gram_schmidt(gram_matrix_of_basis(L), involution(L))
  C = coefficient_ideals(L)
  Gi = inv(G)
  new_bmat = Gi * B
  return quadratic_lattice(base_field(L), pseudo_matrix(new_bmat, [inv(c) for c in C]), gram_ambient_space = gram_matrix(ambient_space(L)))
end

function dual(L::HermLat)
  G, B = _gram_schmidt(gram_matrix_of_basis(L), involution(L))
  C = coefficient_ideals(L)
  Gi = inv(G)
  new_bmat = Gi * B
  return hermitian_lattice(base_field(L), pseudo_matrix(new_bmat, [inv(induce_image(c, involution(L))) for c in C]), gram_ambient_space = gram_matrix(ambient_space(L)))
end

################################################################################
#
#  Volume
#
################################################################################

function volume(L::AbsLat)
  return discriminant(L)
end

################################################################################
#
#  Modularity
#
################################################################################

function ismodular(L::AbsLat)
  a = scale(L)
  if volume(L) == a^rank(L)
    return true, a
  else
    return false, a
  end
end

function ismodular(L::AbsLat, p)
  a = scale(L)
  v = valuation(a, p)
  if v * rank(L) == valuation(volume(L), p)
    return true, v
  else
    return false, 0
  end
end

################################################################################
#
#  Bad primes
#
################################################################################

function bad_primes(L::AbsLat; even::Bool = false)
  s = scale(L)
  f = factor(scale(L))
  ff = factor(volume(L))
  for (p, e) in ff
    f[p] = 0
  end
  if even
    for p in prime_decomposition(s, 2)
      f[p] = 0
    end
  end
  return collect(keys(f))
end

################################################################################
#
#  Jordan decomposition
#
################################################################################

function jordan_decomposition(L::AbsLat, p)
  F = gram_matrix(ambient_space(L))
  even = 2 in p
  if even
    pi = elem_in_nf(uniformizer(p))
  else
    pi = zero(nf(order(p)))
  end

  oldval = PosInf()
  blocks = Int[]
  exponents = Int[]
  S = local_basis_matrix(L,p)
  @show S
  @show F
  n = ncols(S)
  k = 1
  while k <= n
    G = S * F * transpose(S)
    @show k
    @show G
    X = Union{Int, PosInf}[ valuation(G[i, i], p) for i in k:n]
    m, ii = findmin(X)
    ii = ii + (k - 1)
    pair = (ii, ii)

    for i in k:n
      for j in (i + 1):n
        tmp = iszero(G[i, j]) ? inf : valuation(G[i, j], p)
        if tmp < m
          m = temp
          pair = (i, j)
        end
      end
    end

    if m != oldval
      push!(blocks, k)
      oldval = m
      push!(exponents, m)
    end

    if even && pair[1] != pair[2]
      swap_rows!(S, pair[1], k)
      swap_rows!(S, pair[2], k +1)

      T12 = (sub(S, k:k, 1:ncols(S)) * F * transpose(sub(S, k+1:k+1, 1:ncols(S))))[1, 1]
      for l in 1:ncols(S)
        S[k, l] = S[k, l] * pi^valuation(T12, p)//T12
      end

      T = (i, j) -> (sub(S, i:i, 1:ncols(S)) * F * transpose(sub(S, j:j, 1:ncols(S))))[1, 1]
      T11 = T(k, k)
      T22 = T(k + 1, k + 1)
      T12 = T(k, k + 1)
      d = T11*T22 - T12^2
      for l in (k + 2):n
        tl = T12 * T(k + 1, l) - T22 * T(k, l)
        ul = T12 * T(k, l) - T11 * T(k + 1, l)
        for u in 1:ncols(S)
          S[l, u] = (tl//d) * S[k, l] + (ul//d) * S[k + 1, u]
        end
      end
      k = k + 2
    else
      if pair[1] == pair[2]
        swap_rows!(S, pair[1], k)
      else
        for u in 1:ncols(S)
          S[pair[1], u] = S[pair[1], u] + S[pair[2], u]
          swap_rows!(S, pair[1], k)
        end
      end
      nrm = (sub(S, k:k, 1:ncols(S)) * F * transpose(sub(S, k:k, 1:ncols(S))))[1, 1]
      XX = sub(S, k:k, 1:ncols(S)) * F * transpose(S)
      for l in k+1:n
        for u in 1:ncols(S)
          S[l, u] = S[l, u] - XX[1,l]//nrm * S[k, u]
        end
      end
      k = k + 1
    end
  end

  push!(blocks, n+1)
  matrices = [ sub(S, blocks[i]:(blocks[i+1] - 1), 1:ncols(S)) for i in 1:(length(blocks) - 1)]
  return matrices, [ m * F * transpose(m) for m in matrices], exponents
end

################################################################################
#
#  Helper
#
################################################################################

function _sum_modules(a::PMat, b::PMat)
  c = vcat(a, b)
  H = pseudo_hnf(H, :lowerleft)
  r = 0
  for i in 1:nrows(H)
    if !iszero_row(matrix(H), i)
      r = i
    end
  end
  @assert r != 0
  return sub(H, r:nrows(H), 1:cols(H))
end

function _intersect_modules(a::PMat, b::PMat)
  M1 = hcat(a, deepcopy(a))
  z = zero_matrix(base_ring(matrix(a)), d, d)
  M2 = hcat(pseudo_matrix(z, b.coeffs), b)
  M = vcat(M1, M2)
  H = sub(pseudo_hnf(M, :lowerleft), 1:d, 1:d)
end

function _modules_equality(a::PMat, b::PMat)
  _spans_subset_of_pseudohnf(a, b, :lowerleft) && _spans_subset_of_pseudohnf(b, a, :lowerleft)
end

function _module_scale_ideal(a::NfAbsOrdIdl, b::PMat)
  return pseudo_matrix(matrix(b), [ a * c for c in coefficient_ideals(b)])
end

_module_scale_ideal(a::PMat, b::NfAbsOrdIdl) = _module_scale_ideal(b, a)

function _module_scale_ideal(a::NfOrdFracIdl, b::PMat)
  return pseudo_matrix(matrix(b), a .* coefficient_ideals(b))
end

_module_scale_ideal(a::PMat, b::NfOrdFracIdl) = _module_scale_ideal(b, a)

function _module_scale_ideal(a::NfRelOrdIdl, b::PMat)
  return pseudo_matrix(matrix(b), a .* coefficient_ideals(b))
end

_module_scale_ideal(a::PMat, b::NfRelOrdIdl) = _module_scale_ideal(b, a)

function _module_scale_ideal(a::NfRelOrdFracIdl, b::PMat)
  return pseudo_matrix(matrix(b), a .* coefficient_ideals(b))
end

_module_scale_ideal(a::PMat, b::NfRelOrdFracIdl) = _module_scale_ideal(b, a)

function _local_basis_matrix(a::PMat, p::NfOrdIdl)
  uni = uniformizer(p)
  z = zero_matrix(base_ring(matrix(a)), nrows(a), ncols(a))
  for i in 1:nrows(a)
    c = coefficient_ideals(a)[i]
    x = uni^valuation(c, p)
    for j in 1:ncols(a)
      z[i, j] = x * matrix(a)[i, j]
    end
  end
  return z
end

function _local_basis_submodule_matrix(a::PMat, p::NfOrdIdl)
  z = zero_matrix(base_ring(matrix(a)), nrows(a), ncols(a))
  for i in 1:nrows(a)
    c = coefficient_ideals(a)[i]
    vpc = valuation(c, p)
    found = false
    local x
    for b in basis(c)
      if valuation(b, p) == vpc
        found = true
        x = b
        break
      end
    end
    @assert found
    for j in 1:ncols(a)
      z[i, j] = x * matrix(a)[i, j]
    end
  end
  return z
end

function _local_basis_supermodule_matrix(a::PMat, p::NfOrdIdl)
  z = zero_matrix(base_ring(matrix(a)), nrows(a), ncols(a))
  for i in 1:nrows(a)
    # need to do an approximate or approximate_simple
    for j in 1:ncols(a)
      z[i, j] = x * matrix(a)[i, j]
    end
  end
  return z
end

function image(S::T, A::NfOrdFracIdl) where {T <: Map}
  return S(numerator(A))//denominator(A)
end

function ideal_trace(I)
  E = nf(order(I))
  K = base_field(E)
  return frac_ideal(maximal_order(K), [trace(b) for b in Basis(I)])
end

function ideal_trace(I::NfOrdFracIdl)
  E = nf(order(I))
  K = base_field(E)
  return reduce(gcd, [trace(b) for b in basis(I)]; init = fmpq(0))
end

# TODO: The scaling of pseudo-matrices with an element scales the ideals and not the matrix ...

ext: jl


text:
push!( LOAD_PATH, "../" )
import InformationInequalities  # Import the module to  have access to functions

"""
List all conditional Mutual Information expressions for a given number `n` of random variables. Conditional Entropies are of the form `I(X;Y|Z)` aka Mutual information between `X` and `Y` given `Z`.
```julia-repl
julia> ConditionalMutualInformationList(2,"🍉")
["I(🍉1;🍉2)" "I(🍉2;🍉1)"]
julia> ConditionalMutualInformationList(3)
["I(X1;X2)","I(X1;X2|X3)","I(X1;X3)","I(X1;X3|X2)","I(X2;X1)","I(X2;X1|X3)","I(X2;X3)","I(X2;X3|X1)","I(X3;X1)","I(X3;X1|X2)","I(X3;X2)","I(X3;X2|X1)"]
```
"""
function ConditionalMutualInformationList(m::Int64=2,RV::AbstractString="X")
	y=  [("$(RV)" .* string(i) * ";" * "$(RV)" .* string(j) * "|" * find_subset(m,i,j,RV)[k]) for i=1:m for j =setdiff(1:m,i) for k=1:Int64(exp2(m-2)) ]
	y0=replace.(y,"|" => "| ")
	y1=replace.(y0,"| $(RV)" => "|$(RV)")
	y2=replace.(y1,"| " => "")
	yy = "I(" .* y2 .* ")"
	return yy[:]
end


text:
function generate_testfile(io::IO)
  write(io, """
  #include <numeric>
  #include <iostream>
  #include <vector>
  #include <stdlib.h>
  #include "./include/P32e2.h"   // <== 32 bit posit, exponent 2

  using namespace std;

  void initializevector(vector <float> &fv){
    for (int idx = 0; idx < 4; ++idx){
      fv[idx] = ((float) rand()) / ((float) RAND_MAX);  // warning, requires posix standard.
    }
  }

  //transfertoposit iterates over the floating point vector and converts it to
  //a vector of P32e2 objects.

  void transfertoposit(vector <P32e2> &pv, vector <float> &fv){
    for (int idx = 0; idx < 4; ++idx){
      pv[idx] = (P32e2) fv[idx];                       //note the very simple type coercion operator
    }
  }

  //positdotproduct performs a dot product on two <P32e2> vectors, but you could
  //have written this as STL paramaterized function like the STL inner product itself is.

  P32e2 positdotproduct(vector <P32e2> &pv1, vector <P32e2> &pv2){

    //this is a placeholder function for future testing of the exact dot product.
    //just use the STL dot product.  Since the + and * operators are overloaded
    //this works just fine with no modification.

    return (P32e2) 0.0;
  }

  // a utility function to print out float vectors.
  void describevector(vector <float> &fv, char const * label){
    cout << label << ": < ";
    for (int idx=0; idx < 4; ++idx){
      cout << fv[idx] << " ";
    }
    cout << "> \\n";
  }

  int main(){

    //don't hesitate to allocate these guys on the stack.  A P32e2 object should
    //only take 32 bits of space, just like a standard float.  There's no extra
    //spinach in the objects, and certainly no virtual functions to create
    //overhead.

    P32e2 v1 = (P32e2) 1.5;
    P32e2 v2 = (P32e2) -1.25;

    // basic type coercion to and from  both float AND for screen or file output
    // you really should really do these conversions.  Another possibility is
    // to output as hexadecimal using your favorite int to hexstring utility and
    // accessing the .data public member data.

    cout << "v1(dec): " << (double) v1 << endl;
    cout << "v2(dec): " << (double) v2 << endl;
    cout << "v1(hex): " << hex << v1.data << endl;
    cout << "v2(hex): " << hex << v2.data << endl;

    //the expected arithmetic operations are implemented.  See README.md for more
    //details.

    cout << "v1 + v2:" << (float) (v1 + v2) << endl;
    cout << "v1 - v2:" << (float) (v1 - v2) << endl;
    cout << "v1 * v2:" << (float) (v1 * v2) << endl;
    cout << "v1 / v2:" << (float) (v1 / v2) << endl;
    cout << "sqrt(v1)" << (float) sqrt(v1)  << endl;

    //allocate, on the stack two float vectors and enough space for the corresponding
    //posit versions.

    vector <float> f1(4);
    vector <float> f2(4);

    vector <P32e2> p1(4);
    vector <P32e2> p2(4);

    //fill them with "random" values.

    initializevector(f1);
    initializevector(f2);

    //print them out.

    describevector(f1, "first  vector: ");
    describevector(f2, "second vector: ");

    //convert to posit.

    transfertoposit(p1, f1);
    transfertoposit(p2, f2);

    // calculate the dot product using the STL library.
    // requires #include<numeric>
    //
    //   the STL inner_product(...) functions guarantee results if addition
    //   and multiplication operatiors have been defined on the templated type.

    P32e2 res = inner_product(p1.begin(), p1.end(), p2.begin(), (P32e2) 0.0);

    cout << "result scalar: " << (float) res << '\\n';

    return 0;
  }

  """)
end


text:
# This file was generated, do not modify it. # hide
using Pkg # hideall
Pkg.activate("_literate/A-stacking/Project.toml")
Pkg.update()
macro OUTPUT()
    return isdefined(Main, :Franklin) ? Franklin.OUT_PATH[] : "/tmp/"
end;

max_issues_repo_name: alan-turing-institute/MLJTutorials

max_forks_repo_name: alan-turing-institute/MLJTutorials


text:
# This is an extended version of exact_space_time_inference.jl. It combines it with
# Optim + ParameterHandling + Zygote to learn the kernel parameters.
# If you understand how to use Optim + ParameterHandling + Zygote for an AbstractGP,
# e.g. that shown on the README for this package, and how exact_space_time_inference.jl
# works, then you should understand this file.

using AbstractGPs
using KernelFunctions
using TemporalGPs

using TemporalGPs: Separable, RectilinearGrid

# Load standard packages from the Julia ecosystem
using Optim # Standard optimisation algorithms.
using ParameterHandling # Helper functionality for dealing with model parameters.
using Zygote # Algorithmic Differentiation

# Declare model parameters using `ParameterHandling.jl` types.
flat_initial_params, unflatten = ParameterHandling.flatten((
    var_kernel = positive(0.6),
    λ_space = positive(2.5),
    λ_time = positive(2.5),
    var_noise = positive(0.1),
));

# Construct a function to unpack flattened parameters and pull out the raw values.
unpack = ParameterHandling.value ∘ unflatten;
params = unpack(flat_initial_params);

function build_gp(params)
    k_space = SEKernel() ∘ ScaleTransform(params.λ_space)
    k_time = Matern52Kernel() ∘ ScaleTransform(params.λ_time)
    k = params.var_kernel * Separable(k_space, k_time)
    return to_sde(GP(k), ArrayStorage(Float64))
end

# Construct a rectilinear grid of points in space and time.
# Exact inference only works for such grids.
# Times must be increasing, points in space can be anywhere.
N = 50;
T = 1_000;
points_in_space = collect(range(-3.0, 3.0; length=N));
points_in_time = RegularSpacing(0.0, 0.01, T);
x = RectilinearGrid(points_in_space, points_in_time);
y = rand(build_gp(params)(x, 1e-4));

# Specify an objective function for Optim to minimise in terms of x and y.
# We choose the usual negative log marginal likelihood (NLML).
function objective(params)
    f = build_gp(params)
    return -logpdf(f(x, params.var_noise), y)
end

# Optimise using Optim. Takes a little while to compile because Zygote.
training_results = Optim.optimize(
    objective ∘ unpack,
    θ -> only(Zygote.gradient(objective ∘ unpack, θ)),
    flat_initial_params + randn(4), # Add some noise to make learning non-trivial
    BFGS(
        alphaguess = Optim.LineSearches.InitialStatic(scaled=true),
        linesearch = Optim.LineSearches.BackTracking(),
    ),
    Optim.Options(show_trace = true);
    inplace=false,
);

# Extracting the final values of the parameters.
# Should be close to truth.
final_params = unpack(training_results.minimizer);

# Construct the posterior as per usual.
f_post = posterior(build_gp(final_params)(x, final_params.var_noise), y);

# Specify some locations at which to make predictions.
T_pr = 1200;
points_in_time_pr = RegularSpacing(0.0, 0.01, T_pr);
x_pr = RectilinearGrid(points_in_space, points_in_time_pr);

# Compute the exact posterior marginals at `x_pr`.
# This isn't optimised at present, so might take a little while.
f_post_marginals = marginals(f_post(x_pr));
m_post_marginals = mean.(f_post_marginals);
σ_post_marginals = std.(f_post_marginals);

# Visualise the posterior marginals. We don't do this during in CI because it causes
# problems.
if get(ENV, "TESTING", "FALSE") == "FALSE"
    using Plots
    savefig(
        plot(
            heatmap(reshape(m_post_marginals, N, T_pr)),
            heatmap(reshape(σ_post_marginals, N, T_pr));
            layout=(1, 2),
        ),
        "posterior.png",
    );
end


text:
#SignalProcessing: Dataset operations
#-------------------------------------------------------------------------------

import MDDatasets: xshift

#Shifts y-values of a dataset by +/-noffset steps:
#TODO: support circular shifts (optional arg)
#-------------------------------------------------------------------------------
function yshift(d::DataTime, noffset::Index)
	noffset = value(noffset)
	xt = d.data.xt
	xt = MDDatasets.shift(xt, noffset)
	return DataTime(d.data.t, xt)
end

#Shifts x-values of a dataset by +/-noffset steps:
#==TODO: Improve support of time shifts
This operation seems succeptible to roundoff errrors.
Maybe we should describe t range differently to better support time shifts.
QUESTION: Should t=0 always be guaranteed to be included in range?
==#
function xshift(d::DataTime, noffset::Index)
	noffset = value(noffset)
	t = d.data.t
	Δt = step(t)*noffset
	return DataTime(t+Δt, copy(d.data.xt))
end

#Last line


text:
function annotate!(simple_plot::SimplePlot, x_position::Number, y_position::Number, cur_text::PlotText)
  cur_font = Dict()

  ( cur_text.font.family == "" ) || ( cur_font["family"] = cur_text.font.family )
  isnothing( cur_text.font.color ) || ( cur_font["color"] = cur_text.font.color )
  ( cur_text.font.pointsize == -1 ) || ( cur_font["size"] = cur_text.font.pointsize )

  push!(
    simple_plot.layout["annotations"],
    Dict(
      "text" => cur_text.str,
      "textangle" => cur_text.font.rotation,
      "showarrow" => false,
      "x" => collect(x_position),
      "y" => collect(y_position),
      "align" => cur_text.font.halign,
      "valign" => cur_text.font.valign,
      "font" => cur_font
    )
  )

  validate!(simple_plot)
end

function annotate!(x_position::Number, y_position::Number, cur_text::PlotText)
  annotate!(_plot, x_position, y_position, cur_text)
end

function annotate!(simple_plot::SimplePlot, x_position::Number, y_position::Number, cur_text::AbstractString)
  annotate!(simple_plot, x_position, y_position, text(cur_text))
end

function annotate!(x_position::Number, y_position::Number, cur_text::AbstractString)
  annotate!(_plot, x_position, y_position, cur_text)
end

export annotate!


text:
"""
Hypergraph configuration model for fixed degree and edge-dimension sequences using
a Markov Chain Monte Carlo scheme.

Based on "Configuration Models of Random Hypergraphs" by Philip S. Chodrow.
The original paper may be found at https://arxiv.org/abs/1902.09302.
"""

"""
`stub_matching`
===============

Produce a random (likely degenerate) hypergraph with given degree and edge
dimension sequences via stub matching. (pp. 2,6)

Arguments
---------
   - `D::Vector{Int64}`: The desired degree sequence
   - `K::Vector{Int64}`: The desired edge dimension sequence
   - `vals::Any (=ones())`: The values located at each node

Examples
-------
~~~~
stub_matching([3,2,2,2], [4,3,2])
stub_matching([1], [2], ["foo"])
~~~~
"""
function stub_matching(D::Vector{Int64}, K::Vector{Int64}; vals=ones(length(D)))
   hypergraph = Hypergraph(Vector{Vector{Float64}}(), 0, 0) # Blank hypergraph
   degs = copy(D)
   valid_nodes = 1:length(D)

   for k = 1:size(K,1) # Iterate over edge dimension sequence
      edge = zeros(K[k]) # Empty edge of length K[k]
      for d = 1:K[k] # Choose K[k] random stubs for the edge
         node = rand(1:length(degs))
         edge[d] = node
         degs[node] -= 1 # Remove stub
         if degs[node] == 0
            setdiff!(valid_nodes, [node])
         end
      end
      push!(hypergraph.edges, sort(edge, by=x -> D[x], rev=true))
   end
   hypergraph.n = size(D,1) # Set hypergraph fields
   hypergraph.m = size(K,1)
   hypergraph.D = D
   hypergraph.K = K
   hypergraph.vals = vals
   return hypergraph
end

"""
`pairwise_reshuffle!`
=====================

Performs a pairwise reshuffle on the edges numbered `e1` and `e2`.

Arguments
---------
   - `H::Hypergraph`: The hypergraph containing the edges to shuffle
   - `e1::Int64`: The index of the first edge
   - `e2::Int64`: The index of the second edge

Preconditions
-------------
   - `e1, e2 ∈ (0,m]` where `m` is the number of edges in the hypergraph
   - `e1 ≠ e2`

A pairwise reshuffle is an operation that randomly swaps nodes between
two edges while leaving their intersection intact. Stubs within the
intersection may be exchanged. (pp. 6-7)

Example
-------
~~~~
pairwise_reshuffle(H, 1, 2) # Shuffles edges 1 and 2
~~~~
"""
function pairwise_reshuffle!(H::Hypergraph, e1::Int64, e2::Int64)
   @assert 0 < e1 <= H.m && 0 < e2 <= H.m && e1 != e2 # Edges to be reshuffled are valid

   inter = collect(edge_intersect(h, e1, e2)) # edge1 ⋂ edge2
   exclu = setdiff([h.edges[e1]; h.edges[e2]], inter) # (edge1 ⋃ edge2) \ inter

   h.edges[e1] = copy(inter) # Initialize e1 as intersection
   for i = 1:h.K[e1] - size(inter,1) # Randomly assign |e1 \ e2| nodes to e1
      new_n = rand(exclu)
      push!(h.edges[e1], new_n)
      setdiff!(exclu, [new_n])
   end

   h.edges[e2] = [inter; exclu] # Assign the rest to e2

   sort!(h.edges[e1], by=x -> H.D[x], rev=true)
   sort!(h.edges[e2], by=x -> H.D[x], rev=true)
end

"""
`MCMC`
======

Randomly explore the space of hypergraphs (vertex- or stub-labeled) with the degree
and edge-dimension sequences of a given hypergraph via random pairwise shuffling. (pp. 7-9)

Arguments
---------
   - `intiial::Hypergraph`: The hypergraph from which exploration begins
   - `samples::Int64 (=1000)`: The number of pairwise reshuffles to perform
   - `type::String (="vertex")`: Which distribution to draw from. Choices are
                                 "vertex" or "stub".

Preconditions
-------------
   - `samples > 0`
"""
function MCMC(initial::Hypergraph; samples::Int64=1000, type::String="vertex")
   @assert samples > 0 # Positive number of samples
   if type == "stub"
      return MCMC_s(initial; samples=samples)
   elseif type == "vertex"
      return MCMC_v(initial; samples=samples)
   else
      error("Invalid type argument. Choices are 'vertex' and 'stub'.")
   end
end

"""
`MCMC_s`
========

See `MCMC`.

We generate a random stub-labeled hypergraph by performing a swap at every
iteration of the loop.
"""
function MCMC_s(initial::Hypergraph; samples=1000)
   c = copy(initial)
   for t = 1:samples
      sample_edges = random_edge_indices(c, 2) # Random pair of edges
      pairwise_reshuffle!(c, sample_edges[1], sample_edges[2])
   end
   return c
end

"""
`MCMC_v`
========

See `MCMC`.

To generate a random vertex-labeled hypergraph, we can't swap at every iteration;
otherwise, we effectively perform the stub-labeled process, with the effect of
biasing the end result towards vertex-labeled hypergraphs that have more equivalent
realizations as stub-labeled hypergraphs. Thus, we must sample from the below
distribution.
"""
function MCMC_v(initial::Hypergraph; samples=1000)
   k = 0 # Number of iterations performed
   n_rejected = 0 # Number of iterations in which a swap was rejected

   c = copy(initial)
   parallels = Dict() # Keeps track of the number of hyperedges parallel (multiset equal) to each distinct hyperedge
   for i = 1:c.m
      if !haskey(parallels, c.edges[i])
         parallels[c.edges[i]] = 0
      end
      parallels[c.edges[i]] += 1
   end

   while k - n_rejected < samples # Number of performed swaps < desired
      n_rand = 20000

      n_ = 1
      p_ = 1
      inds = zeros(Int64, n_rand) # Random edge indices
      for i = 0:2:n_rand-1
         pair = sample(1:initial.m, 2, replace=false) # Prohibit swaps between an edge and itself
         inds[i+1], inds[i+2] = pair[1], pair[2]
      end
      probs = rand(Float64, Int(n_rand / 2)) # Generate many random floats at once

      while true
         if n_ >= n_rand / 2 # Refresh random values
            n_ = 1
            p_ = 1
            inds = zeros(Int64, n_rand)
            for i = 0:2:n_rand-1
               pair = sample(1:initial.m, 2, replace=false)
               inds[i+1], inds[i+2] = pair[1], pair[2]
            end
            probs = rand(Float64, n_rand)
         end

         i, j = inds[n_], inds[n_+1]
         ei, ej = c.edges[i], c.edges[j]

         n_ += 2 # Go to next pair of edges
         p_ += 1 # Next probability
         inter = 2.0^-length(intersect(ei, ej)) # Larger intersection = less likely to swap

         if probs[p_] > inter / (parallels[ei] * parallels[ej]) # Randomly decide whether to shuffle
            n_rejected += 1
            k += 1
         else
            if haskey(parallels, ei)
               parallels[ei] -= 1
               if parallels[ei] <= 0
                  delete!(parallels, ei)
               end
            end

            if haskey(parallels, ej)
               parallels[ej] -= 1
               if parallels[ej] <= 0
                  delete!(parallels, ej)
               end
            end

            pairwise_reshuffle!(c, i, j) # Perform shuffle

            new_ei = c.edges[i]
            new_ej = c.edges[j]

            if !haskey(parallels, new_ei)
               parallels[new_ei] = 0
            end
            if !haskey(parallels, new_ej)
               parallels[new_ej] = 0
            end
            parallels[new_ei] += 1 # Update parallels
            parallels[new_ej] += 1

            k += 1
            break
         end
      end
   end

   return c
end


text:
import Pkg;
Pkg.activate(".")
using SimpleSDMLayers
using DataFrames
using CSV
using Plots

## Prepare test data
#=
df = CSV.read("../betadiversity-hotspots/data/raw/ebd_warblers_head.csv")
newnames = names(df) .|>
    string .|>
    titlecase .|>
    lowercasefirst .|>
    x -> replace(x, " " => "") .|>
    Symbol
rename!(df, newnames)
rename!(df, :scientificName => :species)
df.species = replace.(df.species, " " => "_")
show(df, allcols = true)
newdf = select(df, [:species, :latitude, :longitude])
CSV.write("data/test-data.csv", newdf)
=#

## Test
testdf = CSV.read("data/test-data.csv")
latitude = :latitude
longitude = :longitude
temperature = worldclim(1)
wc_vars = worldclim(1:19)
layer = temperature
layers = wc_vars
col = :values
ty = :SimpleSDMResponse

# Getindex
temperature[df]

# Setindex
tmp = copy(convert(SimpleSDMResponse, temperature))
tmp[df]
# tmp[df] = Float32.(collect(1:nrow(df))) # let's put it aside for now

# Inconsistent use of missing/nothing/NaN in GBIF code
using GBIF
kingfisher = GBIF.taxon("Megaceryle alcyon", strict=true)
kf_occurrences = occurrences(kingfisher)
for i in 1:9
  occurrences!(kf_occurrences)
end
tmp = [k.latitude for k in kf_occurrences]
sort(tmp) # none missing
tmp = layer[kf_occurrences]
sort(tmp) # none missing
temperature_clip = clip(temperature, kf_occurrences)
# whatever...

# Clip
temperature_clip = clip(temperature, df)
temperature_clip.grid
# using Plots
# plot(temperature_clip)

# DataFrame
@time DataFrame(temperature)
@time DataFrame(layers)

# SimpleSDMResponse/Predictor
df = DataFrame(temperature)
@time SimpleSDMResponse(df, :values, layer)
@time SimpleSDMPredictor(df, :values, layer)
minidf = DataFrame(latitude = [1.0, 0.0], longitude = [1.0, 0.0], values = [42, 3])
@time SimpleSDMResponse(minidf, :values, layer)
mediumdf = df[rand(1:nrow(df), 10_000), :]
@time SimpleSDMResponse(mediumdf, :values, layer)
valuesdf = filter(x -> !isnothing(x.values), df)
@time tmp = SimpleSDMResponse(valuesdf, :values, layer)


text:
# See https://udel.edu/~arthij/MD.pdf for information on forces
# See https://arxiv.org/pdf/1401.1181.pdf for applying forces to atoms
# See OpenMM documentation and Gromacs manual for other aspects of forces

export
    ustrip_vec,
    accelerations,
    force,
    SpecificForce2Atoms,
    SpecificForce3Atoms,
    SpecificForce4Atoms,
    forces

"""
    ustrip_vec(x)

Broadcasted form of `ustrip` from Unitful.jl, allowing e.g. `ustrip_vec.(coords)`.
"""
ustrip_vec(x) = ustrip.(x)

function check_force_units(fdr, force_units)
    if unit(first(fdr)) != force_units
        error("System force units are ", force_units, " but encountered force units ", unit(first(fdr)))
    end
end

"""
    accelerations(system, neighbors=nothing; parallel=true)

Calculate the accelerations of all atoms using the pairwise and specific
interactions and Newton's second law.
If the interactions use neighbor lists, the neighbors should be computed
first and passed to the function.
"""
function accelerations(s, neighbors=nothing; parallel::Bool=true)
    return forces(s, neighbors; parallel=parallel) ./ mass.(s.atoms)
end

"""
    force(inter::PairwiseInteraction, vec_ij, coord_i, coord_j,
          atom_i, atom_j, box_size)
    force(inter::SpecificInteraction, coord_i, coord_j,
          box_size)
    force(inter::SpecificInteraction, coord_i, coord_j,
          coord_k, box_size)
    force(inter::SpecificInteraction, coord_i, coord_j,
          coord_k, coord_l, box_size)

Calculate the force between atoms due to a given interation type.
For `PairwiseInteraction`s returns a single force vector and for
`SpecificInteraction`s returns a type such as `SpecificForce2Atoms`.
Custom pairwise and specific interaction types should implement
this function.
"""
function force end

@inline @inbounds function force!(fs, inter, s::System, i::Integer, j::Integer, force_units, weight_14::Bool=false)
    dr = vector(s.coords[i], s.coords[j], s.box_size)
    if weight_14
        fdr = force(inter, dr, s.coords[i], s.coords[j], s.atoms[i], s.atoms[j], s.box_size, true)
    else
        fdr = force(inter, dr, s.coords[i], s.coords[j], s.atoms[i], s.atoms[j], s.box_size)
    end
    check_force_units(fdr, force_units)
    fdr_ustrip = ustrip.(fdr)
    fs[i] -= fdr_ustrip
    fs[j] += fdr_ustrip
    return nothing
end

@inline @inbounds function force_nounit(inters, coord_i, coord_j, atom_i, atom_j,
                                        box_size, force_units, weight_14::Bool=false)
    dr = vector(coord_i, coord_j, box_size)
    sum(inters) do inter
        if weight_14
            fdr = force(inter, dr, coord_i, coord_j, atom_i, atom_j, box_size, true)
        else
            fdr = force(inter, dr, coord_i, coord_j, atom_i, atom_j, box_size)
        end
        check_force_units(fdr, force_units)
        return ustrip.(fdr)
    end
end

accumulateadd(x) = accumulate(+, x)

# Accumulate values in an array based on the ordered boundaries in bounds
# Used to speed up views with repeated indices on the GPU when you have the bounds
@views @inbounds function accumulate_bounds(arr, bounds)
    zf = zero(arr[1:1])
    accum_pad = vcat(zf, accumulateadd(arr))
    accum_bounds = accum_pad[bounds]
    accum_bounds_offset = vcat(zf, accum_bounds[1:(end - 1)])
    return accum_bounds .- accum_bounds_offset
end

# Uses the Zygote path which gives wrong gradients on the GPU for repeated indices
# Hence only used when we know the indices don't contain repeats
# See https://github.com/FluxML/Zygote.jl/pull/1131
unsafe_getindex(arr, inds) = @view arr[inds]

# Sum forces on neighboring atom pairs to get forces on each atom
# Neighbor forces are accumulated and then atom forces extracted by subtraction
#   at the atom boundaries
# The forces are re-arranged for the other atom index and the process is repeated
# This isn't pretty but it works on the GPU
@views @inbounds function sum_forces(nb_forces, neighbors)
    fs_accum_i = accumulate_bounds(nb_forces, neighbors.atom_bounds_i)
    fs_accum_j = accumulate_bounds(unsafe_getindex(nb_forces, neighbors.sortperm_j), neighbors.atom_bounds_j)
    return fs_accum_j .- fs_accum_i
end

# Functions defined to allow us to write rrules
getindices_i(arr, neighbors) = @view arr[neighbors.nbsi]
getindices_j(arr, neighbors) = @view arr[neighbors.nbsj]

@views function forces_inters(inters, coords, atoms, neighbors, box_size, force_units, weights_14)
    coords_i, atoms_i = getindices_i(coords, neighbors), getindices_i(atoms, neighbors)
    coords_j, atoms_j = getindices_j(coords, neighbors), getindices_j(atoms, neighbors)
    @inbounds nb_forces = force_nounit.((inters,), coords_i, coords_j, atoms_i, atoms_j,
                                        (box_size,), force_units, weights_14)
    return sum_forces(nb_forces, neighbors)
end

"""
    SpecificForce2Atoms(f1, f2)

Forces on two atoms arising from an interaction.
"""
struct SpecificForce2Atoms{D, T}
    f1::SVector{D, T}
    f2::SVector{D, T}
end

"""
    SpecificForce3Atoms(f1, f2, f3)

Forces on three atoms arising from an interaction.
"""
struct SpecificForce3Atoms{D, T}
    f1::SVector{D, T}
    f2::SVector{D, T}
    f3::SVector{D, T}
end

"""
    SpecificForce4Atoms(f1, f2, f3, f4)

Forces on four atoms arising from an interaction.
"""
struct SpecificForce4Atoms{D, T}
    f1::SVector{D, T}
    f2::SVector{D, T}
    f3::SVector{D, T}
    f4::SVector{D, T}
end

function SpecificForce2Atoms(f1::StaticArray{Tuple{D}, T}, f2::StaticArray{Tuple{D}, T}) where {D, T}
    return SpecificForce2Atoms{D, T}(f1, f2)
end

function SpecificForce3Atoms(f1::StaticArray{Tuple{D}, T}, f2::StaticArray{Tuple{D}, T},
                            f3::StaticArray{Tuple{D}, T}) where {D, T}
    return SpecificForce3Atoms{D, T}(f1, f2, f3)
end

function SpecificForce4Atoms(f1::StaticArray{Tuple{D}, T}, f2::StaticArray{Tuple{D}, T},
                            f3::StaticArray{Tuple{D}, T}, f4::StaticArray{Tuple{D}, T}) where {D, T}
    return SpecificForce4Atoms{D, T}(f1, f2, f3, f4)
end

Base.:+(x::SpecificForce2Atoms, y::SpecificForce2Atoms) = SpecificForce2Atoms(x.f1 + y.f1, x.f2 + y.f2)
Base.:+(x::SpecificForce3Atoms, y::SpecificForce3Atoms) = SpecificForce3Atoms(x.f1 + y.f1, x.f2 + y.f2, x.f3 + y.f3)
Base.:+(x::SpecificForce4Atoms, y::SpecificForce4Atoms) = SpecificForce4Atoms(x.f1 + y.f1, x.f2 + y.f2, x.f3 + y.f3, x.f4 + y.f4)

getf1(x) = x.f1
getf2(x) = x.f2
getf3(x) = x.f3
getf4(x) = x.f4

@views function specific_force(inter_list::InteractionList2Atoms, coords, box_size, force_units, n_atoms)
    sparse_fs = Array(force.(inter_list.inters, coords[inter_list.is], coords[inter_list.js], (box_size,)))
    fis, fjs = getf1.(sparse_fs), getf2.(sparse_fs)
    check_force_units(first(first(fis)), force_units)
    return sparsevec(inter_list.is, fis, n_atoms) + sparsevec(inter_list.js, fjs, n_atoms)
end

@views function specific_force(inter_list::InteractionList3Atoms, coords, box_size, force_units, n_atoms)
    sparse_fs = Array(force.(inter_list.inters, coords[inter_list.is], coords[inter_list.js],
                                coords[inter_list.ks], (box_size,)))
    fis, fjs, fks = getf1.(sparse_fs), getf2.(sparse_fs), getf3.(sparse_fs)
    check_force_units(first(first(fis)), force_units)
    return sparsevec(inter_list.is, fis, n_atoms) + sparsevec(inter_list.js, fjs, n_atoms) + sparsevec(inter_list.ks, fks, n_atoms)
end

@views function specific_force(inter_list::InteractionList4Atoms, coords, box_size, force_units, n_atoms)
    sparse_fs = Array(force.(inter_list.inters, coords[inter_list.is], coords[inter_list.js],
                                coords[inter_list.ks], coords[inter_list.ls], (box_size,)))
    fis, fjs, fks, fls = getf1.(sparse_fs), getf2.(sparse_fs), getf3.(sparse_fs), getf4.(sparse_fs)
    check_force_units(first(first(fis)), force_units)
    return sparsevec(inter_list.is, fis, n_atoms) + sparsevec(inter_list.js, fjs, n_atoms) + sparsevec(inter_list.ks, fks, n_atoms) + sparsevec(inter_list.ls, fls, n_atoms)
end

"""
    forces(system, neighbors=nothing; parallel=true)

Calculate the forces on all atoms in the system.
If the interactions use neighbor lists, the neighbors should be computed
first and passed to the function.

    forces(inter, system, neighbors=nothing)

Calculate the forces on all atoms in the system arising from a general
interaction.
If the interaction uses neighbor lists, the neighbors should be computed
first and passed to the function.
Custom general interaction types should implement this function.
"""
function forces(s::System{D, false}, neighbors=nothing; parallel::Bool=true) where D
    n_atoms = length(s)

    if parallel && nthreads() > 1 && n_atoms >= 100
        fs_threads = [ustrip_vec.(zero(s.coords)) for i in 1:nthreads()]

        # Loop over interactions and calculate the acceleration due to each
        for inter in values(s.pairwise_inters)
            if inter.nl_only
                if isnothing(neighbors)
                    error("An interaction uses the neighbor list but neighbors is nothing")
                end
                @threads for ni in 1:neighbors.n
                    i, j, w = neighbors.list[ni]
                    force!(fs_threads[threadid()], inter, s, i, j, s.force_units, w)
                end
            else
                @threads for i in 1:n_atoms
                    for j in 1:(i - 1)
                        force!(fs_threads[threadid()], inter, s, i, j, s.force_units)
                    end
                end
            end
        end

        fs = sum(fs_threads)
    else
        fs = ustrip_vec.(zero(s.coords))

        for inter in values(s.pairwise_inters)
            if inter.nl_only
                if isnothing(neighbors)
                    error("An interaction uses the neighbor list but neighbors is nothing")
                end
                for ni in 1:neighbors.n
                    i, j, w = neighbors.list[ni]
                    force!(fs, inter, s, i, j, s.force_units, w)
                end
            else
                for i in 1:n_atoms
                    for j in 1:(i - 1)
                        force!(fs, inter, s, i, j, s.force_units)
                    end
                end
            end
        end
    end

    for inter_list in values(s.specific_inter_lists)
        sparse_vec = specific_force(inter_list, s.coords, s.box_size, s.force_units, n_atoms)
        fs += ustrip_vec.(Array(sparse_vec))
    end

    for inter in values(s.general_inters)
        # Force type not checked
        fs += ustrip_vec.(forces(inter, s, neighbors))
    end

    return fs * s.force_units
end

function forces(s::System{D, true}, neighbors=nothing; parallel::Bool=true) where D
    fs = ustrip_vec.(zero(s.coords))

    pairwise_inters_nonl = filter(inter -> !inter.nl_only, values(s.pairwise_inters))
    @views if length(pairwise_inters_nonl) > 0
        fs += Zygote.checkpointed(forces_inters, pairwise_inters_nonl, s.coords, s.atoms, neighbors.all,
                                    s.box_size, s.force_units, false)
    end

    pairwise_inters_nl = filter(inter -> inter.nl_only, values(s.pairwise_inters))
    if length(pairwise_inters_nl) > 0 && length(neighbors.close.nbsi) > 0
        fs += Zygote.checkpointed(forces_inters, pairwise_inters_nl, s.coords, s.atoms, neighbors.close,
                                    s.box_size, s.force_units, neighbors.close.weights_14)
    end

    for inter_list in values(s.specific_inter_lists)
        sparse_vec = specific_force(inter_list, s.coords, s.box_size, s.force_units, length(s))
        # Move back to GPU if required
        fs += convert(typeof(fs), ustrip_vec.(Array(sparse_vec)))
    end

    for inter in values(s.general_inters)
        # Force type not checked
        fs += ustrip_vec.(forces(inter, s, neighbors))
    end

    return fs * s.force_units
end


text:
using LinearAlgebra, Krylov, LinearOperators, SparseArrays, Stopping

#Krylov @kdot
macro kdot(n, x, y)
  return esc(:(Krylov.krylov_dot($n, $x, 1, $y, 1)))
end

using SolverTools, Logging
"""
Randomized coordinate descent

Sect. 3.7 in Gower, R. M., & Richtárik, P. (2015).
Randomized iterative methods for linear systems.
SIAM Journal on Matrix Analysis and Applications, 36(4), 1660-1690.
"""
function RandomizedCD(A :: AbstractMatrix,
                      b :: AbstractVector{T};
                      is_zero_start :: Bool = true,
                      x0 :: AbstractVector{T} = zeros(T,size(A,2)),
                      atol :: AbstractFloat = 1e-7,
                      rtol :: AbstractFloat = 1e-15,
                      max_iter :: Int = size(A,2)^2,
                      max_time :: Float64 = 60.,
                      max_cntrs = Stopping._init_max_counters_linear_operators(quick=20000),
                      verbose :: Int = 100,
                      kwargs...) where T <: AbstractFloat

    m, n = size(A)
    x    = copy(x0)
    res  = is_zero_start ? b : b - A*x
    nrm0 = norm(res)

    time_init = time()
    elapsed_time = time_init

    cntrs = LACounters()
    max_f = false

    OK = nrm0 <= atol
    k  = 0

    #@info log_header([:iter, :un, :time], [Int, T, T])
    #@info log_row(Any[0, res[1], elapsed_time])
    while !OK && (k <= max_iter) && (elapsed_time - time_init <= max_time) && !max_f

     #rand a number between 1 and n
     #224.662 ns (4 allocations: 79 bytes) - independent of the n
     i  = mod(k,n)+1#Int(floor(rand() * n) + 1)
     Ai = A[:,i]

     #ei = zeros(n); ei[i] = 1.0 #unit vector in R^n
     #xk  = Ai == 0 ? x0 : x0 - dot(Ai,res)/norm(Ai,2)^2 * ei
     Aires = @kdot(m, Ai, res)
     nAi   = @kdot(m, Ai, Ai)
     x[i] -= Aires/nAi

     #res = b - A*x
     res += Ai * Aires/nAi
     cntrs.nprod += 1
     nrm = norm(res,Inf)
     OK  = nrm <= atol + nrm0 * rtol

     sum, max_f = 0, false
     for f in [:nprod, :ntprod, :nctprod]
      ff    = getfield(cntrs, f)
      max_f = max_f || (ff > max_cntrs[f])
      sum  += ff
     end
     max_f = max_f || sum > max_cntrs[:neval_sum]
     k += 1
     elapsed_time = time()
     if mod(k, verbose) == 0 #print every 20 iterations
     # @info log_row(Any[k, res[1], elapsed_time])
     end

    end

 return x, OK, k
end


text:
module DistributionFits

using Reexport
@reexport using Distributions

using FillArrays, StaticArrays
using StatsFuns: logit, logistic, normcdf
using Requires: @require

# for extension
import Distributions: mean, var, mode
import StatsBase: fit
# Moments also extends getindex, mean, kurtorsis ....

export
  # fitting distributions
  AbstractMoments, Moments, n_moments, moments,
  QuantilePoint,
  fit_mean_quantile, fit_mode_quantile, fit_median_quantile,
  @qp, @qp_ll, @qp_l, @qp_m, @qp_u, @qp_uu,
  @qs_cf90, @qs_cf95,
  fit_mean_relerror

# LogNormal
export AbstractΣstar, Σstar, σstar

# LogitNormal
export fit_mode_flat, shifloNormal

# dependency inversion: need to define DistributionFits.optimize by user
export AbstractDistributionFitOptimizer, optimize

include("optimizer.jl")

function __init__()
  @require Optim="429524aa-4258-5aef-a3af-852621145aeb" include("requires_optim.jl")
end

# fitting distributions to stats
include("fitstats.jl")
include("univariates.jl")

end


text:
abstract type RobustVariableType end
abstract type VariableType end
abstract type RobustVariable <: JuMP.AbstractVariable end
abstract type RobustVariableRef <: JuMP.AbstractVariableRef end

Base.copy(v::RobustVariableRef) = v

mutable struct UncertainVariableRef <: RobustVariableRef
    model::RobustModel # `model` owning the variable
    idx::Int       # Index of global variable
    type_idx::Int    #Index of this specific type
    Type::Char
end

mutable struct DecisionVariableRef <: RobustVariableRef
    model::RobustModel # `model` owning the variable
    idx::Int       # Index of global variable
    type_idx::Int    #Index of this specific type
    Type::Char
end

abstract type RobustConstraintType end
abstract type ConstraintType end
abstract type AbstractRobustIndex end
abstract type RobustConstraint{S} <: AbstractConstraint end
abstract type RobustConstraintRef end

mutable struct UncertainConstraintRef <: RobustConstraintRef
    model::RobustModel
    idx::Int64
    type_idx::Int    #Index of this specific type
    Type::Char
end

mutable struct UncertaintySetConstraintRef <: RobustConstraintRef
    model::RobustModel
    idx::Int64
    type_idx::Int    #Index of this specific type
    Type::Char
end
abstract type AbstractDataObject end

export RobustConstraint,UncertaintyConstraint,RobustConstraintRef,AbstractDataObject,UncertaintySetConstraintRef


text:
mutable struct UncertaintyTrapEcon{TF<:AbstractFloat, TI<:Integer}
    a::TF          # Risk aversion
    γ_x::TF        # Production shock precision
    ρ::TF          # Correlation coefficient for θ
    σ_θ::TF        # Standard dev of θ shock
    num_firms::TI  # Number of firms
    σ_F::TF        # Std dev of fixed costs
    c::TF          # External opportunity cost
    μ::TF          # Initial value for μ
    γ::TF          # Initial value for γ
    θ::TF          # Initial value for θ
    σ_x::TF        # Standard deviation of shock
end

function UncertaintyTrapEcon(;a::AbstractFloat=1.5, γ_x::AbstractFloat=0.5,
                             ρ::AbstractFloat=0.99, σ_θ::AbstractFloat=0.5,
                             num_firms::Integer=100, σ_F::AbstractFloat=1.5,
                             c::AbstractFloat=-420.0, μ_init::AbstractFloat=0.0,
                             γ_init::AbstractFloat=4.0,
                             θ_init::AbstractFloat=0.0)
    σ_x = sqrt(a / γ_x)
    UncertaintyTrapEcon(a, γ_x, ρ, σ_θ, num_firms, σ_F, c, μ_init,
                        γ_init, θ_init, σ_x)

end

function ψ(uc::UncertaintyTrapEcon, F::Real)
    temp1 = -uc.a * (uc.μ - F)
    temp2 = 0.5 * uc.a^2 * (1 / uc.γ + 1 / uc.γ_x)
    return (1 / uc.a) * (1 - exp(temp1 + temp2)) - uc.c
end

"""
Update beliefs (μ, γ) based on aggregates X and M.
"""
function update_beliefs!(uc::UncertaintyTrapEcon, X::Real, M::Real)
    # Simplify names
    γ_x, ρ, σ_θ = uc.γ_x, uc.ρ, uc.σ_θ

    # Update μ
    temp1 = ρ * (uc.γ * uc.μ + M * γ_x * X)
    temp2 = uc.γ + M * γ_x
    uc.μ =  temp1 / temp2

    # Update γ
    uc.γ = 1 / (ρ^2 / (uc.γ + M * γ_x) + σ_θ^2)
end

update_θ!(uc::UncertaintyTrapEcon, w::Real) =
    (uc.θ = uc.ρ * uc.θ + uc.σ_θ * w)

"""
Generate aggregates based on current beliefs (μ, γ).  This
is a simulation step that depends on the draws for F.
"""
function gen_aggregates(uc::UncertaintyTrapEcon)
    F_vals = uc.σ_F * randn(uc.num_firms)

    M = sum(ψ.(uc, F_vals) .> 0)  # Counts number of active firms
    if M > 0
        x_vals = uc.θ + uc.σ_x * randn(M)
        X = mean(x_vals)
    else
        X = 0.0
    end
    return X, M
end


text:
function SynchronizedRandomGenerator(arg0::RandomGenerator)
    return SynchronizedRandomGenerator((RandomGenerator,), arg0)
end

function next_boolean(obj::SynchronizedRandomGenerator)
    return jcall(obj, "nextBoolean", jboolean, ())
end

function next_bytes(obj::SynchronizedRandomGenerator, arg0::Vector{jbyte})
    return jcall(obj, "nextBytes", void, (Vector{jbyte},), arg0)
end

function next_bytes(obj::SynchronizedRandomGenerator, arg0::Vector{jbyte}, arg1::jint, arg2::jint)
    return jcall(obj, "nextBytes", void, (Vector{jbyte}, jint, jint), arg0, arg1, arg2)
end

function next_double(obj::SynchronizedRandomGenerator)
    return jcall(obj, "nextDouble", jdouble, ())
end

function next_float(obj::SynchronizedRandomGenerator)
    return jcall(obj, "nextFloat", jfloat, ())
end

function next_gaussian(obj::SynchronizedRandomGenerator)
    return jcall(obj, "nextGaussian", jdouble, ())
end

function next_int(obj::SynchronizedRandomGenerator)
    return jcall(obj, "nextInt", jint, ())
end

function next_int(obj::SynchronizedRandomGenerator, arg0::jint)
    return jcall(obj, "nextInt", jint, (jint,), arg0)
end

function next_long(obj::SynchronizedRandomGenerator)
    return jcall(obj, "nextLong", jlong, ())
end

function next_long(obj::SynchronizedRandomGenerator, arg0::jlong)
    return jcall(obj, "nextLong", jlong, (jlong,), arg0)
end

function set_seed(obj::SynchronizedRandomGenerator, arg0::Vector{jint})
    return jcall(obj, "setSeed", void, (Vector{jint},), arg0)
end

function set_seed(obj::SynchronizedRandomGenerator, arg0::jint)
    return jcall(obj, "setSeed", void, (jint,), arg0)
end

function set_seed(obj::SynchronizedRandomGenerator, arg0::jlong)
    return jcall(obj, "setSeed", void, (jlong,), arg0)
end


text:
module Hydrogen_Vh_module
    using LinearAlgebra

    struct Hydrogen_Vh_param
        INTEGTABLENUM::Int64
        RESULT_FILENAME::String
    end

    mutable struct Hydrogen_Vh_variables
        mat_A_ele::Array{Float64, 3}
        mat_A_glo::SymTridiagonal{Float64,Array{Float64,1}}
        ug::Array{Float64, 1}
        vec_b_ele::Array{Float64, 2}
        vec_b_glo::Array{Float64, 1}
        w::Array{Float64, 1}
        x::Array{Float64, 1}
    end
end


text:
module H3Geometry

using GeoInterface
using LibGEOS
using H3
using H3.Lib
using H3.Lib: H3Index, GeoCoord, Geofence, GeoPolygon
import H3.API.geoToH3
using Geodesy

"""
# geoToH3(p, resolution)

Converts an abstract point p defined in terms of latitude and longitude in degrees into an H3Index.

# Example

```
julia> geoToH3(LibGEOS.Point(42, -110), 6)
0x08626b3cafffffff
```
"""
function geoToH3(p::AbstractPoint, resolution::Int) :: H3Index
    geoToH3(coordinates(p)..., resolution)
end

"""
# geoToH3(latitude, longitude, resolution)

Converts a point defined in terms of latitude and longitude in degrees into an H3Index.

# Example

```
julia> geoToH3(42, -110, 6)
0x08626b3cafffffff

julia> poly = LibGEOS.Polygon([[[0.0,0.0],[1,1],[2,0],[0,0]]])
julia> (x->geoToH3(x..., 5)).(coordinates(boundary(poly)))
4-element Vector{UInt64}:
 0x085754e67fffffff
 0x0857541affffffff
 0x0857542b7fffffff
 0x085754e67fffffff
```
"""
function geoToH3(latitude::Number, longitude::Number, resolution::Int) :: H3Index
    geoToH3(H3.API.GeoCoord(deg2rad(latitude), deg2rad(longitude)), resolution)
end

"""
# geoToH3([latitude, longitude], resolution)

Converts a point into an H3Index where the point is defined in terms
of a vector containing latitude and longitude in degrees.

# Example
```
julia> geoToH3(42, -110, 6)
0x08626b3cafffffff

julia> poly = LibGEOS.Polygon([[[0.0,0.0],[1,1],[2,0],[0,0]]])
julia> H3.API.geoToH3.(coordinates(boundary(poly)), 5)
4-element Vector{UInt64}:
 0x085754e67fffffff
 0x0857541affffffff
 0x0857542b7fffffff
 0x085754e67fffffff
```
"""
function geoToH3(point::Vector{Float64}, resolution::Int) :: H3Index
    geoToH3(point..., resolution)
end

"""
# h3ToPolygon(id)

Converts a single H3Index into the corresponding GeoPolygon. Useful for drawing.
"""
function h3ToPolygon(id::H3Index)
    px = map(p -> [rad2deg(p.lat), rad2deg(p.lon)], H3.API.h3ToGeoBoundary(id))
    push!(px, px[1])
    return LibGEOS.Polygon([px])
end

"""
# h3ToPolygon(Vector{H3Index})

Converts a vector of hexagons (represented as H3Index values) into a polygon which
is the union of all of the hexagons.
"""
function h3ToPolygon(ids::Vector{H3Index})
    return reduce(LibGEOS.union, h3ToPolygon.(ids))
end

"""
# polyfill(p::AbstractPolygon, resolution::Int; cover::Float64)

Covers the polygon p (which is expressed in lat/long) with hexagons and returns
the list of H3Indexes for each. Note that this is only an approximate covering.
Some hexagons will likely extend outside the polygon being covered and the
polygon may not be entirely covered.

To avoid undercoverage this function expands poly by an amount determined by the
cover parameter. The default value will result in a cover that rarely (if ever)
has any under-coverage. Increasing the setting to 2 will prevent any under-coverage
but will result in lots of overspray. Setting cover=0 will avoid all expansion, but will
almost always result in significant under-coverage as well. At about cover=1.1, the
undercoverage should be very small (<< 0.1%), but will still happen on occasion.

# Example
```
julia> p = LibGEOS.Polygon([[[40.0, -110.0], [41,-110], [41.4,-109], [40,-110]]])
julia> plot(h3ToPolygon(polyfill(p, 2)))
julia> plot!(h3ToPolygon(polyfill(p, 3)))
julia> plot!(h3ToPolygon(polyfill(p, 4)))
julia> plot!(h3ToPolygon(polyfill(p, 5)))
julia> plot!(p)
```
## Highlight over and under-coverage
```
julia> px = h3ToPolygon(polyfill(p, 4, cover=0)
julia> plot(p)
julia> plot!(difference(p,px))
julia> plot!(difference(px,p))
```
"""
function polyfill(poly::AbstractPolygon, resolution::Int; cover=1.2)::Vector{H3Index}
    if !LibGEOS.isValid(poly)
        throw(ArgumentError("Argument must not be self-intersecting polygon"))
    end
    cover = max(0.0, cover)
    if cover > 0.0
        # we expand by an amount related to the size of a hex edge
        fudge = cover * Lib.edgeLengthM(resolution)

        # this projection puts the first vertex of the polygon at the origin
        # and is expressed in meters [east, north, up] against the wgs84 ellipsoid
        origin = coordinates(boundary(poly))[1]
        tx = ENUfromLLA(LLA(origin...,0), wgs84)

        # expand our polygon projected form. We also simplify to avoid large number of vertices
        projected = map(x->tx(LLA(x..., 0))[1:2], coordinates(boundary(poly)))
        expanded = simplify(buffer(LibGEOS.Polygon([projected]), fudge), fudge/5)
        # expanded can be a Polygon or a MultiPolygon
        if expanded isa LibGEOS.MultiPolygon
            throw(ArgumentError("Buffering polygon resulted in Multipolygon ... shouldn't happen"))
        else
            bounds = coordinates(boundary(expanded))
            coords = map(lla -> [lla.lat, lla.lon], map(x->inv(tx)(ENU(x..., 0)), bounds))
            poly = LibGEOS.Polygon([coords])
        end
    end
    vertices = map(x->GeoCoord(map(deg2rad, x)...), coordinates(boundary(poly)))
    fence = Geofence(length(vertices), pointer(vertices))
    h3poly = GeoPolygon(fence, 0, C_NULL)

    n = Lib.maxPolyfillSize(Ref(h3poly), resolution)
    p_hexagons = Libc.calloc(n, sizeof(H3Index))
    Lib.polyfill(Ref(h3poly), resolution, p_hexagons)
    p = Base.unsafe_convert(Ptr{H3Index}, p_hexagons)
    hexagons = unsafe_wrap(Vector{H3Index}, p, n)

    return filter(!iszero, hexagons)
end

end # module


text:
using DrawSimpleGraphs, SimpleGraphs
using Test
G =  Cycle(12)
draw(G)
@test 1==1


text:
using HilbertSpaces
using Base.Test

S = AngularSpace

i = bra(S)(1, 0)
j = ket(S)(0, 0)
C = op(S)(1, 0)

@testset "Init" begin
    @test_throws ErrorException bra(S)(1, 2)
    @test_throws ErrorException ket(S)(1, 2)
    @test_throws ErrorException op(S)(1, 2)
end

@testset "Comparisons" begin
    @test i == HilbertSpaces.AngKet(1, 0)'
    @test j == HilbertSpaces.AngBra(0, 0)'
    @test C == HilbertSpaces.SphericalTensor(1, 0)
end

@testset "Spaces" begin
    @test space(i) == AngularSpace
    @test space(j) == AngularSpace
    @test space(C) == AngularSpace
    @test op(i) == HilbertSpaces.SphericalTensor
    @test op(j) == HilbertSpaces.SphericalTensor
    @test op(HilbertSpaces.AngularSpace) == HilbertSpaces.SphericalTensor
    @test identity(op(i)) == op(S)(0, 0)
    @test bra(HilbertSpaces.AngularSpace) == HilbertSpaces.AngBra
    @test ket(HilbertSpaces.AngularSpace) == HilbertSpaces.AngKet
    @test scalar(i) == HilbertSpaces.AngScalar
    @test scalar(j) == HilbertSpaces.AngScalar
    @test scalar(HilbertSpaces.AngularSpace) == HilbertSpaces.AngScalar
end

@testset "Products" begin
    @test typeof(i|j) == scalar(S)
    @test typeof((i|C)|j) == scalar(S)
    @test typeof(i|(C|j)) == scalar(S)
    @test float(i|j) == 0
    @test float(i|i') ≈ 1
    @test float(j'|j) ≈ 1

    @test_throws ErrorException C|i
    @test_throws ErrorException j|C
end

@testset "Scalars" begin
    v = (i|C|j)
    @test float(v) ≈ 1/√3
end

@testset "Strings" begin
    @test string(bra(S)(0,0)) == "〈s|"
    @test string(bra(S)(1,0)) == "〈p|"
    @test string(bra(S)(1,1)) == "〈p⁺|"
    @test string(bra(S)(1,-1)) == "〈p⁻|"
    @test string(bra(S)(3,-2)) == "〈f;-2|"
    @test string(bra(S)(3//2, -1//2)) == "〈³⁄₂;⁻¹⁄₂|"
    @test string(ket(S)(1,0)) == "|p〉"

    @test string(op(S)(2,1)) == "C₁⁽²⁾"

    @test string(C|j) == "C₀⁽¹⁾|s〉"
    @test string(i|C) == "〈p|C₀⁽¹⁾"
    @test string(i|C|j) == @sprintf("〈p|C₀⁽¹⁾|s〉 ≈ %0.5g", float(i|C|j))
    @test string(i|j) == "〈p|s〉 ≈ 0"
    @test string(i|i') == "〈p|p〉 ≈ 1"
end