text:
# LLVM IR optimization

function optimize!(job::CompilerJob, mod::LLVM.Module)
    tm = llvm_machine(job.target)

    function initialize!(pm)
        add_library_info!(pm, triple(mod))
        add_transform_info!(pm, tm)
    end

    global current_job
    current_job = job

    # Julia-specific optimizations
    #
    # NOTE: we need to use multiple distinct pass managers to force pass ordering;
    #       intrinsics should never get lowered before Julia has optimized them.

    ModulePassManager() do pm
        initialize!(pm)
        ccall(:jl_add_optimization_passes, Cvoid,
                (LLVM.API.LLVMPassManagerRef, Cint, Cint),
                pm, Base.JLOptions().opt_level, #=lower_intrinsics=# 0)
        run!(pm, mod)
    end

    ModulePassManager() do pm
        initialize!(pm)

        # lower intrinsics
        add!(pm, FunctionPass("LowerGCFrame", lower_gc_frame!))
        aggressive_dce!(pm) # remove dead uses of ptls
        add!(pm, ModulePass("LowerPTLS", lower_ptls!))

        # the Julia GC lowering pass also has some clean-up that is required
        late_lower_gc_frame!(pm)

        remove_julia_addrspaces!(pm)

        run!(pm, mod)
    end

    # target-specific optimizations
    ModulePassManager() do pm
        initialize!(pm)

        # Julia's operand bundles confuse the inliner, so repeat here now they are gone.
        # FIXME: we should fix the inliner so that inlined code gets optimized early-on
        always_inliner!(pm)

        add_optimization_passes!(job, pm)

        run!(pm, mod)
    end

    # we compile a module containing the entire call graph,
    # so perform some interprocedural optimizations.
    #
    # for some reason, these passes need to be distinct from the regular optimization chain,
    # or certain values (such as the constant arrays used to populare llvm.compiler.user ad
    # part of the LateLowerGCFrame pass) aren't collected properly.
    #
    # these might not always be safe, as Julia's IR metadata isn't designed for IPO.
    ModulePassManager() do pm
        dead_arg_elimination!(pm)   # parent doesn't use return value --> ret void

        run!(pm, mod)
    end

    return
end

## lowering intrinsics

# lower object allocations to to PTX malloc
#
# this is a PoC implementation that is very simple: allocate, and never free. it also runs
# _before_ Julia's GC lowering passes, so we don't get to use the results of its analyses.
# when we ever implement a more potent GC, we will need those results, but the relevant pass
# is currently very architecture/CPU specific: hard-coded pool sizes, TLS references, etc.
# such IR is hard to clean-up, so we probably will need to have the GC lowering pass emit
# lower-level intrinsics which then can be lowered to architecture-specific code.
function lower_gc_frame!(fun::LLVM.Function)
    job = current_job::CompilerJob
    mod = LLVM.parent(fun)
    ctx = context(fun)
    changed = false

    # plain alloc
    if haskey(functions(mod), "julia.gc_alloc_obj")
        alloc_obj = functions(mod)["julia.gc_alloc_obj"]
        alloc_obj_ft = eltype(llvmtype(alloc_obj))
        T_prjlvalue = return_type(alloc_obj_ft)
        T_pjlvalue = convert(LLVMType, Any, ctx; allow_boxed=true)

        for use in uses(alloc_obj)
            call = user(use)::LLVM.CallInst

            # decode the call
            ops = collect(operands(call))
            sz = ops[2]

            # replace with PTX alloc_obj
            let builder = Builder(ctx)
                position!(builder, call)
                ptr = call!(builder, Runtime.get(:gc_pool_alloc), [sz])
                replace_uses!(call, ptr)
                dispose(builder)
            end

            unsafe_delete!(LLVM.parent(call), call)

            changed = true
        end

        @compiler_assert isempty(uses(alloc_obj)) job
    end

    # we don't care about write barriers
    if haskey(functions(mod), "julia.write_barrier")
        barrier = functions(mod)["julia.write_barrier"]

        for use in uses(barrier)
            call = user(use)::LLVM.CallInst
            unsafe_delete!(LLVM.parent(call), call)
            changed = true
        end

        @compiler_assert isempty(uses(barrier)) job
    end

    return changed
end

# lower the `julia.ptls_states` intrinsic by removing it, since it is GPU incompatible.
#
# this assumes and checks that the TLS is unused, which should be the case for most GPU code
# after lowering the GC intrinsics to TLS-less code and having run DCE.
#
# TODO: maybe don't have Julia emit actual uses of the TLS, but use intrinsics instead,
#       making it easier to remove or reimplement that functionality here.
function lower_ptls!(mod::LLVM.Module)
    job = current_job::CompilerJob
    changed = false

    if haskey(functions(mod), "julia.ptls_states")
        ptls_getter = functions(mod)["julia.ptls_states"]

        for use in uses(ptls_getter)
            val = user(use)
            if !isempty(uses(val))
                error("Thread local storage is not implemented")
            end
            unsafe_delete!(LLVM.parent(val), val)
            changed = true
        end

        @compiler_assert isempty(uses(ptls_getter)) job
     end

    return changed
end


text:
function integrate_scenario_radau(rr::RadauIntegrator{MechanismScenario{Dual{Nothing,Float64,NC}},NX,NC,NR,NSM};
        t_start::Float64=0.0, t_final::Float64=1.0, max_steps::Int64=1000, is_disp_count::Bool=false) where {NX,NC,NR,NSM}

    x = get_state(rr.de_object)
    data_state = zeros(max_steps + 1, NX)
    data_time = zeros(max_steps + 1)
    data_state[1, :] = x
    return integrate_scenario_radau(rr, data_state, data_time, t_final=t_final, is_disp_count=is_disp_count)
end

function integrate_scenario_radau(rr::RadauIntegrator{MechanismScenario{Dual{Nothing,Float64,NC}},NX,NC,NR,NSM},
        data_state::Matrix{Float64}, data_time::Vector{Float64}; t_final::Float64=1.0,
        is_disp_count::Bool=false) where {NX,NC,NR,NSM}

    mech_scen = rr.de_object
    s1_state, s2_state = size(data_state)
    s_time = length(data_time)
    (s2_state == NX) || error("The length of x ($(length(x))) is different from the number of variables in the scenario ($n_dof). Did you forget the state variables for bristle friction?")
    (s1_state == s_time) || error("data_state and data_time are different lengths")
    t_cumulative = data_time[1]
    x = data_state[1, :]
    for k = 2:s1_state
        is_disp_count && println(k)
        if mech_scen.discrete_controller != nothing
            while mech_scen.discrete_controller.t_last <= t_cumulative
                mech_scen.discrete_controller.t_last += mech_scen.discrete_controller.dt
                mech_scen.discrete_controller.control(x, mech_scen, t_cumulative)
            end
        end
        h, x = solveRadau(rr, x, t_cumulative)
        principal_value!(mech_scen, x)
        t_cumulative += h
        data_time[k] = t_cumulative
        data_state[k, :] = x
        if t_final < t_cumulative
            return data_time[1:k], data_state[1:k, :]
        end
    end
    return data_time, data_state
end

ext: jl


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

"""
    AddProfileKey()

Associates a new key value with a specific profile, such as a Contact Trace Record (CTR) ContactId. A profile object can have a single unique key and any number of additional keys that can be used to identify the profile that it belongs to.

# Required Parameters
- `DomainName`: The unique name of the domain.
- `KeyName`: A searchable identifier of a customer profile.
- `ProfileId`: The unique identifier of a customer profile.
- `Values`: A list of key values.

"""
add_profile_key(DomainName, KeyName, ProfileId, Values; aws_config::AWSConfig=global_aws_config()) = customer_profiles("POST", "/domains/$(DomainName)/profiles/keys", Dict{String, Any}("KeyName"=>KeyName, "ProfileId"=>ProfileId, "Values"=>Values); aws_config=aws_config)
add_profile_key(DomainName, KeyName, ProfileId, Values, args::AbstractDict{String, <:Any}; aws_config::AWSConfig=global_aws_config()) = customer_profiles("POST", "/domains/$(DomainName)/profiles/keys", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("KeyName"=>KeyName, "ProfileId"=>ProfileId, "Values"=>Values), args)); aws_config=aws_config)

"""
    CreateDomain()

Creates a domain, which is a container for all customer data, such as customer profile attributes, object types, profile keys, and encryption keys. You can create multiple domains, and each domain can have multiple third-party integrations. Each Amazon Connect instance can be associated with only one domain. Multiple Amazon Connect instances can be associated with one domain.

# Required Parameters
- `DefaultExpirationDays`: The default number of days until the data within the domain expires.
- `DomainName`: The unique name of the domain.

# Optional Parameters
- `DeadLetterQueueUrl`: The URL of the SQS dead letter queue, which is used for reporting errors associated with ingesting data from third party applications. You must set up a policy on the DeadLetterQueue for the SendMessage operation to enable Amazon Connect Customer Profiles to send messages to the DeadLetterQueue.
- `DefaultEncryptionKey`: The default encryption key, which is an AWS managed key, is used when no specific type of encryption key is specified. It is used to encrypt all data before it is placed in permanent or semi-permanent storage.
- `Tags`: The tags used to organize, track, or control access for this resource.
"""
create_domain(DefaultExpirationDays, DomainName; aws_config::AWSConfig=global_aws_config()) = customer_profiles("POST", "/domains/$(DomainName)", Dict{String, Any}("DefaultExpirationDays"=>DefaultExpirationDays); aws_config=aws_config)
create_domain(DefaultExpirationDays, DomainName, args::AbstractDict{String, <:Any}; aws_config::AWSConfig=global_aws_config()) = customer_profiles("POST", "/domains/$(DomainName)", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("DefaultExpirationDays"=>DefaultExpirationDays), args)); aws_config=aws_config)

"""
    CreateProfile()

Creates a standard profile. A standard profile represents the following attributes for a customer profile in a domain.

# Required Parameters
- `DomainName`: The unique name of the domain.

# Optional Parameters
- `AccountNumber`: A unique account number that you have given to the customer.
- `AdditionalInformation`: Any additional information relevant to the customer's profile.
- `Address`: A generic address associated with the customer that is not mailing, shipping, or billing.
- `Attributes`: A key value pair of attributes of a customer profile.
- `BillingAddress`: The customer’s billing address.
- `BirthDate`: The customer’s birth date.
- `BusinessEmailAddress`: The customer’s business email address.
- `BusinessName`: The name of the customer’s business.
- `BusinessPhoneNumber`: The customer’s business phone number.
- `EmailAddress`: The customer's email address, which has not been specified as a personal or business address.
- `FirstName`: The customer’s first name.
- `Gender`: The gender with which the customer identifies.
- `HomePhoneNumber`: The customer’s home phone number.
- `LastName`: The customer’s last name.
- `MailingAddress`: The customer’s mailing address.
- `MiddleName`: The customer’s middle name.
- `MobilePhoneNumber`: The customer’s mobile phone number.
- `PartyType`: The type of profile used to describe the customer.
- `PersonalEmailAddress`: The customer’s personal email address.
- `PhoneNumber`: The customer's phone number, which has not been specified as a mobile, home, or business number.
- `ShippingAddress`: The customer’s shipping address.
"""
create_profile(DomainName; aws_config::AWSConfig=global_aws_config()) = customer_profiles("POST", "/domains/$(DomainName)/profiles"; aws_config=aws_config)
create_profile(DomainName, args::AbstractDict{String, <:Any}; aws_config::AWSConfig=global_aws_config()) = customer_profiles("POST", "/domains/$(DomainName)/profiles", args; aws_config=aws_config)

"""
    DeleteDomain()

Deletes a specific domain and all of its customer data, such as customer profile attributes and their related objects.

# Required Parameters
- `DomainName`: The unique name of the domain.

"""
delete_domain(DomainName; aws_config::AWSConfig=global_aws_config()) = customer_profiles("DELETE", "/domains/$(DomainName)"; aws_config=aws_config)
delete_domain(DomainName, args::AbstractDict{String, <:Any}; aws_config::AWSConfig=global_aws_config()) = customer_profiles("DELETE", "/domains/$(DomainName)", args; aws_config=aws_config)

