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

"""
    configure_logs_for_playback_configuration(percent_enabled, playback_configuration_name)
    configure_logs_for_playback_configuration(percent_enabled, playback_configuration_name, params::Dict{String,<:Any})

Configures Amazon CloudWatch log settings for a playback configuration.

# Arguments
- `percent_enabled`: The percentage of session logs that MediaTailor sends to your
  Cloudwatch Logs account. For example, if your playback configuration has 1000 sessions and
  percentEnabled is set to 60, MediaTailor sends logs for 600 of the sessions to CloudWatch
  Logs. MediaTailor decides at random which of the playback configuration sessions to send
  logs for. If you want to view logs for a specific session, you can use the debug log mode.
  Valid values: 0 - 100
- `playback_configuration_name`: The name of the playback configuration.

"""
function configure_logs_for_playback_configuration(
    PercentEnabled,
    PlaybackConfigurationName;
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return mediatailor(
        "PUT",
        "/configureLogs/playbackConfiguration",
        Dict{String,Any}(
            "PercentEnabled" => PercentEnabled,
            "PlaybackConfigurationName" => PlaybackConfigurationName,
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function configure_logs_for_playback_configuration(
    PercentEnabled,
    PlaybackConfigurationName,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return mediatailor(
        "PUT",
        "/configureLogs/playbackConfiguration",
        Dict{String,Any}(
            mergewith(
                _merge,
                Dict{String,Any}(
                    "PercentEnabled" => PercentEnabled,
                    "PlaybackConfigurationName" => PlaybackConfigurationName,
                ),
                params,
            ),
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    create_channel(outputs, playback_mode, channel_name)
    create_channel(outputs, playback_mode, channel_name, params::Dict{String,<:Any})

Creates a channel.

# Arguments
- `outputs`: The channel's output properties.
- `playback_mode`: The type of playback mode to use for this channel. LINEAR - The programs
  in the schedule play once back-to-back in the schedule. LOOP - The programs in the schedule
  play back-to-back in an endless loop. When the last program in the schedule stops playing,
  playback loops back to the first program in the schedule.
- `channel_name`: The identifier for the channel you are working on.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"FillerSlate"`: The slate used to fill gaps between programs in the schedule. You must
  configure filler slate if your channel uses a LINEAR PlaybackMode.
- `"tags"`: The tags to assign to the channel.
"""
function create_channel(
    Outputs, PlaybackMode, channelName; aws_config::AbstractAWSConfig=global_aws_config()
)
    return mediatailor(
        "POST",
        "/channel/$(channelName)",
        Dict{String,Any}("Outputs" => Outputs, "PlaybackMode" => PlaybackMode);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function create_channel(
    Outputs,
    PlaybackMode,
    channelName,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return mediatailor(
        "POST",
        "/channel/$(channelName)",
        Dict{String,Any}(
            mergewith(
                _merge,
                Dict{String,Any}("Outputs" => Outputs, "PlaybackMode" => PlaybackMode),
                params,
            ),
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    create_prefetch_schedule(consumption, name, playback_configuration_name, retrieval)
    create_prefetch_schedule(consumption, name, playback_configuration_name, retrieval, params::Dict{String,<:Any})

Creates a new prefetch schedule for the specified playback configuration.

# Arguments
- `consumption`: The configuration settings for MediaTailor's consumption of the prefetched
  ads from the ad decision server. Each consumption configuration contains an end time and an
  optional start time that define the consumption window. Prefetch schedules automatically
  expire no earlier than seven days after the end time.
- `name`: The identifier for the playback configuration.
- `playback_configuration_name`: The name of the playback configuration.
- `retrieval`: The configuration settings for retrieval of prefetched ads from the ad
  decision server. Only one set of prefetched ads will be retrieved and subsequently consumed
  for each ad break.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"StreamId"`: An optional stream identifier that MediaTailor uses to prefetch ads for
  multiple streams that use the same playback configuration. If StreamId is specified,
  MediaTailor returns all of the prefetch schedules with an exact match on StreamId. If not
  specified, MediaTailor returns all of the prefetch schedules for the playback
  configuration, regardless of StreamId.
"""
function create_prefetch_schedule(
    Consumption,
    Name,
    PlaybackConfigurationName,
    Retrieval;
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return mediatailor(
        "POST",
        "/prefetchSchedule/$(PlaybackConfigurationName)/$(Name)",
        Dict{String,Any}("Consumption" => Consumption, "Retrieval" => Retrieval);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function create_prefetch_schedule(
    Consumption,
    Name,
    PlaybackConfigurationName,
    Retrieval,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return mediatailor(
        "POST",
        "/prefetchSchedule/$(PlaybackConfigurationName)/$(Name)",
        Dict{String,Any}(
            mergewith(
                _merge,
                Dict{String,Any}("Consumption" => Consumption, "Retrieval" => Retrieval),
                params,
            ),
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    create_program(schedule_configuration, source_location_name, vod_source_name, channel_name, program_name)
    create_program(schedule_configuration, source_location_name, vod_source_name, channel_name, program_name, params::Dict{String,<:Any})

Creates a program.

# Arguments
- `schedule_configuration`: The schedule configuration settings.
- `source_location_name`: The name of the source location.
- `vod_source_name`: The name that's used to refer to a VOD source.
- `channel_name`: The identifier for the channel you are working on.
- `program_name`: The identifier for the program you are working on.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"AdBreaks"`: The ad break configuration settings.
"""
function create_program(
    ScheduleConfiguration,
    SourceLocationName,
    VodSourceName,
    channelName,
    programName;
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return mediatailor(
        "POST",
        "/channel/$(channelName)/program/$(programName)",
        Dict{String,Any}(
            "ScheduleConfiguration" => ScheduleConfiguration,
            "SourceLocationName" => SourceLocationName,
            "VodSourceName" => VodSourceName,
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function create_program(
    ScheduleConfiguration,
    SourceLocationName,
    VodSourceName,
    channelName,
    programName,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return mediatailor(
        "POST",
        "/channel/$(channelName)/program/$(programName)",
        Dict{String,Any}(
            mergewith(
                _merge,
                Dict{String,Any}(
                    "ScheduleConfiguration" => ScheduleConfiguration,
                    "SourceLocationName" => SourceLocationName,
                    "VodSourceName" => VodSourceName,
                ),
                params,
            ),
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    create_source_location(http_configuration, source_location_name)
    create_source_location(http_configuration, source_location_name, params::Dict{String,<:Any})

Creates a source location on a specific channel.

# Arguments
- `http_configuration`: The source's HTTP package configurations.
- `source_location_name`: The identifier for the source location you are working on.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"AccessConfiguration"`: Access configuration parameters. Configures the type of
  authentication used to access content from your source location.
- `"DefaultSegmentDeliveryConfiguration"`: The optional configuration for the server that
  serves segments.
- `"tags"`: The tags to assign to the source location.
"""
function create_source_location(
    HttpConfiguration, sourceLocationName; aws_config::AbstractAWSConfig=global_aws_config()
)
    return mediatailor(
        "POST",
        "/sourceLocation/$(sourceLocationName)",
        Dict{String,Any}("HttpConfiguration" => HttpConfiguration);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function create_source_location(
    HttpConfiguration,
    sourceLocationName,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return mediatailor(
        "POST",
        "/sourceLocation/$(sourceLocationName)",
        Dict{String,Any}(
            mergewith(
                _merge, Dict{String,Any}("HttpConfiguration" => HttpConfiguration), params
            ),
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    create_vod_source(http_package_configurations, source_location_name, vod_source_name)
    create_vod_source(http_package_configurations, source_location_name, vod_source_name, params::Dict{String,<:Any})

Creates name for a specific VOD source in a source location.

# Arguments
- `http_package_configurations`: An array of HTTP package configuration parameters for this
  VOD source.
- `source_location_name`: The identifier for the source location you are working on.
- `vod_source_name`: The identifier for the VOD source you are working on.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"tags"`: The tags to assign to the VOD source.
"""
function create_vod_source(
    HttpPackageConfigurations,
    sourceLocationName,
    vodSourceName;
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return mediatailor(
        "POST",
        "/sourceLocation/$(sourceLocationName)/vodSource/$(vodSourceName)",
        Dict{String,Any}("HttpPackageConfigurations" => HttpPackageConfigurations);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function create_vod_source(
    HttpPackageConfigurations,
    sourceLocationName,
    vodSourceName,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return mediatailor(
        "POST",
        "/sourceLocation/$(sourceLocationName)/vodSource/$(vodSourceName)",
        Dict{String,Any}(
            mergewith(
                _merge,
                Dict{String,Any}("HttpPackageConfigurations" => HttpPackageConfigurations),
                params,
            ),
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

Deletes a channel. You must stop the channel before it can be deleted.

# Arguments
- `channel_name`: The identifier for the channel you are working on.

"""
function delete_channel(channelName; aws_config::AbstractAWSConfig=global_aws_config())
    return mediatailor(
        "DELETE",
        "/channel/$(channelName)";
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function delete_channel(
    channelName,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return mediatailor(
        "DELETE",
        "/channel/$(channelName)",
        params;
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

Deletes a channel's IAM policy.

# Arguments
- `channel_name`: The identifier for the channel you are working on.

"""
function delete_channel_policy(
    channelName; aws_config::AbstractAWSConfig=global_aws_config()
)
    return mediatailor(
        "DELETE",
        "/channel/$(channelName)/policy";
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function delete_channel_policy(
    channelName,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return mediatailor(
        "DELETE",
        "/channel/$(channelName)/policy",
        params;
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

Deletes the playback configuration for the specified name.

# Arguments
- `name`: The identifier for the playback configuration.

"""
function delete_playback_configuration(
    Name; aws_config::AbstractAWSConfig=global_aws_config()
)
    return mediatailor(
        "DELETE",
        "/playbackConfiguration/$(Name)";
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function delete_playback_configuration(
    Name, params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()
)
    return mediatailor(
        "DELETE",
        "/playbackConfiguration/$(Name)",
        params;
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    delete_prefetch_schedule(name, playback_configuration_name)
    delete_prefetch_schedule(name, playback_configuration_name, params::Dict{String,<:Any})

Deletes a prefetch schedule for a specific playback configuration. If you call
DeletePrefetchSchedule on an expired prefetch schedule, MediaTailor returns an HTTP 404
status code.

# Arguments
- `name`: The identifier for the playback configuration.
- `playback_configuration_name`: The name of the playback configuration.

"""
function delete_prefetch_schedule(
    Name, PlaybackConfigurationName; aws_config::AbstractAWSConfig=global_aws_config()
)
    return mediatailor(
        "DELETE",
        "/prefetchSchedule/$(PlaybackConfigurationName)/$(Name)";
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function delete_prefetch_schedule(
    Name,
    PlaybackConfigurationName,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return mediatailor(
        "DELETE",
        "/prefetchSchedule/$(PlaybackConfigurationName)/$(Name)",
        params;
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    delete_program(channel_name, program_name)
    delete_program(channel_name, program_name, params::Dict{String,<:Any})

Deletes a specific program on a specific channel.

# Arguments
- `channel_name`: The identifier for the channel you are working on.
- `program_name`: The identifier for the program you are working on.

"""
function delete_program(
    channelName, programName; aws_config::AbstractAWSConfig=global_aws_config()
)
    return mediatailor(
        "DELETE",
        "/channel/$(channelName)/program/$(programName)";
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function delete_program(
    channelName,
    programName,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return mediatailor(
        "DELETE",
        "/channel/$(channelName)/program/$(programName)",
        params;
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

Deletes a source location on a specific channel.

# Arguments
- `source_location_name`: The identifier for the source location you are working on.

"""
function delete_source_location(
    sourceLocationName; aws_config::AbstractAWSConfig=global_aws_config()
)
    return mediatailor(
        "DELETE",
        "/sourceLocation/$(sourceLocationName)";
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function delete_source_location(
    sourceLocationName,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return mediatailor(
        "DELETE",
        "/sourceLocation/$(sourceLocationName)",
        params;
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    delete_vod_source(source_location_name, vod_source_name)
    delete_vod_source(source_location_name, vod_source_name, params::Dict{String,<:Any})

Deletes a specific VOD source in a specific source location.

# Arguments
- `source_location_name`: The identifier for the source location you are working on.
- `vod_source_name`: The identifier for the VOD source you are working on.

"""
function delete_vod_source(
    sourceLocationName, vodSourceName; aws_config::AbstractAWSConfig=global_aws_config()
)
    return mediatailor(
        "DELETE",
        "/sourceLocation/$(sourceLocationName)/vodSource/$(vodSourceName)";
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function delete_vod_source(
    sourceLocationName,
    vodSourceName,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return mediatailor(
        "DELETE",
        "/sourceLocation/$(sourceLocationName)/vodSource/$(vodSourceName)",
        params;
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

Describes the properties of a specific channel.

# Arguments
- `channel_name`: The identifier for the channel you are working on.

"""
function describe_channel(channelName; aws_config::AbstractAWSConfig=global_aws_config())
    return mediatailor(
        "GET",
        "/channel/$(channelName)";
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function describe_channel(
    channelName,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return mediatailor(
        "GET",
        "/channel/$(channelName)",
        params;
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    describe_program(channel_name, program_name)
    describe_program(channel_name, program_name, params::Dict{String,<:Any})

Retrieves the properties of the requested program.

# Arguments
- `channel_name`: The identifier for the channel you are working on.
- `program_name`: The identifier for the program you are working on.

"""
function describe_program(
    channelName, programName; aws_config::AbstractAWSConfig=global_aws_config()
)
    return mediatailor(
        "GET",
        "/channel/$(channelName)/program/$(programName)";
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function describe_program(
    channelName,
    programName,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return mediatailor(
        "GET",
        "/channel/$(channelName)/program/$(programName)",
        params;
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

Retrieves the properties of the requested source location.

# Arguments
- `source_location_name`: The identifier for the source location you are working on.

"""
function describe_source_location(
    sourceLocationName; aws_config::AbstractAWSConfig=global_aws_config()
)
    return mediatailor(
        "GET",
        "/sourceLocation/$(sourceLocationName)";
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function describe_source_location(
    sourceLocationName,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return mediatailor(
        "GET",
        "/sourceLocation/$(sourceLocationName)",
        params;
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    describe_vod_source(source_location_name, vod_source_name)
    describe_vod_source(source_location_name, vod_source_name, params::Dict{String,<:Any})

Provides details about a specific VOD source in a specific source location.

# Arguments
- `source_location_name`: The identifier for the source location you are working on.
- `vod_source_name`: The identifier for the VOD source you are working on.

"""
function describe_vod_source(
    sourceLocationName, vodSourceName; aws_config::AbstractAWSConfig=global_aws_config()
)
    return mediatailor(
        "GET",
        "/sourceLocation/$(sourceLocationName)/vodSource/$(vodSourceName)";
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function describe_vod_source(
    sourceLocationName,
    vodSourceName,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return mediatailor(
        "GET",
        "/sourceLocation/$(sourceLocationName)/vodSource/$(vodSourceName)",
        params;
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

Retrieves information about a channel's IAM policy.

# Arguments
- `channel_name`: The identifier for the channel you are working on.

"""
function get_channel_policy(channelName; aws_config::AbstractAWSConfig=global_aws_config())
    return mediatailor(
        "GET",
        "/channel/$(channelName)/policy";
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function get_channel_policy(
    channelName,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return mediatailor(
        "GET",
        "/channel/$(channelName)/policy",
        params;
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

Retrieves information about your channel's schedule.

# Arguments
- `channel_name`: The identifier for the channel you are working on.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"durationMinutes"`: The schedule duration in minutes. The maximum duration is 4320
  minutes (three days).
- `"maxResults"`: Upper bound on number of records to return. The maximum number of results
  is 100.
- `"nextToken"`: Pagination token from the GET list request. Use the token to fetch the
  next page of results.
"""
function get_channel_schedule(
    channelName; aws_config::AbstractAWSConfig=global_aws_config()
)
    return mediatailor(
        "GET",
        "/channel/$(channelName)/schedule";
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function get_channel_schedule(
    channelName,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return mediatailor(
        "GET",
        "/channel/$(channelName)/schedule",
        params;
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

Returns the playback configuration for the specified name.

# Arguments
- `name`: The identifier for the playback configuration.

"""
function get_playback_configuration(Name; aws_config::AbstractAWSConfig=global_aws_config())
    return mediatailor(
        "GET",
        "/playbackConfiguration/$(Name)";
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function get_playback_configuration(
    Name, params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()
)
    return mediatailor(
        "GET",
        "/playbackConfiguration/$(Name)",
        params;
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    get_prefetch_schedule(name, playback_configuration_name)
    get_prefetch_schedule(name, playback_configuration_name, params::Dict{String,<:Any})

Returns information about the prefetch schedule for a specific playback configuration. If
you call GetPrefetchSchedule on an expired prefetch schedule, MediaTailor returns an HTTP
404 status code.

# Arguments
- `name`: The identifier for the playback configuration.
- `playback_configuration_name`: The name of the playback configuration.

"""
function get_prefetch_schedule(
    Name, PlaybackConfigurationName; aws_config::AbstractAWSConfig=global_aws_config()
)
    return mediatailor(
        "GET",
        "/prefetchSchedule/$(PlaybackConfigurationName)/$(Name)";
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function get_prefetch_schedule(
    Name,
    PlaybackConfigurationName,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return mediatailor(
        "GET",
        "/prefetchSchedule/$(PlaybackConfigurationName)/$(Name)",
        params;
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

Returns a list of alerts for the given resource.

# Arguments
- `resource_arn`: The Amazon Resource Name (ARN) of the resource.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"maxResults"`: Upper bound on number of records to return. The maximum number of results
  is 100.
- `"nextToken"`: Pagination token from the GET list request. Use the token to fetch the
  next page of results.
"""
function list_alerts(resourceArn; aws_config::AbstractAWSConfig=global_aws_config())
    return mediatailor(
        "GET",
        "/alerts",
        Dict{String,Any}("resourceArn" => resourceArn);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function list_alerts(
    resourceArn,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return mediatailor(
        "GET",
        "/alerts",
        Dict{String,Any}(
            mergewith(_merge, Dict{String,Any}("resourceArn" => resourceArn), params)
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

Retrieves a list of channels that are associated with this account.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"maxResults"`: Upper bound on number of records to return. The maximum number of results
  is 100.
- `"nextToken"`: Pagination token from the GET list request. Use the token to fetch the
  next page of results.
"""
function list_channels(; aws_config::AbstractAWSConfig=global_aws_config())
    return mediatailor(
        "GET", "/channels"; aws_config=aws_config, feature_set=SERVICE_FEATURE_SET
    )
end
function list_channels(
    params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()
)
    return mediatailor(
        "GET", "/channels", params; aws_config=aws_config, feature_set=SERVICE_FEATURE_SET
    )
end

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

Returns a list of the playback configurations defined in AWS Elemental MediaTailor. You can
specify a maximum number of configurations to return at a time. The default maximum is 50.
Results are returned in pagefuls. If MediaTailor has more configurations than the specified
maximum, it provides parameters in the response that you can use to retrieve the next
pageful.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"MaxResults"`: Maximum number of records to return.
- `"NextToken"`: Pagination token returned by the GET list request when results exceed the
  maximum allowed. Use the token to fetch the next page of results.
"""
function list_playback_configurations(; aws_config::AbstractAWSConfig=global_aws_config())
    return mediatailor(
        "GET",
        "/playbackConfigurations";
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function list_playback_configurations(
    params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()
)
    return mediatailor(
        "GET",
        "/playbackConfigurations",
        params;
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

Creates a new prefetch schedule.

# Arguments
- `playback_configuration_name`: The name of the playback configuration.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"MaxResults"`: The maximum number of prefetch schedules that you want MediaTailor to
  return in response to the current request. If the playback configuration has more than
  MaxResults prefetch schedules, use the value of NextToken in the response to get the next
  page of results.
- `"NextToken"`: (Optional) If the playback configuration has more than MaxResults prefetch
  schedules, use NextToken to get the second and subsequent pages of results. For the first
  ListPrefetchSchedulesRequest request, omit this value. For the second and subsequent
  requests, get the value of NextToken from the previous response and specify that value for
  NextToken in the request. If the previous response didn't include a NextToken element,
  there are no more prefetch schedules to get.
- `"StreamId"`: An optional filtering parameter whereby MediaTailor filters the prefetch
  schedules to include only specific streams.
"""
function list_prefetch_schedules(
    PlaybackConfigurationName; aws_config::AbstractAWSConfig=global_aws_config()
)
    return mediatailor(
        "POST",
        "/prefetchSchedule/$(PlaybackConfigurationName)";
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function list_prefetch_schedules(
    PlaybackConfigurationName,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return mediatailor(
        "POST",
        "/prefetchSchedule/$(PlaybackConfigurationName)",
        params;
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

Retrieves a list of source locations.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"maxResults"`: Upper bound on number of records to return. The maximum number of results
  is 100.
- `"nextToken"`: Pagination token from the GET list request. Use the token to fetch the
  next page of results.
"""
function list_source_locations(; aws_config::AbstractAWSConfig=global_aws_config())
    return mediatailor(
        "GET", "/sourceLocations"; aws_config=aws_config, feature_set=SERVICE_FEATURE_SET
    )
end
function list_source_locations(
    params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()
)
    return mediatailor(
        "GET",
        "/sourceLocations",
        params;
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

Returns a list of the tags assigned to the specified playback configuration resource.

# Arguments
- `resource_arn`: The Amazon Resource Name (ARN) for the playback configuration. You can
  get this from the response to any playback configuration request.

"""
function list_tags_for_resource(
    ResourceArn; aws_config::AbstractAWSConfig=global_aws_config()
)
    return mediatailor(
        "GET",
        "/tags/$(ResourceArn)";
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function list_tags_for_resource(
    ResourceArn,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return mediatailor(
        "GET",
        "/tags/$(ResourceArn)",
        params;
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

Lists all the VOD sources in a source location.

# Arguments
- `source_location_name`: The identifier for the source location you are working on.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"maxResults"`: Upper bound on number of records to return. The maximum number of results
  is 100.
- `"nextToken"`: Pagination token from the GET list request. Use the token to fetch the
  next page of results.
"""
function list_vod_sources(
    sourceLocationName; aws_config::AbstractAWSConfig=global_aws_config()
)
    return mediatailor(
        "GET",
        "/sourceLocation/$(sourceLocationName)/vodSources";
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function list_vod_sources(
    sourceLocationName,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return mediatailor(
        "GET",
        "/sourceLocation/$(sourceLocationName)/vodSources",
        params;
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    put_channel_policy(policy, channel_name)
    put_channel_policy(policy, channel_name, params::Dict{String,<:Any})

Creates an IAM policy for the channel.

# Arguments
- `policy`: Adds an IAM role that determines the permissions of your channel.
- `channel_name`: The identifier for the channel you are working on.

"""
function put_channel_policy(
    Policy, channelName; aws_config::AbstractAWSConfig=global_aws_config()
)
    return mediatailor(
        "PUT",
        "/channel/$(channelName)/policy",
        Dict{String,Any}("Policy" => Policy);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function put_channel_policy(
    Policy,
    channelName,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return mediatailor(
        "PUT",
        "/channel/$(channelName)/policy",
        Dict{String,Any}(mergewith(_merge, Dict{String,Any}("Policy" => Policy), params));
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

Adds a new playback configuration to AWS Elemental MediaTailor.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"AdDecisionServerUrl"`: The URL for the ad decision server (ADS). This includes the
  specification of static parameters and placeholders for dynamic parameters. AWS Elemental
  MediaTailor substitutes player-specific and session-specific parameters as needed when
  calling the ADS. Alternately, for testing you can provide a static VAST URL. The maximum
  length is 25,000 characters.
- `"AvailSuppression"`: The configuration for avail suppression, also known as ad
  suppression. For more information about ad suppression, see Ad Suppression.
- `"Bumper"`: The configuration for bumpers. Bumpers are short audio or video clips that
  play at the start or before the end of an ad break. To learn more about bumpers, see
  Bumpers.
- `"CdnConfiguration"`: The configuration for using a content delivery network (CDN), like
  Amazon CloudFront, for content and ad segment management.
- `"ConfigurationAliases"`: The player parameters and aliases used as dynamic variables
  during session initialization. For more information, see Domain Variables.
- `"DashConfiguration"`: The configuration for DASH content.
- `"LivePreRollConfiguration"`: The configuration for pre-roll ad insertion.
- `"ManifestProcessingRules"`: The configuration for manifest processing rules. Manifest
  processing rules enable customization of the personalized manifests created by MediaTailor.
- `"Name"`: The identifier for the playback configuration.
- `"PersonalizationThresholdSeconds"`: Defines the maximum duration of underfilled ad time
  (in seconds) allowed in an ad break. If the duration of underfilled ad time exceeds the
  personalization threshold, then the personalization of the ad break is abandoned and the
  underlying content is shown. This feature applies to ad replacement in live and VOD
  streams, rather than ad insertion, because it relies on an underlying content stream. For
  more information about ad break behavior, including ad replacement and insertion, see Ad
  Behavior in AWS Elemental MediaTailor.
- `"SlateAdUrl"`: The URL for a high-quality video asset to transcode and use to fill in
  time that's not used by ads. AWS Elemental MediaTailor shows the slate to fill in gaps in
  media content. Configuring the slate is optional for non-VPAID configurations. For VPAID,
  the slate is required because MediaTailor provides it in the slots that are designated for
  dynamic ad content. The slate must be a high-quality asset that contains both audio and
  video.
- `"TranscodeProfileName"`: The name that is used to associate this playback configuration
  with a custom transcode profile. This overrides the dynamic transcoding defaults of
  MediaTailor. Use this only if you have already set up custom profiles with the help of AWS
  Support.
- `"VideoContentSourceUrl"`: The URL prefix for the parent manifest for the stream, minus
  the asset ID. The maximum length is 512 characters.
- `"tags"`: The tags to assign to the playback configuration.
"""
function put_playback_configuration(; aws_config::AbstractAWSConfig=global_aws_config())
    return mediatailor(
        "PUT",
        "/playbackConfiguration";
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function put_playback_configuration(
    params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()
)
    return mediatailor(
        "PUT",
        "/playbackConfiguration",
        params;
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

Starts a specific channel.

# Arguments
- `channel_name`: The identifier for the channel you are working on.

"""
function start_channel(channelName; aws_config::AbstractAWSConfig=global_aws_config())
    return mediatailor(
        "PUT",
        "/channel/$(channelName)/start";
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function start_channel(
    channelName,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return mediatailor(
        "PUT",
        "/channel/$(channelName)/start",
        params;
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

Stops a specific channel.

# Arguments
- `channel_name`: The identifier for the channel you are working on.

"""
function stop_channel(channelName; aws_config::AbstractAWSConfig=global_aws_config())
    return mediatailor(
        "PUT",
        "/channel/$(channelName)/stop";
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function stop_channel(
    channelName,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return mediatailor(
        "PUT",
        "/channel/$(channelName)/stop",
        params;
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

Adds tags to the specified playback configuration resource. You can specify one or more
tags to add.

# Arguments
- `resource_arn`: The Amazon Resource Name (ARN) for the playback configuration. You can
  get this from the response to any playback configuration request.
- `tags`: A comma-separated list of tag key:value pairs.

"""
function tag_resource(ResourceArn, tags; aws_config::AbstractAWSConfig=global_aws_config())
    return mediatailor(
        "POST",
        "/tags/$(ResourceArn)",
        Dict{String,Any}("tags" => tags);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function tag_resource(
    ResourceArn,
    tags,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return mediatailor(
        "POST",
        "/tags/$(ResourceArn)",
        Dict{String,Any}(mergewith(_merge, Dict{String,Any}("tags" => tags), params));
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

Removes tags from the specified playback configuration resource. You can specify one or
more tags to remove.

# Arguments
- `resource_arn`: The Amazon Resource Name (ARN) for the playback configuration. You can
  get this from the response to any playback configuration request.
- `tag_keys`: A comma-separated list of the tag keys to remove from the playback
  configuration.

"""
function untag_resource(
    ResourceArn, tagKeys; aws_config::AbstractAWSConfig=global_aws_config()
)
    return mediatailor(
        "DELETE",
        "/tags/$(ResourceArn)",
        Dict{String,Any}("tagKeys" => tagKeys);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function untag_resource(
    ResourceArn,
    tagKeys,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return mediatailor(
        "DELETE",
        "/tags/$(ResourceArn)",
        Dict{String,Any}(mergewith(_merge, Dict{String,Any}("tagKeys" => tagKeys), params));
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    update_channel(outputs, channel_name)
    update_channel(outputs, channel_name, params::Dict{String,<:Any})

Updates an existing channel.

# Arguments
- `outputs`: The channel's output properties.
- `channel_name`: The identifier for the channel you are working on.

"""
function update_channel(
    Outputs, channelName; aws_config::AbstractAWSConfig=global_aws_config()
)
    return mediatailor(
        "PUT",
        "/channel/$(channelName)",
        Dict{String,Any}("Outputs" => Outputs);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function update_channel(
    Outputs,
    channelName,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return mediatailor(
        "PUT",
        "/channel/$(channelName)",
        Dict{String,Any}(mergewith(_merge, Dict{String,Any}("Outputs" => Outputs), params));
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    update_source_location(http_configuration, source_location_name)
    update_source_location(http_configuration, source_location_name, params::Dict{String,<:Any})

Updates a source location on a specific channel.

# Arguments
- `http_configuration`: The HTTP configuration for the source location.
- `source_location_name`: The identifier for the source location you are working on.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"AccessConfiguration"`: Access configuration parameters. Configures the type of
  authentication used to access content from your source location.
- `"DefaultSegmentDeliveryConfiguration"`: The optional configuration for the host server
  that serves segments.
"""
function update_source_location(
    HttpConfiguration, sourceLocationName; aws_config::AbstractAWSConfig=global_aws_config()
)
    return mediatailor(
        "PUT",
        "/sourceLocation/$(sourceLocationName)",
        Dict{String,Any}("HttpConfiguration" => HttpConfiguration);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function update_source_location(
    HttpConfiguration,
    sourceLocationName,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return mediatailor(
        "PUT",
        "/sourceLocation/$(sourceLocationName)",
        Dict{String,Any}(
            mergewith(
                _merge, Dict{String,Any}("HttpConfiguration" => HttpConfiguration), params
            ),
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    update_vod_source(http_package_configurations, source_location_name, vod_source_name)
    update_vod_source(http_package_configurations, source_location_name, vod_source_name, params::Dict{String,<:Any})

Updates a specific VOD source in a specific source location.

# Arguments
- `http_package_configurations`: An array of HTTP package configurations for the VOD source
  on this account.
- `source_location_name`: The identifier for the source location you are working on.
- `vod_source_name`: The identifier for the VOD source you are working on.

"""
function update_vod_source(
    HttpPackageConfigurations,
    sourceLocationName,
    vodSourceName;
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return mediatailor(
        "PUT",
        "/sourceLocation/$(sourceLocationName)/vodSource/$(vodSourceName)",
        Dict{String,Any}("HttpPackageConfigurations" => HttpPackageConfigurations);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function update_vod_source(
    HttpPackageConfigurations,
    sourceLocationName,
    vodSourceName,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return mediatailor(
        "PUT",
        "/sourceLocation/$(sourceLocationName)/vodSource/$(vodSourceName)",
        Dict{String,Any}(
            mergewith(
                _merge,
                Dict{String,Any}("HttpPackageConfigurations" => HttpPackageConfigurations),
                params,
            ),
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end


text:
#=
zwart_powell.jl
Translated by Harshit Nanda from ir_radon_zwart_powell.m in 2022.
Matlab author: Seongjin Yoon, Univ. of Michigan, Jan 2015
=#

export zwart_powell

const TOLERANCE = 1e-07

"""
    output = zwart_powell(r, ϕ)
Analytic 2D Radon transform value of Zwart-Powell box spline,
for radial distance `r` (normalized by pixel size) and angle `ϕ` (in radians).
"""
function zwart_powell(r::Real, ϕ::Real)
    s, c = sincos(ϕ)
    return zwart_powell(r, (c, s, c+s, c-s))
end

function zwart_powell(r::Real, z::NTuple{4,T}) where T <: Real
    N = sum(z -> abs(z) > TOLERANCE, z)
    return BoxSp4(r, z..., N) / factorial(N-1)
end

BoxSp0(y::RealU, N::Int) = y > 0 ? y^(N-1) : zero(y)

BoxSp1(y::RealU, z1::T, N::Int) where T <: Real =
    abs(z1) > TOLERANCE ?
        (BoxSp0(y + 0.5 * z1, N) -
         BoxSp0(y - 0.5 * z1, N)) / z1 :
        BoxSp0(y, N)

BoxSp2(y::RealU, z1::T, z2::T, N::Int) where T <: Real =
    abs(z2) > TOLERANCE ?
        (BoxSp1(y + 0.5 * z2, z1, N) -
         BoxSp1(y - 0.5 * z2, z1, N)) / z2 :
        BoxSp1(y, z1, N)

BoxSp3(y::RealU, z1::T, z2::T, z3::T, N::Int) where T <: Real =
    abs(z3) > TOLERANCE ?
        (BoxSp2(y + 0.5 * z3, z1, z2, N) -
         BoxSp2(y - 0.5 * z3, z1, z2, N)) / z3 :
        BoxSp2(y, z1, z2, N)

function BoxSp4(y::RealU, z1::T, z2::T, z3::T, z4::T, N::Int) where T <: Real
    return abs(z4) > TOLERANCE ?
        (BoxSp3(y + 0.5 * z4, z1, z2, z3, N) -
         BoxSp3(y - 0.5 * z4, z1, z2, z3, N)) / z4 :
        BoxSp3(y, z1, z2, z3, N)
end

ext: jl


text:
const PSYMS = (
    :Tconn => :Tconn, :Pi => :Pi, :Bwi => :Bwi, :Boi => :Boi,
    :cw => :cw, :co => :co, :cf => :cf, :Swi => :Swi, :Vpi => :Vpi,
    :Tconst => :Tconst, :Prod_index => :Jp, :Inj_index => :Jinj,
    :Frac_inj => :λ, :Min_Pres => :Pmin, :Max_Pres => :Pmax,
    :Geom_factor => :Gw, :Total_mobility => :M,
)

const CSV_ENC = enc"WINDOWS-1251"

abstract type AbstractLinearSolver{T<:AbstractFloat} end
abstract type AbstractNonlinearSolver{T<:AbstractFloat} end

abstract type AbstractTargetTerm{T<:AbstractFloat} end
abstract type AbstractFittingParameter{T<:AbstractFloat} end
abstract type AbstractParametersScaling{T<:AbstractFloat} end

const ColumnSlice{T} = SubArray{T, 1, Matrix{T}, Tuple{Base.Slice{Base.OneTo{Int}}, Int}, true}
const ColumnSliceBool = SubArray{Bool, 1, BitMatrix, Tuple{Base.Slice{Base.OneTo{Int}}, Int}, true}
const RowRange{T} = SubArray{T, 1, Matrix{T}, Tuple{Int, UnitRange{Int}}, true}
const CartesianView{T} = SubArray{T, 1, Matrix{T}, Tuple{Vector{CartesianIndex{2}}}, false}
const VectorRange{T} = SubArray{T, 1, Vector{T}, Tuple{UnitRange{Int}}, true}
const VectorView{T} = SubArray{T, 1, Vector{T}, Tuple{Vector{Int}}, false}
const VectorViewBool = SubArray{Bool, 1, BitVector, Tuple{Vector{Int}}, false}


text:
#TODO other plotting options:  plot bipartite, node-pins, or clique-graph
"""
    PlasmoPlots.layout_plot(graph::OptiGraph; node_labels = false, subgraph_colors = false, node_colors = false, linewidth = 2.0,linealpha = 1.0, markersize = 30,labelsize = 20, markercolor = :grey,
    layout_options = Dict(:tol => 0.01,:C => 2, :K => 4, :iterations => 2),
    plt_options = Dict(:legend => false,:framestyle => :box,:grid => false,:size => (800,800),:axis => nothing),
    line_options = Dict(:linecolor => :blue,:linewidth => linewidth,:linealpha => linealpha))

Plot a graph layout of the optigraph `graph`. The following keyword arguments can be provided to customize the graph layout.

* `node_labels = false`: whether to label nodes using the corresponding optinode label attribute.
* `subgraph_colors = false`: whether to color nodes according to their subgraph.
* `node_colors = false`: whether to color nodes.  Only active if `subgraph_colors = false`.
* `linewidth = 2.0`: the linewidth attribute for each edge in `graph`.
* `linealpha = 1.0`: the linealpha attribute for each edge in `graph`.
* `markersize = 30`: the markersize which determines the size of each node in `graph`.
* `labelsize = 20`: the size for each node label.  Only active if `node_labels = true`.
* `markercolor = :grey`: the color for each node.

* `layout_options = Dict(:tol => 0.01,:C => 2, :K => 4, :iterations => 2)`: dictionary with options for the layout algorithm.
    * `tol`: permitted distance between a current and calculated co-ordinate.
    * `C`,`K`: scaling parameters.
    * `iterations`: number of iterations used to apply forces.
* `plt_options = Dict(:legend => false,:framestyle => :box,:grid => false,:size => (800,800),:axis => nothing)`: dictionary with primary plotting options.
    * `legend`: whether to include legend, or legend position.
    * `framestyle`: style of frame used for plot.
    * `size`: size of the resulting plot.
    * `axis`: whether to include the axis.  The axis typically does not make sense for a graph layout plot.
    * It is also possible to use various plotting options compatible with `Plots.scatter` from the `Plots.jl` package.
* `line_options = Dict(:linecolor => :blue,:linewidth => linewidth,:linealpha => linealpha)`: line plotting options used to display edges in the graph.
    * `linecolor`: color to use for each line.
    * `linewidth`: linewidth to use for each edge.  Defaults to the above option.
    * `linealpha`: linealpha to use for each edge. Default to the above option.
"""
function layout_plot(graph::OptiGraph;
    node_labels = false,
    subgraph_colors = false,
    node_colors = false,
    linewidth = 2.0,
    linealpha = 1.0,
    markersize = 30,
    labelsize = 20,
    markercolor = :grey,
    layout_options = Dict(:tol => 0.01, :C => 2, :K => 4, :iterations => 2),
    plt_options = Dict(:legend => false,:framestyle => :box,:grid => false,:size => (800,800),:axis => nothing),
    line_options = Dict(:linecolor => :blue,:linewidth => linewidth,:linealpha => linealpha))

    if subgraph_colors
        markercolor = []
        n_graphs = length(graph.subgraphs) + 1
        cols = Colors.distinguishable_colors(n_graphs)
        if cols[1] == Colors.parse(Colorant,"black")
            cols[1] = Colors.parse(Colorant,"grey")
        end
        for node in getnodes(graph)
            push!(markercolor,cols[1])
        end
        i = 2
        for subgraph in getsubgraphs(graph)
            for node in all_nodes(subgraph)
                push!(markercolor,cols[i])
            end
            i += 1
        end

    elseif node_colors
        cols = Colors.distinguishable_colors(length(all_nodes(graph)) + 1)
        if cols[1] == Colors.parse(Colorant,"black")
            cols[1] = Colors.parse(Colorant,"grey")
        end
        markercolor = cols[2:end]
    else
        markercolor = markercolor
    end

    #hypergraph,hyper_map = hyper_graph(graph)
    # clique_graph,clique_map = clique_expansion(hypergraph)
    clique_graph,clique_map = Plasmo.clique_graph(graph)
    lgraph = clique_graph.graph

    startpositions = Array{Point{2,Float32},1}()
    for i = 1:LightGraphs.nv(lgraph)
        push!(startpositions,Point(rand(),rand()))
    end
    mat = LightGraphs.adjacency_matrix(lgraph)
    # positions = SFDP.layout(mat,Point2f0,startpositions = startpositions;layout_options...)
    positions = NetworkLayout.sfdp(mat,initialpos = startpositions;layout_options...)

    #marker colors should be based on subgraphs
    scat_plt = Plots.scatter(positions;markersize = markersize,markercolor = markercolor,plt_options...);

    if node_labels
        for (i,node) in enumerate(all_nodes(graph))
            pos = positions[i]
            Plots.annotate!(scat_plt,pos[1],pos[2],Plots.text(node.label,labelsize))
        end
    end

    for edge in edges(lgraph)
        n_from_index = edge.src
        n_to_index = edge.dst
        Plots.plot!(scat_plt,[positions[n_from_index][1],positions[n_to_index][1]],[positions[n_from_index][2],positions[n_to_index][2]];line_options...)
    end

    return scat_plt
end

#Overlap plots
function layout_plot(graph::OptiGraph,subgraphs::Vector{OptiGraph};
    node_labels = false,
    linewidth = 2.0,
    linealpha = 1.0,
    markersize = 30,
    labelsize = 20,
    markercolor = :grey,
    layout_options = Dict(:tol => 0.01,:C => 2, :K => 4, :iterations => 2), plt_options = Dict(:legend => false,:framestyle => :box,:grid => false,
    :size => (800,800),:axis => nothing),
    line_options = Dict(:linecolor => :blue,:linewidth => linewidth,:linealpha => linealpha))

    nodes = all_nodes(graph)

    #COLORS
    markercolors = []
    markersizes = []
    n_graphs = length(subgraphs)
    cols = Colors.distinguishable_colors(n_graphs)
    if cols[1] == Colors.parse(Colorant,"black")
        cols[1] = Colors.parse(Colorant,"grey")
    end

    #subgraph_colors = Dict()
    node_colors = Dict((node,[]) for node in all_nodes(graph))

    for (i,subgraph) in enumerate(subgraphs)
        for node in all_nodes(subgraph)
            push!(node_colors[node],cols[i])
        end
    end

    #Now average node colors
    for node in all_nodes(graph)
        if haskey(node_colors,node)
            node_cols = node_colors[node]
            ave_r = mean([node_cols[i].r for i = 1:length(node_cols)])
            ave_g = mean([node_cols[i].g for i = 1:length(node_cols)])
            ave_b = mean([node_cols[i].b for i = 1:length(node_cols)])

            new_col = RGB(ave_r,ave_g,ave_b)
            push!(markercolors,new_col)
            if length(node_cols) > 1
                push!(markersizes,markersize*2)
            else
                push!(markersizes,markersize)
            end
        else
            push!(markercolors,Colors.parse(Colorant,"grey"))
            push!(markersizes,markersize)
        end
    end

    #LAYOUT
    # hypergraph,hyper_map = gethypergraph(graph)
    # clique_graph,clique_map = clique_expansion(hypergraph)
    clique_graph,clique_map = Plasmo.clique_graph(graph)
    lgraph = clique_graph.graph

    startpositions = Array{Point{2,Float32},1}()
    for i = 1:LightGraphs.nv(lgraph)
        push!(startpositions,Point(rand(),rand()))
    end
    mat = LightGraphs.adjacency_matrix(lgraph)
    # positions = SFDP.layout(mat,Point2f0,startpositions = startpositions;layout_options...)
    positions = NetworkLayout.sfdp(mat,initialpos = startpositions;layout_options...)

    #marker colors should be based on subgraphs
    scat_plt = Plots.scatter(positions;markersize = markersizes,markercolor = markercolors,plt_options...);

    if node_labels
        for (i,node) in enumerate(all_nodes(graph))
            pos = positions[i]
            Plots.annotate!(scat_plt,pos[1],pos[2],Plots.text(node.label,labelsize))
        end
    end

    for edge in edges(lgraph)
        n_from_index = edge.src
        n_to_index = edge.dst
        Plots.plot!(scat_plt,[positions[n_from_index][1],positions[n_to_index][1]],[positions[n_from_index][2],positions[n_to_index][2]];line_options...)
    end

    return scat_plt
end

Plots.plot(graph::OptiGraph;kwargs...) = layout_plot(graph;kwargs...)
Plots.plot(graph::OptiGraph,subgraphs::Vector{OptiGraph};kwargs...) = layout_plot(graph,subgraphs;kwargs...)


text:
using Treaps
using Base.Test

# write your own tests here
@test 1 == 1

function test_treap()
	n = 10000
	a = shuffle([i for i in 1:n])
	t = Treap{Int}()

	for i in 1:n
		add!(t, a[i])
	end
	sort!(a)

	@assert !isempty(t)
	@assert maximum(t) == maximum(a)
	@assert minimum(t) == minimum(a)

	for v in a
		remove!(t, v)
	end

	@assert isempty(t)

	println("Treap: Test succeeded.")

end

test_treap()

using LowDimNearestNeighbors

immutable Vec3{T}
	x::T
	y::T
	z::T
end
Base.getindex(v::Vec3, n::Int) = n == 1 ? v.x : n == 2 ? v.y : n == 3 ? v.z : throw("Vec3 indexing error.")
Base.length(v::Vec3) = 3
Base.rand{T}(::Type{Vec3{T}}) = Vec3(rand(T), rand(T), rand(T))
<(a::Vec3, b::Vec3) = shuffless(a, b)

function benchmark_treap(numelements, numqueries)
	const check_correctness = false

	for i in 1:10
		arr = unique([rand(Vec3{UInt8}) for i in 1:numelements])
		t = Treap{Vec3{UInt8}}()
		for v in arr
			add!(t, v)
		end

		queries = [rand(Vec3{UInt8}) for i in 1:numqueries]

		check_correctness && preprocess!(arr)
		@time for q in queries
			result = nearest(t, q)
			if check_correctness
				result_sqdist = LowDimNearestNeighbors.sqdist(q, result)

				correct_result = nearest(arr, q)
				correct_sqdist = LowDimNearestNeighbors.sqdist(q, correct_result)

				if result_sqdist != correct_sqdist
					result_dist = sqrt(result_sqdist)
					correct_dist = sqrt(correct_sqdist)
					println("Mismatch when searching for ", q, ":")
					println("\t Result: ", result, "\t", result_dist)
					println("\tCorrect: ", correct_result, "\t", correct_dist)
					println("\t% error: ", 100 * (1 - correct_dist / result_dist), "%")
					println()
				end
			end
		end
	end
end

benchmark_treap(100000, 100000)


text:
function find_interval(tier::Tier, time::Real)
    throw_interval_class_error(tier)
    time < tier.xmin && return 0
    time > tier.xmax && return 0
    time == tier.xmax && return tier.size
    return findfirst(x -> x.xmin ≤ time < x.xmax, tier.contents)
end

find_interval(tg::TextGrid, num::Int, time::Real) = find_interval(tg[num], time)

function find_low_interval(tier::Tier, time::Real)
    index = find_interval(tier, time)
    if tier.contents[index].xmin == time
        index -= 1
    end
    return index
end

find_low_interval(tg::TextGrid, num::Int, time::Real) = find_low_interval(tg[num], time)

function find_high_interval(tier::Tier, time::Real)
    index = find_interval(tier, time)
    if tier.contents[index].xmax == time
        index += 1
    end
    return index
end

find_high_interval(tg::TextGrid, num::Int, time::Real) = find_high_interval(tg[num], time)

function find_interval_by_edge(tier::Tier, time::Real; inner::Bool = false)
    throw_interval_class_error(tier)

    if time == tier.xmax
        inner && return 0
        return tier.size
    end

    if time == tier.xmin
        inner && return 0
        return 1
    end

    index = findfirst(x -> time == x.xmin, tier.contents)
    return isnothing(index) ? 0 : index
end

find_interval_by_edge(tg::TextGrid, num::Int, time::Real; inner::Bool = false) = find_interval_by_edge(tg[num], time, inner = inner)

function find_low_point(tier::Tier, time::Real)
    throw_point_class_error(tier)
    time < tier.contents[1].xmin && return 0
    return findlast(x -> x.xmin ≤ time, tier.contents)
end

find_low_point(tg::TextGrid, num::Int, time::Real) = find_low_point(tg[num], time)

function find_high_point(tier::Tier, time::Real)
    throw_point_class_error(tier)
    time > tier.contents[end].xmax && return tier.size + 1
    return findfirst(x -> x.xmin ≥ time, tier.contents)
end

find_high_point(tg::TextGrid, num::Int, time::Real) = find_high_point(tg[num], time)

function find_nearest_point(tier::Tier, time::Real)
    throw_point_class_error(tier)
    return findmin([abs(x.xmin - time) for x in tier.contents])[2]
end

find_nearest_point(tg::TextGrid, num::Int, time::Real) = find_nearest_point(tg[num], time)

throw_interval_class_error(tier::Tier) = tier.class == "interval" || error("Cannot find intervals or boundaries in point tiers.")
throw_point_class_error(tier::Tier) = tier.class == "point" || error("Cannot find points in interval tiers.")


text:
module ElectronicStructurePySCF

using ElectronicStructure: Atom, Geometry, MolecularData, MolecularSpec
import PyCall
using PyCall: PyObject

export PyMol, PySCF
export to_pyscf, hartree_fock!

## In order to make this a global const it is defined outside the
## try/catch block. See the `PyCall` documentation for this idiom.
const pyscf = PyCall.PyNULL()

function __init__()
    try
        copy!(pyscf, PyCall.pyimport("pyscf"))
    catch
        error("Unable to load python module pyscf.")
    end
end

"""
    to_pyscf(atom::Atom)

Convert `atom` to pyscf geometry string for a single atom (or component).

# Examples
```jldoctest
julia> to_pyscf(Atom(:Li, (0.0, 0.0, 1.4)))
"Li 0.0 0.0 1.4"
```
"""
to_pyscf(atom::Atom) = string(atom.species, " ", join(string.(atom.coords), " "))

"""
    to_pyscf(geom::Geometry)

Convert `geom` to pyscf geometry string, a semi-colon separated list of specifications of
species and their positions.

# Examples
```jldoctest
julia> to_pyscf(Geometry(Atom(:H, (0., 0., 0.)), Atom(:H, (0., 0., 0.7414))))
"H 0.0 0.0 0.0;H 0.0 0.0 0.7414"
```
"""
to_pyscf(geom::Geometry) = join((to_pyscf(atom) for atom in geom), ";")

"""
    to_pyscf(mol_data::MolecularSpec)

Convert `mol_data` to a `pyscf` python `Mol` object, and call the `Mol.build()` method.
"""
function to_pyscf(md::MolecularSpec)
    pymol = pyscf.gto.Mole(atom=to_pyscf(md.geometry), basis=md.basis)
    pymol.spin = md.spin
    pymol.charge = md.charge
    pymol.symmetry = false
    pymol.build()
    return pymol
end

"""
    struct PyMol

Wraps the Python class `pyscf.gto.Mole`.
"""
struct PyMol
    pymol::PyObject

    function PyMol(pymol::PyObject)
        class = PyCall.pytypeof(pymol).__name__
        if class != "Mole"
            throw(ErrorException("`PyMol` expecting python class `Mole`."))
        end
        return new(pymol)
    end
end

"""
    PyMol(md::MolecularSpec)

Create an instance of Python class `pyscf.gto.Mole` and wrap in `PyMol`. This
first converts the `ElectronicStructure`-native `MolecularSpec` type to the corresponding `pyscf`
type, then wraps the resulting Python object in `PyMol`.
"""
PyMol(md::MolecularSpec) = PyMol(to_pyscf(md))

"""
    struct PySCF

Wraps an `scf` calculation object from `pyscf`. The `scf` is the central object
for nearly all calculations available from `pyscf`. You must call `hartree_fock!`
on this object before running other calculations.
"""
struct PySCF
    scf::PyObject
end

function PySCF(pymol::PyMol)
    if pymol.pymol.spin != 0  # from OpenFermion
        pyscf_scf = pyscf.scf.ROHF(pymol.pymol)
    else
        pyscf_scf = pyscf.scf.RHF(pymol.pymol)
    end
    return PySCF(pyscf_scf)
end

"""
    hartree_fock!(scf::PySCF)

Perform Hartee-Fock calculation using the `scf` calculational object.  This method must be
called on `scf` before any other calculations, such as integrals, may be performed.
"""
function hartree_fock!(scf::PySCF)
    verbose = scf.scf.verbose
    scf.scf.verbose = 0
    scf.scf.run()
    scf.scf.verbose = verbose
    return scf
end

## These integrals, etc. are calculated in two places in OpenFermion,
## perhaps and older and newer way of doing it.
## _pyscf_molecular_data.y seems simpler, more organized, and clear
## _run_pyscf.py is more complicated.
## The latter makes a distinction between 1d compressed and not integrals

## Both OpenFermion and qiskit.nature do mo_coeff' * hcore * mo_coeff.
## OpenFermion reshapes this into one_electron, but it really looks like a no-op to me.
## Wait,... In _pyscf_molecular_data.py, they seem to agree with me.
"""
    one_electron_integrals(scf::PySCF)

Compute one-electron MO integrals. This uses pyscf to get AO integrals
and transforms them to MO integrals.
"""
function one_electron_integrals(scf::PySCF)
    mo_coeff = scf.scf.mo_coeff
    hcore = scf.scf.get_hcore()
    return mo_coeff' * hcore * mo_coeff
end

## This is copied from OpenFermion
"""
    two_electron_integrals(pymol::PyMol, scf::PySCF)

Compute two-electron MO integrals. This uses pyscf to get AO integrals
and transforms them to MO integrals. The integrals are indexed in chemists'
order.
"""
function two_electron_integrals(pymol::PyMol, scf::PySCF)
    two_electron_compressed = pyscf.ao2mo.kernel(pymol.pymol, scf.scf.mo_coeff)
    n_orbitals = size(scf.scf.mo_coeff)[2]
    symmetry_code = 1 # No permutation symmetry
    return pyscf.ao2mo.restore(symmetry_code, two_electron_compressed, n_orbitals)
end

## See OpenFermion molecular_data.py
## get_molecular_hamiltonian for how to convert these integrals to InteractionOperator

"""
    MolecularData(::Type{PySCF}, mol_spec::MolecularSpec)

Compute coefficient integrals and energies for molecular Hamiltonian from specification of
problem `mol_spec` using `PySCF`.
The two-body integrals are indexed in chemists' order.
"""
function MolecularData(::Type{PySCF}, mol_spec::MolecularSpec)
    pymol = PyMol(mol_spec)  # Convert MolecularSpec to pyscf equiv.
    scf = PySCF(pymol)       # Create scf calc object.
    hartree_fock!(scf)       # Run basic, neccesary calcs.
    one_e_ints = one_electron_integrals(scf)  # Compute one and two body integrals
    two_e_ints = two_electron_integrals(pymol, scf)
    nuclear_repulsion = pymol.pymol.energy_nuc() # Compute constant energy
    return MolecularData(mol_spec, nuclear_repulsion, one_e_ints, two_e_ints)
end

end


text:
module FiniteMesh

using PyCall
using LinearAlgebra

export FM
export read_mesh
export Mesh,
       Cells,
       extract_cell
export mesh_connectivity_2D,
       mesh_face_connectivity_2D,
       mesh_face_center,
       mesh_face_type,
       mesh_face_area_2D,
       mesh_cell_neighbor_2D,
       mesh_cell_type,
       mesh_cell_center,
       mesh_cell_area_2D,
       mesh_cell_face,
       mesh_cell_normals_2D
export add_group!,
       su2_group!
export unstructured_index,
       unstructured_grid,
       triangulate
export unit_normal
export regularize_cell_neighbor, regularize_cell_face

const FM = FiniteMesh

include("tools.jl")
include("struct.jl")
include("connectivity.jl")
include("group.jl")
include("transform.jl")
include("regularize.jl")
include("check.jl")

"""
    read_mesh(file::T) where {T<:AbstractString}

Read mesh file

* @return cells: node ids inside cells
* @return points: are saved with 3D coordinates (z=0 for 2D case)
"""
function read_mesh(file::T) where {T<:AbstractString}
    py"""
    import meshio
    def read(file):
        m0 = meshio.read(file)
        points = m0.points
        cells = m0.cells
        keys = []
        vals = []
        for cell in cells:
            keys.append(cell.type)
            vals.append(cell.data)
        return points, keys, vals
    """

    points, keys, vals = py"read"(file)
    for val in vals
        val .+= 1 # python index is zero-based
    end

    cells = Cells(keys, vals)

    return cells, points
end

end # module


text:
struct BackpropTraceMode end
struct BackpropParamsMode end

const gradient_prefix = gensym("gradient")
gradient_var(node::StaticIRNode) = Symbol("$(gradient_prefix)_$(node.name)")

const value_trie_prefix = gensym("value_trie")
value_trie_var(node::GenerativeFunctionCallNode) = Symbol("$(value_trie_prefix)_$(node.addr)")

const gradient_trie_prefix = gensym("gradient_trie")
gradient_trie_var(node::GenerativeFunctionCallNode) = Symbol("$(gradient_trie_prefix)_$(node.addr)")

const tape_prefix = gensym("tape")
tape_var(node::JuliaNode) = Symbol("$(tape_prefix)_$(node.name)")

const maybe_tracked_value_prefix = gensym("maybe_tracked_value")
maybe_tracked_value_var(node::JuliaNode) = Symbol("$(maybe_tracked_value_prefix)_$(node.name)")

const maybe_tracked_arg_prefix = gensym("maybe_tracked_arg")
maybe_tracked_arg_var(node::JuliaNode, i::Int) = Symbol("$(maybe_tracked_arg_prefix)_$(node.name)_$i")

function fwd_pass!(selected_choices, selected_calls, fwd_marked, node::TrainableParameterNode)
    # TODO: only need to mark it if we are doing backprop params
    push!(fwd_marked, node)
end

function fwd_pass!(selected_choices, selected_calls, fwd_marked, node::ArgumentNode)
    if node.compute_grad
        push!(fwd_marked, node)
    end
end

function fwd_pass!(selected_choices, selected_calls, fwd_marked, node::JuliaNode)
    if any(input_node in fwd_marked for input_node in node.inputs)
        push!(fwd_marked, node)
    end
end

function fwd_pass!(selected_choices, selected_calls, fwd_marked, node::RandomChoiceNode)
    if node in selected_choices
        push!(fwd_marked, node)
    end
end

function fwd_pass!(selected_choices, selected_calls, fwd_marked, node::GenerativeFunctionCallNode)
    if node in selected_calls || any(input_node in fwd_marked for input_node in node.inputs)
        push!(fwd_marked, node)
    end
end

function back_pass!(back_marked, node::TrainableParameterNode) end

function back_pass!(back_marked, node::ArgumentNode) end

function back_pass!(back_marked, node::JuliaNode)
    if node in back_marked
        for input_node in node.inputs
            push!(back_marked, input_node)
        end
    end
end

function back_pass!(back_marked, node::RandomChoiceNode)
    # the logpdf of every random choice is a SINK
    for input_node in node.inputs
        push!(back_marked, input_node)
    end
    # the value of every random choice is in back_marked, since it affects its logpdf
    push!(back_marked, node)
end

function back_pass!(back_marked, node::GenerativeFunctionCallNode)
    # the logpdf of every generative function call is a SINK
    for input_node in node.inputs
        push!(back_marked, input_node)
    end
end

function fwd_codegen!(stmts, fwd_marked, back_marked, node::TrainableParameterNode)
    if node in back_marked
        push!(stmts, :($(node.name) = $(QuoteNode(get_param))($(QuoteNode(get_gen_fn))(trace),
            $(QuoteNode(node.name)))))
    end

    if node in fwd_marked && node in back_marked

        # initialize gradient to zero
        push!(stmts, :($(gradient_var(node)) = zero($(node.name))))
    end
end

function fwd_codegen!(stmts, fwd_marked, back_marked, node::ArgumentNode)
    if node in fwd_marked && node in back_marked

        # initialize gradient to zero
        push!(stmts, :($(gradient_var(node)) = zero($(node.name))))
    end
end

function fwd_codegen!(stmts, fwd_marked, back_marked, node::JuliaNode)

    if node in back_marked && any(input_node in fwd_marked for input_node in node.inputs)

        # tracked forward execution
        tape = tape_var(node)
        push!(stmts, :($tape = $(QuoteNode(new_tape))()))
        args_maybe_tracked = Symbol[]
        for (i, input_node) in enumerate(node.inputs)
            arg = input_node.name
            arg_maybe_tracked = maybe_tracked_arg_var(node, i)
            if input_node in fwd_marked
                push!(stmts, :($arg_maybe_tracked = $(QuoteNode(track))($arg, $tape)))
            else
                push!(stmts, :($arg_maybe_tracked = $arg))
            end
            push!(args_maybe_tracked, arg_maybe_tracked)
        end
        maybe_tracked_value = maybe_tracked_value_var(node)
        push!(stmts, :($maybe_tracked_value = $(QuoteNode(node.fn))($(args_maybe_tracked...))))
        push!(stmts, :($(node.name) = $(QuoteNode(value))($maybe_tracked_value)))

        # initialize gradient to zero
        push!(stmts, :($(gradient_var(node)) = zero($(node.name))))
    else

        # regular forward execution.

        # we need the value for initializing gradient to zero (to get the type
        # and e.g. shape), and for reference by other nodes during
        # back_codegen! we could be more selective about which JuliaNodes need
        # to be evalutaed, that is a performance optimization for the future
        args = map((input_node) -> input_node.name, node.inputs)
        push!(stmts, :($(node.name) = $(QuoteNode(node.fn))($(args...))))
    end
end

function fwd_codegen!(stmts, fwd_marked, back_marked, node::RandomChoiceNode)
    # for reference by other nodes during back_codegen!
    # could performance optimize this away
    push!(stmts, :($(node.name) = trace.$(get_value_fieldname(node))))

    # every random choice is in back_marked, since it affects it logpdf, but
    # also possibly due to other downstream usage of the value
    @assert node in back_marked

    if node in fwd_marked
        # the only way we are fwd_marked is if this choice was selected

        # initialize gradient with respect to the value of the random choice to zero
        # it will be a runtime error, thrown here, if there is no zero() method
        push!(stmts, :($(gradient_var(node)) = zero($(node.name))))
    end
end

function fwd_codegen!(stmts, fwd_marked, back_marked, node::GenerativeFunctionCallNode)
    # for reference by other nodes during back_codegen!
    # could performance optimize this away
    subtrace_fieldname = get_subtrace_fieldname(node)
    push!(stmts, :($(node.name) = get_retval(trace.$subtrace_fieldname)))

    # NOTE: we will still potentially run choice_gradients recursively on the generative function,
    # we just might not use its return value gradient.
    if node in fwd_marked && node in back_marked
        # we are fwd_marked if an input was fwd_marked, or if we were selected internally
        push!(stmts, :($(gradient_var(node)) = zero($(node.name))))
    end
end

function back_codegen!(stmts, ir, selected_calls, fwd_marked, back_marked, node::TrainableParameterNode, mode)

    # handle case when it is the return node
    if node === ir.return_node && node in fwd_marked
        @assert node in back_marked
        push!(stmts, :(isnothing(retval_grad) && error("Required return value gradient but got nothing")))
        push!(stmts, :($(gradient_var(node)) += retval_grad))
    end

    if node in fwd_marked && node in back_marked
        cur_param_grad = :($(QuoteNode(get_param_grad))(trace.$static_ir_gen_fn_ref,
            $(QuoteNode(node.name))))
        push!(stmts, :($(QuoteNode(set_param_grad!))(trace.$static_ir_gen_fn_ref,
            $(QuoteNode(node.name)),
            $cur_param_grad + $(gradient_var(node)))))
    end
end

function back_codegen!(stmts, ir, selected_calls, fwd_marked, back_marked, node::ArgumentNode, mode)

    # handle case when it is the return node
    if node === ir.return_node && node in fwd_marked
        @assert node in back_marked
        push!(stmts, :($(gradient_var(node)) += retval_grad))
    end
end

function back_codegen!(stmts, ir, selected_calls, fwd_marked, back_marked, node::JuliaNode, mode)
    # handle case when it is the return node
    if node === ir.return_node && node in fwd_marked
        @assert node in back_marked
        push!(stmts, :($(gradient_var(node)) += retval_grad))
    end
    if node in back_marked && any(input_node in fwd_marked for input_node in node.inputs)

        # do backward pass through the Julia code
        push!(stmts, :($(QuoteNode(deriv!))($(maybe_tracked_value_var(node)), $(gradient_var(node)))))
        push!(stmts, :($(QuoteNode(reverse_pass!))($(tape_var(node)))))

        # increment gradients of input nodes that are in fwd_marked
        for (i, input_node) in enumerate(node.inputs)
            if input_node in fwd_marked
                arg_maybe_tracked = maybe_tracked_arg_var(node, i)
                push!(stmts, :($(gradient_var(input_node)) += $(QuoteNode(deriv))($arg_maybe_tracked)))
            end
        end
    end

end

function back_codegen_random_choice_to_inputs!(stmts, ir, fwd_marked, back_marked,
                                               node::RandomChoiceNode, logpdf_grad::Symbol)
    # only evaluate the gradient of the logpdf if we need to
    if any(input_node in fwd_marked for input_node in node.inputs) || node in fwd_marked
        args = map((input_node) -> input_node.name, node.inputs)
        push!(stmts, :($logpdf_grad = logpdf_grad($(node.dist), $(node.name), $(args...))))
    end

    # increment gradients of input nodes that are in fwd_marked
    for (i, input_node) in enumerate(node.inputs)
        if input_node in fwd_marked
            @assert input_node in back_marked # this ensured its gradient will have been initialized
            if !has_argument_grads(node.dist)[i]
                error("Distribution $(node.dist) does not have logpdf gradient for argument $i")
            end
            push!(stmts, :($(gradient_var(input_node)) += $logpdf_grad[$(QuoteNode(i+1))]))
        end
    end

    # handle case when it is the return node
    if node === ir.return_node && node in fwd_marked
        @assert node in back_marked
        push!(stmts, :($(gradient_var(node)) += retval_grad))
    end
end

function back_codegen!(stmts, ir, selected_calls, fwd_marked, back_marked,
                       node::RandomChoiceNode, ::BackpropTraceMode)
    logpdf_grad = gensym("logpdf_grad")

    # backpropagate to the inputs
    back_codegen_random_choice_to_inputs!(stmts, ir, fwd_marked, back_marked, node, logpdf_grad)

    # backpropagate to the value (if it was selected)
    if node in fwd_marked
        if !has_output_grad(node.dist)
            error("Distribution $dist does not logpdf gradient for its output value")
        end
        push!(stmts, :($(gradient_var(node)) += $logpdf_grad[1]))
    end
end

function back_codegen!(stmts, ir, selected_calls, fwd_marked, back_marked,
                       node::RandomChoiceNode, ::BackpropParamsMode)
    logpdf_grad = gensym("logpdf_grad")
    back_codegen_random_choice_to_inputs!(stmts, ir, fwd_marked, back_marked, node, logpdf_grad)
end

function back_codegen!(stmts, ir, selected_calls, fwd_marked, back_marked,
                       node::GenerativeFunctionCallNode, mode::BackpropTraceMode)

    # handle case when it is the return node
    if node === ir.return_node && node in fwd_marked
        @assert node in back_marked
        push!(stmts, :($(gradient_var(node)) += retval_grad))
    end

    if node in fwd_marked
        input_grads = gensym("call_input_grads")
        value_trie = value_trie_var(node)
        gradient_trie = gradient_trie_var(node)
        subtrace_fieldname = get_subtrace_fieldname(node)
        call_selection = gensym("call_selection")
        if node in selected_calls
            push!(stmts, :($call_selection = $qn_static_getindex(selection, $(QuoteNode(Val(node.addr))))))
        else
            push!(stmts, :($call_selection = EmptySelection()))
        end
        retval_grad = node in back_marked ? gradient_var(node) : :(nothing)
        push!(stmts, :(($input_grads, $value_trie, $gradient_trie) = choice_gradients(
            trace.$subtrace_fieldname, $call_selection, $retval_grad)))
    end

    # increment gradients of input nodes that are in fwd_marked
    for (i, input_node) in enumerate(node.inputs)
        if input_node in fwd_marked
            @assert input_node in back_marked # this ensured its gradient will have been initialized
            push!(stmts, :($(gradient_var(input_node)) += $input_grads[$(QuoteNode(i))]))
        end
    end

    # NOTE: the value_trie and gradient_trie are dealt with later
end

function back_codegen!(stmts, ir, selected_calls, fwd_marked, back_marked,
                       node::GenerativeFunctionCallNode, mode::BackpropParamsMode)

    # handle case when it is the return node
    if node === ir.return_node && node in fwd_marked
        @assert node in back_marked
        push!(stmts, :($(gradient_var(node)) += retval_grad))
    end

    if node in fwd_marked
        input_grads = gensym("call_input_grads")
        subtrace_fieldname = get_subtrace_fieldname(node)
        retval_grad = node in back_marked ? gradient_var(node) : :(nothing)
        push!(stmts, :($input_grads = accumulate_param_gradients!(trace.$subtrace_fieldname, $retval_grad)))
    end

    # increment gradients of input nodes that are in fwd_marked
    for (i, input_node) in enumerate(node.inputs)
        if input_node in fwd_marked
            @assert input_node in back_marked # this ensured its gradient will have been initialized
            push!(stmts, :($(gradient_var(input_node)) += $input_grads[$(QuoteNode(i))]))
        end
    end
end

function generate_value_gradient_trie(selected_choices::Set{RandomChoiceNode},
                                      selected_calls::Set{GenerativeFunctionCallNode},
                                      value_trie::Symbol, gradient_trie::Symbol)
    selected_choices_vec = collect(selected_choices)
    quoted_leaf_keys = map((node) -> QuoteNode(node.addr), selected_choices_vec)
    leaf_values = map((node) -> :(trace.$(get_value_fieldname(node))), selected_choices_vec)
    leaf_gradients = map((node) -> gradient_var(node), selected_choices_vec)

    selected_calls_vec = collect(selected_calls)
    quoted_internal_keys = map((node) -> QuoteNode(node.addr), selected_calls_vec)
    internal_values = map((node) -> :(get_choices(trace.$(get_subtrace_fieldname(node)))),
                          selected_calls_vec)
    internal_gradients = map((node) -> gradient_trie_var(node), selected_calls_vec)
    quote
        $value_trie = StaticChoiceMap(
            NamedTuple{($(quoted_leaf_keys...),)}(($(leaf_values...),)),
            NamedTuple{($(quoted_internal_keys...),)}(($(internal_values...),)))
        $gradient_trie = StaticChoiceMap(
            NamedTuple{($(quoted_leaf_keys...),)}(($(leaf_gradients...),)),
            NamedTuple{($(quoted_internal_keys...),)}(($(internal_gradients...),)))
    end
end

function get_selected_choices(::EmptyAddressSchema, ::StaticIR)
    Set{RandomChoiceNode}()
end

function get_selected_choices(::AllAddressSchema, ir::StaticIR)
    Set{RandomChoiceNodes}(ir.choice_nodes)
end

function get_selected_choices(schema::StaticAddressSchema, ir::StaticIR)
    selected_choice_addrs = Set(keys(schema))
    selected_choices = Set{RandomChoiceNode}()
    for node in ir.choice_nodes
        if node.addr in selected_choice_addrs
            push!(selected_choices, node)
        end
    end
    selected_choices
end

function get_selected_calls(::EmptyAddressSchema, ::StaticIR)
    Set{GenerativeFunctionCallNode}()
end

function get_selected_calls(::AllAddressSchema, ir::StaticIR)
    Set{GenerativeFunctionCallNode}(ir.call_nodes)
end

function get_selected_calls(schema::StaticAddressSchema, ir::StaticIR)
    selected_call_addrs = Set(keys(schema))
    selected_calls = Set{GenerativeFunctionCallNode}()
    for node in ir.call_nodes
        if node.addr in selected_call_addrs
            push!(selected_calls, node)
        end
    end
    selected_calls
end

function codegen_choice_gradients(trace_type::Type{T}, selection_type::Type,
                                retval_grad_type::Type) where {T<:StaticIRTrace}
    gen_fn_type = get_gen_fn_type(trace_type)
    schema = get_address_schema(selection_type)

    # convert a hierarchical selection to a static selection if it is not already one
    if !(isa(schema, StaticAddressSchema) || isa(schema, EmptyAddressSchema) || isa(schema, AllAddressSchema))
        return quote choice_gradients(trace, StaticSelection(selection), retval_grad) end
    end

    ir = get_ir(gen_fn_type)
    selected_choices = get_selected_choices(schema, ir)
    selected_calls = get_selected_calls(schema, ir)

    # forward marking pass
    fwd_marked = Set{StaticIRNode}()
    for node in ir.nodes
        fwd_pass!(selected_choices, selected_calls, fwd_marked, node)
    end

    # backward marking pass
    back_marked = Set{StaticIRNode}()
    push!(back_marked, ir.return_node)
    for node in reverse(ir.nodes)
        back_pass!(back_marked, node)
    end

    stmts = Expr[]

    # unpack arguments from the trace
    arg_names = Symbol[arg_node.name for arg_node in ir.arg_nodes]
    push!(stmts, :($(Expr(:tuple, arg_names...)) = get_args(trace)))

    # forward code-generation pass (initialize gradients to zero, create needed references)
    for node in ir.nodes
        fwd_codegen!(stmts, fwd_marked, back_marked, node)
    end

    # backward code-generation pass (increment gradients)
    for node in reverse(ir.nodes)
        back_codegen!(stmts, ir, selected_calls, fwd_marked, back_marked, node, BackpropTraceMode())
    end

    # assemble value_trie and gradient_trie
    value_trie = gensym("value_trie")
    gradient_trie = gensym("gradient_trie")
    push!(stmts, generate_value_gradient_trie(selected_choices, selected_calls,
        value_trie, gradient_trie))

    # gradients with respect to inputs
    arg_grad = (node::ArgumentNode) -> node.compute_grad ? gradient_var(node) : :(nothing)
    input_grads = Expr(:tuple, map(arg_grad, ir.arg_nodes)...)

    # return values
    push!(stmts, :(return ($input_grads, $value_trie, $gradient_trie)))

    Expr(:block, stmts...)
end

function codegen_accumulate_param_gradients!(trace_type::Type{T},
                                 retval_grad_type::Type) where {T<:StaticIRTrace}
    gen_fn_type = get_gen_fn_type(trace_type)
    ir = get_ir(gen_fn_type)

    # unlike choice_gradients we don't take gradients w.r.t. the value of random choices
    selected_choices = Set{RandomChoiceNode}()

    # we need to guarantee that we visit every generative function call,
    # because we need to backpropagate to its trainable parameters
    selected_calls = Set{GenerativeFunctionCallNode}(
        node for node in ir.nodes if isa(node, GenerativeFunctionCallNode))

    # forward marking pass
    fwd_marked = Set{StaticIRNode}()
    for node in ir.nodes
        fwd_pass!(selected_choices, selected_calls, fwd_marked, node)
    end

    # backward marking pass
    back_marked = Set{StaticIRNode}()
    push!(back_marked, ir.return_node)
    for node in reverse(ir.nodes)
        back_pass!(back_marked, node)
    end

    stmts = Expr[]

    # unpack arguments from the trace
    arg_names = Symbol[arg_node.name for arg_node in ir.arg_nodes]
    push!(stmts, :($(Expr(:tuple, arg_names...)) = get_args(trace)))

    # forward code-generation pass (initialize gradients to zero, create needed references)
    for node in ir.nodes
        fwd_codegen!(stmts, fwd_marked, back_marked, node)
    end

    # backward code-generation pass (increment gradients)
    for node in reverse(ir.nodes)
        back_codegen!(stmts, ir, selected_calls, fwd_marked, back_marked, node, BackpropParamsMode())
    end

    # gradients with respect to inputs
    arg_grad = (node::ArgumentNode) -> node.compute_grad ? gradient_var(node) : :(nothing)
    input_grads = Expr(:tuple, map(arg_grad, ir.arg_nodes)...)

    # return values
    push!(stmts, :(return $input_grads))

    Expr(:block, stmts...)
end

push!(generated_functions, quote
@generated function $(Expr(:(.), Gen, QuoteNode(:choice_gradients)))(trace::T, selection::$(QuoteNode(Selection)),
                                       retval_grad) where {T<:$(QuoteNode(StaticIRTrace))}
    $(QuoteNode(codegen_choice_gradients))(trace, selection, retval_grad)
end
end)

push!(generated_functions, quote
@generated function $(Expr(:(.), Gen, QuoteNode(:accumulate_param_gradients!)))(trace::T, retval_grad) where {T<:$(QuoteNode(StaticIRTrace))}
    $(QuoteNode(codegen_accumulate_param_gradients!))(trace, retval_grad)
end
end)

max_stars_repo_name: ali-ramadhan/Gen

max_issues_repo_name: ali-ramadhan/Gen

max_forks_repo_name: ali-ramadhan/Gen


text:
function allocate_daughter(model::TierraModel, organism::TierrianOrganism)
    if organism.has_daughter
        organism.error_flag = true
        return
    end

    daughter_length = organism.c

    if (daughter_length == 0 ||
        daughter_length < MIN_DAUGHTER_SIZE ||
        organism.length * MAX_DAUGHTER_GROWTH < daughter_length)

        organism.error_flag = true
        return
    end

    alloc_start = allocate_free_memory!(model, daughter_length)

    if alloc_start == -1 # no memory found
        organism.error_flag = true
        return
    end

    organism.has_daughter = true
    organism.daughter_address = alloc_start
    organism.daughter_length = daughter_length
    organism.a = alloc_start
end

function divide(model::TierraModel, organism::TierrianOrganism)
    if !organism.has_daughter
        organism.error_flag = true
        return
    end

    daughter = TierrianOrganism(organism.daughter_address, organism.daughter_length)
    new_key = add_organism!(model, daughter)

    if new_key === nothing
        organism.error_flag = true
        organism.has_daughter = false
        return
    end

    daughter.parent_hash = organism.hash
    set_hash!(daughter, model)

    log_birth(model.logger, model, daughter, [organism])

    organism.has_daughter = false
    push!(organism.daughters, organism)
end

max_stars_repo_name: tochsner/ALifeBenchmark

max_issues_repo_name: tochsner/ALifeBenchmark

max_forks_repo_name: tochsner/ALifeBenchmark


text:
export AbstractModel, Heisenberg
export heisenberg_ij, hamiltonian, heisenberg_term, ground_state, energy, energy_exact, get_bonds, energy, heisenberg_2d, nspins

abstract type AbstractModel{D} end
abstract type AbstractHeisenberg{D} <: AbstractModel{D} end

nspins(model::AbstractModel) = prod(size(model))

"""
    energy_exact(tc, model::AbstractModel) -> Float64

Exact ground state energy.
"""
function energy_exact(tc, model::AbstractModel)
    nbit = nspins(tc)
    expect(hamiltonian(model), state_exact(tc)) |> real
end

"""
    ground_state(model::AbstractModel) -> DefaultRegister

Get the exact ground state of a model.
"""
function ground_state(model::AbstractModel)
    # get the ground state
    hami = hamiltonian(model)
    E, v = eigsolve(mat(hami), 1, :SR)
    E[1], ArrayReg(v[1])
end

"""
    get_bonds(model::AbstractHeisenberg) -> Vector

Get the weighted bonds of a Heisenberg model in the form Tuple(i,j,w_ij).
"""
function get_bonds end

include("Heisenberg.jl")
include("J1J2.jl")


text:
import Base.*, Base.(.*), Base./, Base.(./)

export *, /

## Constants

# Should only be used internally
.*(x::Constant, y::Constant) = Constant(x.value .* y.value)
*(x::Constant, y::Constant) = Constant(x.value * y.value)

## Affine expressions

# dot multiply currently only used to implement scalar multiplication
# can be extended in the future to support other forms
function .*(x::Constant, y::AffineExpr)
  if x.size != (1, 1) && y.size != (1, 1) && x.size != y.size
    error("Cannot dot multiply two expressions of sizes $(x.size) and $(y.size)")
  end

  if y.size == (1, 1) && x.size != (1, 1)
    y = repmat(y, x.size[1], x.size[2])
  end

  # vec_x needs to be repmat'd for this to work for other forms of dot mult
  vec_x = vec(x)
  vars_to_coeffs_map = Dict{UInt64, Constant}()
  for (v, c) in y.vars_to_coeffs_map
    if vec_x.size != (1, 1) && c.size[2] > 1
      rep_x = repmat(vec_x, 1, c.size[2])
      vars_to_coeffs_map[v] = rep_x .* c
    else
      vars_to_coeffs_map[v] = vec_x .* c
    end
  end
  constant = vec_x .* y.constant

  this = AffineExpr(:.*, (x, y), vars_to_coeffs_map, constant, y.size)
  this.evaluate = ()->x.evaluate() .* y.evaluate()
  return this
end

.*(x::AffineExpr, y::Constant) = y .* x

function *(x::Constant, y::AffineExpr)
  # scalar multiplication
  if x.size == (1, 1) || y.size == (1, 1)
    return x .* y
  # matrix multiplication
  elseif x.size[2] == y.size[1]
    x_kron = Constant(kron(speye(y.size[2]), x.value))

    # Build the coefficient map for x * y
    vars_to_coeffs_map = Dict{UInt64, Constant}()
    for (v, c) in y.vars_to_coeffs_map
      vars_to_coeffs_map[v] = x_kron * c
    end
    constant = x_kron * y.constant
    this = AffineExpr(:*, (x, y), vars_to_coeffs_map, constant, (x.size[1], y.size[2]))
    this.evaluate = ()->x.evaluate() * y.evaluate()
    return this
  else
    error("Cannot multiply two expressions of sizes $(x.size) and $(y.size)")
  end
end

function *(x::AffineExpr, y::Constant)
  # scalar multiplication
  if y.size == (1, 1) || x.size == (1, 1)
    return y .* x
  # matrix multiplication
  elseif x.size[2] == y.size[1]
    y_kron = Constant(kron(y.value', speye(x.size[1])))
    vars_to_coeffs_map = Dict{UInt64, Constant}()
    for (v, c) in x.vars_to_coeffs_map
      vars_to_coeffs_map[v] = y_kron * c
    end
    constant = y_kron * x.constant
    this = AffineExpr(:*, (x, y), vars_to_coeffs_map, constant, (x.size[1], y.size[2]))
    this.evaluate = ()->x.evaluate() * y.evaluate()
    return this
  else
    error("Cannot multiply two expressions of sizes $(x.size) and $(y.size)")
  end
end

*(x::Numeric, y::AffineExpr) = *(convert(Constant, x), y)
*(x::AffineExpr, y::Numeric) = *(x, convert(Constant, y))
.*(x::Numeric, y::AffineExpr) = .*(convert(Constant, x), y)
.*(x::AffineExpr, y::Numeric) = .*(x, convert(Constant, y))

# Will also work for dot division if .* is fully implemented for dot multiplication
function ./(x::AffineExpr, y::Constant)
  if y.size != (1, 1) && x.size != (1, 1) && y.size != x.size
    error("Cannot dot divide two expressions of sizes $(x.size) and $(y.size)")
  end
  return Constant(1 ./ y.value) .* x
end

# Only support scalar division
function /(x::AffineExpr, y::Constant)
  if y.value == 0
    error("Cannot divide an affine expression by 0")
  else
    return x ./ y
  end
end

/(x::AffineExpr, y::Number) = /(x, convert(Constant, y))

## Sum of squares expressions

function *(x::Number, y::SumSquaresExpr)
  try
    x = convert(Float64, x)
  catch
    error("Sum Squares expressions can only be multiplied by real numbers")
  end
  if x < 0
    error("Sum Squares expressions can only be multiplied by nonegative scalars")
  end
  return SumSquaresExpr(:*, y.affines, x * y.multipliers, x * y.scalar)
end

*(x::SumSquaresExpr, y::Number) = *(y, x)

function /(x::SumSquaresExpr, y::Number)
  try
    y = convert(Float64, y)
  catch
    error("Sum Squares expressions can only be divided by real numbers")
  end
  if y <= 0
    error("Sum Squares expressions can only be divided by positive scalars")
  end
  return SumSquaresExpr(:/, x.affines, x.multipliers / y, x.scalar / y)
end


text:
immutable Word<:SetEx w::WordType end
==(x::Word, y::Word) = x.w == y.w

macro wx_str(w)
    Word(w)
end

immutable BotS<:SetEx end
immutable TopS<:SetEx end

bots() = BotS()
tops() = TopS()

==(x::BotS, y::BotS) = true
==(x::TopS, y::TopS) = true

immutable AltS{S<:SetEx,T<:SetEx}<:SetEx a::S; b::T end
immutable CatS{S<:SetEx,T<:SetEx}<:SetEx a::S; b::T end
immutable IntS{S<:SetEx,T<:SetEx}<:SetEx a::S; b::T end

==(x::AltS, y::AltS) = x.a == y.a && x.b == y.b
==(x::CatS, y::CatS) = x.a == y.a && x.b == y.b
==(x::IntS, y::IntS) = x.a == y.a && x.b == y.b

alt(x1::SetEx, x2::SetEx, xs...) = (isempty(xs) ? AltS(x1, x2)
                                    : AltS(x1, alt(x2, xs...)))

cat(x1::SetEx, x2::SetEx, xs...) = (isempty(xs) ? CatS(x1, x2)
                                    : CatS(x1, cat(x2, xs...)))

int(x1::SetEx, x2::SetEx, xs...) = (isempty(xs) ? IntS(x1, x2)
                                    : IntS(x1, int(x2, xs...)))

immutable StarS{S<:SetEx}<:SetEx a::S end
immutable PlusS{S<:SetEx}<:SetEx a::S end

==(x::StarS, y::StarS) = x.a == y.a
==(x::PlusS, y::PlusS) = x.a == y.a

star(x::SetEx) = StarS(x)
plus(x::SetEx) = PlusS(x)


text:
# Autogenerated wrapper script for TopOpt_in_PETSc_jll for x86_64-linux-musl-cxx11
export libtopopt, topopt

using PETSc_jll
using CompilerSupportLibraries_jll
JLLWrappers.@generate_wrapper_header("TopOpt_in_PETSc")
JLLWrappers.@declare_library_product(libtopopt, "libtopopt.so")
JLLWrappers.@declare_executable_product(topopt)
function __init__()
    JLLWrappers.@generate_init_header(PETSc_jll, CompilerSupportLibraries_jll)
    JLLWrappers.@init_library_product(
        libtopopt,
        "lib/libtopopt.so",
        RTLD_LAZY | RTLD_DEEPBIND,
    )

    JLLWrappers.@init_executable_product(
        topopt,
        "bin/topopt",
    )

    JLLWrappers.@generate_init_footer()
end  # __init__()


text:
@testset "Agent" begin
    action_space = DiscreteSpace(3)
    agent = Agent(;
        policy = RandomPolicy(action_space),
        trajectory = CircularCompactSARTSATrajectory(;
            capacity = 10_000,
            state_type = Float32,
            state_size = (4,),
        ),
    )

    @testset "loading/saving Agent" begin
        mktempdir() do dir
            RLCore.save(dir, agent)
            @test length(readdir(dir)) != 0
            agent = RLCore.load(dir, Agent)
        end
    end
end


text:
using Gadfly

set_default_plot_size(12inch, 3inch)

hstack(
    plot(x=[2,3,4],y=[2,3,4],Geom.point,intercept=[0],slope=[1],Geom.abline),
    plot(x=[2,3,4],y=[2,3,4],Geom.point,intercept=[0],Geom.abline),
    plot(x=[2,3,4],y=[2,3,4],Geom.point,slope=[1],Geom.abline),
    plot(x=[2,3,4],y=[2,3,4],Geom.point,intercept=[0.1],Geom.abline),
    plot(x=[2,3,4],y=[2,3,4],Geom.point,slope=[1.1],Geom.abline) )