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

Cancels a cluster job. You can only cancel a cluster job while it's in the AwaitingQuorum status. You'll have at least an hour after creating a cluster job to cancel it.

# Required Parameters
- `ClusterId`: The 39-character ID for the cluster that you want to cancel, for example CID123e4567-e89b-12d3-a456-426655440000.

"""

cancel_cluster(ClusterId; aws_config::AWSConfig=global_aws_config()) = snowball("CancelCluster", Dict{String, Any}("ClusterId"=>ClusterId); aws_config=aws_config)
cancel_cluster(ClusterId, args::AbstractDict{String, <:Any}; aws_config::AWSConfig=global_aws_config()) = snowball("CancelCluster", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("ClusterId"=>ClusterId), args)); aws_config=aws_config)

"""
    CancelJob()

Cancels the specified job. You can only cancel a job before its JobState value changes to PreparingAppliance. Requesting the ListJobs or DescribeJob action returns a job's JobState as part of the response element data returned.

# Required Parameters
- `JobId`: The 39-character job ID for the job that you want to cancel, for example JID123e4567-e89b-12d3-a456-426655440000.

"""

cancel_job(JobId; aws_config::AWSConfig=global_aws_config()) = snowball("CancelJob", Dict{String, Any}("JobId"=>JobId); aws_config=aws_config)
cancel_job(JobId, args::AbstractDict{String, <:Any}; aws_config::AWSConfig=global_aws_config()) = snowball("CancelJob", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("JobId"=>JobId), args)); aws_config=aws_config)

"""
    CreateAddress()

Creates an address for a Snow device to be shipped to. In most regions, addresses are validated at the time of creation. The address you provide must be located within the serviceable area of your region. If the address is invalid or unsupported, then an exception is thrown.

# Required Parameters
- `Address`: The address that you want the Snow device shipped to.

"""

create_address(Address; aws_config::AWSConfig=global_aws_config()) = snowball("CreateAddress", Dict{String, Any}("Address"=>Address); aws_config=aws_config)
create_address(Address, args::AbstractDict{String, <:Any}; aws_config::AWSConfig=global_aws_config()) = snowball("CreateAddress", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("Address"=>Address), args)); aws_config=aws_config)

"""
    CreateCluster()

Creates an empty cluster. Each cluster supports five nodes. You use the CreateJob action separately to create the jobs for each of these nodes. The cluster does not ship until these five node jobs have been created.

# Required Parameters
- `AddressId`: The ID for the address that you want the cluster shipped to.
- `JobType`: The type of job for this cluster. Currently, the only job type supported for clusters is LOCAL_USE.
- `Resources`: The resources associated with the cluster job. These resources include Amazon S3 buckets and optional AWS Lambda functions written in the Python language.
- `RoleARN`: The RoleARN that you want to associate with this cluster. RoleArn values are created by using the CreateRole API action in AWS Identity and Access Management (IAM).
- `ShippingOption`: The shipping speed for each node in this cluster. This speed doesn't dictate how soon you'll get each Snowball Edge device, rather it represents how quickly each device moves to its destination while in transit. Regional shipping speeds are as follows:    In Australia, you have access to express shipping. Typically, Snow devices shipped express are delivered in about a day.   In the European Union (EU), you have access to express shipping. Typically, Snow devices shipped express are delivered in about a day. In addition, most countries in the EU have access to standard shipping, which typically takes less than a week, one way.   In India, Snow device are delivered in one to seven days.   In the United States of America (US), you have access to one-day shipping and two-day shipping.     In Australia, you have access to express shipping. Typically, devices shipped express are delivered in about a day.   In the European Union (EU), you have access to express shipping. Typically, Snow devices shipped express are delivered in about a day. In addition, most countries in the EU have access to standard shipping, which typically takes less than a week, one way.   In India, Snow device are delivered in one to seven days.   In the US, you have access to one-day shipping and two-day shipping.

# Optional Parameters
- `Description`: An optional description of this specific cluster, for example Environmental Data Cluster-01.
- `ForwardingAddressId`: The forwarding address ID for a cluster. This field is not supported in most regions.
- `KmsKeyARN`: The KmsKeyARN value that you want to associate with this cluster. KmsKeyARN values are created by using the CreateKey API action in AWS Key Management Service (AWS KMS).
- `Notification`: The Amazon Simple Notification Service (Amazon SNS) notification settings for this cluster.
- `SnowballType`: The type of AWS Snow Family device to use for this cluster.   For cluster jobs, AWS Snow Family currently supports only the EDGE device type.
- `TaxDocuments`: The tax documents required in your AWS Region.
"""

create_cluster(AddressId, JobType, Resources, RoleARN, ShippingOption; aws_config::AWSConfig=global_aws_config()) = snowball("CreateCluster", Dict{String, Any}("AddressId"=>AddressId, "JobType"=>JobType, "Resources"=>Resources, "RoleARN"=>RoleARN, "ShippingOption"=>ShippingOption); aws_config=aws_config)
create_cluster(AddressId, JobType, Resources, RoleARN, ShippingOption, args::AbstractDict{String, <:Any}; aws_config::AWSConfig=global_aws_config()) = snowball("CreateCluster", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("AddressId"=>AddressId, "JobType"=>JobType, "Resources"=>Resources, "RoleARN"=>RoleARN, "ShippingOption"=>ShippingOption), args)); aws_config=aws_config)

"""
    CreateJob()

Creates a job to import or export data between Amazon S3 and your on-premises data center. Your AWS account must have the right trust policies and permissions in place to create a job for a Snow device. If you're creating a job for a node in a cluster, you only need to provide the clusterId value; the other job attributes are inherited from the cluster.

# Optional Parameters
- `AddressId`: The ID for the address that you want the Snow device shipped to.
- `ClusterId`: The ID of a cluster. If you're creating a job for a node in a cluster, you need to provide only this clusterId value. The other job attributes are inherited from the cluster.
- `Description`: Defines an optional description of this specific job, for example Important Photos 2016-08-11.
- `DeviceConfiguration`: Defines the device configuration for an AWS Snowcone job.
- `ForwardingAddressId`: The forwarding address ID for a job. This field is not supported in most regions.
- `JobType`: Defines the type of job that you're creating.
- `KmsKeyARN`: The KmsKeyARN that you want to associate with this job. KmsKeyARNs are created using the CreateKey AWS Key Management Service (KMS) API action.
- `Notification`: Defines the Amazon Simple Notification Service (Amazon SNS) notification settings for this job.
- `Resources`: Defines the Amazon S3 buckets associated with this job. With IMPORT jobs, you specify the bucket or buckets that your transferred data will be imported into. With EXPORT jobs, you specify the bucket or buckets that your transferred data will be exported from. Optionally, you can also specify a KeyRange value. If you choose to export a range, you define the length of the range by providing either an inclusive BeginMarker value, an inclusive EndMarker value, or both. Ranges are UTF-8 binary sorted.
- `RoleARN`: The RoleARN that you want to associate with this job. RoleArns are created using the CreateRole AWS Identity and Access Management (IAM) API action.
- `ShippingOption`: The shipping speed for this job. This speed doesn't dictate how soon you'll get the Snow device, rather it represents how quickly the Snow device moves to its destination while in transit. Regional shipping speeds are as follows:   In Australia, you have access to express shipping. Typically, Snow devices shipped express are delivered in about a day.   In the European Union (EU), you have access to express shipping. Typically, Snow devices shipped express are delivered in about a day. In addition, most countries in the EU have access to standard shipping, which typically takes less than a week, one way.   In India, Snow devices are delivered in one to seven days.   In the US, you have access to one-day shipping and two-day shipping.
- `SnowballCapacityPreference`: If your job is being created in one of the US regions, you have the option of specifying what size Snow device you'd like for this job. In all other regions, Snowballs come with 80 TB in storage capacity.
- `SnowballType`: The type of AWS Snow Family device to use for this job.   For cluster jobs, AWS Snow Family currently supports only the EDGE device type.  The type of AWS Snow device to use for this job. Currently, the only supported device type for cluster jobs is EDGE. For more information, see Snowball Edge Device Options in the Snowball Edge Developer Guide.
- `TaxDocuments`: The tax documents required in your AWS Region.
"""

create_job(; aws_config::AWSConfig=global_aws_config()) = snowball("CreateJob"; aws_config=aws_config)
create_job(args::AbstractDict{String, <:Any}; aws_config::AWSConfig=global_aws_config()) = snowball("CreateJob", args; aws_config=aws_config)

"""
    CreateReturnShippingLabel()

Creates a shipping label that will be used to return the Snow device to AWS.

# Required Parameters
- `JobId`: The ID for a job that you want to create the return shipping label for. For example JID123e4567-e89b-12d3-a456-426655440000.

# Optional Parameters
- `ShippingOption`: The shipping speed for a particular job. This speed doesn't dictate how soon the device is returned to AWS. This speed represents how quickly it moves to its destination while in transit. Regional shipping speeds are as follows:
"""

create_return_shipping_label(JobId; aws_config::AWSConfig=global_aws_config()) = snowball("CreateReturnShippingLabel", Dict{String, Any}("JobId"=>JobId); aws_config=aws_config)
create_return_shipping_label(JobId, args::AbstractDict{String, <:Any}; aws_config::AWSConfig=global_aws_config()) = snowball("CreateReturnShippingLabel", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("JobId"=>JobId), args)); aws_config=aws_config)

"""
    DescribeAddress()

Takes an AddressId and returns specific details about that address in the form of an Address object.

# Required Parameters
- `AddressId`: The automatically generated ID for a specific address.

"""

describe_address(AddressId; aws_config::AWSConfig=global_aws_config()) = snowball("DescribeAddress", Dict{String, Any}("AddressId"=>AddressId); aws_config=aws_config)
describe_address(AddressId, args::AbstractDict{String, <:Any}; aws_config::AWSConfig=global_aws_config()) = snowball("DescribeAddress", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("AddressId"=>AddressId), args)); aws_config=aws_config)

"""
    DescribeAddresses()

Returns a specified number of ADDRESS objects. Calling this API in one of the US regions will return addresses from the list of all addresses associated with this account in all US regions.

# Optional Parameters
- `MaxResults`: The number of ADDRESS objects to return.
- `NextToken`: HTTP requests are stateless. To identify what object comes \"next\" in the list of ADDRESS objects, you have the option of specifying a value for NextToken as the starting point for your list of returned addresses.
"""

describe_addresses(; aws_config::AWSConfig=global_aws_config()) = snowball("DescribeAddresses"; aws_config=aws_config)
describe_addresses(args::AbstractDict{String, <:Any}; aws_config::AWSConfig=global_aws_config()) = snowball("DescribeAddresses", args; aws_config=aws_config)

"""
    DescribeCluster()

Returns information about a specific cluster including shipping information, cluster status, and other important metadata.

# Required Parameters
- `ClusterId`: The automatically generated ID for a cluster.

"""

describe_cluster(ClusterId; aws_config::AWSConfig=global_aws_config()) = snowball("DescribeCluster", Dict{String, Any}("ClusterId"=>ClusterId); aws_config=aws_config)
describe_cluster(ClusterId, args::AbstractDict{String, <:Any}; aws_config::AWSConfig=global_aws_config()) = snowball("DescribeCluster", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("ClusterId"=>ClusterId), args)); aws_config=aws_config)

"""
    DescribeJob()

Returns information about a specific job including shipping information, job status, and other important metadata.

# Required Parameters
- `JobId`: The automatically generated ID for a job, for example JID123e4567-e89b-12d3-a456-426655440000.

"""

describe_job(JobId; aws_config::AWSConfig=global_aws_config()) = snowball("DescribeJob", Dict{String, Any}("JobId"=>JobId); aws_config=aws_config)
describe_job(JobId, args::AbstractDict{String, <:Any}; aws_config::AWSConfig=global_aws_config()) = snowball("DescribeJob", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("JobId"=>JobId), args)); aws_config=aws_config)

"""
    DescribeReturnShippingLabel()

Information on the shipping label of a Snow device that is being returned to AWS.

# Optional Parameters
- `JobId`: The automatically generated ID for a job, for example JID123e4567-e89b-12d3-a456-426655440000.
"""

describe_return_shipping_label(; aws_config::AWSConfig=global_aws_config()) = snowball("DescribeReturnShippingLabel"; aws_config=aws_config)
describe_return_shipping_label(args::AbstractDict{String, <:Any}; aws_config::AWSConfig=global_aws_config()) = snowball("DescribeReturnShippingLabel", args; aws_config=aws_config)

"""
    GetJobManifest()

Returns a link to an Amazon S3 presigned URL for the manifest file associated with the specified JobId value. You can access the manifest file for up to 60 minutes after this request has been made. To access the manifest file after 60 minutes have passed, you'll have to make another call to the GetJobManifest action. The manifest is an encrypted file that you can download after your job enters the WithCustomer status. The manifest is decrypted by using the UnlockCode code value, when you pass both values to the Snow device through the Snowball client when the client is started for the first time. As a best practice, we recommend that you don't save a copy of an UnlockCode value in the same location as the manifest file for that job. Saving these separately helps prevent unauthorized parties from gaining access to the Snow device associated with that job. The credentials of a given job, including its manifest file and unlock code, expire 90 days after the job is created.

# Required Parameters
- `JobId`: The ID for a job that you want to get the manifest file for, for example JID123e4567-e89b-12d3-a456-426655440000.