"""
    DeleteIntegration()

Removes an integration from a specific domain.

# Required Parameters
- `DomainName`: The unique name of the domain.

# Optional Parameters
- `Uri`: The URI of the S3 bucket or any other type of data source.
"""
delete_integration(DomainName; aws_config::AWSConfig=global_aws_config()) = customer_profiles("POST", "/domains/$(DomainName)/integrations/delete"; aws_config=aws_config)
delete_integration(DomainName, args::AbstractDict{String, <:Any}; aws_config::AWSConfig=global_aws_config()) = customer_profiles("POST", "/domains/$(DomainName)/integrations/delete", args; aws_config=aws_config)

"""
    DeleteProfile()

Deletes the standard customer profile and all data pertaining to the profile.

# Required Parameters
- `DomainName`: The unique name of the domain.
- `ProfileId`: The unique identifier of a customer profile.

"""
delete_profile(DomainName, ProfileId; aws_config::AWSConfig=global_aws_config()) = customer_profiles("POST", "/domains/$(DomainName)/profiles/delete", Dict{String, Any}("ProfileId"=>ProfileId); aws_config=aws_config)
delete_profile(DomainName, ProfileId, args::AbstractDict{String, <:Any}; aws_config::AWSConfig=global_aws_config()) = customer_profiles("POST", "/domains/$(DomainName)/profiles/delete", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("ProfileId"=>ProfileId), args)); aws_config=aws_config)

"""
    DeleteProfileKey()

Removes a searchable key from a customer profile.

# Required Parameters
- `DomainName`: The unique name of the domain.
- `KeyName`: A searchable identifier of a customer profile.
- `ProfileId`: The unique identifier of a customer profile.
- `Values`: A list of key values.

"""
delete_profile_key(DomainName, KeyName, ProfileId, Values; aws_config::AWSConfig=global_aws_config()) = customer_profiles("POST", "/domains/$(DomainName)/profiles/keys/delete", Dict{String, Any}("KeyName"=>KeyName, "ProfileId"=>ProfileId, "Values"=>Values); aws_config=aws_config)
delete_profile_key(DomainName, KeyName, ProfileId, Values, args::AbstractDict{String, <:Any}; aws_config::AWSConfig=global_aws_config()) = customer_profiles("POST", "/domains/$(DomainName)/profiles/keys/delete", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("KeyName"=>KeyName, "ProfileId"=>ProfileId, "Values"=>Values), args)); aws_config=aws_config)

"""
    DeleteProfileObject()

Removes an object associated with a profile of a given ProfileObjectType.

# Required Parameters
- `DomainName`: The unique name of the domain.
- `ObjectTypeName`: The name of the profile object type.
- `ProfileId`: The unique identifier of a customer profile.
- `ProfileObjectUniqueKey`: The unique identifier of the profile object generated by the service.

"""
delete_profile_object(DomainName, ObjectTypeName, ProfileId, ProfileObjectUniqueKey; aws_config::AWSConfig=global_aws_config()) = customer_profiles("POST", "/domains/$(DomainName)/profiles/objects/delete", Dict{String, Any}("ObjectTypeName"=>ObjectTypeName, "ProfileId"=>ProfileId, "ProfileObjectUniqueKey"=>ProfileObjectUniqueKey); aws_config=aws_config)
delete_profile_object(DomainName, ObjectTypeName, ProfileId, ProfileObjectUniqueKey, args::AbstractDict{String, <:Any}; aws_config::AWSConfig=global_aws_config()) = customer_profiles("POST", "/domains/$(DomainName)/profiles/objects/delete", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("ObjectTypeName"=>ObjectTypeName, "ProfileId"=>ProfileId, "ProfileObjectUniqueKey"=>ProfileObjectUniqueKey), args)); aws_config=aws_config)

"""
    DeleteProfileObjectType()

Removes a ProfileObjectType from a specific domain as well as removes all the ProfileObjects of that type. It also disables integrations from this specific ProfileObjectType. In addition, it scrubs all of the fields of the standard profile that were populated from this ProfileObjectType.

# Required Parameters
- `DomainName`: The unique name of the domain.
- `ObjectTypeName`: The name of the profile object type.

"""
delete_profile_object_type(DomainName, ObjectTypeName; aws_config::AWSConfig=global_aws_config()) = customer_profiles("DELETE", "/domains/$(DomainName)/object-types/$(ObjectTypeName)"; aws_config=aws_config)
delete_profile_object_type(DomainName, ObjectTypeName, args::AbstractDict{String, <:Any}; aws_config::AWSConfig=global_aws_config()) = customer_profiles("DELETE", "/domains/$(DomainName)/object-types/$(ObjectTypeName)", args; aws_config=aws_config)

"""
    GetDomain()

Returns information about a specific domain.

# Required Parameters
- `DomainName`: A unique name for the domain.

"""
get_domain(DomainName; aws_config::AWSConfig=global_aws_config()) = customer_profiles("GET", "/domains/$(DomainName)"; aws_config=aws_config)
get_domain(DomainName, args::AbstractDict{String, <:Any}; aws_config::AWSConfig=global_aws_config()) = customer_profiles("GET", "/domains/$(DomainName)", args; aws_config=aws_config)

"""
    GetIntegration()

Returns an integration for a domain.

# Required Parameters
- `DomainName`: The unique name of the domain.

# Optional Parameters
- `Uri`: The URI of the S3 bucket or any other type of data source.
"""
get_integration(DomainName; aws_config::AWSConfig=global_aws_config()) = customer_profiles("POST", "/domains/$(DomainName)/integrations"; aws_config=aws_config)
get_integration(DomainName, args::AbstractDict{String, <:Any}; aws_config::AWSConfig=global_aws_config()) = customer_profiles("POST", "/domains/$(DomainName)/integrations", args; aws_config=aws_config)

"""
    GetProfileObjectType()

Returns the object types for a specific domain.

# Required Parameters
- `DomainName`: The unique name of the domain.
- `ObjectTypeName`: The name of the profile object type.

"""
get_profile_object_type(DomainName, ObjectTypeName; aws_config::AWSConfig=global_aws_config()) = customer_profiles("GET", "/domains/$(DomainName)/object-types/$(ObjectTypeName)"; aws_config=aws_config)
get_profile_object_type(DomainName, ObjectTypeName, args::AbstractDict{String, <:Any}; aws_config::AWSConfig=global_aws_config()) = customer_profiles("GET", "/domains/$(DomainName)/object-types/$(ObjectTypeName)", args; aws_config=aws_config)

"""
    GetProfileObjectTypeTemplate()

Returns the template information for a specific object type. A template is a predefined ProfileObjectType, such as “Salesforce-Account” or “Salesforce-Contact.” When a user sends a ProfileObject, using the PutProfileObject API, with an ObjectTypeName that matches one of the TemplateIds, it uses the mappings from the template.

# Required Parameters
- `TemplateId`: A unique identifier for the object template.

"""
get_profile_object_type_template(TemplateId; aws_config::AWSConfig=global_aws_config()) = customer_profiles("GET", "/templates/$(TemplateId)"; aws_config=aws_config)
get_profile_object_type_template(TemplateId, args::AbstractDict{String, <:Any}; aws_config::AWSConfig=global_aws_config()) = customer_profiles("GET", "/templates/$(TemplateId)", args; aws_config=aws_config)

"""
    ListAccountIntegrations()

Lists all of the integrations associated to a specific URI in the AWS account.

# Required Parameters
- `Uri`: The URI of the S3 bucket or any other type of data source.

# Optional Parameters
- `max-results`: The maximum number of objects returned per page.
- `next-token`: The pagination token from the previous ListAccountIntegrations API call.
"""
list_account_integrations(Uri; aws_config::AWSConfig=global_aws_config()) = customer_profiles("POST", "/integrations", Dict{String, Any}("Uri"=>Uri); aws_config=aws_config)
list_account_integrations(Uri, args::AbstractDict{String, <:Any}; aws_config::AWSConfig=global_aws_config()) = customer_profiles("POST", "/integrations", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("Uri"=>Uri), args)); aws_config=aws_config)

"""
    ListDomains()

Returns a list of all the domains for an AWS account that have been created.

# Optional Parameters
- `max-results`: The maximum number of objects returned per page.
- `next-token`: The pagination token from the previous ListDomain API call.
"""
list_domains(; aws_config::AWSConfig=global_aws_config()) = customer_profiles("GET", "/domains"; aws_config=aws_config)
list_domains(args::AbstractDict{String, Any}; aws_config::AWSConfig=global_aws_config()) = customer_profiles("GET", "/domains", args; aws_config=aws_config)

"""
    ListIntegrations()

Lists all of the integrations in your domain.

# Required Parameters
- `DomainName`: The unique name of the domain.

# Optional Parameters
- `max-results`: The maximum number of objects returned per page.
- `next-token`: The pagination token from the previous ListIntegrations API call.
"""
list_integrations(DomainName; aws_config::AWSConfig=global_aws_config()) = customer_profiles("GET", "/domains/$(DomainName)/integrations"; aws_config=aws_config)
list_integrations(DomainName, args::AbstractDict{String, <:Any}; aws_config::AWSConfig=global_aws_config()) = customer_profiles("GET", "/domains/$(DomainName)/integrations", args; aws_config=aws_config)

"""
    ListProfileObjectTypeTemplates()

Lists all of the template information for object types.

# Optional Parameters
- `max-results`: The maximum number of objects returned per page.
- `next-token`: The pagination token from the previous ListObjectTypeTemplates API call.
"""
list_profile_object_type_templates(; aws_config::AWSConfig=global_aws_config()) = customer_profiles("GET", "/templates"; aws_config=aws_config)
list_profile_object_type_templates(args::AbstractDict{String, Any}; aws_config::AWSConfig=global_aws_config()) = customer_profiles("GET", "/templates", args; aws_config=aws_config)

"""
    ListProfileObjectTypes()

Lists all of the templates available within the service.

# Required Parameters
- `DomainName`: The unique name of the domain.

# Optional Parameters
- `max-results`: The maximum number of objects returned per page.
- `next-token`: Identifies the next page of results to return.
"""
list_profile_object_types(DomainName; aws_config::AWSConfig=global_aws_config()) = customer_profiles("GET", "/domains/$(DomainName)/object-types"; aws_config=aws_config)
list_profile_object_types(DomainName, args::AbstractDict{String, <:Any}; aws_config::AWSConfig=global_aws_config()) = customer_profiles("GET", "/domains/$(DomainName)/object-types", args; aws_config=aws_config)

"""
    ListProfileObjects()

Returns a list of objects associated with a profile of a given ProfileObjectType.

# Required Parameters
- `DomainName`: The unique name of the domain.
- `ObjectTypeName`: The name of the profile object type.
- `ProfileId`: The unique identifier of a customer profile.

# Optional Parameters
- `max-results`: The maximum number of objects returned per page.
- `next-token`: The pagination token from the previous call to ListProfileObjects.
"""
list_profile_objects(DomainName, ObjectTypeName, ProfileId; aws_config::AWSConfig=global_aws_config()) = customer_profiles("POST", "/domains/$(DomainName)/profiles/objects", Dict{String, Any}("ObjectTypeName"=>ObjectTypeName, "ProfileId"=>ProfileId); aws_config=aws_config)
list_profile_objects(DomainName, ObjectTypeName, ProfileId, args::AbstractDict{String, <:Any}; aws_config::AWSConfig=global_aws_config()) = customer_profiles("POST", "/domains/$(DomainName)/profiles/objects", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("ObjectTypeName"=>ObjectTypeName, "ProfileId"=>ProfileId), args)); aws_config=aws_config)

"""
    ListTagsForResource()

Displays the tags associated with an Amazon Connect Customer Profiles resource. In Connect Customer Profiles, domains, profile object types, and integrations can be tagged.

# Required Parameters
- `resourceArn`: The ARN of the resource for which you want to view tags.

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

"""
    PutIntegration()

Adds an integration between the service and a third-party service, which includes Amazon AppFlow and Amazon Connect. An integration can belong to only one domain.

# Required Parameters
- `DomainName`: The unique name of the domain.
- `ObjectTypeName`: The name of the profile object type.
- `Uri`: The URI of the S3 bucket or any other type of data source.

