text:
push!(LOAD_PATH, "C:\\Users\\adamm\\Dropbox\\Development\\JuML\\src")
#push!(LOAD_PATH, "C:\\Users\\statfactory\\Documents\\JuML.jl\\src")
using JuML

@time importcsv("C:\\Users\\statfactory\\Documents\\Julia\\kaggle\\train_sample.csv";
                 isnumeric = (colname, levelfreq) -> colname in ["is_attributed"],
                 isdatetime = (colname, levelfreq) -> colname in ["click_time", "attributed_time"] ? (true, "y-m-d H:M:S") : (false, ""))

@time importcsv("C:\\Users\\adamm\\Documents\\Julia\\kaggle\\train.csv";
                 isnumeric = (colname, levelfreq) -> colname in ["is_attributed"],
                 isinteger = (colname, levelfreq) -> colname in ["ip"],
                 isdatetime = (colname, levelfreq) -> colname in ["click_time", "attributed_time"] ? (true, "y-m-d H:M:S") : (false, ""))

@time importcsv("C:\\Users\\adamm\\Documents\\Julia\\kaggle\\test.csv";
                 isnumeric = (colname, levelfreq) -> false,
                 isinteger = (colname, levelfreq) -> colname in ["ip", "click_id"],
                 isdatetime = (colname, levelfreq) -> colname in ["click_time"] ? (true, "y-m-d H:M:S") : (false, ""))

@time importcsv("C:\\Users\\adamm\\Documents\\Julia\\kaggle\\train.csv"; path2 = "C:\\Users\\adamm\\Documents\\Julia\\kaggle\\test.csv",
                 isnumeric = (colname, levelfreq) -> colname in ["is_attributed"],
                 isinteger = (colname, levelfreq) -> colname in ["ip", "click_id"],
                 isdatetime = (colname, levelfreq) -> colname in ["click_time", "attributed_time"] ? (true, "y-m-d H:M:S") : (false, ""))

@time importcsv("C:\\Users\\statfactory\\Documents\\Julia\\kaggle\\train.csv"; path2 = "C:\\Users\\statfactory\\Documents\\Julia\\kaggle\\testsup.csv",
                 isnumeric = (colname, levelfreq) -> colname in ["is_attributed"],
                 isinteger = (colname, levelfreq) -> colname in ["ip", "click_id"],
                 isdatetime = (colname, levelfreq) -> colname in ["click_time", "attributed_time"] ? (true, "y-m-d H:M:S") : (false, ""))

#train_df = DataFrame("C:\\Users\\adamm_000\\Documents\\Julia\\kaggle\\train", preload = false)
test_df = DataFrame("C:\\Users\\adamm\\Documents\\Julia\\kaggle\\test", preload = false)
traintest_df = DataFrame("C:\\Users\\adamm_000\\Documents\\Julia\\kaggle\\traintestsup", preload = false)
#train_df = DataFrame("C:\\Users\\adamm\\Documents\\Julia\\kaggle\\train", preload = false)

#n = length(traintest_df) - length(test_df)
#train_df = filter(BoolVariate("train", BitArray((1:length(traintest_df)) .<= n)), traintest_df)

label = traintest_df["is_attributed"]
click_time = traintest_df["click_time"]
#summary(click_time)

#clickday = factor(JuML.TransDateTimeCovariate("ClickDay", click_time, Dates.dayofweek));
#clickhour = factor(JuML.TransDateTimeCovariate("ClickHour", click_time, Dates.hour));
nmin = 60
clickhour = factor(JuML.TransDateTimeCovariate("ClickHour", click_time, dt -> (24 * 60 * Dates.dayofweek(dt) + 60 * Dates.hour(dt) + Dates.minute(dt)) ÷ nmin)) |> cache;
#clickminute = factor(JuML.TransDateTimeCovariate("ClickMinute", click_time, dt -> 24 * 60 * Dates.dayofweek(dt) + 60 * Dates.hour(dt) + Dates.minute(dt))) |> cache
#clicksec = factor(click_time);
#summary(clickhour)

function maplevels(f::JuML.AbstractFactor, minfreq::Integer)
    levels = Set(map((x -> x.level), filter((x -> x.freq >= minfreq), JuML.getstats(f).levelstats)))
    JuML.MapLevelFactor(JuML.getname(f), f, level -> level in levels ? level : "Other")
end

#p1 = DateTime(2017, 11, 10, 4, 0, 0), DateTime(2017, 11, 10, 6, 0, 0)
#p2 = DateTime(2017, 11, 10, 9, 0, 0), DateTime(2017, 11, 10, 11, 0, 0)
#p3 = DateTime(2017, 11, 10, 13, 0, 0), DateTime(2017, 11, 10, 15, 0, 0)

#teststart = DateTime(2017, 11, 9, 16, 0, 0)
validstart = DateTime(2017, 11, 9, 13, 0, 0)
haslabel = JuML.TransCovBoolVariate("", label, x -> !isnan(x))

trainset = JuML.TransDateTimeBoolVariate("", click_time, t -> t < validstart)
validset = JuML.TransDateTimeBoolVariate("", click_time, t -> t >= validstart) .& haslabel

#trainset = BoolVariate("", BitArray(x <= 57000000 for x in 1:60000000))
#validset = BoolVariate("", BitArray(x > 57000000 for x in 1:60000000))

#zeroset = BoolVariate("", BitArray{1}(0))
#testset = JuML.TransDateTimeBoolVariate("", click_time, t -> t == p1[2])
#summary(testset)
#trainvalidset = or.(trainset, validset)
#allset = or.(trainvalidset, testset) |> cache

#r = rand(Float32, length(label))
#trainset = BoolVariate("", r .<= 0.95) .& haslabel
#validset = BoolVariate("", r .> 0.95) .& haslabel
#allset = or.(haslabel, testset) |> cache
#allcount = count(convert(BitArray{1}, allset))

#r = Vector{Float32}()

# testip = test_df["ip"]
# testos = test_df["os"]
# testapp = test_df["app"]
# testchannel = test_df["channel"]
# testdevice = test_df["device"]

# ip = maplevels(train_df["ip"], testip)
# os = maplevels(train_df["os"], testos)
# app = maplevels(train_df["app"], testapp)
# channel = maplevels(train_df["channel"], testchannel)
# device = maplevels(train_df["device"], testdevice)

ip = traintest_df["ip"]
os = traintest_df["os"]
app = traintest_df["app"]
channel = traintest_df["channel"]
device = traintest_df["device"]

#hourcount = JuML.GroupStatsCovariate("hourcount", getgroupstats(clickhour24)) |> cache
#ipdevicehourpcnt = JuML.GroupStatsCovariate("iphourpcnt", getgroupstats(app, clickhour24)) ./ hourcount

#hourcount = JuML.GroupStatsCovariate("hourcount", getgroupstats(clickhour));

#minutecount = JuML.GroupStatsCovariate("minutecount", getgroupstats(clickminute));

#seccount = JuML.GroupStatsCovariate("minutecount", getgroupstats(clicksec));

#ip per hour:
iphourdaycount = JuML.GroupStatsCovariate("iphourdaycount", getgroupstats(ip, clickhour));
iphourappcount = JuML.GroupStatsCovariate("iphourappcount", getgroupstats(ip, clickhour, app));
iphouroscount = JuML.GroupStatsCovariate("iphouroscount", getgroupstats(ip, clickhour, os));
iphourdevicecount = JuML.GroupStatsCovariate("iphourdevicecount", getgroupstats(ip, clickhour, device));
iphourchannelcount = JuML.GroupStatsCovariate("iphourchannelcount", getgroupstats(ip, clickhour, channel));
iphourappchannelcount = JuML.GroupStatsCovariate("iphourappchannelcount", getgroupstats(ip, clickhour, app, channel));

#1way:
ipcount = JuML.GroupStatsCovariate("ipcount", getgroupstats(ip));
appcount = JuML.GroupStatsCovariate("appcount", getgroupstats(app));
oscount = JuML.GroupStatsCovariate("oscount", getgroupstats(os));
devicecount = JuML.GroupStatsCovariate("devicecount", getgroupstats(device));
channelcount = JuML.GroupStatsCovariate("channelcount", getgroupstats(channel));

#1way/hour:
iphourcount = JuML.GroupStatsCovariate("iphourcount", getgroupstats(ip, clickhour24));
apphourcount = JuML.GroupStatsCovariate("apphourcount", getgroupstats(app, clickhour24));
oshourcount = JuML.GroupStatsCovariate("oshourcount", getgroupstats(os, clickhour24));
devicehourcount = JuML.GroupStatsCovariate("devicehourcount", getgroupstats(device, clickhour24));
channelhourcount = JuML.GroupStatsCovariate("channelhourcount", getgroupstats(channel, clickhour24));

#2way:
ipappcount = JuML.GroupStatsCovariate("ipappcount", getgroupstats(ip, app));
ipdevicecount = JuML.GroupStatsCovariate("ipdevicecount", getgroupstats(ip, device));
iposcount = JuML.GroupStatsCovariate("iposcount", getgroupstats(ip, os));
ipchannelcount = JuML.GroupStatsCovariate("ipchannelcount", getgroupstats(ip, channel));
appdevicecount = JuML.GroupStatsCovariate("appdevicecount", getgroupstats(app, device));
apposcount = JuML.GroupStatsCovariate("apposcount", getgroupstats(app, os));
appchannelcount = JuML.GroupStatsCovariate("appchannelcount", getgroupstats(app, channel));
deviceoscount = JuML.GroupStatsCovariate("deviceoscount", getgroupstats(device, os));
devicechannelcount = JuML.GroupStatsCovariate("devicechannelcount", getgroupstats(device, channel));
oschannelcount = JuML.GroupStatsCovariate("oschannelcount", getgroupstats(os, channel));

#3way:
deviceoschannelcount = JuML.GroupStatsCovariate("ipappcount", getgroupstats(device, os, channel));
apposchannelcount = JuML.GroupStatsCovariate("ipdevicecount", getgroupstats(app, os, channel));
deviceappchannelcount = JuML.GroupStatsCovariate("iposcount", getgroupstats(device, app, channel));
deviceapposcount = JuML.GroupStatsCovariate("ipchannelcount", getgroupstats(device, app, os));
iposchannelcount = JuML.GroupStatsCovariate("appdevicecount", getgroupstats(ip, os, channel));
ipdevicechannelcount = JuML.GroupStatsCovariate("apposcount", getgroupstats(ip, device, channel));
iposdevicecount = JuML.GroupStatsCovariate("appchannelcount", getgroupstats(ip, os, device));
ipappchannelcount = JuML.GroupStatsCovariate("deviceoscount", getgroupstats(ip, app, channel));
ipapposcount = JuML.GroupStatsCovariate("devicechannelcount", getgroupstats(ip, app, os));
ipappdevicecount = JuML.GroupStatsCovariate("oschannelcount", getgroupstats(ip, app, device));

oshourcount = JuML.GroupStatsCovariate("oshourcount", getgroupstats(os, clickhour));
apphourcount = JuML.GroupStatsCovariate("apphourcount", getgroupstats(app, clickhour));
channelhourcount = JuML.GroupStatsCovariate("channelhourcount", getgroupstats(channel, clickhour));
devicehourcount = JuML.GroupStatsCovariate("devicehourcount", getgroupstats(device, clickhour));
#ipapposcount = JuML.GroupStatsCovariate("ipapposcount", getgroupstats((ip, app, os)))

# hourcount = JuML.GroupStatsCovariate("hourcount", getgroupstats(clickhour))
# iphourcount = JuML.GroupStatsCovariate("iphourcount", getgroupstats((ip, clickhour)))
# ipappcount = JuML.GroupStatsCovariate("ipappcount", getgroupstats((ip, app)))
# ipdevicecount = JuML.GroupStatsCovariate("ipdevicecount", getgroupstats((ip, device)))

#ipmean = JuML.GroupStatsCovariate("ipcount", getgroupstats(ip, label), s -> isnan(s.mean) ? labelstats.mean : s.mean)
#osmean = JuML.GroupStatsCovariate("oscount", getgroupstats(os, label), s -> isnan(s.mean) ? labelstats.mean : s.mean)
#appmean = JuML.GroupStatsCovariate("appcount", getgroupstats(app, label), s -> isnan(s.mean) ? labelstats.mean : s.mean)
#channelmean = JuML.GroupStatsCovariate("channelcount", getgroupstats(channel, label), s -> isnan(s.mean) ? labelstats.mean : s.mean)
#devicemean = JuML.GroupStatsCovariate("devicecount", getgroupstats(device, label), s -> isnan(s.mean) ? labelstats.mean : s.mean)
#hourmean = JuML.GroupStatsCovariate("hourcount", getgroupstats(clickhour, label), s -> isnan(s.mean) ? labelstats.mean : s.mean)
#iphourcount = JuML.GroupStatsCovariate("iphourcount", getgroupstats((ip, clickhour)))
#ipappcount = JuML.GroupStatsCovariate("ipappcount", getgroupstats((ip, app)))
#ipdevicecount = JuML.GroupStatsCovariate("ipdevicecount", getgroupstats((ip, device)))