"""

get_job_manifest(JobId; aws_config::AWSConfig=global_aws_config()) = snowball("GetJobManifest", Dict{String, Any}("JobId"=>JobId); aws_config=aws_config)
get_job_manifest(JobId, args::AbstractDict{String, <:Any}; aws_config::AWSConfig=global_aws_config()) = snowball("GetJobManifest", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("JobId"=>JobId), args)); aws_config=aws_config)

"""
    GetJobUnlockCode()

Returns the UnlockCode code value for the specified job. A particular UnlockCode value can be accessed for up to 90 days after the associated job has been created. The UnlockCode value is a 29-character code with 25 alphanumeric characters and 4 hyphens. This code is used to decrypt the manifest file when it is passed along with the manifest to the Snow device through the Snowball client when the client is started for the first time. As a best practice, we recommend that you don't save a copy of the UnlockCode in the same location as the manifest file for that job. Saving these separately helps prevent unauthorized parties from gaining access to the Snow device associated with that job.

# Required Parameters
- `JobId`: The ID for the job that you want to get the UnlockCode value for, for example JID123e4567-e89b-12d3-a456-426655440000.

"""

get_job_unlock_code(JobId; aws_config::AWSConfig=global_aws_config()) = snowball("GetJobUnlockCode", Dict{String, Any}("JobId"=>JobId); aws_config=aws_config)
get_job_unlock_code(JobId, args::AbstractDict{String, <:Any}; aws_config::AWSConfig=global_aws_config()) = snowball("GetJobUnlockCode", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("JobId"=>JobId), args)); aws_config=aws_config)

"""
    GetSnowballUsage()

Returns information about the Snow Family service limit for your account, and also the number of Snow devices your account has in use. The default service limit for the number of Snow devices that you can have at one time is 1. If you want to increase your service limit, contact AWS Support.

"""

get_snowball_usage(; aws_config::AWSConfig=global_aws_config()) = snowball("GetSnowballUsage"; aws_config=aws_config)
get_snowball_usage(args::AbstractDict{String, <:Any}; aws_config::AWSConfig=global_aws_config()) = snowball("GetSnowballUsage", args; aws_config=aws_config)

"""
    GetSoftwareUpdates()

Returns an Amazon S3 presigned URL for an update file associated with a specified JobId.

# Required Parameters
- `JobId`: The ID for a job that you want to get the software update file for, for example JID123e4567-e89b-12d3-a456-426655440000.

"""

get_software_updates(JobId; aws_config::AWSConfig=global_aws_config()) = snowball("GetSoftwareUpdates", Dict{String, Any}("JobId"=>JobId); aws_config=aws_config)
get_software_updates(JobId, args::AbstractDict{String, <:Any}; aws_config::AWSConfig=global_aws_config()) = snowball("GetSoftwareUpdates", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("JobId"=>JobId), args)); aws_config=aws_config)

"""
    ListClusterJobs()

Returns an array of JobListEntry objects of the specified length. Each JobListEntry object is for a job in the specified cluster and contains a job's state, a job's ID, and other information.

# Required Parameters
- `ClusterId`: The 39-character ID for the cluster that you want to list, for example CID123e4567-e89b-12d3-a456-426655440000.

# Optional Parameters
- `MaxResults`: The number of JobListEntry objects to return.
- `NextToken`: HTTP requests are stateless. To identify what object comes \"next\" in the list of JobListEntry objects, you have the option of specifying NextToken as the starting point for your returned list.
"""

list_cluster_jobs(ClusterId; aws_config::AWSConfig=global_aws_config()) = snowball("ListClusterJobs", Dict{String, Any}("ClusterId"=>ClusterId); aws_config=aws_config)
list_cluster_jobs(ClusterId, args::AbstractDict{String, <:Any}; aws_config::AWSConfig=global_aws_config()) = snowball("ListClusterJobs", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("ClusterId"=>ClusterId), args)); aws_config=aws_config)

"""
    ListClusters()

Returns an array of ClusterListEntry objects of the specified length. Each ClusterListEntry object contains a cluster's state, a cluster's ID, and other important status information.

# Optional Parameters
- `MaxResults`: The number of ClusterListEntry objects to return.
- `NextToken`: HTTP requests are stateless. To identify what object comes \"next\" in the list of ClusterListEntry objects, you have the option of specifying NextToken as the starting point for your returned list.
"""

list_clusters(; aws_config::AWSConfig=global_aws_config()) = snowball("ListClusters"; aws_config=aws_config)
list_clusters(args::AbstractDict{String, <:Any}; aws_config::AWSConfig=global_aws_config()) = snowball("ListClusters", args; aws_config=aws_config)

"""
    ListCompatibleImages()

This action returns a list of the different Amazon EC2 Amazon Machine Images (AMIs) that are owned by your AWS account that would be supported for use on a Snow device. Currently, supported AMIs are based on the CentOS 7 (x86_64) - with Updates HVM, Ubuntu Server 14.04 LTS (HVM), and Ubuntu 16.04 LTS - Xenial (HVM) images, available on the AWS Marketplace.

# Optional Parameters
- `MaxResults`: The maximum number of results for the list of compatible images. Currently, a Snowball Edge device can store 10 AMIs.
- `NextToken`: HTTP requests are stateless. To identify what object comes \"next\" in the list of compatible images, you can specify a value for NextToken as the starting point for your list of returned images.
"""

list_compatible_images(; aws_config::AWSConfig=global_aws_config()) = snowball("ListCompatibleImages"; aws_config=aws_config)
list_compatible_images(args::AbstractDict{String, <:Any}; aws_config::AWSConfig=global_aws_config()) = snowball("ListCompatibleImages", args; aws_config=aws_config)

"""
    ListJobs()

Returns an array of JobListEntry objects of the specified length. Each JobListEntry object contains a job's state, a job's ID, and a value that indicates whether the job is a job part, in the case of export jobs. Calling this API action in one of the US regions will return jobs from the list of all jobs associated with this account in all US regions.

# Optional Parameters
- `MaxResults`: The number of JobListEntry objects to return.
- `NextToken`: HTTP requests are stateless. To identify what object comes \"next\" in the list of JobListEntry objects, you have the option of specifying NextToken as the starting point for your returned list.
"""

list_jobs(; aws_config::AWSConfig=global_aws_config()) = snowball("ListJobs"; aws_config=aws_config)
list_jobs(args::AbstractDict{String, <:Any}; aws_config::AWSConfig=global_aws_config()) = snowball("ListJobs", args; aws_config=aws_config)

"""
    UpdateCluster()

While a cluster's ClusterState value is in the AwaitingQuorum state, you can update some of the information associated with a cluster. Once the cluster changes to a different job state, usually 60 minutes after the cluster being created, this action is no longer available.

# Required Parameters
- `ClusterId`: The cluster ID of the cluster that you want to update, for example CID123e4567-e89b-12d3-a456-426655440000.

# Optional Parameters
- `AddressId`: The ID of the updated Address object.
- `Description`: The updated description of this cluster.
- `ForwardingAddressId`: The updated ID for the forwarding address for a cluster. This field is not supported in most regions.
- `Notification`: The new or updated Notification object.
- `Resources`: The updated arrays of JobResource objects that can include updated S3Resource objects or LambdaResource objects.
- `RoleARN`: The new role Amazon Resource Name (ARN) that you want to associate with this cluster. To create a role ARN, use the CreateRole API action in AWS Identity and Access Management (IAM).
- `ShippingOption`: The updated shipping option value of this cluster's ShippingDetails object.
"""

update_cluster(ClusterId; aws_config::AWSConfig=global_aws_config()) = snowball("UpdateCluster", Dict{String, Any}("ClusterId"=>ClusterId); aws_config=aws_config)
update_cluster(ClusterId, args::AbstractDict{String, <:Any}; aws_config::AWSConfig=global_aws_config()) = snowball("UpdateCluster", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("ClusterId"=>ClusterId), args)); aws_config=aws_config)

"""
    UpdateJob()

While a job's JobState value is New, you can update some of the information associated with a job. Once the job changes to a different job state, usually within 60 minutes of the job being created, this action is no longer available.

# Required Parameters
- `JobId`: The job ID of the job that you want to update, for example JID123e4567-e89b-12d3-a456-426655440000.

# Optional Parameters
- `AddressId`: The ID of the updated Address object.
- `Description`: The updated description of this job's JobMetadata object.
- `ForwardingAddressId`: The updated ID for the forwarding address for a job. This field is not supported in most regions.
- `Notification`: The new or updated Notification object.
- `Resources`: The updated JobResource object, or the updated JobResource object.
- `RoleARN`: The new role Amazon Resource Name (ARN) that you want to associate with this job. To create a role ARN, use the CreateRoleAWS Identity and Access Management (IAM) API action.
- `ShippingOption`: The updated shipping option value of this job's ShippingDetails object.
- `SnowballCapacityPreference`: The updated SnowballCapacityPreference of this job's JobMetadata object. The 50 TB Snowballs are only available in the US regions.
"""

update_job(JobId; aws_config::AWSConfig=global_aws_config()) = snowball("UpdateJob", Dict{String, Any}("JobId"=>JobId); aws_config=aws_config)
update_job(JobId, args::AbstractDict{String, <:Any}; aws_config::AWSConfig=global_aws_config()) = snowball("UpdateJob", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("JobId"=>JobId), args)); aws_config=aws_config)

"""
    UpdateJobShipmentState()

Updates the state when a the shipment states changes to a different state.

# Required Parameters
- `JobId`: The job ID of the job whose shipment date you want to update, for example JID123e4567-e89b-12d3-a456-426655440000.
- `ShipmentState`: The state of a device when it is being shipped.  Set to RECEIVED when the device arrives at your location. Set to RETURNED when you have returned the device to AWS.

"""

update_job_shipment_state(JobId, ShipmentState; aws_config::AWSConfig=global_aws_config()) = snowball("UpdateJobShipmentState", Dict{String, Any}("JobId"=>JobId, "ShipmentState"=>ShipmentState); aws_config=aws_config)
update_job_shipment_state(JobId, ShipmentState, args::AbstractDict{String, <:Any}; aws_config::AWSConfig=global_aws_config()) = snowball("UpdateJobShipmentState", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("JobId"=>JobId, "ShipmentState"=>ShipmentState), args)); aws_config=aws_config)


text:
push!(LOAD_PATH, joinpath("..", "src"))

using Documenter, PkgTPL

DocMeta.setdocmeta!(PkgTPL, :DocTestSetup, :(using PkgTPL), recursive = true)

makedocs(sitename = "PkgTPL",
         modules = [PkgTPL],
         pages = [
             "Home" => "index.md",
             "API" => "api.md"
         ]
)

deploydocs(
    repo   = "github.com/Nosferican/PkgTPL.jl.git",
    push_preview = true
)

ext: jl


text:
# ------------------------------------------------------------------
# Copyright (c) 2017, Júlio Hoffimann Mendes <juliohm@stanford.edu>
# Licensed under the ISC License. See LICENCE in the project root.
# ------------------------------------------------------------------

"""
    VisualComparison([plot options])

Compare solvers by plotting the results side by side.

## Examples