# Optional Parameters
- `Tags`: The tags used to organize, track, or control access for this resource.
"""
put_integration(DomainName, ObjectTypeName, Uri; aws_config::AWSConfig=global_aws_config()) = customer_profiles("PUT", "/domains/$(DomainName)/integrations", Dict{String, Any}("ObjectTypeName"=>ObjectTypeName, "Uri"=>Uri); aws_config=aws_config)
put_integration(DomainName, ObjectTypeName, Uri, args::AbstractDict{String, <:Any}; aws_config::AWSConfig=global_aws_config()) = customer_profiles("PUT", "/domains/$(DomainName)/integrations", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("ObjectTypeName"=>ObjectTypeName, "Uri"=>Uri), args)); aws_config=aws_config)

"""
    PutProfileObject()

Adds additional objects to customer profiles of a given ObjectType. When adding a specific profile object, like a Contact Trace Record (CTR), an inferred profile can get created if it is not mapped to an existing profile. The resulting profile will only have a phone number populated in the standard ProfileObject. Any additional CTRs with the same phone number will be mapped to the same inferred profile. When a ProfileObject is created and if a ProfileObjectType already exists for the ProfileObject, it will provide data to a standard profile depending on the ProfileObjectType definition. PutProfileObject needs an ObjectType, which can be created using PutProfileObjectType.

# Required Parameters
- `DomainName`: The unique name of the domain.
- `Object`: A string that is serialized from a JSON object.
- `ObjectTypeName`: The name of the profile object type.

"""
put_profile_object(DomainName, Object, ObjectTypeName; aws_config::AWSConfig=global_aws_config()) = customer_profiles("PUT", "/domains/$(DomainName)/profiles/objects", Dict{String, Any}("Object"=>Object, "ObjectTypeName"=>ObjectTypeName); aws_config=aws_config)
put_profile_object(DomainName, Object, ObjectTypeName, args::AbstractDict{String, <:Any}; aws_config::AWSConfig=global_aws_config()) = customer_profiles("PUT", "/domains/$(DomainName)/profiles/objects", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("Object"=>Object, "ObjectTypeName"=>ObjectTypeName), args)); aws_config=aws_config)

"""
    PutProfileObjectType()

Defines a ProfileObjectType.

# Required Parameters
- `Description`: Description of the profile object type.
- `DomainName`: The unique name of the domain.
- `ObjectTypeName`: The name of the profile object type.

# Optional Parameters
- `AllowProfileCreation`: Indicates whether a profile should be created when data is received if one doesn’t exist for an object of this type. The default is FALSE. If the AllowProfileCreation flag is set to FALSE, then the service tries to fetch a standard profile and associate this object with the profile. If it is set to TRUE, and if no match is found, then the service creates a new standard profile.
- `EncryptionKey`: The customer-provided key to encrypt the profile object that will be created in this profile object type.
- `ExpirationDays`: The number of days until the data in the object expires.
- `Fields`: A map of the name and ObjectType field.
- `Keys`: A list of unique keys that can be used to map data to the profile.
- `Tags`: The tags used to organize, track, or control access for this resource.
- `TemplateId`: A unique identifier for the object template.
"""
put_profile_object_type(Description, DomainName, ObjectTypeName; aws_config::AWSConfig=global_aws_config()) = customer_profiles("PUT", "/domains/$(DomainName)/object-types/$(ObjectTypeName)", Dict{String, Any}("Description"=>Description); aws_config=aws_config)
put_profile_object_type(Description, DomainName, ObjectTypeName, args::AbstractDict{String, <:Any}; aws_config::AWSConfig=global_aws_config()) = customer_profiles("PUT", "/domains/$(DomainName)/object-types/$(ObjectTypeName)", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("Description"=>Description), args)); aws_config=aws_config)

"""
    SearchProfiles()

Searches for profiles within a specific domain name using name, phone number, email address, account number, or a custom defined index.

# Required Parameters
- `DomainName`: The unique name of the domain.
- `KeyName`: A searchable identifier of a customer profile. The predefined keys you can use to search include: _account, _profileId, _fullName, _phone, _email, _ctrContactId, _marketoLeadId, _salesforceAccountId, _salesforceContactId, _zendeskUserId, _zendeskExternalId, _serviceNowSystemId.
- `Values`: A list of key values.

# Optional Parameters
- `max-results`: The maximum number of objects returned per page.
- `next-token`: The pagination token from the previous SearchProfiles API call.
"""
search_profiles(DomainName, KeyName, Values; aws_config::AWSConfig=global_aws_config()) = customer_profiles("POST", "/domains/$(DomainName)/profiles/search", Dict{String, Any}("KeyName"=>KeyName, "Values"=>Values); aws_config=aws_config)
search_profiles(DomainName, KeyName, Values, args::AbstractDict{String, <:Any}; aws_config::AWSConfig=global_aws_config()) = customer_profiles("POST", "/domains/$(DomainName)/profiles/search", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("KeyName"=>KeyName, "Values"=>Values), args)); aws_config=aws_config)

"""
    TagResource()

Assigns one or more tags (key-value pairs) to the specified Amazon Connect Customer Profiles resource. Tags can help you organize and categorize your resources. You can also use them to scope user permissions by granting a user permission to access or change only resources with certain tag values. In Connect Customer Profiles, domains, profile object types, and integrations can be tagged. Tags don't have any semantic meaning to AWS and are interpreted strictly as strings of characters. You can use the TagResource action with a resource that already has tags. If you specify a new tag key, this tag is appended to the list of tags associated with the resource. If you specify a tag key that is already associated with the resource, the new tag value that you specify replaces the previous value for that tag. You can associate as many as 50 tags with a resource.

# Required Parameters
- `resourceArn`: The ARN of the resource that you're adding tags to.
- `tags`: The tags used to organize, track, or control access for this resource.

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

"""
    UntagResource()

Removes one or more tags from the specified Amazon Connect Customer Profiles resource. In Connect Customer Profiles, domains, profile object types, and integrations can be tagged.

# Required Parameters
- `resourceArn`: The ARN of the resource from which you are removing tags.
- `tagKeys`: The list of tag keys to remove from the resource.

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

"""
    UpdateDomain()

Updates the properties of a domain, including creating or selecting a dead letter queue or an encryption key. Once a domain is created, the name can’t be changed.

# Required Parameters
- `DomainName`: The unique name for the domain.

# Optional Parameters
- `DeadLetterQueueUrl`: The URL of the SQS dead letter queue, which is used for reporting errors associated with ingesting data from third party applications. If specified as an empty string, it will clear any existing value. You must set up a policy on the DeadLetterQueue for the SendMessage operation to enable Amazon Connect Customer Profiles to send messages to the DeadLetterQueue.
- `DefaultEncryptionKey`: The default encryption key, which is an AWS managed key, is used when no specific type of encryption key is specified. It is used to encrypt all data before it is placed in permanent or semi-permanent storage. If specified as an empty string, it will clear any existing value.
- `DefaultExpirationDays`: The default number of days until the data within the domain expires.
- `Tags`: The tags used to organize, track, or control access for this resource.
"""
update_domain(DomainName; aws_config::AWSConfig=global_aws_config()) = customer_profiles("PUT", "/domains/$(DomainName)"; aws_config=aws_config)
update_domain(DomainName, args::AbstractDict{String, <:Any}; aws_config::AWSConfig=global_aws_config()) = customer_profiles("PUT", "/domains/$(DomainName)", args; aws_config=aws_config)

"""
    UpdateProfile()

Updates the properties of a profile. The ProfileId is required for updating a customer profile. When calling the UpdateProfile API, specifying an empty string value means that any existing value will be removed. Not specifying a string value means that any value already there will be kept.

# Required Parameters
- `DomainName`: The unique name of the domain.
- `ProfileId`: The unique identifier of a customer profile.