# ipgstats = JuML.getstats([trainip], label)
# osgstats = JuML.getstats([trainos], label)
# appgstats = JuML.getstats([trainapp], label)
# channelgstats = JuML.getstats([trainchannel], label)
# devicegstats = JuML.getstats([traindevice], label)
# hourgstats = JuML.getstats([clickhour], label)

# daygstats = JuML.getstats([clickday], label)

# pred = JuML.GroupStatsCovariate("", gstats, s -> s.mean)
# p = collect(convert(Vector{Float32}, pred))
# summary(pred)

# mpred = map(pred, test_df)

# auc = JuML.getauc(collect(convert(Vector{Float32}, pred)), label)
# f = factor(covtest, 0.0:0.1:1.0)
# summary(f)

# function toordinal(factor::JuML.AbstractFactor)
#     JuML.OrdinalFactor(JuML.getname(factor), factor, (x, y) -> parse(x) < parse(y))
# end

#modelfactors = [clickday, clickhour, fmean(ip, label, labelstats, ipgstats), fcount(ip, label, ipgstats), fmean(os, label, labelstats, osgstats), fcount(os, label, osgstats), fmean(device, label, labelstats, devicegstats), fcount(device, label, devicegstats), fmean(channel, label, labelstats, channelgstats), fcount(channel, label, channelgstats), fmean(app, label, labelstats, appgstats), fcount(app, label, appgstats)]
#modelfactors = [fcount("hourstats", hourgstats), fcount("ipstats", ipgstats), fcount("osstats", osgstats), fcount("devicestats", devicegstats), fcount("channelstats", channelgstats), fcount("appstats", appgstats)]
#modelfactors = map((cov -> quantilebin(cov, 250)), [iphourcount, ipappcount, ipdevicecount, ipcount, oscount, devicecount, appcount, channelcount, hourcount])
onewayfactors = map((cov -> JuML.factor(cov)), [hourcount, ipcount, oscount, devicecount, appcount, channelcount])
#poswgt = (1.0 - labelstats.mean) / labelstats.mean
#datafactors = [clickhour24, JuML.OrdinalFactor(os), JuML.OrdinalFactor(device), JuML.OrdinalFactor(app,), JuML.OrdinalFactor(channel)]

iphourfactors = map((cov -> JuML.factor(cov)), [iphourdaycount, iphouroscount, iphourdevicecount, iphourappcount, iphourchannelcount, iphourappchannelcount])
#onewayhourfactors = map((cov -> JuML.factor(cov)), [hourcount, iphourcount, oshourcount, devicehourcount, apphourcount, channelhourcount])

twowayfactors = map((cov -> JuML.factor(cov)), [ipappcount, ipdevicecount, ipchannelcount, iposcount, appdevicecount, apposcount, appchannelcount, deviceoscount, devicechannelcount, oschannelcount])

#threewayfactors = map((cov -> JuML.factor(cov)), [deviceoschannelcount, apposchannelcount, deviceappchannelcount, deviceapposcount, iposchannelcount, ipdevicechannelcount, iposdevicecount, ipappchannelcount, ipapposcount, ipappdevicecount])

labelstats = getstats(label)

poswgt = (1.0 - labelstats.mean) * 100.0

mapped = map((f -> maplevels(f, 10000)), [app, device, channel, os])

@time model = xgblogit(label, [iphourfactors; mapped]; trainselector = trainset, validselector = validset, η = 0.1, λ = 1.0, γ = 0.0, μ = 0.5, subsample = 1.0, posweight = poswgt, minchildweight = 0.0, nrounds = 150, maxdepth = 7, ordstumps = true, pruning = false, leafwise = false, maxleaves = 8, caching = true, usefloat64 = false, singlethread = false, slicelength = 1000000);

#@time testpred = predict(model, test_df; posweight = poswgt)

testlen = length(test_df["click_id"])
is_attr = Covariate("is_attributed", model.pred[(length(traintest_df) - testlen + 1):end])

click_id = test_df["click_id"]
sub_df = DataFrame(testlen, JuML.AbstractFactor[], [is_attr], JuML.AbstractBoolVariate[], JuML.AbstractDateTimeVariate[], [click_id], "")
JuML.tocsv("C:\\Users\\adamm\\Documents\\Julia\\kaggle\\submission.csv", sub_df)

max_stars_repo_name: Statfactory/JuML

max_issues_repo_name: Statfactory/JuML

max_forks_repo_name: Statfactory/JuML


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