```julia
julia> compare([solver₁, solver₂], problem, VisualComparison())
```
"""
struct VisualComparison <: AbstractSolverComparison
  plotspecs
end

VisualComparison(; plotspecs...) = VisualComparison(plotspecs)

function compare(solvers::AbstractVector{S}, problem::AbstractProblem,
                 cmp::VisualComparison) where {S<:AbstractSolver}

  # check if Plots.jl is loaded
  isdefined(Main, :Plots) || error("Please load Plots.jl for visual comparison")

  if nworkers() > 1
    # run solvers in parallel
    λ = solver -> solve(problem, solver)
    solutions = pmap(λ, solvers)
  else
    # fallback to serial execution
    solutions = [solve(problem, solver) for solver in solvers]
  end

  # TODO: pass plot specs to recipe
  plts = [RecipesBase.plot(solution) for solution in solutions]

  RecipesBase.plot(plts..., layout=(length(solvers),1))
end


text:
module MaxHelpingHandReskinnableBillboard

using ..Ahorn, Maple

@mapdef Entity "MaxHelpingHand/ReskinnableBillboard" ReskinnableBillboard(x::Integer, y::Integer, width::Integer=Maple.defaultBlockWidth, height::Integer=Maple.defaultBlockHeight,
    borderTexture::String="scenery/tvSlices", renderBloom::Bool=true)

const placements = Ahorn.PlacementDict(
    "Reskinnable Playback Billboard (max480's Helping Hand)" => Ahorn.EntityPlacement(
        ReskinnableBillboard,
        "rectangle"
    ),
)

function getBillboardRectangles(room::Maple.Room)
    entities = filter(e -> e.name == "MaxHelpingHand/ReskinnableBillboard", room.entities)
    rects = Ahorn.Rectangle[
        Ahorn.Rectangle(
            Int(get(e.data, "x", 0)),
            Int(get(e.data, "y", 0)),
            Int(get(e.data, "width", 8)),
            Int(get(e.data, "height", 8))
        ) for e in entities
    ]

    return rects
end

function noAdjacent(entity::ReskinnableBillboard, ox::Integer, oy::Integer, rects::Array{Ahorn.Rectangle, 1})
    x, y = Ahorn.position(entity)

    rect = Ahorn.Rectangle(x + ox * 8, y + oy * 8, 8, 8)

    return !any(Ahorn.checkCollision.(rects, Ref(rect)))
end

fillColor = (0.17, 0.14, 0.33, 1.0)

function renderTileQuad(ctx::Ahorn.Cairo.CairoContext, sprite::Ahorn.Sprite, x::Integer, y::Integer, ix::Integer, iy::Integer)
    w, h = 8, 8
    ox, oy = sprite.offsetX, sprite.offsetY
    dx, dy = x * 8, y * 8
    ix += ox
    iy += oy

    if ix < 0
        w += ox
        dx -= ox
        ix = 0
    end

    if iy < 0
        h += oy
        dy -= oy
        iy = 0
    end

    Ahorn.drawImage(ctx, sprite, dx, dy, ix, iy, w, h)
end

function renderTile(ctx::Ahorn.Cairo.CairoContext, entity::ReskinnableBillboard, x::Integer, y::Integer, rects::Array{Ahorn.Rectangle, 1}, sprite::Ahorn.Sprite)
    if noAdjacent(entity, x, y, rects)
        centerLeft = !noAdjacent(entity, x - 1, y, rects)
        centerRight = !noAdjacent(entity, x + 1, y, rects)
        topCenter = !noAdjacent(entity, x, y - 1, rects)
        bottomCenter = !noAdjacent(entity, x, y + 1, rects)
        topLeft = !noAdjacent(entity, x - 1, y - 1, rects)
        topRight = !noAdjacent(entity, x + 1, y - 1, rects)
        bottomLeft = !noAdjacent(entity, x - 1, y + 1, rects)
        bottomRight = !noAdjacent(entity, x + 1, y + 1, rects)

        if (!centerRight && !bottomCenter) && bottomRight
            renderTileQuad(ctx, sprite, x, y, 0, 0)

        elseif (!centerLeft && !bottomCenter) && bottomLeft
            renderTileQuad(ctx, sprite, x, y, 16, 0)

        elseif (!topCenter && !centerRight) && topRight
            renderTileQuad(ctx, sprite, x, y, 0, 16)

        elseif (!topCenter && !centerLeft) && topLeft
            renderTileQuad(ctx, sprite, x, y, 16, 16)

        elseif centerRight && bottomCenter
            renderTileQuad(ctx, sprite, x, y, 24, 0)

        elseif centerLeft && bottomCenter
            renderTileQuad(ctx, sprite, x, y, 32, 0)

        elseif centerRight && topCenter
            renderTileQuad(ctx, sprite, x, y, 24, 16)

        elseif centerLeft && topCenter
            renderTileQuad(ctx, sprite, x, y, 32, 16)

        elseif bottomCenter
            renderTileQuad(ctx, sprite, x, y, 8, 0)

        elseif centerRight
            renderTileQuad(ctx, sprite, x, y, 0, 8)

        elseif centerLeft
            renderTileQuad(ctx, sprite, x, y, 16, 8)

        elseif topCenter
            renderTileQuad(ctx, sprite, x, y, 8, 16)
        end
    end
end

function renderPlaybackBillboard(ctx::Ahorn.Cairo.CairoContext, entity::ReskinnableBillboard, room::Maple.Room)
    tvSlicesSprite = Ahorn.getSprite(get(entity, "borderTexture", "scenery/tvSlices"), "Gameplay")
    billboardRectangles = getBillboardRectangles(room)
    rng = Ahorn.getSimpleEntityRng(entity)

    x, y = Ahorn.position(entity)

    width = Int(get(entity.data, "width", 32))
    height = Int(get(entity.data, "height", 32))

    tilesWidth = div(width, 8)
    tilesHeight = div(height, 8)

    Ahorn.drawRectangle(ctx, 0, 0, width, height, fillColor)

    for i in -1:tilesWidth
        renderTile(ctx, entity, i, -1, billboardRectangles, tvSlicesSprite)
        renderTile(ctx, entity, i, tilesHeight, billboardRectangles, tvSlicesSprite)
    end

    for j in 0:tilesHeight - 1
        renderTile(ctx, entity, -1, j, billboardRectangles, tvSlicesSprite)
        renderTile(ctx, entity, tilesWidth, j, billboardRectangles, tvSlicesSprite)
    end
end

Ahorn.minimumSize(entity::ReskinnableBillboard) = 16, 16
Ahorn.resizable(entity::ReskinnableBillboard) = true, true

Ahorn.selection(entity::ReskinnableBillboard) = Ahorn.getEntityRectangle(entity)

function Ahorn.render(ctx::Ahorn.Cairo.CairoContext, entity::ReskinnableBillboard, room::Maple.Room)
    renderPlaybackBillboard(ctx, entity, room)
end

end

max_stars_repo_name: Kitty-Cats/MaxHelpingHand

max_issues_repo_name: Kitty-Cats/MaxHelpingHand

max_forks_repo_name: Kitty-Cats/MaxHelpingHand


text:
export simulate_bb

using Game2048
using Game2048:move_updown, Bitboard

"""
Makes a random move. It does not add a new tile
"""
function randmove(bitboard::Bitboard)::Bitboard
    dirs = collect(DIRS)

    for i in 4:-1:1
        j = rand(1:i)
        dir = dirs[j]
        new_bitboard = move(bitboard, dir)
        if new_bitboard != bitboard
            return new_bitboard
        end
        dirs[j] = dirs[i]
    end
    return bitboard
end

function simulate_bb(bitboard::Bitboard=initbboard())
    while true
        new_bitboard = randmove(bitboard)
        if new_bitboard != bitboard
            bitboard = add_tile(new_bitboard)
            if bitboard == new_bitboard
                error("wtf")
            end
        else
            # lost
            return bitboard
        end
    end
end


text:
# Copyright (C) 2020 Argonne National Laboratory
# Written by Alinson Santos Xavier <axavier@anl.gov>

using RELOG, Cbc, JuMP, Printf, JSON, MathOptInterface.FileFormats

basedir = dirname(@__FILE__)

@testset "solve (exact)" begin
    solution_filename_a = tempname()
    solution_filename_b = tempname()
    solution = RELOG.solve("$basedir/../../instances/s1.json", output = solution_filename_a)

    @test isfile(solution_filename_a)

    RELOG.write(solution, solution_filename_b)
    @test isfile(solution_filename_b)

    @test "Costs" in keys(solution)
    @test "Fixed operating (\$)" in keys(solution["Costs"])
    @test "Transportation (\$)" in keys(solution["Costs"])
    @test "Variable operating (\$)" in keys(solution["Costs"])
    @test "Total (\$)" in keys(solution["Costs"])

    @test "Plants" in keys(solution)
    @test "F1" in keys(solution["Plants"])
    @test "F2" in keys(solution["Plants"])
    @test "F3" in keys(solution["Plants"])
    @test "F4" in keys(solution["Plants"])
end

@testset "solve (heuristic)" begin
    # Should not crash
    solution = RELOG.solve("$basedir/../../instances/s1.json", heuristic = true)
end

@testset "solve (infeasible)" begin
    json = JSON.parsefile("$basedir/../../instances/s1.json")
    for (location_name, location_dict) in json["products"]["P1"]["initial amounts"]
        location_dict["amount (tonne)"] *= 1000
    end
    @test_throws ErrorException("No solution available") RELOG.solve(RELOG.parse(json))
end

@testset "solve (with storage)" begin
    basedir = dirname(@__FILE__)
    filename = "$basedir/../fixtures/storage.json"
    instance = RELOG.parsefile(filename)
    @test instance.plants[1].storage_limit == 50.0
    @test instance.plants[1].storage_cost == [2.0, 1.5, 1.0]

    solution = RELOG.solve(filename)
    plant_dict = solution["Plants"]["mega plant"]["Chicago"]
    @test plant_dict["Variable operating cost (\$)"] == [500.0, 0.0, 100.0]
    @test plant_dict["Process (tonne)"] == [50.0, 0.0, 50.0]
    @test plant_dict["Storage (tonne)"] == [50.0, 50.0, 0.0]
    @test plant_dict["Storage cost (\$)"] == [100.0, 75.0, 0.0]

    @test solution["Costs"]["Variable operating (\$)"] == [500.0, 0.0, 100.0]
    @test solution["Costs"]["Storage (\$)"] == [100.0, 75.0, 0.0]
    @test solution["Costs"]["Total (\$)"] == [600.0, 75.0, 100.0]
end

max_stars_repo_name: ANL-CEEESA/RELOG

max_issues_repo_name: ANL-CEEESA/RELOG

max_forks_repo_name: ANL-CEEESA/RELOG


text:
"""
    RandomPolicy(num_actions::Int64)