# Optional Parameters
- `AccountNumber`: A unique account number that you have given to the customer.
- `AdditionalInformation`: Any additional information relevant to the customer's profile.
- `Address`: A generic address associated with the customer that is not mailing, shipping, or billing.
- `Attributes`: A key value pair of attributes of a customer profile.
- `BillingAddress`: The customer’s billing address.
- `BirthDate`: The customer’s birth date.
- `BusinessEmailAddress`: The customer’s business email address.
- `BusinessName`: The name of the customer’s business.
- `BusinessPhoneNumber`: The customer’s business phone number.
- `EmailAddress`: The customer's email address, which has not been specified as a personal or business address.
- `FirstName`: The customer’s first name.
- `Gender`: The gender with which the customer identifies.
- `HomePhoneNumber`: The customer’s home phone number.
- `LastName`: The customer’s last name.
- `MailingAddress`: The customer’s mailing address.
- `MiddleName`: The customer’s middle name.
- `MobilePhoneNumber`: The customer’s mobile phone number.
- `PartyType`: The type of profile used to describe the customer.
- `PersonalEmailAddress`: The customer’s personal email address.
- `PhoneNumber`: The customer's phone number, which has not been specified as a mobile, home, or business number.
- `ShippingAddress`: The customer’s shipping address.
"""
update_profile(DomainName, ProfileId; aws_config::AWSConfig=global_aws_config()) = customer_profiles("PUT", "/domains/$(DomainName)/profiles", Dict{String, Any}("ProfileId"=>ProfileId); aws_config=aws_config)
update_profile(DomainName, ProfileId, args::AbstractDict{String, <:Any}; aws_config::AWSConfig=global_aws_config()) = customer_profiles("PUT", "/domains/$(DomainName)/profiles", Dict{String, Any}(mergewith(_merge, Dict{String, Any}("ProfileId"=>ProfileId), args)); aws_config=aws_config)


text:
#! format: off
block_vec = Block([
    _T_START + Day(1) => [11, 12],
    _T_START + Day(2) => [21, 22],
    _T_START + Day(3) => [31, 32],
])

block_mat = Block([
    _T_START + Day(1) => [11 12; 111 112],
    _T_START + Day(2) => [21 22; 211 212],
    _T_START + Day(3) => [31 32; 311 312],
])

block_tup = Block([
    _T_START + Day(1) => (11, 12),
    _T_START + Day(2) => (21, 22),
    _T_START + Day(3) => (31, 32),
])

block_dict = Block([
    _T_START + Day(1) => Dict(:1 => 11, :2 => 12),
    _T_START + Day(2) => Dict(:1 => 21, :2 => 22),
    _T_START + Day(3) => Dict(:1 => 31, :2 => 32),
])

@testset "getindex" begin
    @testset "vector" begin
        n = block_node(block_vec)

        @test value_type(getindex(n, 1)) == Int64
        @test getindex(n, 1) === getindex(n, 1)
        @test getindex(n, 1) === n[1]

        @test _eval(n[1]) == Block([
            _T_START + Day(1) => 11,
            _T_START + Day(2) => 21,
            _T_START + Day(3) => 31,
        ])

        @test _eval(n[2]) == Block([
            _T_START + Day(1) => 12,
            _T_START + Day(2) => 22,
            _T_START + Day(3) => 32,
        ])

        @test getindex(n, :) === n[:]
        @test n[:] === n
    end

    @testset "matrix" begin
        n = block_node(block_mat)

        @test value_type(getindex(n, 1)) == Int64
        @test getindex(n, 1) === getindex(n, 1)
        @test getindex(n, 1) === n[1]

        @test _eval(n[1]) == Block([
            _T_START + Day(1) => 11,
            _T_START + Day(2) => 21,
            _T_START + Day(3) => 31,
        ])

        @test _eval(n[2]) == Block([
            _T_START + Day(1) => 111,
            _T_START + Day(2) => 211,
            _T_START + Day(3) => 311,
        ])

        @test value_type(getindex(n, 1, 2)) == Int64
        @test getindex(n, 1, 2) === getindex(n, 1, 2)
        @test getindex(n, 1, 2) === n[1, 2]

        @test _eval(n[1, 2]) == Block([
            _T_START + Day(1) => 12,
            _T_START + Day(2) => 22,
            _T_START + Day(3) => 32,
        ])

        @test value_type(getindex(n, 1, :)) == Vector{Int64}
        @test getindex(n, 1, :) === getindex(n, 1, :)
        @test getindex(n, 1, :) === n[1, :]

        @test _eval(n[1, :]) == Block([
            _T_START + Day(1) => [11, 12],
            _T_START + Day(2) => [21, 22],
            _T_START + Day(3) => [31, 32],
        ])
    end

    @testset "tuple" begin
        n = block_node(block_tup)

        @test value_type(getindex(n, 1)) == Int64
        @test getindex(n, 1) === getindex(n, 1)
        @test getindex(n, 1) === n[1]

        @test _eval(n[1]) == Block([
            _T_START + Day(1) => 11,
            _T_START + Day(2) => 21,
            _T_START + Day(3) => 31,
        ])

        @test getindex(n, :) === n[:]
        @test n[:] === n
    end

    @testset "dict" begin
        n = block_node(block_dict)

        @test value_type(getindex(n, :1)) == Int64
        @test getindex(n, :1) === getindex(n, :1)
        @test getindex(n, :1) === n[:1]

        @test _eval(n[:1]) == Block([
            _T_START + Day(1) => 11,
            _T_START + Day(2) => 21,
            _T_START + Day(3) => 31,
        ])
    end
end

@testset "vec" begin
    n = block_node(block_vec)
    @test vec(n) === n

    n = block_node(block_mat)
    @test vec(n) === vec(n)
    @test value_type(vec(n)) == Vector{Int64}

    @test _eval(vec(n)) == Block([
        _T_START + Day(1) => [11, 111, 12, 112],
        _T_START + Day(2) => [21, 211, 22, 212],
        _T_START + Day(3) => [31, 311, 32, 312],
    ])
end

#! format: on


text:
__precompile__()

module Iterators

using Compat
import Base: start, next, done, eltype, length, size

export
    takestrict,
    repeatedly,
    chain,
    product,
    distinct,
    partition,
    groupby,
    imap,
    subsets,
    iterate,
    nth,
    takenth,
    @itr

# iteratorsize is new in 0.5, declare it here for older versions. However,
# we do not actually support calling these, since the traits are not defined
if VERSION < v"0.5.0-dev+3305"
    function iteratorsize(v)
        error("Do not call this on older versions")
    end
else
    import Base: iteratorsize, SizeUnknown, IsInfinite, HasLength
end

# Some iterators have been moved into Base (and count has been renamed as well)

import Base: count
Base.@deprecate count(start::Number, step::Number) countfrom(start, step)
Base.@deprecate count(start::Number) countfrom(start)
Base.@deprecate count() countfrom()

# Iterate through the first n elements, throwing an exception if
# fewer than n items ar encountered.

immutable TakeStrict{I}
    xs::I
    n::Int
end
iteratorsize{T<:TakeStrict}(::Type{T}) = HasLength()

eltype{I}(::Type{TakeStrict{I}}) = eltype(I)

takestrict(xs, n::Int) = TakeStrict(xs, n)

start(it::TakeStrict) = (it.n, start(it.xs))

function next(it::TakeStrict, state)
    n, xs_state = state
    v, xs_state = next(it.xs, xs_state)
    return v, (n - 1, xs_state)
end

function done(it::TakeStrict, state)
    n, xs_state = state
    if n <= 0
        return true
    elseif done(it.xs, xs_state)
        throw(ArgumentError("In takestrict(xs, n), xs had fewer than n items to take."))
    else
        return false
    end
end

function length(it::TakeStrict)
    return it.n
end

# Repeat a function application n (or infinitely many) times.

immutable RepeatCall
    f::Function
    n::Int
end
iteratorsize{T<:RepeatCall}(::Type{T}) = HasLength()

length(it::RepeatCall) = it.n
repeatedly(f, n) = RepeatCall(f, n)

start(it::RepeatCall) = it.n
next(it::RepeatCall, state) = (it.f(), state - 1)
done(it::RepeatCall, state) = state <= 0

immutable RepeatCallForever
    f::Function
end
iteratorsize{T<:RepeatCallForever}(::Type{T}) = IsInfinite()

repeatedly(f) = RepeatCallForever(f)

start(it::RepeatCallForever) = nothing
next(it::RepeatCallForever, state) = (it.f(), nothing)
done(it::RepeatCallForever, state) = false

# Concatenate the output of n iterators

immutable Chain{T<:Tuple}
    xss::T
end

iteratorsize{T<:Chain}(::Type{T}) = SizeUnknown()

eltype{T}(::Type{Chain{T}}) = typejoin([eltype(t) for t in T.parameters]...)

chain(xss...) = Chain(xss)

function start(it::Chain)
    i = 1
    xs_state = nothing
    while i <= length(it.xss)
        xs_state = start(it.xss[i])
        if !done(it.xss[i], xs_state)
            break
        end
        i += 1
    end
    return i, xs_state
end

function next(it::Chain, state)
    i, xs_state = state
    v, xs_state = next(it.xss[i], xs_state)
    while done(it.xss[i], xs_state)
        i += 1
        if i > length(it.xss)
            break
        end
        xs_state = start(it.xss[i])
    end
    return v, (i, xs_state)
end

done(it::Chain, state) = state[1] > length(it.xss)

# Cartesian product as a sequence of tuples

immutable Product{T<:Tuple}
    xss::T
end

iteratorsize{T<:Product}(::Type{T}) = SizeUnknown()

eltype{T}(::Type{Product{T}}) = Tuple{map(eltype, T.parameters)...}
length(p::Product) = mapreduce(length, *, 1, p.xss)

product(xss...) = Product(xss)

function start(it::Product)
    n = length(it.xss)
    js = Any[start(xs) for xs in it.xss]
    for i = 1:n
        if done(it.xss[i], js[i])
            return js, nothing
        end
    end
    vs = Array(Any, n)
    for i = 1:n
        vs[i], js[i] = next(it.xss[i], js[i])
    end
    return js, vs
end

function next(it::Product, state)
    js = copy(state[1])
    vs = copy(state[2])
    ans = tuple(vs...)

    n = length(it.xss)
    for i in 1:n
        if !done(it.xss[i], js[i])
            vs[i], js[i] = next(it.xss[i], js[i])
            return ans, (js, vs)
        end

        js[i] = start(it.xss[i])
        vs[i], js[i] = next(it.xss[i], js[i])
    end
    ans, (js, nothing)
end

done(it::Product, state) = state[2] === nothing

# Filter out reccuring elements.

immutable Distinct{I}
    xs::I

    # Map elements to the index at which it was first seen, so given an iterator
    # state (index) we can test if an element has previously been observed.
    seen::Dict{Any, Int}

    Distinct(xs) = new(xs, Dict{Any, Int}())
end
iteratorsize{T<:Distinct}(::Type{T}) = SizeUnknown()

eltype{I}(::Type{Distinct{I}}) = eltype(I)

distinct{I}(xs::I) = Distinct{I}(xs)

function start(it::Distinct)
    start(it.xs), 1
end

function next(it::Distinct, state)
    s, i = state
    x, s = next(it.xs, s)
    it.seen[x] = i
    i += 1

    while !done(it.xs, s)
        y, t = next(it.xs, s)
        if !haskey(it.seen, y) || it.seen[y] >= i
            break
        end
        s = t
        i += 1
    end

    x, (s, i)
end

done(it::Distinct, state) = done(it.xs, state[1])

# Group output from at iterator into tuples.
# E.g.,
#   partition(count(1), 2) = (1,2), (3,4), (5,6) ...
#   partition(count(1), 2, 1) = (1,2), (2,3), (3,4) ...
#   partition(count(1), 2, 3) = (1,2), (4,5), (7,8) ...

immutable Partition{I, N}
    xs::I
    step::Int
end
iteratorsize{T<:Partition}(::Type{T}) = SizeUnknown()

eltype{I, N}(::Type{Partition{I, N}}) = NTuple{N, eltype(I)}

function partition{I}(xs::I, n::Int)
    Partition{I, n}(xs, n)
end

function partition{I}(xs::I, n::Int, step::Int)
    if step < 1
        throw(ArgumentError("Partition step must be at least 1."))
    end

    Partition{I, n}(xs, step)
end

function start{I, N}(it::Partition{I, N})
    p = Vector{eltype(I)}(N)
    s = start(it.xs)
    for i in 1:(N - 1)
        if done(it.xs, s)
            break
        end
        (p[i], s) = next(it.xs, s)
    end
    (s, p)
end

function next{I, N}(it::Partition{I, N}, state)
    (s, p0) = state
    (x, s) = next(it.xs, s)
    ans = p0; ans[end] = x

    p = similar(p0)
    overlap = max(0, N - it.step)
    for i in 1:overlap
        p[i] = ans[it.step + i]
    end

    # when step > n, skip over some elements
    for i in 1:max(0, it.step - N)
        if done(it.xs, s)
            break
        end
        (x, s) = next(it.xs, s)
    end

    for i in (overlap + 1):(N - 1)
        if done(it.xs, s)
            break
        end

        (x, s) = next(it.xs, s)
        p[i] = x
    end

    (tuple(ans...), (s, p))
end

done(it::Partition, state) = done(it.xs, state[1])

# Group output from an iterator based on a key function.
# Consecutive entries from the iterator with the same
# key value will be returned in a single array.
# Inspired by itertools.groupby in python.
# E.g.,
#   x = ["face", "foo", "bar", "book", "baz", "zzz"]
#   groupby(z -> z[1], x) =
#       ["face", "foo"]
#       ["bar", "book", "baz"]
#       ["zzz"]
immutable GroupBy{I}
    keyfunc::Function
    xs::I
end
iteratorsize{T<:GroupBy}(::Type{T}) = SizeUnknown()

# eltype{I}(it::GroupBy{I}) = I
eltype{I}(::Type{GroupBy{I}}) = Vector{eltype(I)}

function groupby(xs, keyfunc::Function)
    Base.warn_once("groupby(xs, keyfunc) should be groupby(keyfunc, xs)")
    groupby(keyfunc, xs)
end

function groupby(keyfunc, xs)
    GroupBy(keyfunc, xs)
end

function start(it::GroupBy)
    s = start(it.xs)
    prev_value = nothing
    prev_key = nothing
    return (s, (prev_key, prev_value))
end

function next{I}(it::GroupBy{I}, state)
    (s, (prev_key, prev_value)) = state
    values = Vector{eltype(I)}(0)
    # We had a left over value from the last time the key changed.
    if prev_value != nothing || prev_key != nothing
        push!(values, prev_value)
    end
    prev_value = nothing
    while !done(it.xs, s)
        (x, s) = next(it.xs, s)
        key = it.keyfunc(x)
        # Did the key change?
        if prev_key != nothing && key != prev_key
            prev_key = key
            prev_value = x
            break
        else
            push!(values, x)
        end
        prev_key = key
    end
    # We either reached the end of the input or the key changed,
    # either way emit what we have so far.
    return (values, (s, (prev_key, prev_value)))
end

function done(it::GroupBy, state)
    return state[2][2] == nothing && done(it.xs, state[1])
end

# Like map, except returns the output as an iterator.  The iterator
# is done when any of the input iterators have been exhausted.
# E.g.,
#   imap(+, count(), [1, 2, 3]) = 1, 3, 5 ...
immutable IMap
    mapfunc::Base.Callable
    xs::Vector{Any}
end
iteratorsize{T<:IMap}(::Type{T}) = SizeUnknown()

function imap(mapfunc, it1, its...)
    IMap(mapfunc, Any[it1, its...])
end

function start(it::IMap)
    map(start, it.xs)
end

function next(it::IMap, state)
    next_result = map(next, it.xs, state)
    return (
        it.mapfunc(map(x -> x[1], next_result)...),
        map(x -> x[2], next_result)
    )
end

function done(it::IMap, state)
    any(map(done, it.xs, state))
end

# Iterate over all subsets of a collection

immutable Subsets{C}
    xs::C
end
iteratorsize{T<:Subsets}(::Type{T}) = HasLength()

eltype{C}(::Type{Subsets{C}}) = Vector{eltype(C)}
length(it::Subsets) = 1 << length(it.xs)

function subsets(xs)
    Subsets(xs)
end

function start(it::Subsets)
    # one extra bit to indicated that we are at the end
    fill(false, length(it.xs) + 1)
end

function next(it::Subsets, state)
    ss = it.xs[state[1:end-1]]

    state = copy(state)
    state[1] = !state[1]
    for i in 2:length(state)
        if !state[i-1]
            state[i] = !state[i]
        else
            break
        end
    end

    (ss, state)
end

function done(it::Subsets, state)
    state[end]
end

# Iterate over all subsets of a collection with a given size

immutable Binomial{T}
    xs::Vector{T}
    n::Int64
    k::Int64
end
iteratorsize{T<:Binomial}(::Type{T}) = HasLength()

eltype{T}(::Type{Binomial{T}}) = Vector{T}
length(it::Binomial) = binomial(it.n,it.k)

subsets(xs,k) = Binomial(xs,length(xs),k)

type BinomialIterState
    idx::Vector{Int64}
    done::Bool
end

start(it::Binomial) = BinomialIterState(collect(Int64, 1:it.k), (it.k > it.n) ? true : false)

function next(it::Binomial, state::BinomialIterState)
    idx = state.idx
    set = it.xs[idx]
    i = it.k
    while(i>0)
        if idx[i] < it.n - it.k + i
            idx[i] += 1

            for j in 1:it.k-i
                idx[i+j] = idx[i] + j
            end

            break
        else
            i -= 1
        end
    end

    state.done = i==0

    return set, state
end

done(it::Binomial, state::BinomialIterState) = state.done

# nth : return the nth element in a collection

function nth(xs, n::Integer)
    n > 0 || throw(BoundsError(xs, n))
    # catch, if possible
    applicable(length, xs) && (n ≤ length(xs) || throw(BoundsError(xs, n)))
    s = start(xs)
    i = 0
    while !done(xs, s)
        (val, s) = next(xs, s)
        i += 1
        i == n && return val
    end
    # catch iterators with no length but actual finite size less then n
    throw(BoundsError(xs, n))
end

nth(xs::AbstractArray, n::Integer) = xs[n]

# takenth(xs,n): take every n'th element from xs

immutable TakeNth{I}
    xs::I
    interval::UInt
end
iteratorsize{I}(::Type{TakeNth{I}}) = iteratorsize(I)

length(x::TakeNth) = div(length(x.xs), x.interval)
size(x::TakeNth) = (length(x),)

function takenth(xs, interval::Integer)
    if interval <= 0
        throw(ArgumentError(string("expected interval to be 1 or more, ",
                                   "got $interval")))
    end
    TakeNth(xs, convert(UInt, interval))
end
eltype{I}(::Type{TakeNth{I}}) = eltype(I)

function start(it::TakeNth)
    i = 1
    state = start(it.xs)
    while i < it.interval && !done(it.xs, state)
        _, state = next(it.xs, state)
        i += 1
    end
    return state
end

function next(it::TakeNth, state)
    value, state = next(it.xs, state)
    i = 1
    while i < it.interval && !done(it.xs, state)
        _, state = next(it.xs, state)
        i += 1
    end
    return value, state
end

done(it::TakeNth, state) = done(it.xs, state)

immutable Iterate{T}
    f::Function
    seed::T
end
iteratorsize{T<:Iterate}(::Type{T}) = SizeUnknown()

iterate(f, seed) = Iterate(f, seed)
start(it::Iterate) = it.seed
next(it::Iterate, state) = (state, it.f(state))
done(it::Iterate, state) = (state==Union{})

using Base.Meta

## @itr macro for auto-inlining in for loops
#
# it dispatches on macros defined below

macro itr(ex)
    isexpr(ex, :for) || throw(ArgumentError("@itr macro expects a for loop"))
    isexpr(ex.args[1], :(=)) || throw(ArgumentError("malformed or unsupported for loop in @itr macro"))
    isexpr(ex.args[1].args[2], :call) || throw(ArgumentError("@itr macro expects an iterator call, e.g. @itr for (x,y) = zip(a,b)"))
    iterator = ex.args[1].args[2].args[1]
    ex.args[1].args[2] = Expr(:tuple, ex.args[1].args[2].args[2:end]...)
    if iterator == :zip
        rex = :(@zip($(esc(ex))))
    elseif iterator == :enumerate
        rex = :(@enumerate($(esc(ex))))
    elseif iterator == :take
        rex = :(@take($(esc(ex))))
    elseif iterator == :takestrict
        rex = :(@takestrict($(esc(ex))))
    elseif iterator == :drop
        rex = :(@drop($(esc(ex))))
    elseif iterator == :chain
        rex = :(@chain($(esc(ex))))
    else
        throw(ArgumentError("unknown or unsupported iterator $iterator in @itr macro"))
    end
    return rex
end

macro zip(ex)
    @assert ex.head == :for
    @assert ex.args[1].head == :(=)
    isexpr(ex.args[1].args[1], :tuple) || throw(ArgumentError("@zip macro needs explicit tuple arguments"))
    isexpr(ex.args[1].args[2], :tuple) || throw(ArgumentError("@zip macro needs explicit tuple arguments"))
    n = length(ex.args[1].args[1].args)
    length(ex.args[1].args[2].args) == n || throw(ArgumentError("unequal tuple sizes in @zip macro"))
    body = esc(ex.args[2])
    vars = map(esc, ex.args[1].args[1].args)
    iters = map(esc, ex.args[1].args[2].args)
    states = [gensym("s") for i=1:n]
    as = Any[Expr(:call, :(Base.start), iters[i]) for i=1:n]
    startex = Expr(:(=), Expr(:tuple, states...), Expr(:tuple, as...))
    ad = Any[Expr(:call, :(Base.done), iters[i], states[i]) for i = 1:n]
    notdoneex = Expr(:call, :(!), Expr(:||, ad...))
    nextex = Expr(:(=), Expr(:tuple, Any[Expr(:tuple, vars[i], states[i]) for i=1:n]...),
                        Expr(:tuple, Any[Expr(:call, :(Base.next), iters[i], states[i]) for i=1:n]...))
    Expr(:let, Expr(:block,
        startex,
        Expr(:while, notdoneex, Expr(:block,
            nextex,
            body))),
        states...)
end

macro enumerate(ex)
    @assert ex.head == :for
    @assert ex.args[1].head == :(=)
    isexpr(ex.args[1].args[1], :tuple) || throw(ArgumentError("@enumerate macro needs an explicit tuple argument"))
    length(ex.args[1].args[1].args) == 2 || throw(ArgumentError("lentgh of tuple must be 2 in @enumerate macro"))
    body = esc(ex.args[2])
    vars = map(esc, ex.args[1].args[1].args)
    if isexpr(ex.args[1].args[2], :tuple) && length(ex.args[1].args[2].args) == 1
        ex.args[1].args[2] = ex.args[1].args[2].args[1]
    end
    iter = esc(ex.args[1].args[2])
    ind = gensym("i")
    startex = Expr(:(=), ind, 0)
    forex = Expr(:(=), vars[2], iter)
    index = Expr(:(=), vars[1], ind)
    increx = Expr(:(=), ind, Expr(:call, :(+), ind, 1))
    Expr(:let, Expr(:block,
        startex,
        Expr(:for, forex, Expr(:block,
            increx,
            index,
            body))),
        ind)
end

# both @take and @takestrict use @_take

macro _take(ex, strict)
    mname = strict ? "takestrict" : "take"
    @assert ex.head == :for
    @assert ex.args[1].head == :(=)
    isexpr(ex.args[1].args[2], :tuple) || throw(ArgumentError("@$(mname) macro needs an explicit tuple argument"))
    length(ex.args[1].args[2].args) == 2 || throw(ArgumentError("length of tuple must be 2 in @$(mname) macro"))
    body = esc(ex.args[2])
    var = esc(ex.args[1].args[1])
    iter = esc(ex.args[1].args[2].args[1])
    n = esc(ex.args[1].args[2].args[2])
    ind = gensym("i")
    state = gensym("s")
    startex = Expr(:block,
        Expr(:(=), ind, 0),
        Expr(:(=), state, Expr(:call, :(Base.start), iter)))
    notdoneex = Expr(:call, :(!), Expr(:||,
        Expr(:call, :(>=), ind, n),
        Expr(:call, :(Base.done), iter, state)))
    nextex = Expr(:block,
        Expr(:(=), Expr(:tuple, var, state),
                   Expr(:call, :(Base.next), iter, state)),
        Expr(:(=), ind, Expr(:call, :(+), ind, 1)))
    if strict
        checkex = Expr(:if, Expr(:call, :(<), ind, n),
            Expr(:call, :throw, ArgumentError("in takestrict(xs, n), xs had fewer than n items to take.")))
    else
        checkex = :nothing
    end

    Expr(:let, Expr(:block,
        startex,
        Expr(:while, notdoneex, Expr(:block,
            nextex,
            body)),
        checkex),
        ind, state)
end

macro take(ex)
    :(@_take($(esc(ex)), false))
end

macro takestrict(ex)
    :(@_take($(esc(ex)), true))
end

macro drop(ex)
    @assert ex.head == :for
    @assert ex.args[1].head == :(=)
    isexpr(ex.args[1].args[2], :tuple) || throw(ArgumentError("@drop macro needs an explicit tuple argument"))
    length(ex.args[1].args[2].args) == 2 || throw(ArgumentError("length of tuple must be 2 in @drop macro"))
    body = esc(ex.args[2])
    var = esc(ex.args[1].args[1])
    iter = esc(ex.args[1].args[2].args[1])
    n = esc(ex.args[1].args[2].args[2])
    ind = gensym("i")
    state = gensym("s")
    startex = Expr(:block,
        Expr(:(=), ind, 0),
        Expr(:(=), state, Expr(:call, :(Base.start), iter)))
    notdoneex1 = Expr(:call, :(!), Expr(:||,
        Expr(:call, :(>=), ind, n),
        Expr(:call, :(Base.done), iter, state)))
    nextex1 = Expr(:block,
        Expr(:(=), Expr(:tuple, var, state),
                   Expr(:call, :(Base.next), iter, state)),
        Expr(:(=), ind, Expr(:call, :(+), ind, 1)))
    notdoneex2 = Expr(:call, :(!), Expr(:call, :(Base.done), iter, state))
    nextex2 = Expr(:(=), Expr(:tuple, var, state), Expr(:call, :(Base.next), iter, state))

    Expr(:let, Expr(:block,
        startex,
        Expr(:while, notdoneex1, nextex1),
        Expr(:while, notdoneex2, Expr(:block,
            nextex2,
            body))),
        ind, state)
end

macro chain(ex)
    @assert ex.head == :for
    @assert ex.args[1].head == :(=)
    isexpr(ex.args[1].args[2], :tuple) || throw(ArgumentError("@chain macro needs explicit tuple arguments"))
    n = length(ex.args[1].args[2].args)
    body = esc(ex.args[2])
    var = esc(ex.args[1].args[1])
    iters = map(esc, ex.args[1].args[2].args)
    states = [gensym("s") for i=1:n]

    cycleex = [Expr(:block,
        Expr(:(=), states[i], Expr(:call, :(Base.start), iters[i])),
        Expr(:while, Expr(:call, :(!), Expr(:call, :(Base.done), iters[i], states[i])), Expr(:block,
            Expr(:(=), Expr(:tuple, var, states[i]), Expr(:call, :(Base.next), iters[i], states[i])),
            body))) for i = 1:n]

    Expr(:let, Expr(:block, cycleex...), states...)
end

end # module Iterators


text:
module RedisGraph

export getdatabase

export Node, Edge, Path, Graph, QueryResult

export addnode!, addedge!

export commit, query, flush!, delete, merge

include("connection.jl")
include("node.jl")
include("edge.jl")
include("path.jl")
include("graph.jl")
include("result.jl")
end


text:
#######################
# basic setup
#######################

# simulation time and interval
tmin = 0.0
tmax = 4.0
dt = 0.01

# domain and spacing
xmin = -π
xmax = π
dx = 0.02

c = π/2 # celerity
λ² = (c*dt/dx)^2

nx = convert(Int64, round((xmax-xmin)/dx) + 1)
nt = convert(Int64, round((tmax-tmin)/dt) + 1)

X = collect(Float64, LinRange(xmin, xmax, nx))
T = collect(Float64, LinRange(tmin, tmax, nt))

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

#######################
# general solution
#######################
# Gaussian distribution
σ = 1/sqrt(2pi)
U(x,t) = 0.5*(1/(sqrt(2pi)σ))*exp(-(x-c*t)^2/(2σ^2)) + 0.5*(1/(sqrt(2pi)σ))*exp(-(x+c*t)^2/(2σ^2))
u_solution = [ U(x,t) for x in X, t in T ]
#######################

#######################
# numerical simulation
#######################
# preallocate
u = zeros(nx, nt)

# initial condition
# Gaussian distribution
f(x) = 1/(sqrt(2pi)σ)*exp(-x^2/(2σ^2))
u_0 = map(f, X)
u[:,1] = u_0

# 2nd step
#u[:,2] = u[:,1]
for x in 2:nx-1
    u[x,2] = 2(1-λ²)*u[x,1] -u[x,1] +λ²*(u[x-1,1]+u[x+1,1])
    #u[x,2] = u[x,1] + λ²*(u[x+1,1] - 2u[x,1] + u[x-1,1])
end
# Neumann boundary condition
u[1,2] = u[2,2]
u[nx,2] = u[nx-1,2]

#
for t = 3:nt
    for x in 2:nx-1
        u[x,t] = 2(1-λ²)*u[x,t-1] -u[x,t-2] +λ²*(u[x-1,t-1]+u[x+1,t-1])
    end
    # Neumann boundary condition
    u[1,t] = u[2,t]
    u[nx,t] = u[nx-1,t]
end
#######################

#######################
# plot
#######################
# directory where figures are printed
figdir="./fig"
if !isdir(figdir); mkdir(figdir); end

using Printf
using Plots
pyplot()

# 軸目盛り
xt = -π:π/2:π
xtl = ["-π","-π/2","0","π/2","π"]

# profiles
d = 50
plt = plot3d(repeat(X,outer=(1,length(1:d:nt))),
             permutedims(repeat(T[1:d:nt],outer=(1,nx))),
             u[:,1:d:nt],
             legend=false,
             )
plt = plot3d!(plt, aspect_ratio=1.0, view_angle=(55,35))
plt = plot3d!(plt, xlabel="space", ylabel="time", guidefont=9, xtick=(xt, xtl))
savefig(joinpath(figdir,"WaveProfiles.svg"))
##

##
# animation
anim = @animate for t = 1:5:nt
    @printf("%d, ",t)
    Plots.plot(X, u[:,t], linestyle=:solid, lw=2.0,
               ylim=(-0.5,1.0), xtick=(xt, xtl), tickfont=10,
               title=@sprintf("%0.3f s", (t-1)*dt), legend=false)
end
gifname = joinpath(figdir,"WaveEq.gif")
if isfile(gifname); rm(gifname); end
gif(anim, gifname, fps=20) #save the animation
##

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


text:
# example using julia 1.0.3
# -- run using julia fit.jl or include("fit.jl") in repl
#
# like rust, functions in julia can either use an explicit return statement or
# in the absence of one, will evaluate to the last expression in the function

# include if plotting
# using Gadfly

# -- functions
"""find_coeffs: given arrays x, y of length n for a 2D function y = f(x),
                return coefficients for a fitted polynomial of order n"""
function find_coeffs(x::AbstractArray, y::AbstractArray)
    # first thought to use @assert,
    # but after reading https://github.com/JuliaLang/julia/issues/30610
    # found that check() || error(...) preferred over @assert exceptions
    length(x) == length(y) || error("Uneven arrays in find_coeffs")

    # we isolate for the coefficient vector a = [a0, a1, a2, ...] in the
    # expression y = B a .
    # The n by n matrix B has rows of [x0 ^ 0, x0 ^ 1, x0 ^ 2, ...]

    # populate a matrix of these rows using matrix comprehension
    B = [x_i ^ n for x_i in x, n in 0:length(x) - 1]

    # find the coeffecient vector and return it
    inv(B) * y
end

# type constraint here is used to only allow vectors passed in
# -- where T allows T to range over all concrete types
"""Interpolate using the polynomial coefficient values 'coeffs' and the given
argument 'arg'"""
function coeff_interpolate(coeffs::AbstractArray{T,1}, arg) where T
    sum([coeffs[i] * arg ^ (i-1) for i in eachindex(coeffs)])
end

# -- examples --

# ex 1 -> pass in three x and three y values for a parabolic fit
parabola_coeffs = find_coeffs([1,2,3], [1,4,9])
println("The coefficients for points along x^2 are:")
println(parabola_coeffs)
println()

# ex 2 -> pass uneven arrays for length check
# bad_coeffs = find_coeffs([1], [])

# ex 3 -> check the default scalar type in the array
println("Default vector type:")
println(typeof(find_coeffs([1], [1])));
println()

# ex 4 -> force the return vector to be of Float16's
f16_coeffs = find_coeffs(Vector{Float16}([1, 3, 5]), [2, 3, 9])
println("Array type coerced to:")
println(typeof(f16_coeffs), "\n")

# ex 5 -> interpolation
println("The interpolated value at x = 4 for a model of the unit parabola:")
println(coeff_interpolate(parabola_coeffs, 4), "\n")

# # ex 6 -> plotting
# println("Plotting the quadratic fit from zero to ten as output.svg")
# x = 1:10
# y = [coeff_interpolate(parabola_coeffs, xi) for xi in x]
#
# # write output file
# # -- took ~4GiB memory and roughly 30 seconds to plot (output was only 9.6 kB)
#
# # @time draw(SVG("output.svg", 6inch, 3inch), plot(x=x, y=y, Geom.point, Geom.line))

max_stars_repo_name: mxxo/numerics

max_issues_repo_name: mxxo/numerics

max_forks_repo_name: mxxo/numerics


text:
module StrangeNamesTest1
   export σ, ∇a
   σ(x) = 2*x
   *ₛ(x,y) = x*y
   const ∇a = Vector{Vector{Float64}}
   🚗(x) = "car" * x
end

module StrangeNamesTest2
   export σ
   σ(x) = 2*x
end

module StrangeNamesTest3
   σ(x) = 2*x
end

module StrangeNamesTest4
   export harmless1, harmless2, 🚗, 🚗🚗
   σ(x) = 2*x
   harmless1(x) = 17
   harmless2(x) = 17
   harmless3(x) = 17
   🚗(x) = "car" * x
   🚗🚗(x) = "carcar" * x
end

module StrangeNamesTest5
   export harmless1, 🚗
   🚗(x) = "car" * x
   harmless1(x) = 17
end


text:
#=
   Gender height.

   Identify a person's sex by height.

   Cf ~/cplint/gender_height.pl
      ~/blog/gender_height.blog
      ~/webppl/gender_height.wppl

   Note: The observations of height and gender seems to be much better
         using Turings standard "parameter observation" than using
         Dirac etc.

=#

# using Memoization
using Turing, StatsPlots, DataFrames
# using ReverseDiff, Zygote, Tracker
# Turing.setadbackend(:reversediff)
# Turing.setadbackend(:zygote)
# Turing.setadbackend(:tracker)
include("jl_utils.jl")

@model function gender_height(gender=missing,height=missing)
# @model function gender_height()

    male = 1
    female = 2
    gender ~ Categorical([0.5,0.5])

    # From https://en.wikipedia.org/wiki/List_of_average_human_height_worldwide
    # Here are the values of Sweden. I'm not sure about the variance of these heights...
    height ~ gender == male ? Normal(181.5,sqrt(50)) : Normal(166.8,sqrt(50))

    # height ~ gender == male ? Normal(178,17.7) : Normal(163,17.3)

    # Note: Using this way of observing is not good in this model.
    # height == 190.0 || begin Turing.@addlogprob! -Inf; return end
    # obs!(_sampler,_varinfo,height == 190.0)
    # true ~ Dirac(height == 190.0)

    ret = []
    if !ismissing(height)
        push!(ret,height)
    end
    if !ismissing(gender)
        push!(ret,gender)
    end

    return height,gender # ret
    # return ret
end

function run_model(gender=missing,height=missing)

    model = gender_height(gender,height)

    num_chains = 4

    # chains = sample(model, Prior(), MCMCThreads(), 10_000, num_chains)

    chains = sample(model, MH(), 10_000)
    # chains = sample(model, MH(), MCMCThreads(), 40_000, num_chains)

    # chains = sample(model, PG(20), MCMCThreads(), 10_000, num_chains)
    # chains = sample(model, PG(20), 1_000)

    # chains = sample(model, IS(), MCMCThreads(), 10_000, num_chains)
    # chains = sample(model, IS(), 10_000)

    # chains = sample(model, SMC(1000), MCMCThreads(), 10_000, num_chains)
    # chains = sample(model, SMC(1000), 10_000)

    # chains = sample(model, NUTS(1000,0.65), 1_000)
    # chains = sample(model, Gibbs(MH(:gender),NUTS(1000,0.65,:height)), 1_000)
    # chains = sample(model, Gibbs(MH(:gender),NUTS(10,0.65,:height)), 1_000)
    # chains = sample(model, Gibbs(MH(:gender),HMC(0.1,5,:height)), 1_000)
    # chains = sample(model, Gibbs(PG(10,:gender),HMC(0.1,5,:height)), 1_000)
    # chains = sample(model, Gibbs(MH(:gender),NUTS(1_000,0.65,:height)), 1_000)

    if ismissing(height)
        display(chains)
        # display(plot(chains))
    end
    show_var_dist_pct(chains, :gender)
    show_var_dist_pct(chains, :height, 20)

    gen = generated_quantities(model, chains)

    if gen[1] != nothing
        show_var_dist_pct(gen, 20)
    end
end

male = 1
female = 2
for height in [150.0,160.0,170.0,175.0,180.0, 185.0,190.0,200.0]
    println("\ngender:missing height:$height")
    run_model(missing,height)
    println()
end

println("\nmale, missing")
run_model(male,missing)

println("\nfemale, missing")
run_model(female,missing)

max_stars_repo_name: Wikunia/hakank

max_issues_repo_name: Wikunia/hakank

max_forks_repo_name: Wikunia/hakank


text:
include("evaluate_nh/eval.jl")

# optimize with simulated annealing
function siman!(
    ata_model::AbstractModel;
    starting_design = Matrix{Float64}(undef, 0, 0),
    results_folder = "results",
    start_temp = 0.1,
    geom_temp = 0.1,
    max_time = 1000.0,
    max_conv = 2,
    feas_nh = 0,
    opt_nh = 5,
    n_item_sample = 1000,
    n_test_sample = 2,
    opt_feas = 0.0,
    n_fill = 1,
    verbosity = 2,
    kwargs...,
)
    if !isdir(results_folder)
        mkdir(results_folder)
    else
        success!(
            ata_model,
            string(
                "There is already a folder with this name, files in ",
                results_folder,
                " will be overwritten.",
            ),
        )
    end

    ##
    nNH = feas_nh + opt_nh
    opt_nh = feas_nh + 1
    if feas_nh == 0
        fF = false
    else
        fF = true
    end
    T = ata_model.settings.T
    n_fs = ata_model.settings.n_fs
    n_items = ata_model.settings.n_items
    fs_counts = ata_model.settings.fs.counts * ones(Float64, T)'
    iu_max⁺ = 0
    iu_min⁺ = 0
    start_time = copy(time())
    nacc = 0 # total accepted trials
    t = copy(start_temp) # temperature - will initially rise or fall to cover parameter space. Then it will fall
    finish = 0 # convergence indicator 0 (failure), 1 (normal success), or 2 (convergence but near bounds)
    f_evals = 0
    hline = " = "^80
    f_star = typemax(Float64) * ones(2)
    NH = 1

    # starting design check
    if size(starting_design, 1) > 0
        if (
            size(starting_design, 1) != n_fs ||
            size(starting_design, 2) != ata_model.settings.T
        )
            error!(ata_model, "Starting design must be of size: (n_items x T).")
            return nothing
        end
        if any((ata_model.output.design .!= 0.0) .& (ata_model.output.design .!= 1.0))
            error!(ata_model, "Starting design must contain only 1 or 0.")
            return nothing
        end
        x₀ = Float64.(starting_design)
    else
        x₀ = zeros(Float64, n_fs, ata_model.settings.T)
    end

    NH⁺ = Neighbourhood(x₀, Inf, zeros(T), 1e6 * ones(T), zeros(T), zero(Float64))
    bestNH = 1
    if size(x₀, 1) == 0
        NH⁺.x = zeros(Float64, n_fs, T)
    end
    # compute f
    if ata_model.settings.to_apply[1]
        iu_max = sum(NH⁺.x, dims = 2)[:, 1] - ata_model.settings.iu.max
        iu_max = sum_pos(iu_max)
    else
        iu_max = 0
    end
    if ata_model.settings.to_apply[2]
        iu_min = -sum(NH⁺.x, dims = 2)[:, 1] + ata_model.settings.iu.min
        iu_min = sum_pos(iu_min)
    else
        iu_min = 0
    end
    NH⁺.iu = iu_max + iu_min
    t = copy(start_temp)
    for v2 = 1:T
        NH⁺.infeas[v2], x_Iₜ = check_feas(
            ata_model.settings.fs,
            ata_model.constraints[v2],
            NH⁺.x[:, v2],
            n_fs,
            n_items,
        )
        if ata_model.settings.to_apply[3]
            NH⁺.ol = eval_overlap(NH⁺.x, fs_counts, ata_model.settings.ol_max, T, NH⁺.ol)
        end
        if fF == false
            if ata_model.obj.name in
               ["maximin", "minimax", "cc_maximin", "soyster_maximin", "de_jong_maximin"]
                NH⁺.obj[v2] = eval_TIFₜ(x_Iₜ, ata_model.obj.cores[v2])
            elseif ata_model.obj.name == "custom"
                NH⁺.obj = ata_model.obj.fun(x_Iₜ, ata_model.obj.args)
            end
        end
    end
    NH⁺.f = _comp_f(NH⁺, opt_feas)
    if sum(NH⁺.x) <= 1.0
        f⁺ = Inf
    else
        f⁺ = copy(NH⁺.f)
    end
    println("Starting solution: ")
    print_neighbourhood(NH⁺)
    if (Distributed.nprocs() > 1)
        workers = collect(1:(Distributed.nprocs()-1))
    else
        workers = [one(Int64)]
    end
    NHs = copy(workers)
    ata_model.output.neighbourhoods = [Neighbourhood() for n = 1:NHs[end]]
    for nh = 1:NHs[end]
        ata_model.output.neighbourhoods[nh] =
            _mycopy(NH⁺, ata_model.output.neighbourhoods[nh])
    end
    round = 1
    while finish == 0
        #for each proc in nprocs analyse nh
        Distributed.@sync Distributed.@distributed for p in workers
            nh_tot = (NHs[end] * (round - 1)) + NHs[p]
            # println("f was ", ata_model.output.neighbourhoods[NHs[p]].f)
            NH_proc = Neighbourhood()
            NH_proc = _mycopy(ata_model.output.neighbourhoods[NHs[p]], NH_proc)
            if round > 1
                #fill-up phase just in first round (first n_procs neighbourhoods)
                n_fill = 0
            end
            NH_proc = analyse_NH(
                NH_proc,
                ata_model;
                fF = fF,
                n_fill = n_fill,
                opt_feas = opt_feas,
                max_conv = max_conv,
                start_temp = t,
                geom_temp = geom_temp,
                n_item_sample = n_item_sample,
                n_test_sample = n_test_sample,
                verbosity = verbosity,
                start_time = start_time,
                max_time = max_time,
            )
            open(string(results_folder, "/neigh_", nh_tot, "_x.csv"), "w") do io
                return DelimitedFiles.writedlm(io, NH_proc.x)
            end
            open(string(results_folder, "/neigh_", nh_tot, "_infeas.csv"), "w") do io
                return DelimitedFiles.writedlm(io, NH_proc.infeas)
            end
            open(string(results_folder, "/neigh_", nh_tot, "_obj.csv"), "w") do io
                return DelimitedFiles.writedlm(io, NH_proc.obj)
            end
            open(string(results_folder, "/neigh_", nh_tot, "_ol.csv"), "w") do io
                return DelimitedFiles.writedlm(io, NH_proc.ol)
            end
            open(string(results_folder, "/neigh_", nh_tot, "_iu.csv"), "w") do io
                return DelimitedFiles.writedlm(io, NH_proc.iu)
            end
            open(string(results_folder, "/neigh_", nh_tot, "_f.csv"), "w") do io
                return DelimitedFiles.writedlm(io, NH_proc.f)
            end
            println("\nNeighbourhood ", nh_tot, " fully explored, increase temperature")
        end
        #save last nprocs nhs in text files
        for p in workers
            nh_tot = (NHs[end] * (round - 1)) + NHs[p]
            ata_model.output.neighbourhoods[NHs[p]].x = DelimitedFiles.readdlm(
                string(results_folder, "/neigh_", nh_tot, "_x.csv"),
                '\t',
            )
            ata_model.output.neighbourhoods[NHs[p]].obj = DelimitedFiles.readdlm(
                string(results_folder, "/neigh_", nh_tot, "_obj.csv"),
                '\t',
            )[
                :,
                1,
            ]
            ata_model.output.neighbourhoods[NHs[p]].infeas = DelimitedFiles.readdlm(
                string(results_folder, "/neigh_", nh_tot, "_infeas.csv"),
                '\t',
            )[
                :,
                1,
            ]
            ata_model.output.neighbourhoods[NHs[p]].ol = DelimitedFiles.readdlm(
                string(results_folder, "/neigh_", nh_tot, "_ol.csv"),
                '\t',
            )[
                :,
                1,
            ]
            ata_model.output.neighbourhoods[NHs[p]].iu = DelimitedFiles.readdlm(
                string(results_folder, "/neigh_", nh_tot, "_iu.csv"),
                '\t',
            )[
                1,
                1,
            ]
            ata_model.output.neighbourhoods[NHs[p]].f = DelimitedFiles.readdlm(
                string(results_folder, "/neigh_", nh_tot, "_f.csv"),
                '\t',
            )[
                1,
                1,
            ]
        end
        # NH+= 1
        # find best nh
        f_loc, nh_loc = findmin([ata_model.output.neighbourhoods[n].f for n = 1:NHs[end]])
        # copy first best result as benchmark (avoid to keep empty design as best solution in large scale models)
        if f_loc <= f⁺
            bestNH = (NHs[end] * (round - 1)) + nh_loc
            f⁺ = copy(f_loc)
            NH⁺ = _mycopy(ata_model.output.neighbourhoods[nh_loc], NH⁺)
        end
        # NHs = NHs.+Distributed.nprocs()
        if NHs[end] * round + 1 > feas_nh
            fF = false
        end
        if NHs[end] * round + 1 > nNH
            finish = 1
        else
            # perturbate the solution and go to warmup
        end
        round += 1
        # f_evals+= f_evals_NH
        # if f_evals >= max_evals
        # 	finish = 2
        # end
        if verbosity >= 1
            println(hline)
            println("results")
            if (finish == 1)
                println(" == > Maximum number of neighbourhoods explored <== ")
                # elseif (finish == 2) #time max reached
                # 	println(" == > Maximum number of evaluations reached <== ")
            elseif (finish == 3) # evals max reached
                println(" == > Maximum time reached <== ")
            end
            Printf.@printf("\n obj. value:	%5.1f", NH⁺.f)
            Printf.@printf("\n Optimality:	%5.1f", minimum(NH⁺.obj))
            Printf.@printf("\n Infeasibility:	%5.1f", sum(NH⁺.infeas + NH⁺.ol) + NH⁺.iu)
            Printf.@printf("\n Elapsed Time:	%5.1f", time() - start_time)
            Printf.@printf("\n Best Neighbourhood:	%5d", bestNH)
            println("\n")
            println(hline)
        end
        if time() - start_time >= max_time
            ata_model.output.elapsed_time = time() - start_time
            finish = 3
        end
        if finish > 0
            # f⁺, NH = findmin([ata_model.output.neighbourhoods[n].f for n = 1:nNH])
            # x⁺ = ata_model.output.neighbourhoods[NH].x
            # infeas⁺ = ata_model.output.neighbourhoods[NH].infeas+ata_model.output.neighbourhoods[NH].ol
            # iu⁺ = ata_model.output.neighbourhoods[NH].iu
            # TIF⁺ = ata_model.output.neighbourhoods[NH].obj
            ata_model.output.design = NH⁺.x
            ata_model.output.feas = NH⁺.infeas + NH⁺.ol
            ata_model.output.f = NH⁺.f
            ata_model.output.TIF = NH⁺.obj
        end
        # j = 1
        # for v = 1:ata_model.settings.T
        # 	for i = 1:ata_model.settings.n_fs
        # 		if NH⁺.x[i, v] == one(Float64)
        # 			if j == 1
        # 				NH⁺.x[i, v] = zero(Float64)
        # 				j+= 1
        # 			else
        # 				j - = 1
        # 			end
        # 		end
        # 	end
        # end
    end # end of finish
    JLD2.@save string(results_folder, "/ata_model.jld2") ata_model
    DelimitedFiles.writedlm(
        string(results_folder, "/design.csv"),
        reshape(ata_model.output.design, n_fs, T),
    )
    open(string(results_folder, "/results_ata.txt"), "w") do io
        write(io, "tests")
        write(io, "\r\n")
        DelimitedFiles.writedlm(io, collect(1:T)', ", ")
        write(io, "\r\n")
        write(io, "f⁺")
        write(io, "\r\n")
        DelimitedFiles.writedlm(io, ata_model.output.f)
        write(io, "\r\n")
        write(io, "infeasibility")
        write(io, "\r\n")
        DelimitedFiles.writedlm(io, ata_model.output.feas', ", ")
        write(io, "\r\n")
        write(io, "Item use")
        write(io, "\r\n")
        write(io, string(NH⁺.iu))
        write(io, "\r\n")
        write(io, "TIF")
        write(io, "\r\n")
        DelimitedFiles.writedlm(io, ata_model.output.TIF', ", ")
        write(io, "\r\n")
        write(io, "elapsed Time for optimization")
        write(io, "\r\n")
        write(io, string(ata_model.output.elapsed_time))
        return write(io, "\r\n")
    end
    return nothing
end


text:
using Test
include("../src/RunTests.jl")


text:
using Polyhedra

include("config.jl")

include("jump.jl")
include("misc.jl")
include("decompose.jl")

polyhedratests = Dict("jump" => jumptest,
                      "misc" => misctest,
                      "decompose" => decomposetest)

@polytestset polyhedra true


text:
using MendelIterHardThreshold
@static if VERSION < v"0.7.0-DEV.2005"
    using Base.Test
else
    using Test
end

# write your own tests here
include("MendelIterHardThreshold_test.jl")
include("IHT_utilities_test.jl")

# julia -e 'Pkg.test("MendelIterHardThreshold",coverage=true)'
# @show get_summary(process_file("src/MendelIterHardThreshold.jl"))
# @show get_summary(process_file("src/IHT_utilities.jl"))


text:
module Axes
import ..Cubes: caxes, Cubes
using Dates
using Base.Iterators: take, drop
import DataStructures: counter
using YAXArrayBase: YAXArrayBase

export CubeAxis, RangeAxis, CategoricalAxis, getAxis

################################
# Types
################################

"""
    abstract type CubeAxis{T,S}