"""
    accept_inbound_connection(connection_id)
    accept_inbound_connection(connection_id, params::Dict{String,<:Any})

Allows the remote domain owner to accept an inbound cross-cluster connection request.

# Arguments
- `connection_id`: The ID of the inbound connection you want to accept.

"""
function accept_inbound_connection(
    ConnectionId; aws_config::AbstractAWSConfig=global_aws_config()
)
    return opensearch(
        "PUT",
        "/2021-01-01/opensearch/cc/inboundConnection/$(ConnectionId)/accept";
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function accept_inbound_connection(
    ConnectionId,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return opensearch(
        "PUT",
        "/2021-01-01/opensearch/cc/inboundConnection/$(ConnectionId)/accept",
        params;
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    add_tags(arn, tag_list)
    add_tags(arn, tag_list, params::Dict{String,<:Any})

Attaches tags to an existing domain. Tags are a set of case-sensitive key value pairs. An
domain can have up to 10 tags. See  Tagging Amazon OpenSearch Service domains for more
information.

# Arguments
- `arn`: Specify the ARN of the domain you want to add tags to.
- `tag_list`: List of Tag to add to the domain.

"""
function add_tags(ARN, TagList; aws_config::AbstractAWSConfig=global_aws_config())
    return opensearch(
        "POST",
        "/2021-01-01/tags",
        Dict{String,Any}("ARN" => ARN, "TagList" => TagList);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function add_tags(
    ARN,
    TagList,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return opensearch(
        "POST",
        "/2021-01-01/tags",
        Dict{String,Any}(
            mergewith(_merge, Dict{String,Any}("ARN" => ARN, "TagList" => TagList), params)
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    associate_package(domain_name, package_id)
    associate_package(domain_name, package_id, params::Dict{String,<:Any})

Associates a package with an Amazon OpenSearch Service domain.

# Arguments
- `domain_name`: The name of the domain to associate the package with.
- `package_id`: Internal ID of the package to associate with a domain. Use DescribePackages
  to find this value.

"""
function associate_package(
    DomainName, PackageID; aws_config::AbstractAWSConfig=global_aws_config()
)
    return opensearch(
        "POST",
        "/2021-01-01/packages/associate/$(PackageID)/$(DomainName)";
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function associate_package(
    DomainName,
    PackageID,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return opensearch(
        "POST",
        "/2021-01-01/packages/associate/$(PackageID)/$(DomainName)",
        params;
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    cancel_service_software_update(domain_name)
    cancel_service_software_update(domain_name, params::Dict{String,<:Any})

Cancels a scheduled service software update for an Amazon OpenSearch Service domain. You
can only perform this operation before the AutomatedUpdateDate and when the UpdateStatus is
in the PENDING_UPDATE state.

# Arguments
- `domain_name`: The name of the domain that you want to stop the latest service software
  update on.

"""
function cancel_service_software_update(
    DomainName; aws_config::AbstractAWSConfig=global_aws_config()
)
    return opensearch(
        "POST",
        "/2021-01-01/opensearch/serviceSoftwareUpdate/cancel",
        Dict{String,Any}("DomainName" => DomainName);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function cancel_service_software_update(
    DomainName,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return opensearch(
        "POST",
        "/2021-01-01/opensearch/serviceSoftwareUpdate/cancel",
        Dict{String,Any}(
            mergewith(_merge, Dict{String,Any}("DomainName" => DomainName), params)
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    create_domain(domain_name)
    create_domain(domain_name, params::Dict{String,<:Any})

Creates a new Amazon OpenSearch Service domain. For more information, see Creating and
managing Amazon OpenSearch Service domains  in the Amazon OpenSearch Service Developer
Guide.

# Arguments
- `domain_name`: The name of the Amazon OpenSearch Service domain you're creating. Domain
  names are unique across the domains owned by an account within an AWS region. Domain names
  must start with a lowercase letter and can contain the following characters: a-z
  (lowercase), 0-9, and - (hyphen).

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"AccessPolicies"`: IAM access policy as a JSON-formatted string.
- `"AdvancedOptions"`: Option to allow references to indices in an HTTP request body. Must
  be false when configuring access to individual sub-resources. By default, the value is
  true. See Advanced cluster parameters  for more information.
- `"AdvancedSecurityOptions"`: Specifies advanced security options.
- `"AutoTuneOptions"`: Specifies Auto-Tune options.
- `"ClusterConfig"`: Configuration options for a domain. Specifies the instance type and
  number of instances in the domain.
- `"CognitoOptions"`: Options to specify the Cognito user and identity pools for OpenSearch
  Dashboards authentication. For more information, see Configuring Amazon Cognito
  authentication for OpenSearch Dashboards.
- `"DomainEndpointOptions"`: Options to specify configurations that will be applied to the
  domain endpoint.
- `"EBSOptions"`: Options to enable, disable, and specify the type and size of EBS storage
  volumes.
- `"EncryptionAtRestOptions"`: Options for encryption of data at rest.
- `"EngineVersion"`: String of format Elasticsearch_X.Y or OpenSearch_X.Y to specify the
  engine version for the Amazon OpenSearch Service domain. For example, \"OpenSearch_1.0\" or
  \"Elasticsearch_7.9\". For more information, see Creating and managing Amazon OpenSearch
  Service domains .
- `"LogPublishingOptions"`: Map of LogType and LogPublishingOption, each containing options
  to publish a given type of OpenSearch log.
- `"NodeToNodeEncryptionOptions"`: Node-to-node encryption options.
- `"SnapshotOptions"`: Option to set time, in UTC format, of the daily automated snapshot.
  Default value is 0 hours.
- `"TagList"`: A list of Tag added during domain creation.
- `"VPCOptions"`: Options to specify the subnets and security groups for a VPC endpoint.
  For more information, see Launching your Amazon OpenSearch Service domains using a VPC .
"""
function create_domain(DomainName; aws_config::AbstractAWSConfig=global_aws_config())
    return opensearch(
        "POST",
        "/2021-01-01/opensearch/domain",
        Dict{String,Any}("DomainName" => DomainName);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function create_domain(
    DomainName,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return opensearch(
        "POST",
        "/2021-01-01/opensearch/domain",
        Dict{String,Any}(
            mergewith(_merge, Dict{String,Any}("DomainName" => DomainName), params)
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    create_outbound_connection(connection_alias, local_domain_info, remote_domain_info)
    create_outbound_connection(connection_alias, local_domain_info, remote_domain_info, params::Dict{String,<:Any})

Creates a new cross-cluster connection from a local OpenSearch domain to a remote
OpenSearch domain.

# Arguments
- `connection_alias`: The connection alias used used by the customer for this cross-cluster
  connection.
- `local_domain_info`: The  AWSDomainInformation  for the local OpenSearch domain.
- `remote_domain_info`: The  AWSDomainInformation  for the remote OpenSearch domain.

"""
function create_outbound_connection(
    ConnectionAlias,
    LocalDomainInfo,
    RemoteDomainInfo;
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return opensearch(
        "POST",
        "/2021-01-01/opensearch/cc/outboundConnection",
        Dict{String,Any}(
            "ConnectionAlias" => ConnectionAlias,
            "LocalDomainInfo" => LocalDomainInfo,
            "RemoteDomainInfo" => RemoteDomainInfo,
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function create_outbound_connection(
    ConnectionAlias,
    LocalDomainInfo,
    RemoteDomainInfo,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return opensearch(
        "POST",
        "/2021-01-01/opensearch/cc/outboundConnection",
        Dict{String,Any}(
            mergewith(
                _merge,
                Dict{String,Any}(
                    "ConnectionAlias" => ConnectionAlias,
                    "LocalDomainInfo" => LocalDomainInfo,
                    "RemoteDomainInfo" => RemoteDomainInfo,
                ),
                params,
            ),
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    create_package(package_name, package_source, package_type)
    create_package(package_name, package_source, package_type, params::Dict{String,<:Any})

Create a package for use with Amazon OpenSearch Service domains.

# Arguments
- `package_name`: Unique identifier for the package.
- `package_source`: The Amazon S3 location from which to import the package.
- `package_type`: Type of package. Currently supports only TXT-DICTIONARY.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"PackageDescription"`: Description of the package.
"""
function create_package(
    PackageName,
    PackageSource,
    PackageType;
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return opensearch(
        "POST",
        "/2021-01-01/packages",
        Dict{String,Any}(
            "PackageName" => PackageName,
            "PackageSource" => PackageSource,
            "PackageType" => PackageType,
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function create_package(
    PackageName,
    PackageSource,
    PackageType,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return opensearch(
        "POST",
        "/2021-01-01/packages",
        Dict{String,Any}(
            mergewith(
                _merge,
                Dict{String,Any}(
                    "PackageName" => PackageName,
                    "PackageSource" => PackageSource,
                    "PackageType" => PackageType,
                ),
                params,
            ),
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    delete_domain(domain_name)
    delete_domain(domain_name, params::Dict{String,<:Any})

Permanently deletes the specified domain and all of its data. Once a domain is deleted, it
cannot be recovered.

# Arguments
- `domain_name`: The name of the domain you want to permanently delete.

"""
function delete_domain(DomainName; aws_config::AbstractAWSConfig=global_aws_config())
    return opensearch(
        "DELETE",
        "/2021-01-01/opensearch/domain/$(DomainName)";
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function delete_domain(
    DomainName,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return opensearch(
        "DELETE",
        "/2021-01-01/opensearch/domain/$(DomainName)",
        params;
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    delete_inbound_connection(connection_id)
    delete_inbound_connection(connection_id, params::Dict{String,<:Any})

Allows the remote domain owner to delete an existing inbound cross-cluster connection.

# Arguments
- `connection_id`: The ID of the inbound connection to permanently delete.

"""
function delete_inbound_connection(
    ConnectionId; aws_config::AbstractAWSConfig=global_aws_config()
)
    return opensearch(
        "DELETE",
        "/2021-01-01/opensearch/cc/inboundConnection/$(ConnectionId)";
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function delete_inbound_connection(
    ConnectionId,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return opensearch(
        "DELETE",
        "/2021-01-01/opensearch/cc/inboundConnection/$(ConnectionId)",
        params;
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    delete_outbound_connection(connection_id)
    delete_outbound_connection(connection_id, params::Dict{String,<:Any})

Allows the local domain owner to delete an existing outbound cross-cluster connection.

# Arguments
- `connection_id`: The ID of the outbound connection you want to permanently delete.

"""
function delete_outbound_connection(
    ConnectionId; aws_config::AbstractAWSConfig=global_aws_config()
)
    return opensearch(
        "DELETE",
        "/2021-01-01/opensearch/cc/outboundConnection/$(ConnectionId)";
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function delete_outbound_connection(
    ConnectionId,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return opensearch(
        "DELETE",
        "/2021-01-01/opensearch/cc/outboundConnection/$(ConnectionId)",
        params;
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    delete_package(package_id)
    delete_package(package_id, params::Dict{String,<:Any})

Deletes the package.

# Arguments
- `package_id`: The internal ID of the package you want to delete. Use DescribePackages to
  find this value.

"""
function delete_package(PackageID; aws_config::AbstractAWSConfig=global_aws_config())
    return opensearch(
        "DELETE",
        "/2021-01-01/packages/$(PackageID)";
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function delete_package(
    PackageID,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return opensearch(
        "DELETE",
        "/2021-01-01/packages/$(PackageID)",
        params;
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    describe_domain(domain_name)
    describe_domain(domain_name, params::Dict{String,<:Any})

Returns domain configuration information about the specified domain, including the domain
ID, domain endpoint, and domain ARN.

# Arguments
- `domain_name`: The name of the domain for which you want information.

"""
function describe_domain(DomainName; aws_config::AbstractAWSConfig=global_aws_config())
    return opensearch(
        "GET",
        "/2021-01-01/opensearch/domain/$(DomainName)";
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function describe_domain(
    DomainName,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return opensearch(
        "GET",
        "/2021-01-01/opensearch/domain/$(DomainName)",
        params;
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    describe_domain_auto_tunes(domain_name)
    describe_domain_auto_tunes(domain_name, params::Dict{String,<:Any})

Provides scheduled Auto-Tune action details for the domain, such as Auto-Tune action type,
description, severity, and scheduled date.

# Arguments
- `domain_name`: The domain name for which you want Auto-Tune action details.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"MaxResults"`: Set this value to limit the number of results returned. If not specified,
  defaults to 100.
- `"NextToken"`: NextToken is sent in case the earlier API call results contain the
  NextToken. Used for pagination.
"""
function describe_domain_auto_tunes(
    DomainName; aws_config::AbstractAWSConfig=global_aws_config()
)
    return opensearch(
        "GET",
        "/2021-01-01/opensearch/domain/$(DomainName)/autoTunes";
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function describe_domain_auto_tunes(
    DomainName,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return opensearch(
        "GET",
        "/2021-01-01/opensearch/domain/$(DomainName)/autoTunes",
        params;
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    describe_domain_config(domain_name)
    describe_domain_config(domain_name, params::Dict{String,<:Any})

Provides cluster configuration information about the specified domain, such as the state,
creation date, update version, and update date for cluster options.

# Arguments
- `domain_name`: The domain you want to get information about.

"""
function describe_domain_config(
    DomainName; aws_config::AbstractAWSConfig=global_aws_config()
)
    return opensearch(
        "GET",
        "/2021-01-01/opensearch/domain/$(DomainName)/config";
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function describe_domain_config(
    DomainName,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return opensearch(
        "GET",
        "/2021-01-01/opensearch/domain/$(DomainName)/config",
        params;
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    describe_domains(domain_names)
    describe_domains(domain_names, params::Dict{String,<:Any})

Returns domain configuration information about the specified domains, including the domain
ID, domain endpoint, and domain ARN.

# Arguments
- `domain_names`: The domains for which you want information.

"""
function describe_domains(DomainNames; aws_config::AbstractAWSConfig=global_aws_config())
    return opensearch(
        "POST",
        "/2021-01-01/opensearch/domain-info",
        Dict{String,Any}("DomainNames" => DomainNames);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function describe_domains(
    DomainNames,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return opensearch(
        "POST",
        "/2021-01-01/opensearch/domain-info",
        Dict{String,Any}(
            mergewith(_merge, Dict{String,Any}("DomainNames" => DomainNames), params)
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

Lists all the inbound cross-cluster connections for a remote domain.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"Filters"`:  A list of filters used to match properties for inbound cross-cluster
  connections. Available  Filter  values are:  connection-id local-domain-info.domain-name
  local-domain-info.owner-id local-domain-info.region remote-domain-info.domain-name
- `"MaxResults"`: Set this value to limit the number of results returned. If not specified,
  defaults to 100.
- `"NextToken"`: If more results are available and NextToken is present, make the next
  request to the same API with the received NextToken to paginate the remaining results.
"""
function describe_inbound_connections(; aws_config::AbstractAWSConfig=global_aws_config())
    return opensearch(
        "POST",
        "/2021-01-01/opensearch/cc/inboundConnection/search";
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function describe_inbound_connections(
    params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()
)
    return opensearch(
        "POST",
        "/2021-01-01/opensearch/cc/inboundConnection/search",
        params;
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    describe_instance_type_limits(engine_version, instance_type)
    describe_instance_type_limits(engine_version, instance_type, params::Dict{String,<:Any})

 Describe the limits for a given instance type and OpenSearch or Elasticsearch version.
When modifying an existing domain, specify the  DomainName  to see which limits you can
modify.

# Arguments
- `engine_version`:  Version of OpenSearch for which  Limits  are needed.
- `instance_type`:  The instance type for an OpenSearch cluster for which OpenSearch
  Limits  are needed.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"domainName"`:  The name of the domain you want to modify. Only include this value if
  you're querying OpenSearch  Limits  for an existing domain.
"""
function describe_instance_type_limits(
    EngineVersion, InstanceType; aws_config::AbstractAWSConfig=global_aws_config()
)
    return opensearch(
        "GET",
        "/2021-01-01/opensearch/instanceTypeLimits/$(EngineVersion)/$(InstanceType)";
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function describe_instance_type_limits(
    EngineVersion,
    InstanceType,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return opensearch(
        "GET",
        "/2021-01-01/opensearch/instanceTypeLimits/$(EngineVersion)/$(InstanceType)",
        params;
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

Lists all the outbound cross-cluster connections for a local domain.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"Filters"`:  A list of filters used to match properties for outbound cross-cluster
  connections. Available  Filter  names for this operation are:  connection-id
  remote-domain-info.domain-name remote-domain-info.owner-id remote-domain-info.region
  local-domain-info.domain-name
- `"MaxResults"`: Set this value to limit the number of results returned. If not specified,
  defaults to 100.
- `"NextToken"`: NextToken is sent in case the earlier API call results contain the
  NextToken parameter. Used for pagination.
"""
function describe_outbound_connections(; aws_config::AbstractAWSConfig=global_aws_config())
    return opensearch(
        "POST",
        "/2021-01-01/opensearch/cc/outboundConnection/search";
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function describe_outbound_connections(
    params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()
)
    return opensearch(
        "POST",
        "/2021-01-01/opensearch/cc/outboundConnection/search",
        params;
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

Describes all packages available to Amazon OpenSearch Service domains. Includes options for
filtering, limiting the number of results, and pagination.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"Filters"`: Only returns packages that match the DescribePackagesFilterList values.
- `"MaxResults"`: Limits results to a maximum number of packages.
- `"NextToken"`: Used for pagination. Only necessary if a previous API call includes a
  non-null NextToken value. If provided, returns results for the next page.
"""
function describe_packages(; aws_config::AbstractAWSConfig=global_aws_config())
    return opensearch(
        "POST",
        "/2021-01-01/packages/describe";
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function describe_packages(
    params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()
)
    return opensearch(
        "POST",
        "/2021-01-01/packages/describe",
        params;
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

Lists available reserved OpenSearch instance offerings.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"maxResults"`: Set this value to limit the number of results returned. If not specified,
  defaults to 100.
- `"nextToken"`: Provides an identifier to allow retrieval of paginated results.
- `"offeringId"`: The offering identifier filter value. Use this parameter to show only the
  available offering that matches the specified reservation identifier.
"""
function describe_reserved_instance_offerings(;
    aws_config::AbstractAWSConfig=global_aws_config()
)
    return opensearch(
        "GET",
        "/2021-01-01/opensearch/reservedInstanceOfferings";
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function describe_reserved_instance_offerings(
    params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()
)
    return opensearch(
        "GET",
        "/2021-01-01/opensearch/reservedInstanceOfferings",
        params;
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

Returns information about reserved OpenSearch instances for this account.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"maxResults"`: Set this value to limit the number of results returned. If not specified,
  defaults to 100.
- `"nextToken"`: Provides an identifier to allow retrieval of paginated results.
- `"reservationId"`: The reserved instance identifier filter value. Use this parameter to
  show only the reservation that matches the specified reserved OpenSearch instance ID.
"""
function describe_reserved_instances(; aws_config::AbstractAWSConfig=global_aws_config())
    return opensearch(
        "GET",
        "/2021-01-01/opensearch/reservedInstances";
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function describe_reserved_instances(
    params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()
)
    return opensearch(
        "GET",
        "/2021-01-01/opensearch/reservedInstances",
        params;
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    dissociate_package(domain_name, package_id)
    dissociate_package(domain_name, package_id, params::Dict{String,<:Any})

Dissociates a package from the Amazon OpenSearch Service domain.

# Arguments
- `domain_name`: The name of the domain to associate the package with.
- `package_id`: The internal ID of the package to associate with a domain. Use
  DescribePackages to find this value.

"""
function dissociate_package(
    DomainName, PackageID; aws_config::AbstractAWSConfig=global_aws_config()
)
    return opensearch(
        "POST",
        "/2021-01-01/packages/dissociate/$(PackageID)/$(DomainName)";
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function dissociate_package(
    DomainName,
    PackageID,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return opensearch(
        "POST",
        "/2021-01-01/packages/dissociate/$(PackageID)/$(DomainName)",
        params;
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

 Returns a list of upgrade-compatible versions of OpenSearch/Elasticsearch. You can
optionally pass a  DomainName  to get all upgrade-compatible versions of
OpenSearch/Elasticsearch for that specific domain.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"domainName"`:
"""
function get_compatible_versions(; aws_config::AbstractAWSConfig=global_aws_config())
    return opensearch(
        "GET",
        "/2021-01-01/opensearch/compatibleVersions";
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function get_compatible_versions(
    params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()
)
    return opensearch(
        "GET",
        "/2021-01-01/opensearch/compatibleVersions",
        params;
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    get_package_version_history(package_id)
    get_package_version_history(package_id, params::Dict{String,<:Any})

Returns a list of package versions, along with their creation time and commit message.

# Arguments
- `package_id`: Returns an audit history of package versions.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"maxResults"`: Limits results to a maximum number of package versions.
- `"nextToken"`: Used for pagination. Only necessary if a previous API call includes a
  non-null NextToken value. If provided, returns results for the next page.
"""
function get_package_version_history(
    PackageID; aws_config::AbstractAWSConfig=global_aws_config()
)
    return opensearch(
        "GET",
        "/2021-01-01/packages/$(PackageID)/history";
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function get_package_version_history(
    PackageID,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return opensearch(
        "GET",
        "/2021-01-01/packages/$(PackageID)/history",
        params;
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    get_upgrade_history(domain_name)
    get_upgrade_history(domain_name, params::Dict{String,<:Any})

Retrieves the complete history of the last 10 upgrades performed on the domain.

# Arguments
- `domain_name`:

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"maxResults"`:
- `"nextToken"`:
"""
function get_upgrade_history(DomainName; aws_config::AbstractAWSConfig=global_aws_config())
    return opensearch(
        "GET",
        "/2021-01-01/opensearch/upgradeDomain/$(DomainName)/history";
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function get_upgrade_history(
    DomainName,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return opensearch(
        "GET",
        "/2021-01-01/opensearch/upgradeDomain/$(DomainName)/history",
        params;
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    get_upgrade_status(domain_name)
    get_upgrade_status(domain_name, params::Dict{String,<:Any})

Retrieves the latest status of the last upgrade or upgrade eligibility check performed on
the domain.

# Arguments
- `domain_name`:

"""
function get_upgrade_status(DomainName; aws_config::AbstractAWSConfig=global_aws_config())
    return opensearch(
        "GET",
        "/2021-01-01/opensearch/upgradeDomain/$(DomainName)/status";
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function get_upgrade_status(
    DomainName,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return opensearch(
        "GET",
        "/2021-01-01/opensearch/upgradeDomain/$(DomainName)/status",
        params;
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

Returns the names of all domains owned by the current user's account.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"engineType"`:  Optional parameter to filter the output by domain engine type.
  Acceptable values are 'Elasticsearch' and 'OpenSearch'.
"""
function list_domain_names(; aws_config::AbstractAWSConfig=global_aws_config())
    return opensearch(
        "GET", "/2021-01-01/domain"; aws_config=aws_config, feature_set=SERVICE_FEATURE_SET
    )
end
function list_domain_names(
    params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()
)
    return opensearch(
        "GET",
        "/2021-01-01/domain",
        params;
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    list_domains_for_package(package_id)
    list_domains_for_package(package_id, params::Dict{String,<:Any})

Lists all Amazon OpenSearch Service domains associated with the package.

# Arguments
- `package_id`: The package for which to list associated domains.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"maxResults"`: Limits the results to a maximum number of domains.
- `"nextToken"`: Used for pagination. Only necessary if a previous API call includes a
  non-null NextToken value. If provided, returns results for the next page.
"""
function list_domains_for_package(
    PackageID; aws_config::AbstractAWSConfig=global_aws_config()
)
    return opensearch(
        "GET",
        "/2021-01-01/packages/$(PackageID)/domains";
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function list_domains_for_package(
    PackageID,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return opensearch(
        "GET",
        "/2021-01-01/packages/$(PackageID)/domains",
        params;
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    list_instance_type_details(engine_version)
    list_instance_type_details(engine_version, params::Dict{String,<:Any})

# Arguments
- `engine_version`:

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"domainName"`:
- `"maxResults"`:
- `"nextToken"`:
"""
function list_instance_type_details(
    EngineVersion; aws_config::AbstractAWSConfig=global_aws_config()
)
    return opensearch(
        "GET",
        "/2021-01-01/opensearch/instanceTypeDetails/$(EngineVersion)";
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function list_instance_type_details(
    EngineVersion,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return opensearch(
        "GET",
        "/2021-01-01/opensearch/instanceTypeDetails/$(EngineVersion)",
        params;
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    list_packages_for_domain(domain_name)
    list_packages_for_domain(domain_name, params::Dict{String,<:Any})

Lists all packages associated with the Amazon OpenSearch Service domain.

# Arguments
- `domain_name`: The name of the domain for which you want to list associated packages.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"maxResults"`: Limits results to a maximum number of packages.
- `"nextToken"`: Used for pagination. Only necessary if a previous API call includes a
  non-null NextToken value. If provided, returns results for the next page.
"""
function list_packages_for_domain(
    DomainName; aws_config::AbstractAWSConfig=global_aws_config()
)
    return opensearch(
        "GET",
        "/2021-01-01/domain/$(DomainName)/packages";
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function list_packages_for_domain(
    DomainName,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return opensearch(
        "GET",
        "/2021-01-01/domain/$(DomainName)/packages",
        params;
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    list_tags(arn)
    list_tags(arn, params::Dict{String,<:Any})

Returns all tags for the given domain.

# Arguments
- `arn`: Specify the ARN of the domain that the tags you want to view are attached to.

"""
function list_tags(arn; aws_config::AbstractAWSConfig=global_aws_config())
    return opensearch(
        "GET",
        "/2021-01-01/tags/",
        Dict{String,Any}("arn" => arn);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function list_tags(
    arn, params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()
)
    return opensearch(
        "GET",
        "/2021-01-01/tags/",
        Dict{String,Any}(mergewith(_merge, Dict{String,Any}("arn" => arn), params));
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

List all supported versions of OpenSearch and Elasticsearch.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"maxResults"`:  Set this value to limit the number of results returned. Value must be
  greater than 10 or it won't be honored.
- `"nextToken"`:
"""
function list_versions(; aws_config::AbstractAWSConfig=global_aws_config())
    return opensearch(
        "GET",
        "/2021-01-01/opensearch/versions";
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function list_versions(
    params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()
)
    return opensearch(
        "GET",
        "/2021-01-01/opensearch/versions",
        params;
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    purchase_reserved_instance_offering(reservation_name, reserved_instance_offering_id)
    purchase_reserved_instance_offering(reservation_name, reserved_instance_offering_id, params::Dict{String,<:Any})

Allows you to purchase reserved OpenSearch instances.

# Arguments
- `reservation_name`: A customer-specified identifier to track this reservation.
- `reserved_instance_offering_id`: The ID of the reserved OpenSearch instance offering to
  purchase.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"InstanceCount"`: The number of OpenSearch instances to reserve.
"""
function purchase_reserved_instance_offering(
    ReservationName,
    ReservedInstanceOfferingId;
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return opensearch(
        "POST",
        "/2021-01-01/opensearch/purchaseReservedInstanceOffering",
        Dict{String,Any}(
            "ReservationName" => ReservationName,
            "ReservedInstanceOfferingId" => ReservedInstanceOfferingId,
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function purchase_reserved_instance_offering(
    ReservationName,
    ReservedInstanceOfferingId,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return opensearch(
        "POST",
        "/2021-01-01/opensearch/purchaseReservedInstanceOffering",
        Dict{String,Any}(
            mergewith(
                _merge,
                Dict{String,Any}(
                    "ReservationName" => ReservationName,
                    "ReservedInstanceOfferingId" => ReservedInstanceOfferingId,
                ),
                params,
            ),
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    reject_inbound_connection(connection_id)
    reject_inbound_connection(connection_id, params::Dict{String,<:Any})

Allows the remote domain owner to reject an inbound cross-cluster connection request.

# Arguments
- `connection_id`: The ID of the inbound connection to reject.

"""
function reject_inbound_connection(
    ConnectionId; aws_config::AbstractAWSConfig=global_aws_config()
)
    return opensearch(
        "PUT",
        "/2021-01-01/opensearch/cc/inboundConnection/$(ConnectionId)/reject";
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function reject_inbound_connection(
    ConnectionId,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return opensearch(
        "PUT",
        "/2021-01-01/opensearch/cc/inboundConnection/$(ConnectionId)/reject",
        params;
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    remove_tags(arn, tag_keys)
    remove_tags(arn, tag_keys, params::Dict{String,<:Any})

Removes the specified set of tags from the given domain.

# Arguments
- `arn`: The ARN of the domain from which you want to delete the specified tags.
- `tag_keys`: The TagKey list you want to remove from the domain.

"""
function remove_tags(ARN, TagKeys; aws_config::AbstractAWSConfig=global_aws_config())
    return opensearch(
        "POST",
        "/2021-01-01/tags-removal",
        Dict{String,Any}("ARN" => ARN, "TagKeys" => TagKeys);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function remove_tags(
    ARN,
    TagKeys,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return opensearch(
        "POST",
        "/2021-01-01/tags-removal",
        Dict{String,Any}(
            mergewith(_merge, Dict{String,Any}("ARN" => ARN, "TagKeys" => TagKeys), params)
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    start_service_software_update(domain_name)
    start_service_software_update(domain_name, params::Dict{String,<:Any})

Schedules a service software update for an Amazon OpenSearch Service domain.

# Arguments
- `domain_name`: The name of the domain that you want to update to the latest service
  software.

"""
function start_service_software_update(
    DomainName; aws_config::AbstractAWSConfig=global_aws_config()
)
    return opensearch(
        "POST",
        "/2021-01-01/opensearch/serviceSoftwareUpdate/start",
        Dict{String,Any}("DomainName" => DomainName);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function start_service_software_update(
    DomainName,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return opensearch(
        "POST",
        "/2021-01-01/opensearch/serviceSoftwareUpdate/start",
        Dict{String,Any}(
            mergewith(_merge, Dict{String,Any}("DomainName" => DomainName), params)
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    update_domain_config(domain_name)
    update_domain_config(domain_name, params::Dict{String,<:Any})

Modifies the cluster configuration of the specified domain, such as setting the instance
type and the number of instances.

# Arguments
- `domain_name`: The name of the domain you're updating.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"AccessPolicies"`: IAM access policy as a JSON-formatted string.
- `"AdvancedOptions"`: Modifies the advanced option to allow references to indices in an
  HTTP request body. Must be false when configuring access to individual sub-resources. By
  default, the value is true. See Advanced options  for more information.
- `"AdvancedSecurityOptions"`: Specifies advanced security options.
- `"AutoTuneOptions"`: Specifies Auto-Tune options.
- `"ClusterConfig"`: The type and number of instances to instantiate for the domain cluster.
- `"CognitoOptions"`: Options to specify the Cognito user and identity pools for OpenSearch
  Dashboards authentication. For more information, see Configuring Amazon Cognito
  authentication for OpenSearch Dashboards.
- `"DomainEndpointOptions"`: Options to specify configuration that will be applied to the
  domain endpoint.
- `"DryRun"`: This flag, when set to True, specifies whether the UpdateDomain request
  should return the results of validation checks (DryRunResults) without actually applying
  the change.
- `"EBSOptions"`: Specify the type and size of the EBS volume to use.
- `"EncryptionAtRestOptions"`: Specifies encryption of data at rest options.
- `"LogPublishingOptions"`: Map of LogType and LogPublishingOption, each containing options
  to publish a given type of OpenSearch log.
- `"NodeToNodeEncryptionOptions"`: Specifies node-to-node encryption options.
- `"SnapshotOptions"`: Option to set the time, in UTC format, for the daily automated
  snapshot. Default value is 0 hours.
- `"VPCOptions"`: Options to specify the subnets and security groups for the VPC endpoint.
  For more information, see Launching your Amazon OpenSearch Service domains using a VPC .
"""
function update_domain_config(DomainName; aws_config::AbstractAWSConfig=global_aws_config())
    return opensearch(
        "POST",
        "/2021-01-01/opensearch/domain/$(DomainName)/config";
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function update_domain_config(
    DomainName,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return opensearch(
        "POST",
        "/2021-01-01/opensearch/domain/$(DomainName)/config",
        params;
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    update_package(package_id, package_source)
    update_package(package_id, package_source, params::Dict{String,<:Any})

Updates a package for use with Amazon OpenSearch Service domains.

# Arguments
- `package_id`: The unique identifier for the package.
- `package_source`:

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"CommitMessage"`: A commit message for the new version which is shown as part of
  GetPackageVersionHistoryResponse.
- `"PackageDescription"`: A new description of the package.
"""
function update_package(
    PackageID, PackageSource; aws_config::AbstractAWSConfig=global_aws_config()
)
    return opensearch(
        "POST",
        "/2021-01-01/packages/update",
        Dict{String,Any}("PackageID" => PackageID, "PackageSource" => PackageSource);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function update_package(
    PackageID,
    PackageSource,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return opensearch(
        "POST",
        "/2021-01-01/packages/update",
        Dict{String,Any}(
            mergewith(
                _merge,
                Dict{String,Any}(
                    "PackageID" => PackageID, "PackageSource" => PackageSource
                ),
                params,
            ),
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    upgrade_domain(domain_name, target_version)
    upgrade_domain(domain_name, target_version, params::Dict{String,<:Any})

Allows you to either upgrade your domain or perform an upgrade eligibility check to a
compatible version of OpenSearch or Elasticsearch.

# Arguments
- `domain_name`:
- `target_version`: The version of OpenSearch you intend to upgrade the domain to.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"AdvancedOptions"`:
- `"PerformCheckOnly"`:  When true, indicates that an upgrade eligibility check needs to be
  performed. Does not actually perform the upgrade.
"""
function upgrade_domain(
    DomainName, TargetVersion; aws_config::AbstractAWSConfig=global_aws_config()
)
    return opensearch(
        "POST",
        "/2021-01-01/opensearch/upgradeDomain",
        Dict{String,Any}("DomainName" => DomainName, "TargetVersion" => TargetVersion);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function upgrade_domain(
    DomainName,
    TargetVersion,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return opensearch(
        "POST",
        "/2021-01-01/opensearch/upgradeDomain",
        Dict{String,Any}(
            mergewith(
                _merge,
                Dict{String,Any}(
                    "DomainName" => DomainName, "TargetVersion" => TargetVersion
                ),
                params,
            ),
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

ext: jl


text:
@testset "taitt" begin
    t1, t2 = taitt(2453750.5, 0.892482639)
    @test t1 ≈ 2453750.5 atol=1e-6
    @test t2 ≈ 0.892855139 atol=1e-12
end

@testset "taiut1" begin
    u1, u2 = taiut1(2453750.5, 0.892482639, -32.6659)
    @test u1 ≈ 2453750.5 atol=1e-6
    @test u2 ≈ 0.8921045614537037037 atol=1e-12
end

@testset "taiutc" begin
    u1, u2 = taiutc(2453750.5, 0.892482639)
    @test u1 ≈ 2453750.5 atol=1e-6
    @test u2 ≈ 0.8921006945555555556 atol=1e-12
end

@testset "tcbtdb" begin
    b1, b2 = tcbtdb(2453750.5, 0.893019599)
    @test b1 ≈ 2453750.5 atol=1e-6
    @test b2 ≈ 0.8928551362746343397 atol=1e-12
end

@testset "tcgtt" begin
    t1, t2 = tcgtt(2453750.5,  0.892862531)
    @test t1 ≈ 2453750.5 atol=1e-6
    @test t2 ≈ 0.8928551387488816828 atol=1e-12
end

@testset "tdbtcb" begin
    b1, b2 = tdbtcb(2453750.5, 0.892855137)
    @test b1 ≈ 2453750.5 atol=1e-6
    @test b2 ≈ 0.8930195997253656716 atol=1e-12
end

@testset "tdbtt" begin
    t1, t2 = tdbtt(2453750.5,  0.892855137, -0.000201)
    @test t1 ≈ 2453750.5 atol=1e-6
    @test t2 ≈ 0.8928551393263888889 atol=1e-12
end

@testset "tf2a" begin
    a = tf2a('+', 4, 58, 20.2)
    @test a ≈ 1.301739278189537429 atol=1e-12
    @test_throws ERFAException tf2a('+', 24, 58, 20.2)
    @test_throws ERFAException tf2a('+', 4, 60, 20.2)
    @test_throws ERFAException tf2a('+', 4, 58, 60.2)
end

@testset "tf2d" begin
    d = tf2d('+', 23, 55, 10.9)
    @test d ≈ 0.9966539351851851852 atol=1e-12
    @test_throws ERFAException tf2d('+', 24, 58, 20.2)
    @test_throws ERFAException tf2d('+', 4, 60, 20.2)
    @test_throws ERFAException tf2d('+', 4, 58, 60.2)
end

@testset "tpors" begin
    xi = -0.03
    eta = 0.07
    ra = 1.3
    dec = 1.5

    status, az1, bz1, az2, bz2 = ERFA.tpors(xi, eta, ra, dec)

    @test az1 ≈ 1.736621577783208748 atol=1e-13
    @test bz1 ≈ 1.436736561844090323 atol=1e-13

    @test az2 ≈ 4.004971075806584490 atol=1e-13
    @test bz2 ≈ 1.565084088476417917 atol=1e-13

    @test status == 2
end

@testset "tporv" begin
    xi = -0.03
    eta = 0.07
    ra = 1.3
    dec = 1.5

    v = ERFA.s2c(ra, dec)

    status, vz1, vz2 = ERFA.tporv(xi, eta, v)

    @test vz1[1] ≈ -0.02206252822366888610 atol=1e-15
    @test vz1[2] ≈ 0.1318251060359645016 atol=1e-14
    @test vz1[3] ≈ 0.9910274397144543895 atol=1e-14

    @test vz2[1] ≈ -0.003712211763801968173 atol=1e-16
    @test vz2[2] ≈ -0.004341519956299836813 atol=1e-16
    @test vz2[3] ≈ 0.9999836852110587012 atol=1e-14

    @test status == 2
end

@testset "tpsts" begin
    xi = -0.03
    eta = 0.07
    raz = 2.3
    decz = 1.5

    ra, dec = ERFA.tpsts(xi, eta, raz, decz)

    @test ra ≈ 0.7596127167359629775 atol=1e-14
    @test dec ≈ 1.540864645109263028 atol=1e-13
end

@testset "tpstv" begin
    xi = -0.03
    eta = 0.07
    raz = 2.3
    decz = 1.5

    vz = ERFA.s2c(raz, decz)

    v = ERFA.tpstv(xi, eta, vz)

    @test v[1] ≈ 0.02170030454907376677 atol=1e-15
    @test v[2] ≈ 0.02060909590535367447 atol=1e-15
    @test v[3] ≈ 0.9995520806583523804 atol=1e-14
end

@testset "tpxes" begin
    ra = 1.3
    dec = 1.55
    raz = 2.3
    decz = 1.5

    status, xi, eta = ERFA.tpxes(ra, dec, raz, decz)

    @test xi ≈ -0.01753200983236980595 atol=1e-15
    @test eta ≈ 0.05962940005778712891 atol=1e-15

    @test status == 0
end

@testset "tpxev" begin
    ra = 1.3
    dec = 1.55
    raz = 2.3
    decz = 1.5
    v = ERFA.s2c(ra, dec)
    vz = ERFA.s2c(raz, decz)

    status, xi, eta = ERFA.tpxev(v, vz)

    @test xi ≈ -0.01753200983236980595 atol=1e-15
    @test eta ≈ 0.05962940005778712891 atol=1e-15

    @test status == 0
end

@testset "tr" begin
    r = [2.0 3.0 2.0;
         3.0 2.0 3.0;
         3.0 4.0 5.0]
    rt = ERFA._tr(r)
    @test rt[1,1] ≈ 2.0 atol=0.0
    @test rt[1,2] ≈ 3.0 atol=0.0
    @test rt[1,3] ≈ 3.0 atol=0.0
    @test rt[2,1] ≈ 3.0 atol=0.0
    @test rt[2,2] ≈ 2.0 atol=0.0
    @test rt[2,3] ≈ 4.0 atol=0.0
    @test rt[3,1] ≈ 2.0 atol=0.0
    @test rt[3,2] ≈ 3.0 atol=0.0
    @test rt[3,3] ≈ 5.0 atol=0.0
    @test_throws ArgumentError ERFA._tr(r[1:2,:])
    @test rt == r'
end

@testset "trxp" begin
    r = [2.0 3.0 2.0;
         3.0 2.0 3.0;
         3.0 4.0 5.0]
    p = [0.2,1.5,0.1]
    trp = ERFA._trxp(r, p)
    @test trp[1] ≈ 5.2 atol=1e-12
    @test trp[2] ≈ 4.0 atol=1e-12
    @test trp[3] ≈ 5.4 atol=1e-12
    @test_throws ArgumentError ERFA._trxp(r[1:2,:], p)
    @test_throws ArgumentError ERFA._trxp(r, p[1:2])
    @test trp == r' * p
end

@testset "trxpv" begin
    r = [2.0 3.0 2.0;
         3.0 2.0 3.0;
         3.0 4.0 5.0]
    pv = [[0.2,1.5,0.1],
          [1.5,0.2,0.1]]
    trpv = ERFA._trxpv(r, pv)
    @test trpv[1][1] ≈ 5.2 atol=1e-12
    @test trpv[1][2] ≈ 4.0 atol=1e-12
    @test trpv[1][3] ≈ 5.4 atol=1e-12
    @test trpv[2][1] ≈ 3.9 atol=1e-12
    @test trpv[2][2] ≈ 5.3 atol=1e-12
    @test trpv[2][3] ≈ 4.1 atol=1e-12
    pve = [[1.5,0.1],
          [1.5,0.2,0.1]]
    @test_throws ArgumentError ERFA._trxpv(r[1:2,:], pv)
    @test_throws ArgumentError ERFA._trxpv(r, pve)
    trpvj = [r' * pv[1], r' * pv[2]]
    for (v, vj) in zip(trpv, trpvj)
        for i in eachindex(v, vj)
            @test v[i] ≈ vj[i]
        end
    end
end

@testset "tttai" begin
    t1, t2 = tttai(2453750.5, 0.892482639)
    @test t1 ≈ 2453750.5 atol=1e-6
    @test t2 ≈ 0.892110139 atol=1e-12
end

@testset "tttcg" begin
    t1, t2 = tttcg(2453750.5, 0.892482639)
    @test t1 ≈ 2453750.5 atol=1e-6
    @test t2 ≈ 0.8924900312508587113 atol=1e-12
end

@testset "tttdb" begin
    t1, t2 = tttdb(2453750.5, 0.892855139, -0.000201)
    @test t1 ≈ 2453750.5 atol=1e-6
    @test t2 ≈ 0.8928551366736111111 atol=1e-12
end

@testset "ttut1" begin
    t1, t2 = ttut1(2453750.5, 0.892855139, 64.8499)
    @test t1 ≈ 2453750.5 atol=1e-6
    @test t2 ≈ 0.8921045614537037037 atol=1e-12
end


text:
include("../imports.jl")

@testset ExtendedTestSet "`GarbageCollect`" begin
    cb = GarbageCollect()
    learner = testlearner(Recorder(), Metrics(), cb)
    @test_nowarn fit!(learner, 1)
end


text:
immutable IdentityMap{T}<:AbstractLinearMap{T} # T will be determined from maps to which I is added
    M::Int
end
IdentityMap(T::Type,M::Int)=IdentityMap{T}(M)
IdentityMap(T::Type,M::Int,N::Int)=(M==N ? IdentityMap{T}(M) : error("IdenityMap needs to be square"))
IdentityMap(T::Type,sz::Tuple{Int,Int})=(sz[1]==sz[2] ? IdentityMap{T}(sz[1]) : error("IdenityMap needs to be square"))
IdentityMap(M::Int)=IdentityMap(Bool,M)
IdentityMap(M::Int,N::Int)=IdentityMap(Bool,M,N)
IdentityMap(sz::Tuple{Int,Int})=IdentityMap(Bool,sz)

# properties
Base.size(A::IdentityMap,n)=(n==1 || n==2 ? A.M : error("AbstractLinearMap objects have only 2 dimensions"))
Base.size(A::IdentityMap)=(A.M,A.M)
Base.isreal(::IdentityMap)=true
Base.issym(::IdentityMap)=true
Base.ishermitian(::IdentityMap)=true
Base.isposdef(::IdentityMap)=true

# multiplication with vector
Base.A_mul_B!(y::AbstractVector,A::IdentityMap,x::AbstractVector)=(length(x)==length(y)==A.M ? copy!(y,x) : throw(DimensionMismatch("A_mul_B!")))
*(A::IdentityMap,x::AbstractVector)=x

Base.At_mul_B!(y::AbstractVector,A::IdentityMap,x::AbstractVector)=(length(x)==length(y)==A.M ? copy!(y,x) : throw(DimensionMismatch("At_mul_B!")))
Base.At_mul_B(A::IdentityMap,x::AbstractVector)=(length(x)==A.M ? x : throw(DimensionMismatch("At_mul_B")))

Base.Ac_mul_B!(y::AbstractVector,A::IdentityMap,x::AbstractVector)=(length(x)==length(y)==A.M ? copy!(y,x) : throw(tMismatch("Ac_mul_B!")))
Base.Ac_mul_B(A::IdentityMap,x::AbstractVector)=(length(x)==A.M ? x : throw(DimensionMismatch("Ac_mul_B")))

# combine AbstractLinearMap and UniformScaling objects in linear combinations
+{T}(A1::AbstractLinearMap,A2::UniformScaling{T})=A1+A2[1,1]*IdentityMap{T}(size(A1,1))
+{T}(A1::UniformScaling{T},A2::AbstractLinearMap)=A1[1,1]*IdentityMap{T}(size(A2,1))+A2


text:
using DiffEqFlux, OrdinaryDiffEq, Flux, Optim, Plots
using DifferentialEquations
using DiffEqSensitivity
using Zygote
using ForwardDiff
using LinearAlgebra
using Random
using Statistics
using ProgressBars, Printf
using Flux.Optimise: update!
using Flux.Losses: mae
using BSON: @save, @load
using LatinHypercubeSampling
using LsqFit

is_restart = false;
n_epoch = 2000;
n_plot = 10;

opt = ADAMW(0.005, (0.9, 0.999), 1.f-6);
datasize = 40;
batchsize = 16;
n_exp_train = 20;
n_exp_val = 5;
n_exp = n_exp_train + n_exp_val;
noise = 1.f-4;
ns = 3;
nr = 6;

grad_max = 10 ^ (0.5);
maxiters = 10000;

# alg = AutoTsit5(Rosenbrock23(autodiff=false));
alg = Rosenbrock23(autodiff=false);
atol = [1e-6, 1e-8, 1e-6];
rtol = [1e-3, 1e-3, 1e-3];
lb = 1e-8;
ub = 1.f1;

np = nr * (2 * ns + 1) + 1;
p = (rand(Float32, np) .- 0.5) * 2 * sqrt(6 / (ns + nr));
p[end] = 1.e-1;

# Generate datasets
u0_list = rand(Float32, (n_exp, ns)) .* 2 .+ 0.5;
u0_list[:, 2:2] .= 0 .+ lb;
u0_list[:, [1, 3]] .= randomLHC(n_exp, 2) ./ n_exp .+ 0.5

tsteps = 10 .^ range(0, 5, length=datasize);
tspan = Float32[0, tsteps[end]];
t_end = tsteps[end]

k = [4.f-2, 3.f7, 1.f4];
ode_data_list = zeros(Float32, (n_exp, ns, datasize));
yscale_list = [];

function trueODEfunc(dydt, y, k, t)
    r1 = k[1] * y[1]
    r2 = k[2] * y[2] * y[2]
    r3 = k[3] * y[2] * y[3]
    dydt[1] = -r1 + r3
    dydt[2] = r1 - r2 - r3
    dydt[3] = r2
end

u0 = u0_list[1, :];
prob_trueode = ODEProblem(trueODEfunc, u0, tspan, k);

function max_min(ode_data)
    return maximum(ode_data, dims=2) .- minimum(ode_data, dims=2)
end

for i = 1:n_exp
    u0 = u0_list[i, :]
    prob_trueode = ODEProblem(trueODEfunc, u0, tspan, k)
    ode_data = Array(solve(prob_trueode, alg, saveat=tsteps, atol=atol, rtol=rtol))
    ode_data += randn(size(ode_data)) .* ode_data .* noise
    ode_data_list[i, :, :] = ode_data
    push!(yscale_list, max_min(ode_data))
end

yscale = maximum(hcat(yscale_list...), dims=2);
dydt_scale = yscale[:, 1] ./ t_end
show(stdout, "text/plain", round.(yscale', digits=8))

function p2vec(p)
    slope = abs(p[end])
    w_b = @view(p[1:nr]) .* (10 * slope)

    w_in = reshape(@view(p[nr * (ns + 1) + 1:nr * (2 * ns + 1)]), ns, nr)

    w_out = reshape(@view(p[nr + 1:nr * (ns + 1)]), ns, nr)
    w_out = @. -w_in * (10 ^ w_out)

    w_in = clamp.(w_in, 0, 2.5)
    return w_in, w_b, w_out
end

function display_p(p)
    w_in, w_b, w_out = p2vec(p)
    println("species (column) reaction (row)")
    println("w_in | w_b | w_out")
    display(hcat(w_in', w_b, w_out'))

    println("w_out_scale")
    w_out_ = (w_out .* dydt_scale)' .* exp.(w_b)
    # display(w_out_)
    # display(maximum(abs.(w_out_), dims=2)')
    display(w_out_ ./ maximum(abs.(w_out_), dims=2))
    println("slope = $(p[end])")
end
display_p(p)

function crnn(du, u, p, t)
    w_in_x = w_in' * @. log(clamp(u, lb, Inf))
    du .= w_out * (@. exp(w_in_x + w_b)) .* dydt_scale
end

u0 = u0_list[1, :]
prob = ODEProblem(crnn, u0, tspan, saveat=tsteps, atol=atol, rtol=rtol)

sense = BacksolveAdjoint(checkpointing=true; autojacvec=false);
function predict_neuralode(u0, p; sample = datasize)
    global w_in, w_b, w_out = p2vec(p)
    _prob = remake(prob, tspan=[0, tsteps[sample]])
    sol = solve(prob, alg, u0=u0, p=p, saveat=tsteps[1:sample],
                sensalg=sense, verbose=false, maxiters=maxiters)
    pred = Array(sol)

    if sol.retcode == :Success
        nothing
    else
        println("ode solver failed")
    end
    return pred
end
pred = predict_neuralode(u0, p);

function loss_neuralode(p, i_exp; sample = datasize)
    pred = predict_neuralode(u0_list[i_exp, :], p; sample)
    ode_data = ode_data_list[i_exp, :, 1:size(pred)[2]]
    loss = mae(ode_data ./ yscale, pred ./ yscale)
    return loss
end
loss_neuralode(p, 1)

cbi = function (p, i_exp)
    ode_data = ode_data_list[i_exp, :, :]
    pred = predict_neuralode(u0_list[i_exp, :], p)
    l_plt = []
    for i = 1:ns
        plt = scatter(tsteps, ode_data[i, :], xscale=:log10,
                      markercolor=:transparent, label=string("data"))
        plot!(plt, tsteps[1:size(pred)[2]], pred[i, :], xscale=:log10, label=string("pred"))
        ylabel!(plt, "y$i")
        if i == ns
            xlabel!(plt, "Time [s]")
            plot!(plt, legend=:topleft)
        else
            plot!(plt, legend=false)
        end
        push!(l_plt, plt)
    end
    plt_all = plot(l_plt..., framestyle=:box, layouts = (ns, 1))
    png(plt_all, string("figs/i_exp_", i_exp))

    return false
end

l_loss_train = []
l_loss_val = []
l_grad = []
iter = 1
cb = function (p, loss_train, loss_val, g_norm)
    global l_loss_train, l_loss_val, l_grad, iter
    push!(l_loss_train, loss_train)
    push!(l_loss_val, loss_val)
    push!(l_grad, g_norm)

    if iter % n_plot == 0
        display_p(p)

        l_exp = randperm(n_exp)[1:1]
        println("update plot for ", l_exp)
        for i_exp in l_exp
            cbi(p, i_exp)
        end

        plt_loss = plot(l_loss_train, xscale=:identity, yscale=:log10, label="train")
        plot!(plt_loss, l_loss_val, xscale=:identity, yscale=:log10, label="val")
        plt_grad = plot(l_grad, xscale=:identity, yscale=:log10, label="grad_norm")
        xlabel!(plt_loss, "Epoch")
        xlabel!(plt_grad, "Epoch")
        ylabel!(plt_loss, "Loss")
        ylabel!(plt_grad, "Grad Norm")
        ylims!(plt_loss, (-Inf, 1))
        plt_all = plot([plt_loss, plt_grad]..., legend=:top)
        png(plt_all, "figs/loss_grad")

        @save "./checkpoint/mymodel.bson" p opt l_loss_train l_loss_val l_grad iter
    end
    iter += 1
end

if is_restart
    @load "./checkpoint/mymodel.bson" p opt l_loss_train l_loss_val l_grad iter
    iter += 1
    # opt = ADAMW(0.001, (0.9, 0.999), 1.f-6)
end

function loss_lm(p)
    [loss_neuralode(p, i) for i in 1:n_exp_train]
end
loss_lm(p);

g = function (p)
    return ForwardDiff.jacobian(x -> loss_lm(x), p)
end
g(p);

epochs = ProgressBar(iter:n_epoch);
loss_epoch = zeros(Float32, n_exp);
grad_norm = zeros(Float32, n_exp_train);
for epoch in epochs
    global p
    for i_exp in randperm(n_exp_train)
        sample = rand(batchsize:datasize)
        grad = ForwardDiff.gradient(x -> loss_neuralode(x, i_exp; sample), p)
        grad_norm[i_exp] = norm(grad, 2)
        if grad_norm[i_exp] > grad_max
            grad = grad ./ grad_norm[i_exp] .* grad_max
        end
        update!(opt, p, grad)
    end
    for i_exp in 1:n_exp
        loss_epoch[i_exp] = loss_neuralode(p, i_exp)
    end
    loss_train = mean(loss_epoch[1:n_exp_train]);
    loss_val = mean(loss_epoch[n_exp_train + 1:end]);
    g_norm = mean(grad_norm)
    set_description(epochs, string(@sprintf("Loss train %.4e val %.4e gnorm %.4e", loss_train, loss_val, g_norm)))
    cb(p, loss_train, loss_val, g_norm);

    if loss_train < 0.1
        break
    end
end

fit = LsqFit.lmfit(loss_lm, g, p, Float64[]; show_trace=true, maxIter=2000, x_tol=1e-8)
p_fit = fit.param;
display_p(p_fit)
cbi(p_fit, 1)
cbi(p_fit, n_exp)

# @printf("min loss train %.4e val %.4e\n", minimum(l_loss_train), minimum(l_loss_val))

# for i_exp in 1:n_exp
#     cbi(p, i_exp)
# end

max_stars_repo_name: DENG-MIT/CRNN

max_issues_repo_name: DENG-MIT/CRNN

max_forks_repo_name: DENG-MIT/CRNN


text:
abstract type Shape end

struct Rectangle <: Shape
    corner_upper_left  :: Tuple{Float64, Float64}
    corner_lower_right :: Tuple{Float64, Float64}
end

struct Circle <: Shape
    center      :: Tuple{Float64, Float64}
    radius      :: Float64
end

struct GrayShape{shapeType <: Shape} <: Shape
    parent      :: shapeType
    gray_value  :: Float64
end

struct AlphaShape{shapeType <: Shape} <: Shape
    parent      :: shapeType
    alpha_value :: Float64
end

r1 = AlphaShape(GrayShape(Rectangle((0.0, 1.0), (1.0, 0.0)), 0.5), 0.9)
r2 = GrayShape(AlphaShape(Rectangle((0.0, 1.0), (1.0, 0.0)), 0.9), 0.5)

# vim:syn=julia:cc=79:fdm=marker:sw=4:ts=4:


text:
using Combinatorics
using ITensors
using Test

include("util.jl")

function basicRandomMPO(sites; dim=4)
  M = MPO(sites)
  N = length(M)
  links = [Index(dim,"n=$(n-1),Link") for n=1:N+1]
  for n=1:N
    M[n] = randomITensor(links[n],sites[n],sites[n]',links[n+1])
  end
  M[1] *= delta(links[1])
  M[N] *= delta(links[N+1])
  return M
end

@testset "[first]siteinds(::MPO)" begin
  N = 5
  s = siteinds("S=1/2", N)
  M = randomMPO(s)
  v = siteinds(M)
  for n in 1:N
    @test hassameinds(v[n], (s[n], s[n]'))
  end
  @test firstsiteinds(M) == s
end

@testset "MPO Basics" begin
  N = 6
  sites = [Index(2,"Site,n=$n") for n=1:N]
  @test length(MPO()) == 0
  O = MPO(sites)
  @test length(O) == N

  str = split(sprint(show, O), '\n')
  @test str[1] == "MPO"
  @test length(str) == length(O) + 2

  O[1] = ITensor(sites[1], prime(sites[1]))
  @test hasind(O[1],sites[1])
  @test hasind(O[1],prime(sites[1]))
  P = copy(O)
  @test hasind(P[1],sites[1])
  @test hasind(P[1],prime(sites[1]))
  # test constructor from Vector{ITensor}
  K = randomMPO(sites)
  @test ITensors.data(MPO(copy(ITensors.data(K)))) == ITensors.data(K)

  @testset "orthogonalize!" begin
    phi = randomMPS(sites)
    K = randomMPO(sites)
    orthogonalize!(phi, 1)
    orthogonalize!(K, 1)
    orig_inner = ⋅(phi, K, phi)
    orthogonalize!(phi, div(N, 2))
    orthogonalize!(K, div(N, 2))
    @test ⋅(phi, K, phi) ≈ orig_inner
  end

  @testset "norm MPO" begin
    A = randomMPO(sites)
    Adag = ITensors.sim_linkinds(dag(A))
    A² = ITensor(1)
    for j = 1:N
      A² *= Adag[j] * A[j]
    end
    @test A²[] ≈ inner(A, A)
    @test sqrt(A²[]) ≈ norm(A)
    for j in 1:N
      A[j] ./= j
    end
    @test norm(A) ≈ 1 / factorial(N)
  end

  @testset "lognorm MPO" begin
    A = randomMPO(sites)
    for j in 1:N
      A[j] .*= j
    end
    Adag = ITensors.sim_linkinds(dag(A))
    A² = ITensor(1)
    for j = 1:N
      A² *= Adag[j] * A[j]
    end
    @test A²[] ≈ A ⋅ A
    @test 0.5 * log(A²[]) ≈ lognorm(A)
    @test lognorm(A) ≈ log(factorial(N))
  end

  @testset "inner <y|A|x>" begin
    phi = randomMPS(sites)
    K = randomMPO(sites)
    @test maxlinkdim(K) == 1
    psi = randomMPS(sites)
    phidag = dag(phi)
    prime!(phidag)
    phiKpsi = phidag[1]*K[1]*psi[1]
    for j = 2:N
      phiKpsi *= phidag[j]*K[j]*psi[j]
    end
    @test phiKpsi[] ≈ inner(phi,K,psi)

    badsites = [Index(2,"Site") for n=1:N+1]
    badpsi = randomMPS(badsites)
    @test_throws DimensionMismatch inner(phi,K,badpsi)

    # make bigger random MPO...
    for link_dim in 2:5
        mpo_tensors  = ITensor[ITensor() for ii in 1:N]
        mps_tensors  = ITensor[ITensor() for ii in 1:N]
        mps_tensors2 = ITensor[ITensor() for ii in 1:N]
        mpo_link_inds = [Index(link_dim, "r$ii,Link") for ii in 1:N-1]
        mps_link_inds = [Index(link_dim, "r$ii,Link") for ii in 1:N-1]
        mpo_tensors[1] = randomITensor(mpo_link_inds[1], sites[1], sites[1]')
        mps_tensors[1] = randomITensor(mps_link_inds[1], sites[1])
        mps_tensors2[1] = randomITensor(mps_link_inds[1], sites[1])
        for ii in 2:N-1
            mpo_tensors[ii] = randomITensor(mpo_link_inds[ii], mpo_link_inds[ii-1], sites[ii], sites[ii]')
            mps_tensors[ii] = randomITensor(mps_link_inds[ii], mps_link_inds[ii-1], sites[ii])
            mps_tensors2[ii] = randomITensor(mps_link_inds[ii], mps_link_inds[ii-1], sites[ii])
        end
        mpo_tensors[N] = randomITensor(mpo_link_inds[N-1], sites[N], sites[N]')
        mps_tensors[N] = randomITensor(mps_link_inds[N-1], sites[N])
        mps_tensors2[N] = randomITensor(mps_link_inds[N-1], sites[N])
        K   = MPO(mpo_tensors, 0, N+1)
        psi = MPS(mps_tensors, 0, N+1)
        phi = MPS(mps_tensors2, 0, N+1)
        orthogonalize!(psi, 1; maxdim=link_dim)
        orthogonalize!(K, 1; maxdim=link_dim)
        orthogonalize!(phi, 1; normalize=true, maxdim=link_dim)
        phidag = dag(phi)
        prime!(phidag)
        phiKpsi = phidag[1]*K[1]*psi[1]
        for j = 2:N
          phiKpsi *= phidag[j]*K[j]*psi[j]
        end
        @test scalar(phiKpsi) ≈ inner(phi,K,psi)
    end
  end

  @testset "inner <By|A|x>" begin
    phi = makeRandomMPS(sites)

    K = makeRandomMPO(sites,chi=2)
    J = makeRandomMPO(sites,chi=2)

    psi = makeRandomMPS(sites)
    phidag = dag(phi)
    prime!(phidag, 2)
    Jdag = dag(J)
    prime!(Jdag)
    for j ∈ eachindex(Jdag)
      swapprime!(Jdag[j],2,3)
      swapprime!(Jdag[j],1,2)
      swapprime!(Jdag[j],3,1)
    end

    phiJdagKpsi = phidag[1]*Jdag[1]*K[1]*psi[1]
    for j ∈ eachindex(psi)[2:end]
      phiJdagKpsi = phiJdagKpsi*phidag[j]*Jdag[j]*K[j]*psi[j]
    end

    @test phiJdagKpsi[] ≈ inner(J,phi,K,psi)

    badsites = [Index(2,"Site") for n=1:N+1]
    badpsi = randomMPS(badsites)
    @test_throws DimensionMismatch inner(J,phi,K,badpsi)
  end

  @testset "error_contract" begin
    phi = makeRandomMPS(sites)
    K = makeRandomMPO(sites,chi=2)

    psi = makeRandomMPS(sites)

    dist = sqrt(abs(1 + (inner(phi,phi) - 2*real(inner(phi,K,psi)))
                        /inner(K,psi,K,psi)))
    @test dist ≈ error_contract(phi,K,psi)

    badsites = [Index(2,"Site") for n=1:N+1]
    badpsi = randomMPS(badsites)
    # Apply K to phi and check that error_contract is close to 0.
    Kphi = contract(K, phi; method="naive", cutoff=1E-8)
    @test error_contract(Kphi, K, phi) ≈ 0. atol=1e-4

    @test_throws DimensionMismatch contract(K,badpsi;method="naive", cutoff=1E-8)
    @test_throws DimensionMismatch error_contract(phi,K,badpsi)
  end

  @testset "contract" begin
    phi = randomMPS(sites)
    K   = randomMPO(sites)
    @test maxlinkdim(K) == 1
    psi = randomMPS(sites)
    psi_out = contract(K, psi; maxdim=1)
    @test inner(phi,psi_out) ≈ inner(phi,K,psi)
    @test_throws ArgumentError contract(K, psi, method="fakemethod")

    badsites = [Index(2,"Site") for n=1:N+1]
    badpsi = randomMPS(badsites)
    @test_throws DimensionMismatch contract(K,badpsi)

    # make bigger random MPO...
    for link_dim in 2:5
      mpo_tensors  = ITensor[ITensor() for ii in 1:N]
      mps_tensors  = ITensor[ITensor() for ii in 1:N]
      mps_tensors2 = ITensor[ITensor() for ii in 1:N]
      mpo_link_inds = [Index(link_dim, "r$ii,Link") for ii in 1:N-1]
      mps_link_inds = [Index(link_dim, "r$ii,Link") for ii in 1:N-1]
      mpo_tensors[1] = randomITensor(mpo_link_inds[1], sites[1], sites[1]')
      mps_tensors[1] = randomITensor(mps_link_inds[1], sites[1])
      mps_tensors2[1] = randomITensor(mps_link_inds[1], sites[1])
      for ii in 2:N-1
        mpo_tensors[ii] = randomITensor(mpo_link_inds[ii], mpo_link_inds[ii-1], sites[ii], sites[ii]')
        mps_tensors[ii] = randomITensor(mps_link_inds[ii], mps_link_inds[ii-1], sites[ii])
        mps_tensors2[ii] = randomITensor(mps_link_inds[ii], mps_link_inds[ii-1], sites[ii])
      end
      mpo_tensors[N] = randomITensor(mpo_link_inds[N-1], sites[N], sites[N]')
      mps_tensors[N] = randomITensor(mps_link_inds[N-1], sites[N])
      mps_tensors2[N] = randomITensor(mps_link_inds[N-1], sites[N])
      K   = MPO(mpo_tensors, 0, N+1)
      psi = MPS(mps_tensors, 0, N+1)
      phi = MPS(mps_tensors2, 0, N+1)
      orthogonalize!(psi, 1; maxdim=link_dim)
      orthogonalize!(K, 1; maxdim=link_dim)
      orthogonalize!(phi, 1; normalize=true, maxdim=link_dim)
      psi_out = contract(deepcopy(K), deepcopy(psi); maxdim=10*link_dim, cutoff=0.0)
      @test inner(phi, psi_out) ≈ inner(phi, K, psi)
    end
  end

  @testset "add(::MPO, ::MPO)" begin
    shsites = siteinds("S=1/2", N)
    K = randomMPO(shsites)
    L = randomMPO(shsites)
    M = add(K, L)
    @test length(M) == N
    psi = randomMPS(shsites)
    k_psi = contract(K, psi, maxdim=1)
    l_psi = contract(L, psi, maxdim=1)
    @test inner(psi, k_psi + l_psi) ≈ ⋅(psi, M, psi) atol=5e-3
    @test inner(psi, sum([k_psi, l_psi])) ≈ dot(psi, M, psi) atol=5e-3
    for dim in 2:4
        shsites = siteinds("S=1/2",N)
        K = basicRandomMPO(shsites; dim=dim)
        L = basicRandomMPO(shsites; dim=dim)
        M = K + L
        @test length(M) == N
        psi = randomMPS(shsites)
        k_psi = contract(K, psi)
        l_psi = contract(L, psi)
        @test inner(psi, k_psi + l_psi) ≈ dot(psi, M, psi) atol=5e-3
        @test inner(psi, sum([k_psi, l_psi])) ≈ inner(psi, M, psi) atol=5e-3
        psi = randomMPS(shsites)
        M = add(K, L; cutoff=1E-9)
        k_psi = contract(K, psi)
        l_psi = contract(L, psi)
        @test inner(psi, k_psi + l_psi) ≈ inner(psi, M, psi) atol=5e-3
    end
  end

  @testset "+(::MPO, ::MPO)" begin
    conserve_qns = true
    s = siteinds("S=1/2", N; conserve_qns = conserve_qns)

    ops = n -> isodd(n) ? "Sz" : "Id"
    H₁ = MPO(s, ops)
    H₂ = MPO(s, ops)

    H = H₁ + H₂

    @test inner(H, H) ≈ inner_add(H₁, H₂)
    @test maxlinkdim(H) ≤ maxlinkdim(H₁) + maxlinkdim(H₂)

    α₁ = 2.2
    α₂ = 3.4 + 1.2im

    H = α₁ * H₁ + H₂

    @test inner(H, H) ≈ inner_add((α₁, H₁), H₂)
    @test maxlinkdim(H) ≤ maxlinkdim(H₁) + maxlinkdim(H₂)

    H = H₁ - H₂

    @test inner(H, H) ≈ inner_add(H₁, (-1, H₂))
    @test maxlinkdim(H) ≤ maxlinkdim(H₁) + maxlinkdim(H₂)

    H = α₁ * H₁ - α₂ * H₂

    @test inner(H, H) ≈ inner_add((α₁, H₁), (-α₂, H₂))
    @test maxlinkdim(H) ≤ maxlinkdim(H₁) + maxlinkdim(H₂)
  end

  @testset "contract(::MPO, ::MPO)" begin
    psi = randomMPS(sites)
    K = randomMPO(sites)
    L = randomMPO(sites)
    @test maxlinkdim(K) == 1
    @test maxlinkdim(L) == 1
    KL = contract(prime(K), L, maxdim=1)
    psi_kl_out = contract(prime(K), contract(L, psi, maxdim=1), maxdim=1)
    @test inner(psi,KL,psi) ≈ inner(psi, psi_kl_out) atol=5e-3

    # where both K and L have differently labelled sites
    othersitesk = [Index(2,"Site,aaa") for n=1:N]
    othersitesl = [Index(2,"Site,bbb") for n=1:N]
    K = randomMPO(sites)
    L = randomMPO(sites)
    for ii in 1:N
      replaceind!(K[ii], sites[ii]', othersitesk[ii])
      replaceind!(L[ii], sites[ii]', othersitesl[ii])
    end
    KL = contract(K, L, maxdim=1)
    psik = randomMPS(othersitesk)
    psil = randomMPS(othersitesl)
    psi_kl_out = contract(K, contract(L, psil, maxdim=1), maxdim=1)
    @test inner(psik,KL,psil) ≈ inner(psik, psi_kl_out) atol=5e-3

    badsites = [Index(2,"Site") for n=1:N+1]
    badL = randomMPO(badsites)
    @test_throws DimensionMismatch contract(K,badL)
  end

  @testset "*(::MPO, ::MPO)" begin
    psi = randomMPS(sites)
    K = randomMPO(sites)
    L = randomMPO(sites)
    @test maxlinkdim(K) == 1
    @test maxlinkdim(L) == 1
    KL = *(prime(K), L, maxdim=1)
    psi_kl_out = *(prime(K), *(L, psi, maxdim=1), maxdim=1)
    @test ⋅(psi, KL, psi) ≈ dot(psi, psi_kl_out) atol=5e-3

    # where both K and L have differently labelled sites
    othersitesk = [Index(2,"Site,aaa") for n=1:N]
    othersitesl = [Index(2,"Site,bbb") for n=1:N]
    K = randomMPO(sites)
    L = randomMPO(sites)
    for ii in 1:N
      replaceind!(K[ii], sites[ii]', othersitesk[ii])
      replaceind!(L[ii], sites[ii]', othersitesl[ii])
    end
    KL = *(K, L, maxdim=1)
    psik = randomMPS(othersitesk)
    psil = randomMPS(othersitesl)
    psi_kl_out = *(K, *(L, psil, maxdim=1), maxdim=1)
    @test dot(psik, KL, psil) ≈ psik ⋅ psi_kl_out atol=5e-3

    badsites = [Index(2,"Site") for n=1:N+1]
    badL = randomMPO(badsites)
    @test_throws DimensionMismatch K * badL
  end

  sites = siteinds("S=1/2",N)
  O = MPO(sites,"Sz")
  @test length(O) == N # just make sure this works

  @test_throws ArgumentError randomMPO(sites, 2)
  @test isnothing(linkind(MPO(fill(ITensor(), N), 0, N + 1), 1))

  @testset "movesites $N sites" for N in 1:7
    s0 = siteinds("S=1/2", N)
    ψ0 = MPO(s0, "Id")
    for perm in permutations(1:N)
      s = s0[perm]
      ψ = randomMPO(s)
      ns′ = [findsite(ψ0, i) for i in s]
      @test ns′ == perm
      ψ′ = movesites(ψ, 1:N .=> ns′)
      for n in 1:N
        @test hassameinds(siteinds(ψ0, n), siteinds(ψ′, n))
      end
      @test @set_warn_order 15 prod(ψ) ≈ prod(ψ′)
    end
  end

  @testset "Construct MPO from ITensor" begin

    N = 5
    s = siteinds("S=1/2", N)
    l = [Index(3, "left_$n") for n in 1:2]
    r = [Index(3, "right_$n") for n in 1:2]

    sis = IndexSet.(prime.(s), s)

    A = randomITensor(s..., prime.(s)...)
    ψ = MPO(A, sis; orthocenter = 4)
    ls = linkinds(ψ)
    @test hassameinds(ψ[1], (s[1], s[1]', ls[1]))
    @test hassameinds(ψ[N], (s[N], s[N]', ls[N - 1]))
    @test prod(ψ) ≈ A
    @test ITensors.orthocenter(ψ) == 4
    @test maxlinkdim(ψ) == 16

    A = randomITensor(s..., prime.(s)...)
    ψ = MPO(A, s; orthocenter = 4)
    ls = linkinds(ψ)
    @test hassameinds(ψ[1], (s[1], s[1]', ls[1]))
    @test hassameinds(ψ[N], (s[N], s[N]', ls[N - 1]))
    @test prod(ψ) ≈ A
    @test ITensors.orthocenter(ψ) == 4
    @test maxlinkdim(ψ) == 16

    ψ0 = MPO(s, "Id")
    A = prod(ψ0)
    ψ = MPO(A, sis; cutoff = 1e-15, orthocenter = 3)
    ls = linkinds(ψ)
    @test hassameinds(ψ[1], (s[1], s[1]', ls[1]))
    @test hassameinds(ψ[N], (s[N], s[N]', ls[N - 1]))
    @test prod(ψ) ≈ A
    @test ITensors.orthocenter(ψ) == 3
    @test maxlinkdim(ψ) == 1

    ψ0 = MPO(s, "Id")
    A = prod(ψ0)
    ψ = MPO(A, s; cutoff = 1e-15, orthocenter = 3)
    ls = linkinds(ψ)
    @test hassameinds(ψ[1], (s[1], s[1]', ls[1]))
    @test hassameinds(ψ[N], (s[N], s[N]', ls[N - 1]))
    @test prod(ψ) ≈ A
    @test ITensors.orthocenter(ψ) == 3
    @test maxlinkdim(ψ) == 1

    A = randomITensor(s..., prime.(s)..., l[1], r[1])
    ψ = MPO(A, sis, leftinds = l[1])
    ls = linkinds(ψ)
    @test hassameinds(ψ[1], (l[1], s[1], s[1]', ls[1]))
    @test hassameinds(ψ[N], (r[1], s[N], s[N]', ls[N - 1]))
    @test prod(ψ) ≈ A
    @test ITensors.orthocenter(ψ) == N
    @test maxlinkdim(ψ) == 48

    A = randomITensor(s..., prime.(s)..., l[1], r[1])
    ψ = MPO(A, s, leftinds = l[1])
    ls = linkinds(ψ)
    @test hassameinds(ψ[1], (l[1], s[1], s[1]', ls[1]))
    @test hassameinds(ψ[N], (r[1], s[N], s[N]', ls[N - 1]))
    @test prod(ψ) ≈ A
    @test ITensors.orthocenter(ψ) == N
    @test maxlinkdim(ψ) == 48

    A = randomITensor(s..., prime.(s)..., l..., r...)
    ψ = MPO(A, sis, leftinds = l, orthocenter = 2)
    ls = linkinds(ψ)
    @test hassameinds(ψ[1], (l..., s[1], s[1]', ls[1]))
    @test hassameinds(ψ[N], (r..., s[N], s[N]', ls[N - 1]))
    @test @set_warn_order 15 prod(ψ) ≈ A
    @test ITensors.orthocenter(ψ) == 2
    @test maxlinkdim(ψ) == 144

    A = randomITensor(s..., prime.(s)..., l..., r...)
    ψ = MPO(A, s, leftinds = l, orthocenter = 2)
    ls = linkinds(ψ)
    @test hassameinds(ψ[1], (l..., s[1], s[1]', ls[1]))
    @test hassameinds(ψ[N], (r..., s[N], s[N]', ls[N - 1]))
    @test @set_warn_order 15 prod(ψ) ≈ A
    @test ITensors.orthocenter(ψ) == 2
    @test maxlinkdim(ψ) == 144
  end

  @testset "Set range of MPO tensors" begin
    N = 5
    s = siteinds("S=1/2", N)
    ψ0 = randomMPO(s)

    ψ = orthogonalize(ψ0, 2)
    A = prod(ITensors.data(ψ)[2:N-1])
    randn!(A)
    ϕ = MPO(A, s[2:N-1], orthocenter = 1)
    ψ[2:N-1] = ϕ
    @test prod(ψ) ≈ ψ[1] * A * ψ[N]
    @test maxlinkdim(ψ) == 4
    @test ITensors.orthocenter(ψ) == 2

    ψ = orthogonalize(ψ0, 1)
    A = prod(ITensors.data(ψ)[2:N-1])
    randn!(A)
    @test_throws AssertionError ψ[2:N-1] = A

    ψ = orthogonalize(ψ0, 2)
    A = prod(ITensors.data(ψ)[2:N-1])
    randn!(A)
    ψ[2:N-1, orthocenter = 3] = A
    @test prod(ψ) ≈ ψ[1] * A * ψ[N]
    @test maxlinkdim(ψ) == 4
    @test ITensors.orthocenter(ψ) == 3
  end

  @testset "swapbondsites MPO" begin
    N = 5
    sites = siteinds("S=1/2", N)
    ψ0 = randomMPO(sites)

    # TODO: implement this?
    #ψ = replacebond(ψ0, 3, ψ0[3] * ψ0[4];
    #                swapsites = true,
    #                cutoff = 1e-15)
    #@test siteind(ψ, 1) == siteind(ψ0, 1)
    #@test siteind(ψ, 2) == siteind(ψ0, 2)
    #@test siteind(ψ, 4) == siteind(ψ0, 3)
    #@test siteind(ψ, 3) == siteind(ψ0, 4)
    #@test siteind(ψ, 5) == siteind(ψ0, 5)
    #@test prod(ψ) ≈ prod(ψ0)
    #@test maxlinkdim(ψ) == 1

    ψ = swapbondsites(ψ0, 4;
                      cutoff = 1e-15)
    @test siteind(ψ, 1) == siteind(ψ0, 1)
    @test siteind(ψ, 2) == siteind(ψ0, 2)
    @test siteind(ψ, 3) == siteind(ψ0, 3)
    @test siteind(ψ, 5) == siteind(ψ0, 4)
    @test siteind(ψ, 4) == siteind(ψ0, 5)
    @test prod(ψ) ≈ prod(ψ0)
    @test maxlinkdim(ψ) == 1
  end

  @testset "MPO(::MPS)" begin
    i = Index(QN(0, 2) => 1, QN(1, 2) => 1; tags = "i")
    j = settags(i, "j")
    A = randomITensor(ComplexF64, i, j)
    M = A' * dag(A)
    ψ = MPS(A, [i, j])
    @test prod(ψ) ≈ A
    ρ = MPO(ψ)
    @test prod(ρ) ≈ M
  end

  @testset "tr(::MPO)" begin
    N = 5
    s = siteinds("S=1/2", N)
    H = MPO(s, "Id")
    d = dim(s[1])
    @test tr(H) ≈ d^N
  end

  @testset "tr(::MPO) multiple site indices" begin
    N = 6
    s = siteinds("S=1/2", N)
    H = MPO(s, "Id")
    H2 = MPO([H[j] * H[j+1] for j in 1:2:N-1])
    d = dim(s[1])
    @test tr(H) ≈ d^N
    @test tr(H2) ≈ d^N
  end

end

nothing


text:
# This file was generated by the Julia Swagger Code Generator
# Do not modify this file directly. Modify the swagger specification instead.

@doc raw"""A null or empty node selector term matches no objects. The requirements of them are ANDed. The TopologySelectorTerm type implements a subset of the NodeSelectorTerm.

    IoK8sApiCoreV1NodeSelectorTerm(;
        matchExpressions=nothing,
        matchFields=nothing,
    )

    - matchExpressions::Vector{IoK8sApiCoreV1NodeSelectorRequirement} : A list of node selector requirements by node's labels.
    - matchFields::Vector{IoK8sApiCoreV1NodeSelectorRequirement} : A list of node selector requirements by node's fields.
"""
mutable struct IoK8sApiCoreV1NodeSelectorTerm <: SwaggerModel
    matchExpressions::Any # spec type: Union{ Nothing, Vector{IoK8sApiCoreV1NodeSelectorRequirement} } # spec name: matchExpressions
    matchFields::Any # spec type: Union{ Nothing, Vector{IoK8sApiCoreV1NodeSelectorRequirement} } # spec name: matchFields

    function IoK8sApiCoreV1NodeSelectorTerm(;matchExpressions=nothing, matchFields=nothing)
        o = new()
        validate_property(IoK8sApiCoreV1NodeSelectorTerm, Symbol("matchExpressions"), matchExpressions)
        setfield!(o, Symbol("matchExpressions"), matchExpressions)
        validate_property(IoK8sApiCoreV1NodeSelectorTerm, Symbol("matchFields"), matchFields)
        setfield!(o, Symbol("matchFields"), matchFields)
        o
    end
end # type IoK8sApiCoreV1NodeSelectorTerm

const _property_map_IoK8sApiCoreV1NodeSelectorTerm = Dict{Symbol,Symbol}(Symbol("matchExpressions")=>Symbol("matchExpressions"), Symbol("matchFields")=>Symbol("matchFields"))
const _property_types_IoK8sApiCoreV1NodeSelectorTerm = Dict{Symbol,String}(Symbol("matchExpressions")=>"Vector{IoK8sApiCoreV1NodeSelectorRequirement}", Symbol("matchFields")=>"Vector{IoK8sApiCoreV1NodeSelectorRequirement}")
Base.propertynames(::Type{ IoK8sApiCoreV1NodeSelectorTerm }) = collect(keys(_property_map_IoK8sApiCoreV1NodeSelectorTerm))
Swagger.property_type(::Type{ IoK8sApiCoreV1NodeSelectorTerm }, name::Symbol) = Union{Nothing,eval(Base.Meta.parse(_property_types_IoK8sApiCoreV1NodeSelectorTerm[name]))}
Swagger.field_name(::Type{ IoK8sApiCoreV1NodeSelectorTerm }, property_name::Symbol) =  _property_map_IoK8sApiCoreV1NodeSelectorTerm[property_name]

function check_required(o::IoK8sApiCoreV1NodeSelectorTerm)
    true
end

function validate_property(::Type{ IoK8sApiCoreV1NodeSelectorTerm }, name::Symbol, val)
end