Construct a policy that chooses actions at random.
"""
struct RandomPolicy <: AbstractPolicy
    num_actions::Int64
    function RandomPolicy(num_actions::Int64)
        return if num_actions > 0
            new(num_actions)
        else
            throw(ArgumentError("num_actions must be positive"))
        end
    end
end

function (pol::RandomPolicy)(X::AbstractMatrix)
    n = size(X, 2)
    return rand(1:(pol.num_actions), n)
end

function update!(pol::RandomPolicy, X::AbstractMatrix, a::AbstractVector{<:Int}, r::AbstractMatrix) end


text:
module multiobjectiveGraphs

# include("MultiObjectives/objectives.jl")
include("MultiObjectives/multiObjectives.jl")

export greet
greet() = println("######  module multiobjectiveGraphs ready   #######")

end # module

max_stars_repo_name: DRobbes/multiobjectiveGraphs

max_issues_repo_name: DRobbes/multiobjectiveGraphs

max_forks_repo_name: DRobbes/multiobjectiveGraphs


text:
using ArcadeLearningEnvironment
using CGP
using Logging
using Images

function play_qbert()
    game = Game("qbert", 0)
    reward = 0
    frames = 0
    while ~game_over(game.ale)
        inputs = get_inputs(game)
        action = 0
        if inputs[13076] > 0
            action = 2
        end
        reward += act(game.ale, Cint(action))
        frames += 1
        screen = draw(game)
        for pixel_i in -1:1:1
            for pixel_j in -1:1:1
                if ~(pixel_i == 0 && pixel_j == 0)
                  screen[56+pixel_i, 63+pixel_j] = RGB{Float64}(1.0, 1.0, 1.0)
                end
            end
        end
        save(@sprintf("qbert/frame_%06d.png", frames), screen)
    end
    close!(game)
    reward
end

function random_breakout_expert(nin::Int64, nout::Int64)
    # TODO: debug / rewrite without segmentation
    c = HPCGPChromo(nin, nout)
    pos = sort!(rand(15))
    c.genes[1] = pos[1]
    for out in nin+(1:nout)
        c.genes[out] = pos[15]
    end
    c.genes[nin+4] = pos[13]
    c.genes[nin+5] = pos[14]
    CGP.set_genes!(c, nin+1, [
        pos[2], rand(), rand(), CGP.Config.func2f(CGP.Config.f_const), 0.6])
    CGP.set_genes!(c, nin+2, [
        pos[3], rand(), rand(), CGP.Config.func2f(CGP.Config.f_const), 0.8])
    CGP.set_genes!(c, nin+3, [
        pos[4], pos[1], rand(), CGP.Config.func2f(CGP.Config.f_felzenszwalb), 0.8])
    CGP.set_genes!(c, nin+4, [
        pos[5], pos[4], pos[2], CGP.Config.func2f(CGP.Config.f_gt), rand()])
    CGP.set_genes!(c, nin+5, [
        pos[6], pos[4], pos[3], CGP.Config.func2f(CGP.Config.f_lt), rand()])
    CGP.set_genes!(c, nin+6, [
        pos[7], pos[4], pos[3], CGP.Config.func2f(CGP.Config.f_gt), rand()])
    CGP.set_genes!(c, nin+7, [
        pos[8], pos[5], pos[6], CGP.Config.func2f(CGP.Config.f_and), rand()])
    CGP.set_genes!(c, nin+8, [
        pos[9], pos[7], rand(), CGP.Config.func2f(CGP.Config.f_com), rand()])
    CGP.set_genes!(c, nin+9, [
        pos[10], pos[8], rand(), CGP.Config.func2f(CGP.Config.f_com), rand()])
    CGP.set_genes!(c, nin+10, [
        pos[11], pos[9], rand(), CGP.Config.func2f(CGP.Config.f_last), rand()])
    CGP.set_genes!(c, nin+11, [
        pos[12], pos[10], rand(), CGP.Config.func2f(CGP.Config.f_last), rand()])
    CGP.set_genes!(c, nin+12, [
        pos[13], pos[11], pos[12], CGP.Config.func2f(CGP.Config.f_gt), rand()])
    CGP.set_genes!(c, nin+13, [
        pos[14], pos[11], pos[12], CGP.Config.func2f(CGP.Config.f_lt), rand()])
    CGP.set_genes!(c, nin+14, [
        pos[15], rand(), rand(), CGP.Config.func2f(CGP.Config.f_zeros), rand()])
    HPCGPChromo(c.genes, nin, nout)
end

function get_breakout_action(inputs::Array{Float64})
    action = 2
    bullet_box = inputs[94:189, 9:152];
    slider_box = inputs[190:192, 9:152];
    bullet_poses = find(bullet_box)
    slider_poses = find(slider_box)
    if length(bullet_poses) > 0 && length(slider_poses) > 0
        bullet_pos = mean(map(x->ind2sub(bullet_box, x)[2], bullet_poses))
        slider_pos = mean(map(x->ind2sub(slider_box, x)[2], slider_poses))
        if bullet_pos < slider_pos
            action = 5
        else
            action = 4
        end
    end
    action
end

function play_breakout()
    game = Game("breakout")
    reward = 0
    frames = 0
    act(game.ale, game.actions[2])
    life = lives(game.ale)
    while ~game_over(game.ale)
        action = game.actions[2]
        if lives(game.ale) < life
            life = lives(game.ale)
        else
            inputs = get_inputs(game)
            action = game.actions[get_breakout_action(inputs)]
        end
        reward += act(game.ale, action)
        frames += 1
        screen = draw(game)
        save(@sprintf("breakout/frame_%06d.png", frames), screen)
    end
    close!(game)
    reward
end

# ACTION_MEANING = {
#     0 : "NOOP",
#     1 : "FIRE",
#     2 : "UP",
#     3 : "RIGHT",
#     4 : "LEFT",
#     5 : "DOWN",
#     6 : "UPRIGHT",
#     7 : "UPLEFT",
#     8 : "DOWNRIGHT",
#     9 : "DOWNLEFT",
#     10 : "UPFIRE",
#     11 : "RIGHTFIRE",
#     12 : "LEFTFIRE",
#     13 : "DOWNFIRE",
#     14 : "UPRIGHTFIRE",
#     15 : "UPLEFTFIRE",
#     16 : "DOWNRIGHTFIRE",
#     17 : "DOWNLEFTFIRE",
# }


text:
################################################################################
# Spike Rasters
################################################################################

function spikeraster(spikes::Array{Spike,1}; kwargs...)
    points = [Point2f0(s.t, s.n) for s ∈ spikes]
    scatter(points; kwargs...)
end

function spikeraster!(ax, spikes::Array{Spike,1}; kwargs...)
    points = [Point2f0(s.t, s.n) for s ∈ spikes]
    scatter!(ax, points; kwargs...)
end

# function spikeraster(fig::T, spikes::Array{Spike,1}; kwargs...) where
#                      {T <: Union{Figure, AbstractPlotting.FigurePosition}}
#     points = [Point2f0(s.t, s.n) for s ∈ spikes]
#     scatter(fig, points; kwargs...)
# end

# spikeraster(spikes::Array{Spike,1}; kwargs...) = spikeraster(Scene(), spikes; kwargs...)

################################################################################
# Spatial Plots and Animation
################################################################################

function spatialplot(neurontypes, positions, colors::Dict{SimpleNeuronType,Any};
                     size = 5.0, scale=1.0,
                     kwargs...)
    positions = [p .* scale for p ∈ positions]
    scene = Scene()
    colors = [colors[neurontypes[i]] for i ∈ 1:length(neurontypes)]
    scatter!(scene, positions;
             markersize=size, color=colors, kwargs...)
    scene
end

function spatialplot(neurontypes, positions, colors::Dict{SimpleNeuronType,Any},
                     firedidcs::Array{Int,1};
                     firedcolor = :red,
                     firedalpha = 0.5,
                     firedsize = 7.5,
                     kwargs...)
    scene = spatialplot(neurontypes, positions, colors; kwargs...)
    if haskey(kwargs, :scale)
        positions = [p .* kwargs[:scale] for p ∈ positions]
    end
    scatter!(scene, [positions[i] for i ∈ firedidcs];
             color=(firedcolor, firedalpha), markersize=firedsize)
    scene
end

function spatialplot(neurontypes, positions, color::Dict{SimpleNeuronType,Any},
                     graph::Array{Int64,2}; kwargs...)
    scene = spatialplot(neurontypes, positions, color; kwargs...)
	n = size(graph)[1]
    for i ∈ 1:n
        for j ∈ 1:n
			if graph[i,j] != 0
				lines!(scene,
                       [positions[i][1], positions[j][1]],
  					   [positions[i][2], positions[j][2]],
                       [positions[i][3], positions[j][3]];
                       color=(:black, 0.2))
			end
		end
	end
    return scene
end

function spatialanim(neurontypes, positions, colors::Dict{SimpleNeuronType,Any},
                     times::Tuple{Float64,Float64},
                     dt::Float64,
                     spikes::Array{Spike,1};
                     name="spikes",
                     size=0.0100,
                     framerate=20,
                     firedsize=0.0150,
                     numfiringsteps=5,
                     alphas=(0.4,0.8),
                     kwargs...)
    # Set up base plot
    #scene = spatialplot(neurontypes, positions, colors; kwargs...)
    scene = Scene()
    colors = [colors[neurontypes[i]] for i ∈ 1:length(neurontypes)]
    scatter!(scene, positions;
             markersize=size, color=colors, kwargs...)
    # Add overlay of red dots
    firedcolors = [(:red, 0.0) for i ∈ 1:length(neurontypes)]
    firedsizes = [firedsize for _ ∈ 1:length(neurontypes)]
    scatter!(scene, positions;
             markersize=firedsizes, color=firedcolors, strokewidth=0)
    # Bookeeping for animation
    ts = times[1]:dt:times[2]-dt
    prioralphas = LinRange(alphas[2], alphas[1], numfiringsteps)
    priorsizes = LinRange(firedsize, size, numfiringsteps)
    #firedidcs = Array{Array{Int64,1},1}(undef, numfiringsteps)
    firedidcs = [zeros(Int64,0) for _ ∈ 1:numfiringsteps]
    # Start animation
    record(scene, name*".mp4", ts; framerate=framerate) do t
        for i ∈ numfiringsteps:-1:2
            firedidcs[i] = firedidcs[i-1]
        end
        firedcolors = [(:red, 0.0) for i ∈ 1:length(neurontypes)]
        firedsizes = [firedsize for _ ∈ 1:length(neurontypes)]
        firedidcs[1] = [Int64(s.n) for s ∈ filter(s -> t <= s.t < t+dt, spikes)]
        for (i,idcs) ∈ enumerate(firedidcs)
            for idx ∈ idcs
                fcolor = neurontypes[idx] == SimpleNeuronType(:exc) ? (:red) : (:blue)
                firedcolors[idx] = (fcolor,prioralphas[i])
                firedsizes[idx] = priorsizes[i]
            end
        end
        scene[end].color[] = firedcolors
        scene[end].markersize[] = firedsizes
        update!(scene)
    end
end

## Move to plotting functions -- ok, now convert to GLMakie equivalent
# function psthistograms(spikes::Array{Spike,1}, intimes, windur; offset=0.0)
# 	spikeblocks = pstspikes(spikes, intimes, windur; offset=offset)
# 	acs = map(sparseac, spikeblocks)
# 	# acs = [filter(x-> x >= 0, ac) for ac ∈ acs]
# 	hists = [histogram(ac) for ac ∈ acs]
# 	p = plot(hists..., reuse=false)
# end

max_stars_repo_name: ddahlbom/DelayNetPlots

max_issues_repo_name: ddahlbom/DelayNetPlots

max_forks_repo_name: ddahlbom/DelayNetPlots


text:
using RCall
include("ProblemGenerator.jl")
include("VirusCallBacks.jl")

########################################################################
#Plotting States (Figure 3)
########################################################################

#--------------ISD (Det)--------------
ISDprob = ModelSetup(:ISD, :notStochastic, :Homo)
ISDsol = @time solve(ISDprob, CVODE_BDF(linear_solver = :GMRES), saveat = 0.1)

allStateDataISDDet = DataFrame()
for (i, st) in enumerate(statesNames)
  allStateDataISDDet[!, Symbol(st)] = vec(ISDsol[:, :, i, :])
end

timeLength = length(ISDsol.t)
allStateDataISDDet.Cell = repeat(1:nCells, timeLength)
allStateDataISDDet.Time = repeat(ISDsol.t, inner = nCells)
allStateDataISDDet.Infected = repeat(vec(ISDprob.u0[:, :, 2] .> 0.0), timeLength)

#--------------ISD (Stoch)--------------
ISDprob = ModelSetup(:ISD, :Stochastic, :Hetero)
ISDsol = @time solve(ISDprob, CVODE_BDF(linear_solver = :GMRES), saveat = 0.1)

allStateDataISDStoch = DataFrame()
for (i, st) in enumerate(statesNames)
  allStateDataISDStoch[!, Symbol(st)] = vec(ISDsol[:, :, i, :])
end

timeLength = length(ISDsol.t)
allStateDataISDStoch.Cell = repeat(1:nCells, timeLength)
allStateDataISDStoch.Time = repeat(ISDsol.t, inner = nCells)
allStateDataISDStoch.Infected = repeat(vec(ISDprob.u0[:, :, 2] .> 0.0), timeLength)

#--------------Virus--------------
Virusprob = ModelSetup(:Virus, :Stochastic, :Hetero)
Virussol = @time solve(Virusprob,CVODE_BDF(linear_solver = :GMRES),saveat = 0.1,callback = cb)

allStateDataVirus = DataFrame()
for (i, st) in enumerate(statesNames)
  allStateDataVirus[!, Symbol(st)] = vec(Virussol[:, :, i, :])
end

timeLength = length(Virussol.t)
allStateDataVirus.Cell = repeat(1:nCells, timeLength)
allStateDataVirus.Time = repeat(Virussol.t, inner = nCells)
allStateDataVirus.Infected = repeat(vec(Virusprob.u0[:, :, 2] .> 0.0), timeLength)

@rput allStateDataISDDet
@rput allStateDataISDStoch
@rput allStateDataVirus
R"""
library(ggplot2)
library(ggpubr)

#--------------ISD (Det)--------------
stateAveISDDet = aggregate(allStateDataISDDet[,1:14], list(allStateDataISDDet$Infected,allStateDataISDDet$Time), mean)
colnames(stateAveISDDet)[1] <- "Cell.State"
colnames(stateAveISDDet)[2] <- "Time"

logic<- unlist(lapply(stateAveISDDet$Cell.State, function(x) x == TRUE))
stateAveISDDet$Cell.State[which(logic == TRUE)] <- "Primary"
stateAveISDDet$Cell.State[which(logic == FALSE)] <- "Secondary"
lowISDDet = aggregate(allStateDataISDDet[,1:14], list(allStateDataISDDet$Infected,allStateDataISDDet$Time), FUN = 'quantile',probs=0.05)
highISDDet = aggregate(allStateDataISDDet[,1:14], list(allStateDataISDDet$Infected,allStateDataISDDet$Time), FUN = 'quantile',probs=0.95)

#--------------ISD (Stoch)--------------
stateAveISDStoch = aggregate(allStateDataISDStoch[,1:14], list(allStateDataISDStoch$Infected,allStateDataISDStoch$Time), mean)
colnames(stateAveISDStoch)[1] <- "Cell.State"
colnames(stateAveISDStoch)[2] <- "Time"

logic<- unlist(lapply(stateAveISDStoch$Cell.State, function(x) x == TRUE))
stateAveISDStoch$Cell.State[which(logic == TRUE)] <- "Primary"
stateAveISDStoch$Cell.State[which(logic == FALSE)] <- "Secondary"
lowISDStoch = aggregate(allStateDataISDStoch[,1:14], list(allStateDataISDStoch$Infected,allStateDataISDStoch$Time), FUN = 'quantile',probs=0.05)
highISDStoch = aggregate(allStateDataISDStoch[,1:14], list(allStateDataISDStoch$Infected,allStateDataISDStoch$Time), FUN = 'quantile',probs=0.95)

#--------------Virus--------------
stateAveVirus = aggregate(allStateDataVirus[,1:14], list(allStateDataVirus$Infected,allStateDataVirus$Time), mean)
colnames(stateAveVirus)[1] <- "Cell.State"
colnames(stateAveVirus)[2] <- "Time"

logic<- unlist(lapply(stateAveVirus$Cell.State, function(x) x == TRUE))
stateAveVirus$Cell.State[which(logic == TRUE)] <- "Primary"
stateAveVirus$Cell.State[which(logic == FALSE)] <- "Secondary"
lowVirus = aggregate(allStateDataVirus[,1:14], list(allStateDataVirus$Infected,allStateDataVirus$Time), FUN = 'quantile',probs=0.05)
highVirus = aggregate(allStateDataVirus[,1:14], list(allStateDataVirus$Infected,allStateDataVirus$Time), FUN = 'quantile',probs=0.95)

commonFigureOptions <- list( scale_x_continuous(breaks=seq(0, 48, 12)),
  theme_pubr(border=TRUE),
  ylab("nM"),
  xlab("Time (hours)"),
  theme(plot.title = element_text(hjust = 0.5),aspect.ratio = 1))

#--------------ISD Plots--------------
p1 <- ggplot(stateAveISDDet) + geom_line(aes(y=DNA, x=Time, group=Cell.State, color = Cell.State))+
  geom_ribbon(aes(ymin=lowISDDet$DNA, ymax=highISDDet$DNA, x=Time,group=Cell.State,fill = Cell.State), alpha = 0.3) +
  ggtitle("DNA") +
  commonFigureOptions

p2 <- ggplot(stateAveISDDet) + geom_line(aes(y=IFNb, x=Time, group=Cell.State, color = Cell.State))+
  geom_ribbon(aes(ymin=lowISDDet$IFNb, ymax=highISDDet$IFNb, x=Time,group=Cell.State,fill = Cell.State), alpha = 0.3) +
  ggtitle("IFNb") +
  commonFigureOptions

p3 <- ggplot(stateAveISDDet) + geom_line(aes(y=IRF7, x=Time, group=Cell.State, color = Cell.State))+
  geom_ribbon(aes(ymin=lowISDDet$IRF7, ymax=highISDDet$IRF7, x=Time,group=Cell.State,fill = Cell.State), alpha = 0.3) +
  ggtitle("IRF7") +
  commonFigureOptions