Supertype of all axes. Every `CubeAxis` is an 1D Cube itself and can be passed
to mapCube operations.
"""
abstract type CubeAxis{T,S} end

Base.size(x::CubeAxis) = (length(x.values),)
Base.size(x::CubeAxis, i) =
    i == 1 ? length(x.values) : error("Axis has only a single dimension")
Base.ndims(x::CubeAxis) = 1
Base.hash(ax::CubeAxis{<:Any,S}, h::UInt) where {S} = hash(S, hash(ax.values, h))

"""
    struct CategoricalAxis{T,S,RT}

To represent axes that are categorical, where `T` is the element type.
The type parameter `S` denotes the axis name (a symbol).
The default constructor is:

    CategoricalAxis(axname::String,values::Vector{T})

"""
struct CategoricalAxis{T,S,RT} <: CubeAxis{T,S}
    values::RT
end

CategoricalAxis(s::Symbol, v) = CategoricalAxis{eltype(v),s,typeof(v)}(v)
CategoricalAxis(s::AbstractString, v) = CategoricalAxis(Symbol(s), v)

function Base.show(io::IO, a::CategoricalAxis)
    print(io, rpad(Axes.axname(a), 20, " "), "Axis with ", length(a), " elements: ")
    if length(a.values) < 10
        for v in a.values
            print(io, v, " ")
        end
    else
        for v in take(a.values, 2)
            print(io, v, " ")
        end
        print(io, ".. ")
        for v in drop(a.values, length(a.values) - 2)
            print(io, v, " ")
        end
    end
end

"""
    RangeAxis{T,S,R}