p4 <- ggplot(stateAveISDDet) + geom_line(aes(y=TREX1, x=Time, group=Cell.State, color = Cell.State))+
  geom_ribbon(aes(ymin=lowISDDet$TREX1, ymax=highISDDet$TREX1, x=Time,group=Cell.State,fill = Cell.State), alpha = 0.3) +
  ggtitle("TREX1") +
  commonFigureOptions

p5 <- ggplot(stateAveISDStoch) + geom_line(aes(y=DNA, x=Time, group=Cell.State, color = Cell.State))+
  geom_ribbon(aes(ymin=lowISDStoch$DNA, ymax=highISDStoch$DNA, x=Time,group=Cell.State,fill = Cell.State), alpha = 0.3) +
  ggtitle("DNA") +
  commonFigureOptions

p6 <- ggplot(stateAveISDStoch) + geom_line(aes(y=IFNb, x=Time, group=Cell.State, color = Cell.State))+
  geom_ribbon(aes(ymin=lowISDStoch$IFNb, ymax=highISDStoch$IFNb, x=Time,group=Cell.State,fill = Cell.State), alpha = 0.3) +
  ggtitle("IFNb") +
  commonFigureOptions

p7 <- ggplot(stateAveISDStoch) + geom_line(aes(y=IRF7, x=Time, group=Cell.State, color = Cell.State))+
  geom_ribbon(aes(ymin=lowISDStoch$IRF7, ymax=highISDStoch$IRF7, x=Time,group=Cell.State,fill = Cell.State), alpha = 0.3) +
  ggtitle("IRF7") +
  commonFigureOptions

p8 <- ggplot(stateAveISDStoch) + geom_line(aes(y=TREX1, x=Time, group=Cell.State, color = Cell.State))+
  geom_ribbon(aes(ymin=lowISDStoch$TREX1, ymax=highISDStoch$TREX1, x=Time,group=Cell.State,fill = Cell.State), alpha = 0.3) +
  ggtitle("TREX1") +
  commonFigureOptions

#--------------Virus Plots--------------
p9 <- ggplot(stateAveVirus) + geom_line(aes(y=DNA, x=Time, group=Cell.State, color = Cell.State))+
  geom_ribbon(aes(ymin=lowVirus$DNA, ymax=highVirus$DNA, x=Time,group=Cell.State,fill = Cell.State), alpha = 0.3) +
  ggtitle("DNA") +
  commonFigureOptions

p10 <- ggplot(stateAveVirus) + geom_line(aes(y=IFNb, x=Time, group=Cell.State, color = Cell.State))+
  geom_ribbon(aes(ymin=lowVirus$IFNb, ymax=highVirus$IFNb, x=Time,group=Cell.State,fill = Cell.State), alpha = 0.3) +
  ggtitle("IFNb") +
  commonFigureOptions

p11 <- ggplot(stateAveVirus) + geom_line(aes(y=IRF7, x=Time, group=Cell.State, color = Cell.State))+
  geom_ribbon(aes(ymin=lowVirus$IRF7, ymax=highVirus$IRF7, x=Time,group=Cell.State,fill = Cell.State), alpha = 0.3) +
  ggtitle("IRF7") +
  commonFigureOptions

p12 <- ggplot(stateAveVirus) + geom_line(aes(y=TREX1, x=Time, group=Cell.State, color = Cell.State))+
  geom_ribbon(aes(ymin=lowVirus$TREX1, ymax=highVirus$TREX1, x=Time,group=Cell.State,fill = Cell.State), alpha = 0.3) +
  ggtitle("TREX1") +
  commonFigureOptions

figure <- ggarrange(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12,
                    labels = "AUTO",
                    common.legend = TRUE, legend = "right",
                    align = "hv",
                    ncol = 4, nrow = 3)

ggsave("./Figures/Figure3Stoch.pdf",width = 15,height=8,units="in")
"""


text:
@testset "> Construction" begin
    @testset ">> Empty 2d" begin
        crystal = Crystal(eye(2)u"nm")
        @test size(crystal.positions) == (2, 0)
        @test nrow(crystal.properties) == 0
        @test length(crystal) == 0
        @test is_fractional(crystal) == false
    end

    @testset ">> 3d with real positions" begin
        crystal = Crystal(eye(3)u"nm", tpositions=[1 1 1; 2 3 4]u"nm")
        @test length(crystal) == 2
        @test nrow(crystal.properties) == 0
        @test crystal.positions[:, 1] == [1, 1, 1]u"nm"
        @test crystal.positions[:, 2][1] == 2u"nm"
        @test crystal.positions[:, 2][2] == 3u"nm"
        @test crystal.positions[:, 2][3] == 4u"nm"
        @test is_fractional(crystal) == false
    end

    @testset ">> 4d with fractional positions" begin
        crystal = Crystal(eye(4)u"nm", tpositions=[1 1 1 1; 2 3 4 5])
        @test length(crystal) == 2
        @test nrow(crystal.properties) == 0
        @test crystal.positions[:, 1] == [1, 1, 1, 1]
        @test crystal.positions[:, 2] == [2, 3, 4, 5]
        @test is_fractional(crystal) == true
    end

    @testset ">> 2d with atomic properties" begin
        crystal = Crystal(eye(2)u"nm", tpositions=[1 1; 2 3]u"nm",
                          species=["Al", "O"])
        @test length(crystal) == 2
        @test nrow(crystal.properties) == 2
        @test crystal.properties[:species] == ["Al", "O"]
        @test crystal.positions[:, 1] == [1, 1]u"nm"
        @test crystal.positions[:, 2] == [2, 3]u"nm"
        @test is_fractional(crystal) == false
    end

    @testset ">> Convert position to crystal's type" begin
        position_for_crystal = Crystals.Structures.position_for_crystal
        cell = [0 1 1; 1 0 1; 1 1 0]u"nm"
        realpos = Crystal(cell)
        @test !is_fractional(realpos)
        @test position_for_crystal(realpos, [1, 1, 1]) == cell * [1, 1, 1]
        @test position_for_crystal(realpos, [1, 1, 1]u"nm") == [1, 1, 1]u"nm"
        frac = Crystal(Float64[0 1 1; 1 0 1; 1 1 0]u"nm", position=[0, 0, 0])
        @test position_for_crystal(frac, [1, 1, 1]) == [1, 1, 1]
        @test position_for_crystal(frac, [1, 1, 1]u"nm") == inv(frac.cell) * [1, 1, 1]u"nm"
    end

end

@testset "> Pushing" begin
    @testset "> simple line" begin
        crystal = Crystal(Float64[0 1 1; 1 0 1; 1 1 0]u"nm")
        # add fractional position
        push!(crystal, [[0.25, 0.25, 0.25]])
        @test length(crystal) == 1
        @test all(crystal.positions[:, end] .== [0.5, 0.5, 0.5]u"nm")
        @test Set(names(crystal.properties)) == Set()

        crystal[:species] = "Al"
        # add cartesian position, must also add species
        push!(crystal, [[0.25, 0.25, 0.25]u"nm", "α"])
        @test length(crystal) == 2
        @test all(crystal.positions[:, end] .≈ [0.25, 0.25, 0.25]u"nm")
        @test Set(names(crystal.properties)) == Set([:species])
        @test crystal.properties[:species] == ["Al", "α"]

        push!(crystal, Dict(:species=>"β", :position=>[0.2, 0.2, 0.2]u"nm"))
        @test length(crystal) == 3
        @test all(crystal.positions[:, end] .≈ [0.2, 0.2, 0.2]u"nm")
        @test Set(names(crystal.properties)) == Set([:species])
        @test crystal.properties[end, :species] == "β"

        push!(crystal, Dict("species"=>"γ", "position"=>[0.1, 0.2, 0.2]u"nm"))
        @test length(crystal) == 4
        @test all(crystal.positions[:, end] .≈ [0.1, 0.2, 0.2]u"nm")
        @test Set(names(crystal.properties)) == Set([:species])
        @test crystal.properties[end, :species] == "γ"
    end
end

@testset "> Check direct indexing" begin
    @testset ">> getindex" begin
        crystal = Crystal(eye(2)u"nm", species=["Al", "O", "O"],
                          position=[1 1 1; 2 3 4], label=[:+, :-, :-])
        @testset ">>> integer" begin
            @test typeof(crystal[1]) === typeof(crystal)
            @test crystal[1].cell === crystal.cell
            @test crystal[1].positions == crystal.positions[:, 1:1]
            @test crystal[1].properties == crystal.properties[1, :]
        end

        @testset ">>> symbol" begin
            @test crystal[:position] === crystal.positions
            @test crystal[:x] == crystal.positions[1, :]
            @test crystal[:y] == crystal.positions[2, :]
            @test crystal[:fractional] === crystal.positions
            @test crystal[:cartesian] == crystal.cell * crystal.positions
            @test crystal[:species] === crystal.properties[:species]
        end

        @testset ">>> range" begin
            @test typeof(crystal[2:3]) === typeof(crystal)
            @test crystal[2:3].cell === crystal.cell
            @test crystal[2:3].positions == crystal.positions[:, 2:3]
            @test crystal[2:3].properties == crystal.properties[2:3, :]
        end

        @testset ">>> array of integers" begin
            @test typeof(crystal[[3, 2]]) === typeof(crystal)
            @test crystal[[3, 2]].cell === crystal.cell
            @test crystal[[3, 2]].positions == crystal.positions[:, [3, 2]]
        end

        @testset ">>> list of symbols" begin
            @test typeof(crystal[[:species]]) <: DataFrame
            @test names(crystal[[:species]]) == [:species]
            @test crystal[[:species]][:species] == crystal.properties[:species]
            @test typeof(crystal[[:species, :position]]) === typeof(crystal)
            @test names(crystal[[:species, :position]]) == [:species, :position]
            @test crystal[[:species, :position]][:species] == crystal.properties[:species]

            cartesian = crystal[[:species, :cartesian]]
            @test !is_fractional(cartesian)
            @test cartesian[:species] == crystal.properties[:species]
            @test cartesian[:position] == crystal.cell * crystal.positions

            @test crystal[[:species, :y, :x]][:x] == crystal.positions[1, :]
            @test crystal[[:species, :y, :x]][:y] == crystal.positions[2, :]

            fractional = crystal[[:species, :fractional]]
            @test is_fractional(fractional)
            @test fractional[:species] == crystal.properties[:species]
            @test fractional[:position] == crystal.positions
        end

        @testset ">>> integer|range|array of integers|Colon, symbol" begin
            @test crystal[2, :position] == crystal.positions[:, 2]
            @test crystal[2, :fractional] == crystal.positions[:, 2]
            @test crystal[2, :cartesian] == crystal.cell * crystal.positions[:, 2]
            @test crystal[2:3, :species] == crystal.properties[2:3, :species]
            @test crystal[[3, 2], :position] == crystal.positions[:, [3, 2]]
            @test crystal[[3, 2], :species] == crystal.properties[[3, 2], :species]
            @test crystal[[3, 2], :x] == crystal.positions[1, [3, 2]]
            @test crystal[[3, 2], :x] == crystal.positions[1, [3, 2]]
            @test crystal[:, :species] === crystal.properties[:, :species]
            @test crystal[:, :position] === crystal.positions
            @test crystal[:, :fractional] === crystal.positions
            @test crystal[:, :cartesian] == crystal.cell * crystal.positions
        end

        @testset ">>> integer|range|array of integers|Colon, list of symbol" begin
            @test typeof(crystal[2, [:position, :label]]) === typeof(crystal)
            @test names(crystal[2, [:position, :label]]) == [:label, :position]
            @test nrow(crystal[2, [:position, :label]]) == 1
            @test crystal[2, [:position, :label]][:position] == crystal.positions[:, 2:2]
            @test crystal[2, [:position, :label]][:label] == [crystal.properties[2, :label]]
            @test crystal[2, [:x, :y, :label]][:x] == [crystal.positions[1, 2]]
            @test crystal[2, [:x, :y, :label]][:y] == [crystal.positions[2, 2]]

            @test typeof(crystal[2, [:species, :label]]) === typeof(crystal.properties)
            @test crystal[2, [:species, :label]][:label] == [crystal.properties[2, :label]]
            @test names(crystal[2, [:species, :label]]) == [:species, :label]
            @test nrow(crystal[2, [:species, :label]]) == 1

            subcrystal = crystal[[3, 2], [:position, :label]]
            @test typeof(subcrystal) === typeof(crystal)
            @test subcrystal[:position] == crystal.positions[:, [3, 2]]
            @test subcrystal[:label] == crystal.properties[[3, 2], :label]

            @test typeof(crystal[[3, 2], [:species, :label]]) === typeof(crystal.properties)

            subcrystal = crystal[[3, 2], [:fractional, :label]]
            @test typeof(subcrystal) === typeof(crystal)
            @test subcrystal[:position] == crystal.positions[:, [3, 2]]

            subcrystal = crystal[[3, 2], [:cartesian, :label]]
            @test !is_fractional(subcrystal)
            @test subcrystal[:position] == crystal.cell * crystal.positions[:, [3, 2]]
        end

        @testset ">>> position components" begin
            @test crystal[2, :position, 1] == crystal.positions[1, 2]
            @test crystal[:position, 1] == crystal.positions[1, :]
        end
    end

    @testset ">> setindex!" begin
        original = Crystal([0 5 5; 5 0 5; 5 5 0]u"nm", species=["Al", "O", "O"],
                           position=[1, 1, 1]u"nm",
                           position=[1, 2, 1]u"nm",
                           position=[0, 0, 1]u"nm",
                           label=[:+, :-, :-])

        @testset ">>> symbol" begin
            crystal = deepcopy(original)

            crystal[:label] = :u
            @test crystal[:label] == [:u, :u, :u]

            crystal[:label] = [:v, :w, :x]
            @test crystal[:label] == [:v, :w, :x]

            crystal[:position] = [1 0 0; 0 2 1; 4 0 1]u"nm"
            @test crystal[:position] == [1 0 0; 0 2 1; 4 0 1]u"nm"
        end

        @testset ">>> atom number, symbol" begin
            crystal = deepcopy(original)
            crystal[2, :label] = :z
            @test crystal[:label] == [:+, :z, :-]

            crystal[2, :position] = [3, 4, 5]u"nm"
            @test crystal[2, :position] == [3, 4, 5]u"nm"

            crystal[2, :position] = [1, 1, -1]
            @test crystal[2, :position] == [0, 0, 10]u"nm"
        end

        @testset ">>> multiple indices, symbol" begin
            crystal = deepcopy(original)

            crystal[2:3, :label] = :a
            @test crystal[:label] == [:+, :a, :a]

            crystal[[1, 3], :position] = transpose([1 2 3; 4 5 6])u"nm"
            @test crystal.positions == transpose([1 2 3; 1 2 1; 4 5 6])u"nm"

            crystal[[1, 3], :position] = transpose([1 0 0; 1 -1 0])
            @test crystal.positions == transpose([0 5 5; 1 2 1; -5 5 0])u"nm"
        end

        @testset ">>> From dataframe" begin
            crystal = deepcopy(original)

            df = DataFrame(species=["I", "N", "B"], μ=[1, 2, 4])
            crystal[[:species, :μ]] = df
            @test :μ ∈ names(crystal)
            @test crystal[:μ] === df[:μ]
            @test crystal[:species] === df[:species]

            df = DataFrame(species=["A", "B"], μ=[5, 3])
            crystal[[3, 1], [:species, :μ]] = df
            @test crystal[:μ] == [3, 2, 5]
            @test crystal[:species] == ["B", "N", "A"]
        end

        @testset ">>> From crystal" begin
            crystal = deepcopy(original)
            other = deepcopy(original)
            other[:label] = [:a, :b, :c]

            crystal[[:label]] = other
            # follows same logic as for dataframes
            @test crystal[:label] === other[:species]

            crystal = deepcopy(original)
            crystal[[:position, :label]] = other
            # except for positions, which are always correctly extracted
            @test crystal[:position] ≈ other[:position]

            # lets try with a fractional crystal
            crystal = Crystal(1.0 * original.cell, 1.0 * original.positions,
                              deepcopy(original.properties))
            other = Crystal([0 5 5; 5 0 5; 5 5 0]u"nm", species=["Al", "O", "O"],
                            position=[0.2  0.4   0.2; 0.1  0.0   0.1; 0.2  0.4  -0.2],
                            label=[:a, :b, :c])

            crystal[[:position, :label]] = other
            @test crystal[:label] === other[:species]
            @test crystal[:position] ≈ other.cell * other[:position]

            # try with specific rows
            crystal = Crystal(1.0 * original.cell, 1.0 * original.positions,
                              deepcopy(original.properties))
            crystal[[3, 1], [:position]] = other[[1, 3], :]
            @test crystal[[1, 3], :position] ≈ other.cell * other[[3, 1], :position]
            @test crystal[2, :position] ≈ original[2, :position]
        end

        @testset ">>> Set position components" begin
            crystal = deepcopy(original)
            crystal[1, :position, 1] = 8u"nm"
            @test crystal[1, :position, 1] ≈ 8u"nm"

            crystal[2, :position, [3, 1]] = [7, 8]u"nm"
            @test crystal[2, :position, [1, 3]] ≈ [8, 7]u"nm"

            crystal[2, :position, [3, 1]] = [7, 8]u"nm"
            @test crystal[2, :position, [1, 3]] ≈ [8, 7]u"nm"
        end
    end
end

@testset "> Deleting stuff" begin
    original = Crystal([0 5 5; 5 0 5; 5 5 0]u"nm", species=["Al", "O", "O"],
                       position=[1, 1, 1]u"nm",
                       position=[1, 2, 1]u"nm",
                       position=[0, 0, 1]u"nm",
                       label=[:+, :-, :-])
    @testset ">> columns" begin
        crystal = deepcopy(original)
        delete!(crystal, :label)
        @test :label ∉ names(crystal)
        @test :species ∈ names(crystal)

        crystal = deepcopy(original)
        delete!(crystal, [:label, :species])
        @test :label ∉ names(crystal)
        @test :species ∉ names(crystal)

        @test_throws ErrorException delete!(crystal, :position)

        crystal = deepcopy(original)
        empty!(crystal)
        @test nrow(crystal) == 0
        @test ncol(crystal) == 1
    end

    @testset ">> rows" begin
        crystal = deepcopy(original)
        deleterows!(crystal, 1)
        @test nrow(crystal) == nrow(original) - 1
        @test crystal.properties == original.properties[2:end, :]
        @test crystal.positions == original.positions[:, 2:end]

        crystal = deepcopy(original)
        deleterows!(crystal, 1:2:3)
        @test nrow(crystal) == 1
        @test crystal.properties == original.properties[2, :]
        @test crystal.positions == original.positions[:, 2:2]

        crystal = deepcopy(original)
        deleterows!(crystal, :)
        @test nrow(crystal) == 0
        @test ncol(crystal) == ncol(original)

        crystal = deepcopy(original)
        delete!(crystal, :)
        @test nrow(crystal) == 0
        @test ncol(crystal) == ncol(original)
    end
end

@testset "> vcat and append" begin
    crysA = Crystal([0 5 5; 5 0 5; 5 5 0]u"nm", species=["Al", "O", "O"],
                    position=[1, 1, 1]u"nm",
                    position=[1, 2, 1]u"nm",
                    position=[0, 0, 1]u"nm",
                    label=[:+, :-, :-])
    crysB = Crystal([0 5 5; 5 0 5; 5 5 0]u"nm",
                    species=[missing, missing],
                    position=[2, -1, 1]u"nm",
                    position=[0, 0, -1]u"nm",
                    label=[:-, :a])

    crystal = vcat(crysA, crysB)
    @test nrow(crystal) == 5
    @test crystal.positions == transpose([1 1 1; 1 2 1; 0 0 1; 2 -1 1; 0 0 -1])u"nm"
    @test crystal[1:3, :species] == ["Al", "O", "O"]
    @test all(ismissing.(crystal[4:5, :species]))
    @test crystal[:label] == [:+, :-, :-, :-, :a]
    @test_throws ArgumentError vcat(crysA, crysB[[:position, :label]])

    crystal = deepcopy(crysA[[:position, :label]])
    append!(crystal, crysB[[:position, :label]])
    @test nrow(crystal) == 5
    @test crystal.positions == transpose([1 1 1; 1 2 1; 0 0 1; 2 -1 1; 0 0 -1])u"nm"
    @test crystal[:label] == [:+, :-, :-, :-, :a]

    crystal = deepcopy(crysA)
    # eltypes do not match (one is Missing, the other String)
    @test_throws ErrorException append!(crystal, crysB)
    # mismatching columns
    @test_throws ErrorException append!(crystal, crysB[[:position, :label]])
end

@testset "> round crystal cell and positions" begin
    crystal = Crystal([0 0.501 0.501; 0.496 0.001 0.497; 0.497 0.497 0]u"nm",
                      position=[0.001, -0.001, -0.001]u"nm",
                      position=[0.25, 0.251, -0.247]u"nm")
    round!(crystal, 2)
    @test crystal.cell == [0 0.5 0.5; 0.5 0 0.5; 0.5 0.5 0]u"nm"
    @test crystal[:position] == transpose([0 0 0; 0.25 0.25 -0.25]u"nm")
end


text:
module SARSOP

using SARSOP_jll
using POMDPs
using POMDPXFiles
using POMDPModelTools
using POMDPLinter
using POMDPPolicies
using Parameters

export
    SARSOPSolver,
    POMDPFile,
    to_pomdpx,
    load_policy,
    SARSOPSimulator,
    simulate,
    SARSOPEvaluator,
    evaluate,
    PolicyGraphGenerator,
    generate_graph

include("file.jl")
include("solver.jl")
include("simulator.jl")
include("graph.jl")

end # module


text:
naolaplacian00(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = (2*ud + r*ud2)/(2*sqrt(π)*r)
naolaplacian1m1(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = (sqrt(3/π)*sin(θ)*sin(ϕ)*(-2*u + r*(2*ud + r*ud2)))/(2*r^2)
naolaplacian10(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = (sqrt(3/π)*cos(θ)*(-2*u + r*(2*ud + r*ud2)))/(2*r^2)
naolaplacian11(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = (sqrt(3/π)*cos(ϕ)*sin(θ)*(-2*u + r*(2*ud + r*ud2)))/(2*r^2)
naolaplacian2m2(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = -((sqrt(15/π)*sin(θ)^2*sin(2*ϕ)*(-6*u + r*(2*ud + r*ud2)))/(4*r^2))
naolaplacian2m1(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = (sqrt(15/π)*sin(2*θ)*sin(ϕ)*(-6*u + r*(2*ud + r*ud2)))/(4*r^2)
naolaplacian20(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = (sqrt(5/π)*(1 + 3*cos(2*θ))*(-6*u + r*(2*ud + r*ud2)))/(8*r^2)
naolaplacian21(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = (sqrt(15/π)*cos(ϕ)*sin(2*θ)*(-6*u + r*(2*ud + r*ud2)))/(4*r^2)
naolaplacian22(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = (sqrt(15/π)*cos(2*ϕ)*sin(θ)^2*(-6*u + r*(2*ud + r*ud2)))/(4*r^2)
naolaplacian3m3(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = (sqrt(35/(2*π))*sin(θ)^3*sin(3*ϕ)*(-12*u + r*(2*ud + r*ud2)))/(4*r^2)
naolaplacian3m2(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = -((sqrt(105/π)*cos(θ)*sin(θ)^2*sin(2*ϕ)*(-12*u + r*(2*ud + r*ud2)))/(4*r^2))
naolaplacian3m1(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = (sqrt(21/(2*π))*(3 + 5*cos(2*θ))*sin(θ)*sin(ϕ)*(-12*u + r*(2*ud + r*ud2)))/(8*r^2)
naolaplacian30(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = (sqrt(7/π)*(3*cos(θ) + 5*cos(3*θ))*(-12*u + r*(2*ud + r*ud2)))/(16*r^2)
naolaplacian31(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = (sqrt(21/(2*π))*(3 + 5*cos(2*θ))*cos(ϕ)*sin(θ)*(-12*u + r*(2*ud + r*ud2)))/(8*r^2)
naolaplacian32(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = (sqrt(105/π)*cos(θ)*cos(2*ϕ)*sin(θ)^2*(-12*u + r*(2*ud + r*ud2)))/(4*r^2)
naolaplacian33(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = (sqrt(35/(2*π))*cos(3*ϕ)*sin(θ)^3*(-12*u + r*(2*ud + r*ud2)))/(4*r^2)
naolaplacian4m4(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = -((3*sqrt(35/π)*sin(θ)^4*sin(4*ϕ)*(-20*u + r*(2*ud + r*ud2)))/(16*r^2))
naolaplacian4m3(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = (3*sqrt(35/(2*π))*cos(θ)*sin(θ)^3*sin(3*ϕ)*(-20*u + r*(2*ud + r*ud2)))/(4*r^2)
naolaplacian4m2(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = -((3*sqrt(5/π)*(5 + 7*cos(2*θ))*sin(θ)^2*sin(2*ϕ)*(-20*u + r*(2*ud + r*ud2)))/(16*r^2))
naolaplacian4m1(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = (3*sqrt(5/(2*π))*(2*sin(2*θ) + 7*sin(4*θ))*sin(ϕ)*(-20*u + r*(2*ud + r*ud2)))/(32*r^2)
naolaplacian40(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = (3*(9 + 20*cos(2*θ) + 35*cos(4*θ))*(-20*u + r*(2*ud + r*ud2)))/(128*sqrt(π)*r^2)
naolaplacian41(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = (3*sqrt(5/(2*π))*cos(ϕ)*(2*sin(2*θ) + 7*sin(4*θ))*(-20*u + r*(2*ud + r*ud2)))/(32*r^2)
naolaplacian42(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = (3*sqrt(5/π)*(5 + 7*cos(2*θ))*cos(2*ϕ)*sin(θ)^2*(-20*u + r*(2*ud + r*ud2)))/(16*r^2)
naolaplacian43(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = (3*sqrt(35/(2*π))*cos(θ)*cos(3*ϕ)*sin(θ)^3*(-20*u + r*(2*ud + r*ud2)))/(4*r^2)
naolaplacian44(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = (3*sqrt(35/π)*cos(4*ϕ)*sin(θ)^4*(-20*u + r*(2*ud + r*ud2)))/(16*r^2)
naolaplacian5m5(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = (3*sqrt(77/(2*π))*sin(θ)^5*sin(5*ϕ)*(-30*u + r*(2*ud + r*ud2)))/(16*r^2)
naolaplacian5m4(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = -((3*sqrt(385/π)*cos(θ)*sin(θ)^4*sin(4*ϕ)*(-30*u + r*(2*ud + r*ud2)))/(16*r^2))
naolaplacian5m3(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = (sqrt(385/(2*π))*(7 + 9*cos(2*θ))*sin(θ)^3*sin(3*ϕ)*(-30*u + r*(2*ud + r*ud2)))/(32*r^2)
naolaplacian5m2(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = -((sqrt(1155/π)*(5*cos(θ) + 3*cos(3*θ))*sin(θ)^2*sin(2*ϕ)*(-30*u + r*(2*ud + r*ud2)))/(32*r^2))
naolaplacian5m1(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = (sqrt(165/π)*(15 + 28*cos(2*θ) + 21*cos(4*θ))*sin(θ)*sin(ϕ)*(-30*u + r*(2*ud + r*ud2)))/(128*r^2)
naolaplacian50(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = (sqrt(11/π)*(30*cos(θ) + 35*cos(3*θ) + 63*cos(5*θ))*(-30*u + r*(2*ud + r*ud2)))/(256*r^2)
naolaplacian51(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = (sqrt(165/π)*(15 + 28*cos(2*θ) + 21*cos(4*θ))*cos(ϕ)*sin(θ)*(-30*u + r*(2*ud + r*ud2)))/(128*r^2)
naolaplacian52(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = (sqrt(1155/π)*(5*cos(θ) + 3*cos(3*θ))*cos(2*ϕ)*sin(θ)^2*(-30*u + r*(2*ud + r*ud2)))/(32*r^2)
naolaplacian53(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = (sqrt(385/(2*π))*(7 + 9*cos(2*θ))*cos(3*ϕ)*sin(θ)^3*(-30*u + r*(2*ud + r*ud2)))/(32*r^2)
naolaplacian54(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = (3*sqrt(385/π)*cos(θ)*cos(4*ϕ)*sin(θ)^4*(-30*u + r*(2*ud + r*ud2)))/(16*r^2)
naolaplacian55(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = (3*sqrt(77/(2*π))*cos(5*ϕ)*sin(θ)^5*(-30*u + r*(2*ud + r*ud2)))/(16*r^2)
naolaplacian6m6(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = -((sqrt(3003/(2*π))*sin(θ)^6*sin(6*ϕ)*(-42*u + r*(2*ud + r*ud2)))/(32*r^2))
naolaplacian6m5(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = (3*sqrt(1001/(2*π))*cos(θ)*sin(θ)^5*sin(5*ϕ)*(-42*u + r*(2*ud + r*ud2)))/(16*r^2)
naolaplacian6m4(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = -((3*sqrt(91/π)*(9 + 11*cos(2*θ))*sin(θ)^4*sin(4*ϕ)*(-42*u + r*(2*ud + r*ud2)))/(64*r^2))
naolaplacian6m3(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = (sqrt(1365/(2*π))*(21*cos(θ) + 11*cos(3*θ))*sin(θ)^3*sin(3*ϕ)*(-42*u + r*(2*ud + r*ud2)))/(64*r^2)
naolaplacian6m2(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = -((sqrt(1365/(2*π))*(35 + 60*cos(2*θ) + 33*cos(4*θ))*sin(θ)^2*sin(2*ϕ)*(-42*u + r*(2*ud + r*ud2)))/(256*r^2))
naolaplacian6m1(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = (sqrt(273/π)*(50*cos(θ) + 45*cos(3*θ) + 33*cos(5*θ))*sin(θ)*sin(ϕ)*(-42*u + r*(2*ud + r*ud2)))/(256*r^2)
naolaplacian60(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = (sqrt(13/π)*(50 + 105*cos(2*θ) + 126*cos(4*θ) + 231*cos(6*θ))*(-42*u + r*(2*ud + r*ud2)))/(1024*r^2)
naolaplacian61(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = (sqrt(273/π)*(50*cos(θ) + 45*cos(3*θ) + 33*cos(5*θ))*cos(ϕ)*sin(θ)*(-42*u + r*(2*ud + r*ud2)))/(256*r^2)
naolaplacian62(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = (sqrt(1365/(2*π))*(35 + 60*cos(2*θ) + 33*cos(4*θ))*cos(2*ϕ)*sin(θ)^2*(-42*u + r*(2*ud + r*ud2)))/(256*r^2)
naolaplacian63(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = (sqrt(1365/(2*π))*(21*cos(θ) + 11*cos(3*θ))*cos(3*ϕ)*sin(θ)^3*(-42*u + r*(2*ud + r*ud2)))/(64*r^2)
naolaplacian64(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = (3*sqrt(91/π)*(9 + 11*cos(2*θ))*cos(4*ϕ)*sin(θ)^4*(-42*u + r*(2*ud + r*ud2)))/(64*r^2)
naolaplacian65(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = (3*sqrt(1001/(2*π))*cos(θ)*cos(5*ϕ)*sin(θ)^5*(-42*u + r*(2*ud + r*ud2)))/(16*r^2)
naolaplacian66(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = (sqrt(3003/(2*π))*cos(6*ϕ)*sin(θ)^6*(-42*u + r*(2*ud + r*ud2)))/(32*r^2)
naolaplacian7m7(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = (3*sqrt(715/π)*sin(θ)^7*sin(7*ϕ)*(-56*u + r*(2*ud + r*ud2)))/(64*r^2)
naolaplacian7m6(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = -((3*sqrt(5005/(2*π))*cos(θ)*sin(θ)^6*sin(6*ϕ)*(-56*u + r*(2*ud + r*ud2)))/(32*r^2))
naolaplacian7m5(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = (3*sqrt(385/π)*(11 + 13*cos(2*θ))*sin(θ)^5*sin(5*ϕ)*(-56*u + r*(2*ud + r*ud2)))/(128*r^2)
naolaplacian7m4(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = -((3*sqrt(385/π)*(27*cos(θ) + 13*cos(3*θ))*sin(θ)^4*sin(4*ϕ)*(-56*u + r*(2*ud + r*ud2)))/(128*r^2))
naolaplacian7m3(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = (3*sqrt(35/π)*(189 + 308*cos(2*θ) + 143*cos(4*θ))*sin(θ)^3*sin(3*ϕ)*(-56*u + r*(2*ud + r*ud2)))/(512*r^2)
naolaplacian7m2(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = -((3*sqrt(35/(2*π))*(350*cos(θ) + 275*cos(3*θ) + 143*cos(5*θ))*sin(θ)^2*sin(2*ϕ)*(-56*u + r*(2*ud + r*ud2)))/(512*r^2))
naolaplacian7m1(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = (sqrt(105/π)*(350 + 675*cos(2*θ) + 594*cos(4*θ) + 429*cos(6*θ))*sin(θ)*sin(ϕ)*(-56*u + r*(2*ud + r*ud2)))/(2048*r^2)
naolaplacian70(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = (sqrt(15/π)*(175*cos(θ) + 189*cos(3*θ) + 231*cos(5*θ) + 429*cos(7*θ))*(-56*u + r*(2*ud + r*ud2)))/(2048*r^2)
naolaplacian71(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = (sqrt(105/π)*(350 + 675*cos(2*θ) + 594*cos(4*θ) + 429*cos(6*θ))*cos(ϕ)*sin(θ)*(-56*u + r*(2*ud + r*ud2)))/(2048*r^2)
naolaplacian72(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = (3*sqrt(35/(2*π))*(350*cos(θ) + 275*cos(3*θ) + 143*cos(5*θ))*cos(2*ϕ)*sin(θ)^2*(-56*u + r*(2*ud + r*ud2)))/(512*r^2)
naolaplacian73(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = (3*sqrt(35/π)*(189 + 308*cos(2*θ) + 143*cos(4*θ))*cos(3*ϕ)*sin(θ)^3*(-56*u + r*(2*ud + r*ud2)))/(512*r^2)
naolaplacian74(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = (3*sqrt(385/π)*(27*cos(θ) + 13*cos(3*θ))*cos(4*ϕ)*sin(θ)^4*(-56*u + r*(2*ud + r*ud2)))/(128*r^2)
naolaplacian75(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = (3*sqrt(385/π)*(11 + 13*cos(2*θ))*cos(5*ϕ)*sin(θ)^5*(-56*u + r*(2*ud + r*ud2)))/(128*r^2)
naolaplacian76(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = (3*sqrt(5005/(2*π))*cos(θ)*cos(6*ϕ)*sin(θ)^6*(-56*u + r*(2*ud + r*ud2)))/(32*r^2)
naolaplacian77(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = (3*sqrt(715/π)*cos(7*ϕ)*sin(θ)^7*(-56*u + r*(2*ud + r*ud2)))/(64*r^2)
naolaplacian8m8(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = -((3*sqrt(12155/π)*sin(θ)^8*sin(8*ϕ)*(-72*u + r*(2*ud + r*ud2)))/(256*r^2))
naolaplacian8m7(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = (3*sqrt(12155/π)*cos(θ)*sin(θ)^7*sin(7*ϕ)*(-72*u + r*(2*ud + r*ud2)))/(64*r^2)
naolaplacian8m6(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = -((sqrt(7293/(2*π))*(13 + 15*cos(2*θ))*sin(θ)^6*sin(6*ϕ)*(-72*u + r*(2*ud + r*ud2)))/(128*r^2))
naolaplacian8m5(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = (3*sqrt(17017/π)*(11*cos(θ) + 5*cos(3*θ))*sin(θ)^5*sin(5*ϕ)*(-72*u + r*(2*ud + r*ud2)))/(256*r^2)
naolaplacian8m4(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = -((3*sqrt(1309/π)*(99 + 156*cos(2*θ) + 65*cos(4*θ))*sin(θ)^4*sin(4*ϕ)*(-72*u + r*(2*ud + r*ud2)))/
      (1024*r^2))
naolaplacian8m3(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = (sqrt(19635/π)*(126*cos(θ) + 91*cos(3*θ) + 39*cos(5*θ))*sin(θ)^3*sin(3*ϕ)*(-72*u + r*(2*ud + r*ud2)))/(1024*r^2)
naolaplacian8m2(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = -((3*sqrt(595/(2*π))*(210 + 385*cos(2*θ) + 286*cos(4*θ) + 143*cos(6*θ))*sin(θ)^2*sin(2*ϕ)*(-72*u + r*(2*ud + r*ud2)))/(2048*r^2))
naolaplacian8m1(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = (3*sqrt(17/π)*(70*sin(2*θ) + 154*sin(4*θ) + 286*sin(6*θ) + 715*sin(8*θ))*sin(ϕ)*(-72*u + r*(2*ud + r*ud2)))/(8192*r^2)
naolaplacian80(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = (sqrt(17/π)*(1225 + 2520*cos(2*θ) + 2772*cos(4*θ) + 3432*cos(6*θ) + 6435*cos(8*θ))*(-72*u + r*(2*ud + r*ud2)))/(32768*r^2)
naolaplacian81(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = (3*sqrt(17/π)*cos(ϕ)*(70*sin(2*θ) + 154*sin(4*θ) + 286*sin(6*θ) + 715*sin(8*θ))*(-72*u + r*(2*ud + r*ud2)))/(8192*r^2)
naolaplacian82(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = (3*sqrt(595/(2*π))*(210 + 385*cos(2*θ) + 286*cos(4*θ) + 143*cos(6*θ))*cos(2*ϕ)*sin(θ)^2*(-72*u + r*(2*ud + r*ud2)))/(2048*r^2)
naolaplacian83(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = (sqrt(19635/π)*(126*cos(θ) + 91*cos(3*θ) + 39*cos(5*θ))*cos(3*ϕ)*sin(θ)^3*(-72*u + r*(2*ud + r*ud2)))/(1024*r^2)
naolaplacian84(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = (3*sqrt(1309/π)*(99 + 156*cos(2*θ) + 65*cos(4*θ))*cos(4*ϕ)*sin(θ)^4*(-72*u + r*(2*ud + r*ud2)))/(1024*r^2)
naolaplacian85(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = (3*sqrt(17017/π)*(11*cos(θ) + 5*cos(3*θ))*cos(5*ϕ)*sin(θ)^5*(-72*u + r*(2*ud + r*ud2)))/(256*r^2)
naolaplacian86(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = (sqrt(7293/(2*π))*(13 + 15*cos(2*θ))*cos(6*ϕ)*sin(θ)^6*(-72*u + r*(2*ud + r*ud2)))/(128*r^2)
naolaplacian87(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = (3*sqrt(12155/π)*cos(θ)*cos(7*ϕ)*sin(θ)^7*(-72*u + r*(2*ud + r*ud2)))/(64*r^2)
naolaplacian88(u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = (3*sqrt(12155/π)*cos(8*ϕ)*sin(θ)^8*(-72*u + r*(2*ud + r*ud2)))/(256*r^2)

# naolaplacian(l::Int, m::Int, u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) = naolaplaciantab[l,m](u,ud,ud2,r,θ,ϕ)

naolaplacian(l::Int, m::Int, u::Float64, ud::Float64, ud2::Float64, r::Float64, θ::Float64, ϕ::Float64) =
    naolaplaciantab[i_lm(l,m)](u,ud,ud2,r,θ,ϕ)::Float64

naolaplaciantab = [
    naolaplacian00,
    naolaplacian1m1,
    naolaplacian10,
    naolaplacian11,
    naolaplacian2m2,
    naolaplacian2m1,
    naolaplacian20,
    naolaplacian21,
    naolaplacian22,
    naolaplacian3m3,
    naolaplacian3m2,
    naolaplacian3m1,
    naolaplacian30,
    naolaplacian31,
    naolaplacian32,
    naolaplacian33,
    naolaplacian4m4,
    naolaplacian4m3,
    naolaplacian4m2,
    naolaplacian4m1,
    naolaplacian40,
    naolaplacian41,
    naolaplacian42,
    naolaplacian43,
    naolaplacian44,
    naolaplacian5m5,
    naolaplacian5m4,
    naolaplacian5m3,
    naolaplacian5m2,
    naolaplacian5m1,
    naolaplacian50,
    naolaplacian51,
    naolaplacian52,
    naolaplacian53,
    naolaplacian54,
    naolaplacian55,
    naolaplacian6m6,
    naolaplacian6m5,
    naolaplacian6m4,
    naolaplacian6m3,
    naolaplacian6m2,
    naolaplacian6m1,
    naolaplacian60,
    naolaplacian61,
    naolaplacian62,
    naolaplacian63,
    naolaplacian64,
    naolaplacian65,
    naolaplacian66,
    naolaplacian7m7,
    naolaplacian7m6,
    naolaplacian7m5,
    naolaplacian7m4,
    naolaplacian7m3,
    naolaplacian7m2,
    naolaplacian7m1,
    naolaplacian70,
    naolaplacian71,
    naolaplacian72,
    naolaplacian73,
    naolaplacian74,
    naolaplacian75,
    naolaplacian76,
    naolaplacian77,
    naolaplacian8m8,
    naolaplacian8m7,
    naolaplacian8m6,
    naolaplacian8m5,
    naolaplacian8m4,
    naolaplacian8m3,
    naolaplacian8m2,
    naolaplacian8m1,
    naolaplacian80,
    naolaplacian81,
    naolaplacian82,
    naolaplacian83,
    naolaplacian84,
    naolaplacian85,
    naolaplacian86,
    naolaplacian87,
    naolaplacian88
]

max_stars_repo_name: johncsnyder/Element


text:
include("examples.jl")
include("test_linear_algebra.jl")
include("test_circuit.jl")
include("test_pso_model.jl")
include("test_blackbox.jl")
include("test_circuit_components.jl")
include("../paper/radiative_loss.jl")
include("../paper/hybridization.jl")


text:
"""
anki_add_tag(card_id::Array{Int})