To represent axes that are numerical, where `T` is the element type.
The type parameter `S` denotes the axis name (a symbol) and `R` the type of the
range which is used to represent the axis values.
The default constructor is:

    RangeAxis(axname::String,values::Range{T})

"""
struct RangeAxis{T,S,R<:AbstractVector{T}} <: CubeAxis{T,S}
    values::R
end

RangeAxis(s::Symbol, v::AbstractVector{T}) where {T} = RangeAxis{T,s,typeof(v)}(v)
RangeAxis(s::AbstractString, v) = RangeAxis(Symbol(s), v)

Base.length(a::CubeAxis) = length(a.values)

Base.show(io::IO, a::RangeAxis) = print(
    io,
    rpad(Axes.axname(a), 20, " "),
    "Axis with ",
    length(a),
    " Elements from ",
    first(a.values),
    " to ",
    last(a.values),
)

abstract type AxisDescriptor end

struct ByName <: AxisDescriptor
    name::String
end

struct ByInference <: AxisDescriptor end

struct ByValue <: AxisDescriptor
    v::CubeAxis
end

const VecOrTuple{S} = Union{Vector{<:S},Tuple{Vararg{<:S}}} where {S}

################################
# Public
################################

import Base.==
import Base.isequal
==(a::CubeAxis, b::CubeAxis) = (a.values == b.values) && (axname(a) == axname(b))
isequal(a::CubeAxis, b::CubeAxis) = a == b

"""
    getAxis(desc, c)