Adds a tag to cards listed in an array of card IDs.

"""
function anki_add_tag(card_id, tag)
    if length(card_id) == 1
        card_id = "[$card_id]"
    end

    response = HTTP.request(
        :POST,
        "http://localhost:8765",
        [],
        """{
\"action\": \"addTags\",
\"version\": 6,
\"params\": {
	\"notes\": $card_id,
	\"tags\": \"$tag\"
	}
}""",
    )
    String(response.body) |> JSON.parse
end


text:
using Simulacrum
using HyperGraphs, Symbolics
const ChE = ChemicalHyperEdge
const ChX = ChemicalHyperGraph

intvars = [1, 2, 3]
symvars = @variables X Y Z

@test all(length.(vertices.(Simulacrum.connect(intvars, ChE, AllToAll))) .== 3)
@test all(length.(vertices.(Simulacrum.connect(symvars, ChE, AllToAll))) .== 3)
@test all(length.(vertices.(Simulacrum.connect(intvars, ChE, AllToAll, to_hyperedge_idx = false))) .== 2)
@test all(length.(vertices.(Simulacrum.connect(symvars, ChE, AllToAll, to_hyperedge_idx = false))) .== 2)
@test all(length.(vertices.(Simulacrum.connect(intvars, ChE, Cycle))) .== 2)
@test all(length.(vertices.(Simulacrum.connect(symvars, ChE, Cycle))) .== 2)
@test all(length.(vertices.(Simulacrum.connect(intvars, ChE, Cycle, to_hyperedge_idx = false))) .== 2)
@test all(length.(vertices.(Simulacrum.connect(symvars, ChE, Cycle, to_hyperedge_idx = false))) .== 2)
@test all(length.(vertices.(Simulacrum.connect(intvars, ChE, Path))) .== 2)
@test all(length.(vertices.(Simulacrum.connect(symvars, ChE, Path))) .== 2)
@test all(length.(vertices.(Simulacrum.connect(intvars, ChE, Path, to_hyperedge_idx = false))) .== 2)
@test all(length.(vertices.(Simulacrum.connect(symvars, ChE, Path, to_hyperedge_idx = false))) .== 2)

template_che = ChE(SpeciesSet(10, 2), SpeciesSet(20, 1), 3)
connected = Simulacrum.connect(intvars, template_che, Cycle)
@test all(vcat(src_stoich.(connected)...) .== src_stoich(template_che) .== 2)
@test all(vcat(tgt_stoich.(connected)...) .== tgt_stoich(template_che) .== 1)
@test all(weight.(connected) .== weight(template_che) .== 3)

template_che = ChE(SpeciesSet(X, 2), SpeciesSet(X, 1), 3)
connected = Simulacrum.connect(symvars, template_che, Cycle)
@test all(vcat(src_stoich.(connected)...) .== src_stoich(template_che) .== 2)
@test all(vcat(tgt_stoich.(connected)...) .== tgt_stoich(template_che) .== 1)
@test all(weight.(connected) .== weight(template_che) .== 3)

@test Simulacrum.connect!(ChX(intvars, ChE{Int64}[]), intvars, ChE, Cycle)
@test Simulacrum.connect!(ChX(intvars, ChE{Int64}[]), intvars, ChE, Cycle, to_hyperedge_idx = false)
@test Simulacrum.connect!(ChX(symvars, ChE{Num}[]), symvars, ChE, Cycle)
@test Simulacrum.connect!(ChX(symvars, ChE{Num}[]), symvars, ChE, Cycle, to_hyperedge_idx = false)
chx = ChX(symvars, ChE{Num}[])
@test Simulacrum.connect!(chx, symvars, ChE, AllToAll)
@test nhe(chx) == 3
chx = ChX(symvars, ChE{Num}[])
@test Simulacrum.connect!(chx, symvars, ChE, Cycle)
@test nhe(chx) == 3
chx = ChX(symvars, ChE{Num}[])
@test Simulacrum.connect!(chx, symvars, ChE, Path)
@test nhe(chx) == 2

template_che = ChE(SpeciesSet(10, 2), SpeciesSet(20, 1), 3)
chx = ChX(intvars, ChE{Int64}[])
@test Simulacrum.connect!(chx, intvars, template_che, Cycle)
@test all(weight.(hyperedges(chx)) .== weight(template_che) .== 3)
chx = ChX(intvars, ChE{Int64}[])
@test Simulacrum.connect!(chx, intvars, template_che, AllToAll, to_hyperedge_idx = false)
@test all(weight.(hyperedges(chx)) .== weight(template_che) .== 3)
@test nhe(chx) == 6
template_che = ChE(SpeciesSet(X, 2), SpeciesSet(X, 1), 3)
chx = ChX(symvars, ChE{Num}[])
@test Simulacrum.connect!(chx, symvars, template_che, Cycle)
@test all(weight.(hyperedges(chx)) .== weight(template_che) .== 3)
chx = ChX(symvars, ChE{Num}[])
@test Simulacrum.connect!(chx, symvars, template_che, AllToAll, to_hyperedge_idx = false)
@test all(weight.(hyperedges(chx)) .== weight(template_che) .== 3)
@test nhe(chx) == 6

# same as connect but adds the source vertex in the target set, and so the number of vertices is same as connect + 1
@test all(length.(vertices.(couple(intvars, ChE, AllToAll))) .== 4) # 3 + 1
@test all(length.(vertices.(couple(symvars, ChE, AllToAll))) .== 4) # 3 + 1
@test all(length.(vertices.(couple(intvars, ChE, Cycle))) .== 3) # 2 + 1
@test all(length.(vertices.(couple(symvars, ChE, Cycle))) .== 3) # 2 + 1
@test all(length.(vertices.(couple(intvars, ChE, Path))) .== 3) # 2 + 1
@test all(length.(vertices.(couple(symvars, ChE, Path))) .== 3) # 2 + 1


text:
# constant and time-varying drifts

abstract type AbstractDrift end
getdt(drift::AbstractDrift) = drift.dt

struct ConstDrift <: AbstractDrift
    mu::Float64
    dt::Float64

    function ConstDrift(mu::Real, dt::Real)
        dt > zero(dt) || error("dt needs to be positive")
        return new(float(mu), float(dt))
    end
end
getmu(d::ConstDrift, n::Int) = d.mu
getmu(d::ConstDrift) = d.mu
getm(d::ConstDrift, n::Int) = (n-1) * d.dt * d.mu
getmaxn(d::ConstDrift) = typemax(Int)

struct VarDrift <: AbstractDrift
    mu::Vector{Float64}
    m::Vector{Float64}
    dt::Float64

    function VarDrift(mu::AbstractVector{T}, dt::Real) where T <: Real
        dt > zero(dt) || error("dt needs to be positive")
        length(mu) > 0 || error("mu needs to be of non-zero length")
        return new(mu, [0.0; cumsum(mu[1:(end-1)]) * float(dt)], float(dt))
    end
    function VarDrift(mu::AbstractVector{T}, dt::Real, maxt::Real) where T <: Real
        dt > zero(dt) || error("dt needs to be positive")
        maxt >= dt || error("maxt needs to be at least as large as dt")
        n = length(0:dt:maxt)
        nmu = length(mu)
        return nmu < n ?
            VarDrift([mu; fill(mu[end], n - nmu)], dt) :
            VarDrift(mu[1:n], dt)
    end
end
getmu(d::VarDrift, n::Int) = d.mu[n]
getm(d::VarDrift, n::Int) = d.m[n]
getmaxn(d::VarDrift) = length(d.mu)


text:
# moving regressions
@testset "Regressions" begin
    Random.seed!(SEED)
    @testset "Array" begin
        x = cumsum(randn(N))
        tmp = mlr_beta(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 2
        @test sum(isnan.(tmp)) != N
        tmp = mlr_slope(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        @test sum(isnan.(tmp)) != N
        tmp = mlr_intercept(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        @test sum(isnan.(tmp)) != N
        tmp = mlr(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        @test sum(isnan.(tmp)) != N
        tmp = mlr_se(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        @test sum(isnan.(tmp)) != N
        tmp = mlr_ub(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        @test sum(isnan.(tmp)) != N
        tmp = mlr_lb(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        @test sum(isnan.(tmp)) != N
        tmp = mlr_bands(tmp)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 3
        @test sum(isnan.(tmp)) != N
        tmp = mlr_rsq(x, adjusted=true)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        @test sum(isnan.(tmp)) != N
        tmp = mlr_rsq(x, adjusted=false)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        @test sum(isnan.(tmp)) != N
    end
    @testset "Temporal" begin
        x = TS(cumsum(randn(N)))
        # moving regressions
        tmp = mlr_beta(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 2
        tmp = mlr_slope(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        tmp = mlr_intercept(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        tmp = mlr(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        tmp = mlr_se(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        tmp = mlr_ub(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        tmp = mlr_lb(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        tmp = mlr_bands(tmp)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 3
        tmp = mlr_rsq(x, adjusted=true)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        tmp = mlr_rsq(x, adjusted=false)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
    end
end


text:
module DAQ

using NIDAQ
using DataStructures
import ThreadPools.@tspawnat
import NIDAQ: pulses!, pulses, pulsetiming, pulsetiming!, start, stop
export Recording

const NIDAQ_TID = Ref{Int64}(1)

if Threads.nthreads() > 1
    NIDAQ_TID[] = 2
end

mutable struct Recording
    task::DAQTask{AI}
    channels::Dict{Int64, String}
    fs::Int64
    refresh::Float64
    history_seconds::Float64
    history_samples::Int64
    signal::CircularBuffer{Float64}
    chan::Channel
    function Recording(; channels = Dict(2 => "XII", 3 => "Vm"), fs = 20000, refresh = 50, history_seconds = 8)
        history_samples = history_seconds * fs * length(channels)
        signal = CircularBuffer{Float64}(history_samples)
        fill!(signal, 0.0)
        chan = Channel(500)
        dev = DefaultDev()
        task = DAQTask{AI}()
        for (chan, name) in channels
            push!(task, dev.channels[AI][chan], alias = name, tcfg = DAQmx.Diff, range = (-1.0,1.0))
        end
        rec = new(task, channels, fs, refresh, history_seconds, history_samples, signal, chan)
        signal_updates(rec)
        finalizer(rec) do rec
            close(rec.chan)
        end
        return rec
    end
end

mutable struct Stimulus
    task::DAQTask{CO}
    function Stimulus()
        dev = DefaultDev()
        task = DAQTask{CO}()
        source = dev.channels[CO][1]
        pulses(task, source)
        return new(task)
    end
end

function signal_updates(rec::Recording)
    @async begin
        for x in rec.chan
            append!(rec.signal, vec(x))
        end
    end
end

function (rec::Recording)()
    @tspawnat NIDAQ_TID[] record!(rec.task, $(rec.fs), $(rec.refresh); feed = rec.chan)
end

fetch_history(rec::Recording) = fetch(@tspawnat NIDAQ_TID[] collect(rec.signal))

pulsetiming!(s::Stimulus, args...) = NIDAQ.pulsetiming!(s.task, args...)
pulsetiming(s::Stimulus, args...) = NIDAQ.pulsetiming(s.task, args...)
stop(s::Stimulus) = NIDAQ.stop(s.task)

end # module