Given an Axis description and a cube, returns the corresponding axis of the cube.
The Axis description can be:
  - the name as a string or symbol.
  - an Axis object
"""
getAxis(desc, c) = getAxis(desc, caxes(c))
getAxis(desc::ByValue, axlist::Vector{T}) where {T<:CubeAxis} = desc.v
function getAxis(desc, axlist::VecOrTuple{CubeAxis})
    i = findAxis(desc, axlist)
    if isa(i, Nothing)
        return nothing
    else
        return axlist[i]
    end
end

#Implement yaxarray interface
YAXArrayBase.dimname(x::CubeAxis, _) = axname(x)
YAXArrayBase.dimvals(x::CubeAxis, _) = x.values
YAXArrayBase.iscontdim(::RangeAxis, _) = true
YAXArrayBase.iscontdim(::CategoricalAxis, _) = false

################################
# Utility functions (internal)
################################

"""
    axname
"""
axname(::Type{<:CubeAxis{<:Any,U}}) where {U} = string(U)
axname(::CubeAxis{<:Any,U}) where {U} = string(U)

"""
    axsym
"""
axsym(::CubeAxis{<:Any,S}) where {S} = S

"""
    axcopy(x,vals)

Makes a full copy of a `CubeAxis` with the values `vals`
"""
axcopy(ax::RangeAxis, vals) = RangeAxis(axname(ax), vals)
axcopy(ax::CategoricalAxis, vals) = CategoricalAxis(axname(ax), vals)
axcopy(ax::RangeAxis) = RangeAxis(axname(ax), copy(ax.values))
axcopy(ax::CategoricalAxis) = CategoricalAxis(axname(ax), copy(ax.values))

"""
    caxes

Embeds  Cube inside a new Cube
"""
caxes(x::CubeAxis) = CubeAxis[x]

"""
    get_step

returns stepwidth of the RangeAxis
"""
get_step(r::AbstractRange) = step(r)
get_step(r::AbstractVector) = length(r) == 0 ? zero(eltype(r)) : r[2] - r[1]

"""
    abshalf
"""
abshalf(a) = abs(a / 2)
abshalf(a::Day) = abs(Millisecond(a) / 2)
abshalf(a::Month) = iseven(Dates.value(a)) ? a / 2 : Month(a ÷ 2) + Day(15)

"""
    axVal2Index
"""
function axVal2Index(a::RangeAxis{<:Any,<:Any,<:AbstractRange}, v; fuzzy=false)
    dt = v - first(a.values)
    r = round(Int, dt / step(a.values)) + 1
    return max(1, min(length(a.values), r))
end
function axVal2Index(a::RangeAxis{T}, v; fuzzy=false) where {T<:TimeType}
    vconverted = convert_time(T, v)
    dd = map(i -> abs((i - vconverted)), a.values)
    mi, ind = findmin(dd)
    return ind
end
function axVal2Index(
    a::RangeAxis{T,<:Any,<:AbstractRange},
    v;
    fuzzy=false
) where {T<:TimeType}
    vconverted = convert_time(T, v)
    dd = map(i -> abs((i - vconverted)), a.values)
    mi, ind = findmin(dd)
    return ind
end
function axVal2Index(axis::CategoricalAxis{String}, v::String; fuzzy::Bool=false)
    r = findfirst(isequal(v), axis.values)
    if r === nothing
        if fuzzy
            r = findall(axis.values) do i
                startswith(lowercase(i), lowercase(v[1:min(length(i), length(v))]))
            end
            if length(r) == 1
                return (r[1])
            else
                error("Could not find unique value of $v in $axis")
            end
        else
            error("$v not found in $axis")
        end
    end
    r
end
axVal2Index(x, v::CartesianIndex{1}; fuzzy::Bool=false) = min(max(v.I[1], 1), length(x))
function axVal2Index(x, v; fuzzy::Bool=false)
    i = findfirst(isequal(v), x.values)
    if isa(i, Nothing)
        if fuzzy == true
            dd = map(i -> abs(i - v), x.values)
            mi, ind = findmin(dd)
            return ind
        else
            error("Value $v not found in x")
        end
    else
        return i
    end
end

"""
    axVal2Index_ub
"""
axVal2Index_ub(a::RangeAxis, v; fuzzy=false) =
    axVal2Index(a, v - abshalf(get_step(a.values)), fuzzy=fuzzy)
axVal2Index_ub(a::RangeAxis, v::Date; fuzzy=false) =
    axVal2Index(a, DateTime(v) - abshalf(get_step(a.values)), fuzzy=fuzzy)

"""
    axVal2Index_lb
"""
axVal2Index_lb(a::RangeAxis, v; fuzzy=false) =
    axVal2Index(a, v + abshalf(get_step(a.values)), fuzzy=fuzzy)
axVal2Index_lb(a::RangeAxis, v::Date; fuzzy=false) =
    axVal2Index(a, DateTime(v) + abshalf(get_step(a.values)), fuzzy=fuzzy)

"""
    get_bb
"""
get_bb(ax::RangeAxis) = (first(ax.values) - abshalf(get_step(ax.values)),
    last(ax.values) + abshalf(get_step(ax.values)))

"""
    axisfrombb
"""
function axisfrombb(name, bb, n)
    offs = (bb[2] - bb[1]) / (2 * n)
    RangeAxis(name, range(bb[1] + offs, bb[2] - offs, length=n))
end

"""
    convert_time
"""
convert_time(T::Type{<:TimeType}, v::TimeType) =
    T(year(v), month(v), day(v), hour(v), minute(v), second(v))
convert_time(T::Type{<:TimeType}, v::Date) = T(year(v), month(v), day(v), 0, 0, 0)
convert_time(::Type{Date}, v::TimeType) = Date(year(v), month(v), day(v))
convert_time(::Type{Date}, v::Date) = Date(year(v), month(v), day(v))

"""
    get_descriptor(a)

Get the descriptor of an Axis.
This is used to dispatch on the descriptor.
"""
get_descriptor(a::String) = ByName(a)
get_descriptor(a::Symbol) = ByName(String(a))
get_descriptor(a::CubeAxis) = ByValue(a)
get_descriptor(a) = error("$a is not a valid axis description")
get_descriptor(a::AxisDescriptor) = a

"""
    match_axis
"""
function match_axis(bs::ByName, ax)
    startswith(lowercase(axname(ax)), lowercase(bs.name))
end
function match_axis(bs::ByValue, ax)
    isequal(bs.v, ax)
end
match_axis(a, ax) = match_axis(get_descriptor(a), ax)

"""
    findAxis(desc, c)
Given an Axis description and a cube return the index of the Axis.
The Axis description can be:
  - the name as a string or symbol.
  - an Axis object
"""
findAxis(desc, c) = findAxis(desc, caxes(c))
findAxis(a, axlist::VecOrTuple{CubeAxis}) = findAxis(get_descriptor(a), axlist)
function findAxis(bs::AxisDescriptor, axlist::VecOrTuple{CubeAxis})
    m = findall(i -> match_axis(bs, i), axlist)
    if isempty(m)
        return nothing
    elseif length(m) > 1
        error("Multiple possible axis matches found for $bs")
    else
        return m[1]
    end
end

"""
    renameaxis
"""
renameaxis(r::RangeAxis{T,<:Any,V}, newname) where {T,V} =
    RangeAxis{T,Symbol(newname),V}(r.values)
renameaxis(r::CategoricalAxis{T,<:Any,V}, newname) where {T,V} =
    CategoricalAxis{T,Symbol(newname),V}(r.values)

"""
    getOutAxis
"""
getOutAxis(desc, axlist, incubes, pargs, f) = getAxis(desc, unique(axlist))
function getOutAxis(desc::Tuple{ByInference}, axlist, incubes, pargs, f)
    inAxes = map(caxes, incubes)
    inAxSmall = map(i -> filter(j -> in(j, axlist), i) |> collect, inAxes)
    inSizes = map(i -> (map(length, i)...,), inAxSmall)
    intypes = map(eltype, incubes)
    testars = map((s, it) -> zeros(it, s...), inSizes, intypes)
    map(testars) do ta
        ta .= rand(Base.nonmissingtype(eltype(ta)), size(ta)...)
        if eltype(ta) >: Missing
            # Add some missings
            randind = rand(1:length(ta), length(ta) ÷ 10)
            ta[randind] .= missing
        end
    end
    resu = f(testars..., pargs...)
    isa(resu, AbstractArray) ||
        isa(resu, Number) ||
        isa(resu, Missing) ||
        error("Function must return an array or a number")
    (isa(resu, Number) || isa(resu, Missing)) && return ()
    outsizes = size(resu)
    outaxes = map(outsizes, 1:length(outsizes)) do s, il
        if s > 2
            i = findall(i -> i == s, length.(axlist))
            if length(i) == 1
                return axlist[i[1]]
            elseif length(i) > 1
                @info "Found multiple matching axes for output dimension $il"
            end
        end
        return RangeAxis("OutAxis$(il)", 1:s)
    end
    if !allunique(outaxes)
        #TODO: fallback with axis renaming in this case
        error("Could not determine unique output axes from output shape")
    end
    return (outaxes...,)
end

end #module

max_stars_repo_name: meggart/YAXArrays

max_forks_repo_name: meggart/YAXArrays


text:
# This file is a part of Julia. License is MIT: http://julialang.org/license

module Time
export TimeDelta

import Base.show, Base.+, Base.-, Base.convert, Base.promote_rule

immutable TimeDelta{p}
    v::Int64
end

const PREFIXES = [
    "yocto", "zepto", "atto", "femto", "pico", "nano", "micro", "milli",
    "", "kilo", "mega", "giga", "tera", "peta", "exa", "zetta", "yotta",
]
const ZERO_INDEX = 9
const MAX_INDEX = 17

function show{p}(io::IO, x::TimeDelta{p})
    k = max(1,min(MAX_INDEX,fld(p,3)+ZERO_INDEX))
    r = p-3(k-ZERO_INDEX)
    prefix = PREFIXES[k]
    if r == 0
        s = x.v == 1 ? "" : "s"
        print(io, "$(x.v) $(prefix)second$s")
    elseif r > 0
        print(io, "$(x.v*10^r) $(prefix)seconds")
    else
        print(io, "$(x.v/10^-r) $(prefix)seconds")
    end
end

convert{p,q}(::Type{TimeDelta{p}}, x::TimeDelta{q}) =
    TimeDelta{p}(p <= q ? x.v*10^(q-p) : div(x.v,10^(p-q)))

promote_rule{p,q}(::Type{TimeDelta{p}}, ::Type{TimeDelta{q}}) = TimeDelta{min(p,q)}

-{p}(x::TimeDelta{p}) = TimeDelta{p}(-x.v)
+{p}(x::TimeDelta{p}, y::TimeDelta{p}) = TimeDelta{p}(x.v+y.v)
-{p}(x::TimeDelta{p}, y::TimeDelta{p}) = TimeDelta{p}(x.v-y.v)

+(x::TimeDelta, y::TimeDelta) = +(promote(x,y)...)
-(x::TimeDelta, y::TimeDelta) = -(promote(x,y)...)

end # module