text:
module Justly

using AudioSchedules:
    add!,
    AudioSchedule,
    Cycles,
    Hook,
    Line,
    Map,
    QUOTIENT,
    SawTooth,
    Scale,
    triples,
    Weaver,
    write_stateful!,
    write_buffer
import Base: getproperty, Rational, show
using Base.Threads: nthreads, @spawn
using PortAudio: PortAudioStream
using QML:
    exec,
    ListModel,
    loadqml,
    # to avoid QML bug
    QML,
    qmlfunction
using Qt5QuickControls2_jll: Qt5QuickControls2_jll
using SampledSignals: samplerate
using Test: @test
using Unitful: Hz, ms, s
using YAML: YAML, load
import YAML: _print

# reexport to avoid a QML bug
export QML

function write_channel(buffer, channel)
    buffer_at = 0
    for (stateful, stateful_to) in channel
        buffer_at = write_stateful!(stateful, stateful_to, buffer, buffer_at)
    end
    write_buffer(buffer, buffer_at)
end

"""
    pluck(duration; decay = -2.5 / s, slope = 1 / 0.005s, peak = 1)

You can use `pluck` to make an envelope with an exponential decay and ramps at the beginning and end.

"""
function pluck(duration; decay = -2.5 / s, slope = 1 / 0.005s, peak = 1)
    ramp = peak / slope
    (0, Line => ramp, peak, Hook(decay, -slope) => duration - ramp, 0)
end
export pluck

"""
    pedal(duration; slope = 1 / 0.1s, peak = 1, overlap = 1/2)

You can use `pedal` to make an envelope with a sustain and ramps at the beginning and end.
`overlap` is the proportion of the ramps that overlap.
"""
function pedal(duration; slope = 1 / 0.1s, peak = 1, overlap = 1 / 2)
    ramp = peak / slope
    (
        0,
        Line => ramp,
        peak,
        Line => (duration - ramp - ramp + ramp * overlap),
        peak,
        Line => ramp,
        0,
    )
end
export pedal

const DEFAULT_BEAT_DURATION = 0.6s
const DEFAULT_INITIAL_KEY = 220Hz
const DEFAULT_MAKE_ENVELOPE = pedal
const DEFAULT_VOLUME = 0.15
const DEFAULT_RAMP = 0.1s
const DEFAULT_SAMPLE_RATE = 44100Hz
const DEFAULT_WAVE = SawTooth(7)

# we could just store intervals as a rational number
# but there's more than one way to represent a given rational
# so we don't want to lose user information
struct Interval
    numerator::Int
    denominator::Int
    octave::Int
end

const INTERVAL_DEFAULTS = (numerator = 1, denominator = 1, octave = 0)

function Interval(;
    numerator = INTERVAL_DEFAULTS.numerator,
    denominator = INTERVAL_DEFAULTS.denominator,
    octave = INTERVAL_DEFAULTS.octave
)
    Interval(numerator, denominator, octave)
end

function Rational(interval::Interval)
    interval.numerator // interval.denominator * (2 // 1)^interval.octave
end

function Interval(rational::Rational)
    octave = 0
    # first, get it in to the [1, 2) range
    while rational >= 2
        rational = rational / 2
        octave = octave + 1
    end
    while rational < 1
        rational = rational * 2
        octave = octave - 1
    end
    # then, divide the top by two if applicable
    if iseven(rational.num)
        rational = rational / 2
        octave = octave + 1
    end
    Interval(rational.num, rational.den, octave)
end

function get_parse(dictionary, property)
    result = dictionary[String(property)]
    if result === nothing
        getproperty(INTERVAL_DEFAULTS, property)
    else
        parse(Int, result)
    end
end

# TODO: reuse from AudioSchedules
function Interval(text::String)
    a_match = match(QUOTIENT, text)
    if a_match === nothing
        throw(Meta.ParseError("Can't parse interval $text"))
    end
    Interval(
        get_parse(a_match, :numerator),
        get_parse(a_match, :denominator),
        get_parse(a_match, :octave)
    )
end

function Interval(interval::Interval)
    interval
end

@inline function print_no_default(io, interval, prefix, property)
    value = getproperty(interval, property)
    if value != getproperty(INTERVAL_DEFAULTS, property)
        print(io, prefix)
        show(io, value)
    end
end

function show(io::IO, interval::Interval)
    # just show not-obvious parts
    print(io, '"')
    show(io, interval.numerator)
    print_no_default(io, interval,  '/', :denominator)
    print_no_default(io, interval, 'o', :octave)
    print(io, '"')
end

function interval_pieces(note_or_chord)
    (note_or_chord.numerator, note_or_chord.denominator, note_or_chord.octave)
end

get_interval(note_or_chord) = Interval(interval_pieces(note_or_chord)...)

# it would be nice if we could have a nested interval
# but because this will be used as a list-model, we can't
# same goes for chords
mutable struct Note
    numerator::Int
    denominator::Int
    octave::Int
    beats::Int
end

const CHORD_DEFAULTS = (words = "", beats = 1, notes = Note[], interval = Interval())

@inline function getproperty(note::Note, property::Symbol)
    if property === :interval
        get_interval(note)
    else
        getfield(note, property)
    end
end

function Note(;
    interval = CHORD_DEFAULTS.interval,
    beats = CHORD_DEFAULTS.beats
)
    # convert strings and rationals to intervals first
    Note(interval_pieces(Interval(interval))..., beats)
end

"""
mutable struct Chord

A Julia representation of a chord. Pass a vector of `Chord`s to [`edit_song`](@ref).
"""
mutable struct Chord
    words::String
    numerator::Int
    denominator::Int
    octave::Int
    beats::Int
    notes::Vector{Note}
    # we need a separate property here for the list model
    notes_model::ListModel
end
export Chord

@inline function getproperty(chord::Chord, property::Symbol)
    if property === :interval
        get_interval(chord)
    else
        getfield(chord, property)
    end
end

function Chord(;
    words = CHORD_DEFAULTS.words,
    interval = CHORD_DEFAULTS.interval,
    beats = CHORD_DEFAULTS.beats,
    notes = Note[]
)
    Chord(words, interval_pieces(Interval(interval))..., beats, notes, ListModel(notes))
end

@inline function print_no_default(io, note_or_chord, property, level, ignore_level, empty)
    value = getproperty(note_or_chord, property)
    if value != getproperty(CHORD_DEFAULTS, property)
        _print(io, property => value, level, if empty
            ignore_level
        else
            false
        end)
        false
    else
        empty
    end
end

function print_empty(io, empty)
    if empty
        println(io, "{}")
    end
end

# overload the yaml print functions
# print them as if they were dicts
function _print(io::IO, note::Note, level::Int=0, ignore_level::Bool=false)
    empty = true
    empty = print_no_default(io, note, :interval, level, ignore_level, empty)
    empty = print_no_default(io, note, :beats, level, ignore_level, empty)
    print_empty(io, empty)
end

function _print(io::IO, chord::Chord, level::Int=0, ignore_level::Bool=false)
    empty = true
    empty = print_no_default(io, chord, :interval, level, ignore_level, empty)
    empty = print_no_default(io, chord, :words, level, ignore_level, empty)
    empty = print_no_default(io, chord, :beats, level, ignore_level, empty)
    empty = print_no_default(io, chord, :notes, level, ignore_level, empty)
    print_empty(io, empty)
end

# cumulative product of previous modulations
function update_key(song, initial_key, chord_index)
    key = initial_key
    for chord in view(song, 1:(chord_index + 1))
        key = key * Rational(chord.interval)
    end
    key
end

function get_default(dictionary, property)
    get(dictionary, property, getproperty(CHORD_DEFAULTS, property))
end

function parse_note(dictionary)
    Note(;
        interval = get_default(dictionary, :interval),
        beats = get_default(dictionary, :beats)
    )
end

# TODO: add a special constructor to avoid Dict intermediates
function parse_chord(dictionary)
    Chord(;
        interval = get_default(dictionary, :interval),
        words = get_default(dictionary, :words),
        beats = get_default(dictionary, :beats),
        # empty lists will come in as nothing
        notes = map(parse_note, get(dictionary, :notes, Dict{Symbol, Any}[]))
    )
end

function add_chord!(chords_model, dictionary::Dict)
    push!(chords_model, parse_chord(dictionary))
end

function add_chord!(chords_model, chords::Vector)
    for chord in chords
        add_chord!(chords_model, chord)
    end
end

# we are working with the model, not the underlying data, so qt can know what we're doing
function from_yaml!(chords_model, text)
    try
        # load first to catch errors early
        result = load(String(text); dicttype=Dict{Symbol,Any})
        # TODO: does empty! work? PR?
        while length(chords_model) > 0
            delete!(chords_model, 1)
        end
        for dictionary in result
            add_chord!(chords_model, dictionary)
        end
        text
    catch an_error
        string(an_error)
    end
end

function press!(buffer, song, presses, releases;
    beat_duration = DEFAULT_BEAT_DURATION,
    initial_key = DEFAULT_INITIAL_KEY,
    make_envelope = DEFAULT_MAKE_ENVELOPE,
    ramp = DEFAULT_RAMP,
    sample_rate = DEFAULT_SAMPLE_RATE,
    volume = DEFAULT_VOLUME,
    wave = DEFAULT_WAVE
)
    for (chord_index, voice_index) in presses
        buffer_at = 0
        if voice_index < 0
            whole_schedule = make_schedule(
                (@view song[(chord_index + 1):end]);
                beat_duration = beat_duration,
                initial_key = update_key(song, initial_key, chord_index - 1),
                make_envelope = make_envelope,
                sample_rate = sample_rate,
                volume = volume,
                wave = wave
            )
            for (stateful, stateful_to) in whole_schedule
                if isready(releases)
                    break
                end
                buffer_at = write_stateful!(stateful, stateful_to, buffer, buffer_at)
            end
        else
            # TODO: avoid allocating a schedule here
            dummy_schedule = AudioSchedule(; sample_rate = sample_rate)
            add!(dummy_schedule, Map(
                    Scale(volume),
                    Map(
                        wave,
                        Cycles(
                            update_key(song, initial_key, chord_index) *
                            Rational(song[chord_index + 1].notes[voice_index + 1].interval)
                        ),
                    ),
                ),
                0s,
                0, Line => ramp, 1, Line => 0.5s, 1, Line => ramp, 0
            )
            # all three will be pairs of iterators and number of frames
            (ramp_up, sustain, ramp_down) = dummy_schedule
            buffer_at = write_stateful!(ramp_up..., buffer, buffer_at)
            while !isready(releases)
                buffer_at = write_stateful!(sustain..., buffer, buffer_at)
            end
            buffer_at = write_stateful!(ramp_down..., buffer, buffer_at)
        end
        write_buffer(buffer, buffer_at)
        take!(releases)
    end
end

"""
    function edit_song(song; ramp = 0.1s, options...)

Use to edit songs interactively.
The interface might be slow at first while Julia is compiling.

- `song` is a YAML string or a vector of [`Chord`](@refs)s.
- `ramp` is the onset/offset time, in time units (like `s`).
- `options` will be passed to [`make_schedule`](@ref).

For more information, see the `README`.

Try running `ENV["QT_QPA_PLATFORM"] = "xcb"` on [Wayland](https://github.com/barche/QML.jl/issues/125).

```julia
julia> using Justly

julia> song = Chord[];

julia> edit_song(song)
```
"""
function edit_song(song;
    beat_duration = DEFAULT_BEAT_DURATION,
    initial_key = DEFAULT_INITIAL_KEY,
    make_envelope = DEFAULT_MAKE_ENVELOPE,
    ramp = DEFAULT_RAMP,
    sample_rate = DEFAULT_SAMPLE_RATE,
    volume = DEFAULT_VOLUME,
    wave = DEFAULT_WAVE,
    test = false,
)
    if nthreads() < 2
        error("Justly needs at least 2 threads to function")
    end
    qmlfunction("to_yaml", let song = song
        () -> YAML.write(song)
    end)

    chords_model = ListModel(song)
    qmlfunction("from_yaml", let chords_model = chords_model
        text -> from_yaml!(chords_model, text)
    end)

    presses = Channel{Tuple{Int, Int}}(0)
    qmlfunction("press", let presses = presses
        (chord_index, voice_index) -> put!(presses, (chord_index, voice_index))
    end)

    releases = Channel{Nothing}(0)
    qmlfunction("release", let releases = releases
        () -> put!(releases, nothing)
    end)

    loadqml(joinpath(@__DIR__, "Justly.qml"), chords_model = chords_model, test = test)
    stream = PortAudioStream(0, 1, writer = Weaver(); warn_xruns = false)
    buffer = stream.sink_messanger.buffer
    press_task = @spawn press!($buffer, $song, $presses, $releases;
        beat_duration = $beat_duration,
        initial_key = $initial_key,
        make_envelope = $make_envelope,
        ramp = $ramp,
        sample_rate = $sample_rate,
        volume = $volume,
        wave = $wave
    )
    try
        if test
            simple_yaml = "- notes:\n    - interval: \"3/2o1\"\n    - {}\n- {}\n"
            from_yaml!(chords_model, simple_yaml)
            from_yaml!(chords_model, "nonsense")
            from_yaml!(chords_model, simple_yaml)
            # note: this is 1, 1 in julia
            put!(presses, (0, 0))
            put!(releases, nothing)
            put!(presses, (0, -1))
            put!(releases, nothing)
            @test String(YAML.write(song)) == simple_yaml
        end
        exec()
        close(presses)
    finally
        wait(press_task)
    end
    close(releases)
    close(stream)
    nothing
end
export edit_song

function add_note!(a_schedule, note, clock, key;
    wave = DEFAULT_WAVE,
    beat_duration = DEFAULT_BEAT_DURATION,
    make_envelope = DEFAULT_MAKE_ENVELOPE,
    volume = DEFAULT_VOLUME
)
    add!(
        a_schedule,
        Map(Scale(volume), Map(wave, Cycles(key * Rational(note.interval)))),
        clock,
        make_envelope(note.beats * beat_duration)...,
    )
end

function add_chord!(
    a_schedule, chord::Chord, clock, key;
    beat_duration = DEFAULT_BEAT_DURATION,
    make_envelope = DEFAULT_MAKE_ENVELOPE,
    volume = DEFAULT_VOLUME,
    wave = DEFAULT_WAVE
)
    key = key * Rational(chord.interval)
    foreach(
        let a_schedule = a_schedule,
            clock = clock,
            key = key,
            wave = wave,
            beat_duration = beat_duration,
            make_envelope = make_envelope,
            volume = volume
            note -> add_note!(a_schedule, note, clock, key;
                wave = wave,
                beat_duration = beat_duration,
                make_envelope = make_envelope,
                volume = volume
            )
        end,
        chord.notes
    )
    key, clock + chord.beats * beat_duration
end

function add_chord!(a_schedule, dictionary::Dict, clock, key; options...)
    add_chord!(a_schedule, parse_chord(dictionary), clock, key; options...)
end

function add_chord!(a_schedule, song::Vector, clock, key;
    beat_duration = DEFAULT_BEAT_DURATION,
    make_envelope = DEFAULT_MAKE_ENVELOPE,
    volume = DEFAULT_VOLUME,
    wave = DEFAULT_WAVE
)
    for chord in song
        key, clock = add_chord!(a_schedule, chord, clock, key;
            beat_duration = beat_duration,
            make_envelope = make_envelope,
            volume = volume,
            wave = wave
        )
    end
    key, clock
end

"""
    function make_schedule(song;
        beat_duration = 0.6s,
        initial_key = 220Hz,
        make_envelope = pedal,
        sample_rate = 44100Hz,
        volume = 0.15,
        wave = SawTooth(7)
    )

Create an `AudioSchedule` from your song.
- `song` is a `YAML` string or a vector of [`Chord`](@ref)s.
- `beat_duration` is the duration of a beat, with time units (like `s`).
- `initial_key` is initial key of your song, in frequency units (like `Hz`).
- `make_envelope` is a function to make an envelope, like [`pluck`](@ref) or [`pedal`](@ref).
- `sample_rate` is the sample rate, in frequency units (like `Hz`).
- `volume`, ranging from 0-1, is the volume that a single voice is played at.
- `wave` is a function which takes an angle in radians and returns an amplitude between -1 and 1.

For more information, see the `README`.

For example, to create a simple I-IV-I figure,

```jldoctest make_schedule
julia> using Justly

julia> using YAML: load

julia> make_schedule(\"""
            - words: "I"
              notes:
                - {}
                - interval: "3/2"
                - interval: "5/4o1"
            - words: "IV"
              interval: "2/3"
              notes:
                - interval: "3/2"
                - interval: "5/4o1"
                - interval: "o2"
            - words: "I"
              interval: "3/2"
              notes:
                - {}
                - interval: "3/2"
                - interval: "5/4o1"

         \""")
1.85 s 44100.0 Hz AudioSchedule
```

Top-level lists will be unnested, so you can use YAML anchors to repeat themes.

```jldoctest make_schedule
julia> make_schedule(\"""
            - &fifth
                - notes:
                    - {}
                    - interval: "3/2"
            - *fifth
        \""")
1.25 s 44100.0 Hz AudioSchedule
```
"""
function make_schedule(song;
    beat_duration = DEFAULT_BEAT_DURATION,
    initial_key = DEFAULT_INITIAL_KEY,
    make_envelope = DEFAULT_MAKE_ENVELOPE,
    sample_rate = DEFAULT_SAMPLE_RATE,
    volume = DEFAULT_VOLUME,
    wave = DEFAULT_WAVE
)
    clock = 0s
    key = initial_key
    a_schedule = AudioSchedule(; sample_rate = sample_rate)
    for chord in song
        key, clock =
            add_chord!(a_schedule, chord, clock, key;
                beat_duration = beat_duration,
                make_envelope = make_envelope,
                volume = volume,
                wave = wave
        )
    end
    a_schedule
end

function make_schedule(song::AbstractString; kwargs...)
    make_schedule(load(song; dicttype=Dict{Symbol,Any}); kwargs...)
end
export make_schedule

end


text:
"""
    get_dimname(::NCDatasets.Dataset, dim::String)

Scans dataset ds and extract the dimensions X, Y, Z and T. The dataset needs to be CF-compliant. See http://cfconventions.org/.
"""
function get_dimname(ds::NCDatasets.Dataset, dim::String)

    dimensions = keys(ds.dim)

    found_dim = "NA"

    # try finding with the "axis" attribute
    for idim in dimensions
        if idim != "bnds" && idim != "vertices" && idim != "ts" && idim != "string1"
            if haskey(ds[idim].attrib, "axis")
                if ds[idim].attrib["axis"] == dim
                    found_dim = idim
                end
            end
        end
    end

    # if found_dim is still not found, try to find it with the standard_name
    if found_dim == "NA"

        dim_dict = Dict(["T" => ["time"],
                         "Z" => ["pressure", "depth"],
                         "X" => ["longitude"],
                         "Y" => ["latitude"]])

        for idim in dimensions
            if idim != "bnds" && idim != "vertices" && idim != "ts" && idim != "string1"
                attribs = ds[idim].attrib
                if haskey(attribs, "standard_name")
                    name = "standard_name"
                elseif haskey(attribs, "long_name")
                    name = "long_name"
                end
                if in(ds[idim].attrib[name], dim_dict[dim])
                    found_dim = idim
                end
            end
        end
    end

    if found_dim == "NA"
        error(string("Unable to find the dimension ", dim, "!"))
    end

    return found_dim
end

"""
    get_calendar(ds::NCDataset)

Returns the calendar type. See See http://cfconventions.org/.
"""
function get_calendar(ds::NCDataset, timename)
    caltype = ""
    try
        caltype = ds[timename].attrib["calendar"]
    catch
        caltype = "standard"
    end

    return caltype

end

ext: jl


text:
#How to build your first Model

using StatsBase, Gadfly
using Distributions
using Random
using MCHammer

clearconsole()

#Key Variables
n_trials = 10000
Revenue = rand(TriangularDist(2500000,4000000,3000000), n_trials)
Expenses = rand(TriangularDist(1400000,3000000,2000000), n_trials)

#Uncorrelated Model (1)
Profit = Revenue - Expenses

#Apply correlation to random samples
Rev_Exp_Cor = -0.8
cor_matrix = [1 Rev_Exp_Cor; Rev_Exp_Cor 1]

#Join Trial into an array and apply correlation
Trials = hcat(Revenue, Expenses)
Trials = corvar(Trials, n_trials, cor_matrix)

#Correlated Model(2) - Create Correlated Results Array
Profit_C = Trials[!,1] -Trials[!,2]
Trials = hcat(Trials[!,1], Trials[!,2], Profit_C)
cormat(Trials,1)

#Plot Density
plot(x=[Profit Profit_C], Geom.density, color=["Uncorrelated","Correlated"], Guide.Title("Compare Results Methods"))

#Plot S-Curves
# plot(layer(ecdf(Profit),minimum(Profit), maximum(Profit), Theme(default_color="orange")),layer(ecdf(Profit_C), minimum(Profit_C), maximum(Profit_C)), Guide.Title("Compare Portfolio Methods"))
# s_table = hcat(Profit_C,Revenue, Expenses)
# sensitivity_chrt(s_table,1)

println("Probability of Making 1m or less (uncorrelated) :",GetCertainty(Profit, 1000000, 0))
println("Input Correlation: ", cor(Revenue,Expenses),"\n")
println("Probability of Making 1m or less (correlated) :",GetCertainty(Profit_C, 1000000, 0))
println("Input Correlation: ", cor(Trials[:, 1],Trials[:,2]))
println("\n")

println("Model Outputs: Revenue, Expenses, Profit, Profit_C")
Profit_C


text:
# This file contains friendly type-pyracy on `convert` for GeoFormatTypes.jl types

"""
    convert(
        target::Type{<:GeoFormatTypes.GeoFormat},
        mode::Union{GeoFormatTypes.FormatMode, Type{GeoFormatTypes.FormatMode}},
        source::GeoFormatTypes.GeoFormat
    )

Convert a `GeoFormatTypes.GeoFormat` object to Geometry, then to the target
format. The Geom trait is needed to separate out convert for CRS for
WellKnownText and GML, which may contain both.

Both `Geom` and `Mixed` formats are converted to Geometries by default.
To convert a `Mixed` format to crs, `CRS` must be explicitly passed for `mode`.
"""

function Base.convert(
    target::Type{<:GFT.GeoFormat},
    mode::Union{GFT.FormatMode,Type{GFT.FormatMode}},
    source::GFT.GeoFormat,
)
    return convert(target, convert(AbstractGeometry, source))
end

"""
    convert(::Type{<:AbstractGeometry},
        source::GeoFormatTypes.AbstractWellKnownText)
    convert(::Type{<:AbstractGeometry}, source::GeoFormatTypes.WellKnownBinary)
    convert(::Type{<:AbstractGeometry}, source::GeoFormatTypes.GeoJSON)
    convert(::Type{<:AbstractGeometry}, source::GeoFormatTypes.GML)

Convert `GeoFormat` geometry data to an ArchGDAL `Geometry` type
"""

function Base.convert(
    ::Type{<:AbstractGeometry},
    source::GFT.AbstractWellKnownText,
)
    return fromWKT(GFT.val(source))
end
function Base.convert(::Type{<:AbstractGeometry}, source::GFT.WellKnownBinary)
    return fromWKB(GFT.val(source))
end
function Base.convert(::Type{<:AbstractGeometry}, source::GFT.GeoJSON)
    return fromJSON(GFT.val(source))
end
function Base.convert(::Type{<:AbstractGeometry}, source::GFT.GML)
    return fromGML(GFT.val(source))
end

function Base.convert(::Type{IGeometry{wkbUnknown}}, source::AbstractGeometry)
    result = IGeometry(C_NULL)
    result.ptr = unsafe_clone(source).ptr
    return result
end

"""
    convert(::Type{<:GeoFormatTypes.AbstractWellKnownText},
        source::AbstractGeometry)
    convert(::Type{<:GeoFormatTypes.WellKnownBinary}, source::AbstractGeometry)
    convert(::Type{<:GeoFormatTypes.GeoJSON}, source::AbstractGeometry)
    convert(::Type{<:GeoFormatTypes.GML}, source::AbstractGeometry)
    convert(::Type{<:GeoFormatTypes.KML}, source::AbstractGeometry)

Convert `AbstractGeometry` data to any geometry `GeoFormat`.
"""

function Base.convert(
    ::Type{<:GFT.AbstractWellKnownText},
    source::AbstractGeometry,
)
    return GFT.WellKnownText(GFT.Geom(), toWKT(source))
end
function Base.convert(::Type{<:GFT.WellKnownBinary}, source::AbstractGeometry)
    return GFT.WellKnownBinary(GFT.Geom(), toWKB(source))
end
function Base.convert(::Type{<:GFT.GeoJSON}, source::AbstractGeometry)
    return GFT.GeoJSON(toJSON(source))
end
function Base.convert(::Type{<:GFT.GML}, source::AbstractGeometry)
    return GFT.GML(GFT.Geom(), toGML(source))
end
function Base.convert(::Type{<:GFT.KML}, source::AbstractGeometry)
    return GFT.KML(toKML(source))
end

"""
    convert(target::Type{<:GeoFormatTypes.GeoFormat}, mode::CRS,
        source::GeoFormat)

Convert `GeoFormat` CRS data to another `GeoFormat` CRS type.
"""

function Base.convert(
    target::Type{<:GFT.GeoFormat},
    mode::Union{GFT.CRS,Type{GFT.CRS}},
    source::GFT.GeoFormat,
)
    return unsafe_convertcrs(target, importCRS(source))
end

function unsafe_convertcrs(::Type{<:GFT.CoordSys}, crsref)
    return GFT.CoordSys(toMICoordSys(crsref))
end
function unsafe_convertcrs(::Type{<:GFT.ProjString}, crsref)
    return GFT.ProjString(toPROJ4(crsref))
end
function unsafe_convertcrs(::Type{<:GFT.WellKnownText}, crsref)
    return GFT.WellKnownText(GFT.CRS(), toWKT(crsref))
end
function unsafe_convertcrs(::Type{<:GFT.ESRIWellKnownText}, crsref)
    return GFT.ESRIWellKnownText(GFT.CRS(), toWKT(morphtoESRI!(crsref)))
end
unsafe_convertcrs(::Type{<:GFT.GML}, crsref) = GFT.GML(toXML(crsref))


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

"""
    create_dataset(client_token, dataset_name, dataset_schema)
    create_dataset(client_token, dataset_name, dataset_schema, params::Dict{String,<:Any})

Creates a container for a collection of data being ingested for analysis. The dataset
contains the metadata describing where the data is and what the data actually looks like.
In other words, it contains the location of the data source, the data schema, and other
information. A dataset also contains any tags associated with the ingested data.

# Arguments
- `client_token`:  A unique identifier for the request. If you do not set the client
  request token, Amazon Lookout for Equipment generates one.
- `dataset_name`: The name of the dataset being created.
- `dataset_schema`: A JSON description of the data that is in each time series dataset,
  including names, column names, and data types.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"ServerSideKmsKeyId"`: Provides the identifier of the KMS key used to encrypt dataset
  data by Amazon Lookout for Equipment.
- `"Tags"`: Any tags associated with the ingested data described in the dataset.
"""
function create_dataset(
    ClientToken,
    DatasetName,
    DatasetSchema;
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return lookoutequipment(
        "CreateDataset",
        Dict{String,Any}(
            "ClientToken" => ClientToken,
            "DatasetName" => DatasetName,
            "DatasetSchema" => DatasetSchema,
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function create_dataset(
    ClientToken,
    DatasetName,
    DatasetSchema,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return lookoutequipment(
        "CreateDataset",
        Dict{String,Any}(
            mergewith(
                _merge,
                Dict{String,Any}(
                    "ClientToken" => ClientToken,
                    "DatasetName" => DatasetName,
                    "DatasetSchema" => DatasetSchema,
                ),
                params,
            ),
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    create_inference_scheduler(client_token, data_input_configuration, data_output_configuration, data_upload_frequency, inference_scheduler_name, model_name, role_arn)
    create_inference_scheduler(client_token, data_input_configuration, data_output_configuration, data_upload_frequency, inference_scheduler_name, model_name, role_arn, params::Dict{String,<:Any})

 Creates a scheduled inference. Scheduling an inference is setting up a continuous
real-time inference plan to analyze new measurement data. When setting up the schedule, you
provide an S3 bucket location for the input data, assign it a delimiter between separate
entries in the data, set an offset delay if desired, and set the frequency of inferencing.
You must also provide an S3 bucket location for the output data.

# Arguments
- `client_token`:  A unique identifier for the request. If you do not set the client
  request token, Amazon Lookout for Equipment generates one.
- `data_input_configuration`: Specifies configuration information for the input data for
  the inference scheduler, including delimiter, format, and dataset location.
- `data_output_configuration`: Specifies configuration information for the output results
  for the inference scheduler, including the S3 location for the output.
- `data_upload_frequency`:  How often data is uploaded to the source S3 bucket for the
  input data. The value chosen is the length of time between data uploads. For instance, if
  you select 5 minutes, Amazon Lookout for Equipment will upload the real-time data to the
  source bucket once every 5 minutes. This frequency also determines how often Amazon Lookout
  for Equipment starts a scheduled inference on your data. In this example, it starts once
  every 5 minutes.
- `inference_scheduler_name`: The name of the inference scheduler being created.
- `model_name`: The name of the previously trained ML model being used to create the
  inference scheduler.
- `role_arn`: The Amazon Resource Name (ARN) of a role with permission to access the data
  source being used for the inference.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"DataDelayOffsetInMinutes"`: A period of time (in minutes) by which inference on the
  data is delayed after the data starts. For instance, if you select an offset delay time of
  five minutes, inference will not begin on the data until the first data measurement after
  the five minute mark. For example, if five minutes is selected, the inference scheduler
  will wake up at the configured frequency with the additional five minute delay time to
  check the customer S3 bucket. The customer can upload data at the same frequency and they
  don't need to stop and restart the scheduler when uploading new data.
- `"ServerSideKmsKeyId"`: Provides the identifier of the KMS key used to encrypt inference
  scheduler data by Amazon Lookout for Equipment.
- `"Tags"`: Any tags associated with the inference scheduler.
"""
function create_inference_scheduler(
    ClientToken,
    DataInputConfiguration,
    DataOutputConfiguration,
    DataUploadFrequency,
    InferenceSchedulerName,
    ModelName,
    RoleArn;
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return lookoutequipment(
        "CreateInferenceScheduler",
        Dict{String,Any}(
            "ClientToken" => ClientToken,
            "DataInputConfiguration" => DataInputConfiguration,
            "DataOutputConfiguration" => DataOutputConfiguration,
            "DataUploadFrequency" => DataUploadFrequency,
            "InferenceSchedulerName" => InferenceSchedulerName,
            "ModelName" => ModelName,
            "RoleArn" => RoleArn,
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function create_inference_scheduler(
    ClientToken,
    DataInputConfiguration,
    DataOutputConfiguration,
    DataUploadFrequency,
    InferenceSchedulerName,
    ModelName,
    RoleArn,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return lookoutequipment(
        "CreateInferenceScheduler",
        Dict{String,Any}(
            mergewith(
                _merge,
                Dict{String,Any}(
                    "ClientToken" => ClientToken,
                    "DataInputConfiguration" => DataInputConfiguration,
                    "DataOutputConfiguration" => DataOutputConfiguration,
                    "DataUploadFrequency" => DataUploadFrequency,
                    "InferenceSchedulerName" => InferenceSchedulerName,
                    "ModelName" => ModelName,
                    "RoleArn" => RoleArn,
                ),
                params,
            ),
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    create_model(client_token, dataset_name, model_name)
    create_model(client_token, dataset_name, model_name, params::Dict{String,<:Any})

Creates an ML model for data inference.  A machine-learning (ML) model is a mathematical
model that finds patterns in your data. In Amazon Lookout for Equipment, the model learns
the patterns of normal behavior and detects abnormal behavior that could be potential
equipment failure (or maintenance events). The models are made by analyzing normal data and
abnormalities in machine behavior that have already occurred. Your model is trained using a
portion of the data from your dataset and uses that data to learn patterns of normal
behavior and abnormal patterns that lead to equipment failure. Another portion of the data
is used to evaluate the model's accuracy.

# Arguments
- `client_token`: A unique identifier for the request. If you do not set the client request
  token, Amazon Lookout for Equipment generates one.
- `dataset_name`: The name of the dataset for the ML model being created.
- `model_name`: The name for the ML model to be created.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"DataPreProcessingConfiguration"`: The configuration is the TargetSamplingRate, which is
  the sampling rate of the data after post processing by Amazon Lookout for Equipment. For
  example, if you provide data that has been collected at a 1 second level and you want the
  system to resample the data at a 1 minute rate before training, the TargetSamplingRate is 1
  minute. When providing a value for the TargetSamplingRate, you must attach the prefix
  \"PT\" to the rate you want. The value for a 1 second rate is therefore PT1S, the value for
  a 15 minute rate is PT15M, and the value for a 1 hour rate is PT1H
- `"DatasetSchema"`: The data schema for the ML model being created.
- `"EvaluationDataEndTime"`:  Indicates the time reference in the dataset that should be
  used to end the subset of evaluation data for the ML model.
- `"EvaluationDataStartTime"`: Indicates the time reference in the dataset that should be
  used to begin the subset of evaluation data for the ML model.
- `"LabelsInputConfiguration"`: The input configuration for the labels being used for the
  ML model that's being created.
- `"OffCondition"`: Indicates that the asset associated with this sensor has been shut off.
  As long as this condition is met, Lookout for Equipment will not use data from this asset
  for training, evaluation, or inference.
- `"RoleArn"`:  The Amazon Resource Name (ARN) of a role with permission to access the data
  source being used to create the ML model.
- `"ServerSideKmsKeyId"`: Provides the identifier of the KMS key used to encrypt model data
  by Amazon Lookout for Equipment.
- `"Tags"`:  Any tags associated with the ML model being created.
- `"TrainingDataEndTime"`: Indicates the time reference in the dataset that should be used
  to end the subset of training data for the ML model.
- `"TrainingDataStartTime"`: Indicates the time reference in the dataset that should be
  used to begin the subset of training data for the ML model.
"""
function create_model(
    ClientToken, DatasetName, ModelName; aws_config::AbstractAWSConfig=global_aws_config()
)
    return lookoutequipment(
        "CreateModel",
        Dict{String,Any}(
            "ClientToken" => ClientToken,
            "DatasetName" => DatasetName,
            "ModelName" => ModelName,
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function create_model(
    ClientToken,
    DatasetName,
    ModelName,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return lookoutequipment(
        "CreateModel",
        Dict{String,Any}(
            mergewith(
                _merge,
                Dict{String,Any}(
                    "ClientToken" => ClientToken,
                    "DatasetName" => DatasetName,
                    "ModelName" => ModelName,
                ),
                params,
            ),
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

 Deletes a dataset and associated artifacts. The operation will check to see if any
inference scheduler or data ingestion job is currently using the dataset, and if there
isn't, the dataset, its metadata, and any associated data stored in S3 will be deleted.
This does not affect any models that used this dataset for training and evaluation, but
does prevent it from being used in the future.

# Arguments
- `dataset_name`: The name of the dataset to be deleted.

"""
function delete_dataset(DatasetName; aws_config::AbstractAWSConfig=global_aws_config())
    return lookoutequipment(
        "DeleteDataset",
        Dict{String,Any}("DatasetName" => DatasetName);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function delete_dataset(
    DatasetName,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return lookoutequipment(
        "DeleteDataset",
        Dict{String,Any}(
            mergewith(_merge, Dict{String,Any}("DatasetName" => DatasetName), params)
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

Deletes an inference scheduler that has been set up. Already processed output results are
not affected.

# Arguments
- `inference_scheduler_name`: The name of the inference scheduler to be deleted.

"""
function delete_inference_scheduler(
    InferenceSchedulerName; aws_config::AbstractAWSConfig=global_aws_config()
)
    return lookoutequipment(
        "DeleteInferenceScheduler",
        Dict{String,Any}("InferenceSchedulerName" => InferenceSchedulerName);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function delete_inference_scheduler(
    InferenceSchedulerName,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return lookoutequipment(
        "DeleteInferenceScheduler",
        Dict{String,Any}(
            mergewith(
                _merge,
                Dict{String,Any}("InferenceSchedulerName" => InferenceSchedulerName),
                params,
            ),
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

Deletes an ML model currently available for Amazon Lookout for Equipment. This will prevent
it from being used with an inference scheduler, even one that is already set up.

# Arguments
- `model_name`: The name of the ML model to be deleted.

"""
function delete_model(ModelName; aws_config::AbstractAWSConfig=global_aws_config())
    return lookoutequipment(
        "DeleteModel",
        Dict{String,Any}("ModelName" => ModelName);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function delete_model(
    ModelName,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return lookoutequipment(
        "DeleteModel",
        Dict{String,Any}(
            mergewith(_merge, Dict{String,Any}("ModelName" => ModelName), params)
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

Provides information on a specific data ingestion job such as creation time, dataset ARN,
status, and so on.

# Arguments
- `job_id`: The job ID of the data ingestion job.

"""
function describe_data_ingestion_job(
    JobId; aws_config::AbstractAWSConfig=global_aws_config()
)
    return lookoutequipment(
        "DescribeDataIngestionJob",
        Dict{String,Any}("JobId" => JobId);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function describe_data_ingestion_job(
    JobId, params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()
)
    return lookoutequipment(
        "DescribeDataIngestionJob",
        Dict{String,Any}(mergewith(_merge, Dict{String,Any}("JobId" => JobId), params));
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

Provides a JSON description of the data that is in each time series dataset, including
names, column names, and data types.

# Arguments
- `dataset_name`: The name of the dataset to be described.

"""
function describe_dataset(DatasetName; aws_config::AbstractAWSConfig=global_aws_config())
    return lookoutequipment(
        "DescribeDataset",
        Dict{String,Any}("DatasetName" => DatasetName);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function describe_dataset(
    DatasetName,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return lookoutequipment(
        "DescribeDataset",
        Dict{String,Any}(
            mergewith(_merge, Dict{String,Any}("DatasetName" => DatasetName), params)
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

 Specifies information about the inference scheduler being used, including name, model,
status, and associated metadata

# Arguments
- `inference_scheduler_name`: The name of the inference scheduler being described.

"""
function describe_inference_scheduler(
    InferenceSchedulerName; aws_config::AbstractAWSConfig=global_aws_config()
)
    return lookoutequipment(
        "DescribeInferenceScheduler",
        Dict{String,Any}("InferenceSchedulerName" => InferenceSchedulerName);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function describe_inference_scheduler(
    InferenceSchedulerName,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return lookoutequipment(
        "DescribeInferenceScheduler",
        Dict{String,Any}(
            mergewith(
                _merge,
                Dict{String,Any}("InferenceSchedulerName" => InferenceSchedulerName),
                params,
            ),
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

Provides a JSON containing the overall information about a specific ML model, including
model name and ARN, dataset, training and evaluation information, status, and so on.

# Arguments
- `model_name`: The name of the ML model to be described.

"""
function describe_model(ModelName; aws_config::AbstractAWSConfig=global_aws_config())
    return lookoutequipment(
        "DescribeModel",
        Dict{String,Any}("ModelName" => ModelName);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function describe_model(
    ModelName,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return lookoutequipment(
        "DescribeModel",
        Dict{String,Any}(
            mergewith(_merge, Dict{String,Any}("ModelName" => ModelName), params)
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

Provides a list of all data ingestion jobs, including dataset name and ARN, S3 location of
the input data, status, and so on.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"DatasetName"`: The name of the dataset being used for the data ingestion job.
- `"MaxResults"`:  Specifies the maximum number of data ingestion jobs to list.
- `"NextToken"`:  An opaque pagination token indicating where to continue the listing of
  data ingestion jobs.
- `"Status"`: Indicates the status of the data ingestion job.
"""
function list_data_ingestion_jobs(; aws_config::AbstractAWSConfig=global_aws_config())
    return lookoutequipment(
        "ListDataIngestionJobs"; aws_config=aws_config, feature_set=SERVICE_FEATURE_SET
    )
end
function list_data_ingestion_jobs(
    params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()
)
    return lookoutequipment(
        "ListDataIngestionJobs",
        params;
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

Lists all datasets currently available in your account, filtering on the dataset name.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"DatasetNameBeginsWith"`: The beginning of the name of the datasets to be listed.
- `"MaxResults"`:  Specifies the maximum number of datasets to list.
- `"NextToken"`:  An opaque pagination token indicating where to continue the listing of
  datasets.
"""
function list_datasets(; aws_config::AbstractAWSConfig=global_aws_config())
    return lookoutequipment(
        "ListDatasets"; aws_config=aws_config, feature_set=SERVICE_FEATURE_SET
    )
end
function list_datasets(
    params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()
)
    return lookoutequipment(
        "ListDatasets", params; aws_config=aws_config, feature_set=SERVICE_FEATURE_SET
    )
end

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

 Lists all inference executions that have been performed by the specified inference
scheduler.

# Arguments
- `inference_scheduler_name`: The name of the inference scheduler for the inference
  execution listed.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"DataEndTimeBefore"`: The time reference in the inferenced dataset before which Amazon
  Lookout for Equipment stopped the inference execution.
- `"DataStartTimeAfter"`: The time reference in the inferenced dataset after which Amazon
  Lookout for Equipment started the inference execution.
- `"MaxResults"`: Specifies the maximum number of inference executions to list.
- `"NextToken"`: An opaque pagination token indicating where to continue the listing of
  inference executions.
- `"Status"`: The status of the inference execution.
"""
function list_inference_executions(
    InferenceSchedulerName; aws_config::AbstractAWSConfig=global_aws_config()
)
    return lookoutequipment(
        "ListInferenceExecutions",
        Dict{String,Any}("InferenceSchedulerName" => InferenceSchedulerName);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function list_inference_executions(
    InferenceSchedulerName,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return lookoutequipment(
        "ListInferenceExecutions",
        Dict{String,Any}(
            mergewith(
                _merge,
                Dict{String,Any}("InferenceSchedulerName" => InferenceSchedulerName),
                params,
            ),
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

Retrieves a list of all inference schedulers currently available for your account.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"InferenceSchedulerNameBeginsWith"`: The beginning of the name of the inference
  schedulers to be listed.
- `"MaxResults"`:  Specifies the maximum number of inference schedulers to list.
- `"ModelName"`: The name of the ML model used by the inference scheduler to be listed.
- `"NextToken"`:  An opaque pagination token indicating where to continue the listing of
  inference schedulers.
"""
function list_inference_schedulers(; aws_config::AbstractAWSConfig=global_aws_config())
    return lookoutequipment(
        "ListInferenceSchedulers"; aws_config=aws_config, feature_set=SERVICE_FEATURE_SET
    )
end
function list_inference_schedulers(
    params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()
)
    return lookoutequipment(
        "ListInferenceSchedulers",
        params;
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

Generates a list of all models in the account, including model name and ARN, dataset, and
status.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"DatasetNameBeginsWith"`: The beginning of the name of the dataset of the ML models to
  be listed.
- `"MaxResults"`:  Specifies the maximum number of ML models to list.
- `"ModelNameBeginsWith"`: The beginning of the name of the ML models being listed.
- `"NextToken"`:  An opaque pagination token indicating where to continue the listing of ML
  models.
- `"Status"`: The status of the ML model.
"""
function list_models(; aws_config::AbstractAWSConfig=global_aws_config())
    return lookoutequipment(
        "ListModels"; aws_config=aws_config, feature_set=SERVICE_FEATURE_SET
    )
end
function list_models(
    params::AbstractDict{String}; aws_config::AbstractAWSConfig=global_aws_config()
)
    return lookoutequipment(
        "ListModels", 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})

Lists all the tags for a specified resource, including key and value.

# Arguments
- `resource_arn`: The Amazon Resource Name (ARN) of the resource (such as the dataset or
  model) that is the focus of the ListTagsForResource operation.

"""
function list_tags_for_resource(
    ResourceArn; aws_config::AbstractAWSConfig=global_aws_config()
)
    return lookoutequipment(
        "ListTagsForResource",
        Dict{String,Any}("ResourceArn" => 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 lookoutequipment(
        "ListTagsForResource",
        Dict{String,Any}(
            mergewith(_merge, Dict{String,Any}("ResourceArn" => ResourceArn), params)
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

"""
    start_data_ingestion_job(client_token, dataset_name, ingestion_input_configuration, role_arn)
    start_data_ingestion_job(client_token, dataset_name, ingestion_input_configuration, role_arn, params::Dict{String,<:Any})

Starts a data ingestion job. Amazon Lookout for Equipment returns the job status.

# Arguments
- `client_token`:  A unique identifier for the request. If you do not set the client
  request token, Amazon Lookout for Equipment generates one.
- `dataset_name`: The name of the dataset being used by the data ingestion job.
- `ingestion_input_configuration`:  Specifies information for the input data for the data
  ingestion job, including dataset S3 location.
- `role_arn`:  The Amazon Resource Name (ARN) of a role with permission to access the data
  source for the data ingestion job.

"""
function start_data_ingestion_job(
    ClientToken,
    DatasetName,
    IngestionInputConfiguration,
    RoleArn;
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return lookoutequipment(
        "StartDataIngestionJob",
        Dict{String,Any}(
            "ClientToken" => ClientToken,
            "DatasetName" => DatasetName,
            "IngestionInputConfiguration" => IngestionInputConfiguration,
            "RoleArn" => RoleArn,
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function start_data_ingestion_job(
    ClientToken,
    DatasetName,
    IngestionInputConfiguration,
    RoleArn,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return lookoutequipment(
        "StartDataIngestionJob",
        Dict{String,Any}(
            mergewith(
                _merge,
                Dict{String,Any}(
                    "ClientToken" => ClientToken,
                    "DatasetName" => DatasetName,
                    "IngestionInputConfiguration" => IngestionInputConfiguration,
                    "RoleArn" => RoleArn,
                ),
                params,
            ),
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

Starts an inference scheduler.

# Arguments
- `inference_scheduler_name`: The name of the inference scheduler to be started.

"""
function start_inference_scheduler(
    InferenceSchedulerName; aws_config::AbstractAWSConfig=global_aws_config()
)
    return lookoutequipment(
        "StartInferenceScheduler",
        Dict{String,Any}("InferenceSchedulerName" => InferenceSchedulerName);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function start_inference_scheduler(
    InferenceSchedulerName,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return lookoutequipment(
        "StartInferenceScheduler",
        Dict{String,Any}(
            mergewith(
                _merge,
                Dict{String,Any}("InferenceSchedulerName" => InferenceSchedulerName),
                params,
            ),
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

Stops an inference scheduler.

# Arguments
- `inference_scheduler_name`: The name of the inference scheduler to be stopped.

"""
function stop_inference_scheduler(
    InferenceSchedulerName; aws_config::AbstractAWSConfig=global_aws_config()
)
    return lookoutequipment(
        "StopInferenceScheduler",
        Dict{String,Any}("InferenceSchedulerName" => InferenceSchedulerName);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function stop_inference_scheduler(
    InferenceSchedulerName,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return lookoutequipment(
        "StopInferenceScheduler",
        Dict{String,Any}(
            mergewith(
                _merge,
                Dict{String,Any}("InferenceSchedulerName" => InferenceSchedulerName),
                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})

Associates a given tag to a resource in your account. A tag is a key-value pair which can
be added to an Amazon Lookout for Equipment resource as metadata. Tags can be used for
organizing your resources as well as helping you to search and filter by tag. Multiple tags
can be added to a resource, either when you create it, or later. Up to 50 tags can be
associated with each resource.

# Arguments
- `resource_arn`: The Amazon Resource Name (ARN) of the specific resource to which the tag
  should be associated.
- `tags`: The tag or tags to be associated with a specific resource. Both the tag key and
  value are specified.

"""
function tag_resource(ResourceArn, Tags; aws_config::AbstractAWSConfig=global_aws_config())
    return lookoutequipment(
        "TagResource",
        Dict{String,Any}("ResourceArn" => ResourceArn, "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 lookoutequipment(
        "TagResource",
        Dict{String,Any}(
            mergewith(
                _merge,
                Dict{String,Any}("ResourceArn" => ResourceArn, "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 a specific tag from a given resource. The tag is specified by its key.

# Arguments
- `resource_arn`: The Amazon Resource Name (ARN) of the resource to which the tag is
  currently associated.
- `tag_keys`: Specifies the key of the tag to be removed from a specified resource.

"""
function untag_resource(
    ResourceArn, TagKeys; aws_config::AbstractAWSConfig=global_aws_config()
)
    return lookoutequipment(
        "UntagResource",
        Dict{String,Any}("ResourceArn" => ResourceArn, "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 lookoutequipment(
        "UntagResource",
        Dict{String,Any}(
            mergewith(
                _merge,
                Dict{String,Any}("ResourceArn" => ResourceArn, "TagKeys" => TagKeys),
                params,
            ),
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end

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

Updates an inference scheduler.

# Arguments
- `inference_scheduler_name`: The name of the inference scheduler to be updated.

# Optional Parameters
Optional parameters can be passed as a `params::Dict{String,<:Any}`. Valid keys are:
- `"DataDelayOffsetInMinutes"`:  A period of time (in minutes) by which inference on the
  data is delayed after the data starts. For instance, if you select an offset delay time of
  five minutes, inference will not begin on the data until the first data measurement after
  the five minute mark. For example, if five minutes is selected, the inference scheduler
  will wake up at the configured frequency with the additional five minute delay time to
  check the customer S3 bucket. The customer can upload data at the same frequency and they
  don't need to stop and restart the scheduler when uploading new data.
- `"DataInputConfiguration"`:  Specifies information for the input data for the inference
  scheduler, including delimiter, format, and dataset location.
- `"DataOutputConfiguration"`:  Specifies information for the output results from the
  inference scheduler, including the output S3 location.
- `"DataUploadFrequency"`: How often data is uploaded to the source S3 bucket for the input
  data. The value chosen is the length of time between data uploads. For instance, if you
  select 5 minutes, Amazon Lookout for Equipment will upload the real-time data to the source
  bucket once every 5 minutes. This frequency also determines how often Amazon Lookout for
  Equipment starts a scheduled inference on your data. In this example, it starts once every
  5 minutes.
- `"RoleArn"`:  The Amazon Resource Name (ARN) of a role with permission to access the data
  source for the inference scheduler.
"""
function update_inference_scheduler(
    InferenceSchedulerName; aws_config::AbstractAWSConfig=global_aws_config()
)
    return lookoutequipment(
        "UpdateInferenceScheduler",
        Dict{String,Any}("InferenceSchedulerName" => InferenceSchedulerName);
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end
function update_inference_scheduler(
    InferenceSchedulerName,
    params::AbstractDict{String};
    aws_config::AbstractAWSConfig=global_aws_config(),
)
    return lookoutequipment(
        "UpdateInferenceScheduler",
        Dict{String,Any}(
            mergewith(
                _merge,
                Dict{String,Any}("InferenceSchedulerName" => InferenceSchedulerName),
                params,
            ),
        );
        aws_config=aws_config,
        feature_set=SERVICE_FEATURE_SET,
    )
end


text:
function testQueueRequest()
    lava.createGraphics(lava.QueueRequest, "graphics")
    lava.createTransfer(lava.QueueRequest, "transfer")
    lava.createCompute(lava.QueueRequest, "compute")
    lava.createByFlags(lava.QueueRequest, "by_flags", vk.VkQueueFlags(vk.VK_QUEUE_SPARSE_BINDING_BIT), 1.0f0)
    lava.createByFamily(lava.QueueRequest, "by_family", 1, 1.0f0)
    return true
end


text:
#=
    Port of SPPL model fairness-income-model-2.ipynb

    The SPPL model gives the following exact probabilities:
    female_prior: 0.33066502427854466
    female_given_no_hire: 0.33412533774074804
    p_female_given_no hire / p_female: 1.046470962495416

    However, the range of age in the SPPL model is really strange:
    age has a mean of 178 years and the values of capital_gain is way too large: mean = 42765784.8959
    (the decision model checks for capital_gain in the range of 4000...9000).

    Here's the summary of these variables (from this Turing.jl model where adjust_std=false):
    Summary Statistics
            parameters            mean             std      naive_se          mcse         ess      rhat   ess_per_sec
                Symbol         Float64         Float64       Float64       Float64     Float64   Float64       Float64

          capital_gain   43225351.7302   39465143.9277   394651.4393   558730.7594   3705.9663    1.0007      318.3546
                   age        176.7377        115.8681        1.1587        1.9092   3924.8800    1.0005      337.1600

    Quantiles
            parameters           2.5%           25.0%           50.0%           75.0%            97.5%
                Symbol        Float64         Float64         Float64         Float64          Float64

          capital_gain   1283446.4592   13635591.8941   30705497.7817   62573297.6974   147598252.8748
                   age        25.3189         84.0627        155.9575        243.3078         450.6392

    I guess that second parameter to Normal in the SPPL model is the variance, but it should be std.
    After sqrt'ing all the second parameters of capital_gain and age, the quantiles are for:
    * age: between 19..65
    * capital gain: between about 500..21000

    When adjusted for this, the SPPL model shows these probabilities instead:
    female_prior: 0.33076164956248716
    female_given_no_hire: 0.21962121326454123
    p_female_given_no hire / p_female:  -33.60136716120391

    However, I'm not sure that this makes much more sense (the conclusion is opposite of the original).

    This Turing.jl model (for adjust_std = true):
        Summary Statistics
                    parameters        mean         std   naive_se      mcse         ess      rhat   ess_per_sec
                        Symbol     Float64     Float64    Float64   Float64     Float64   Float64       Float64

                        sex      1.7770      0.4163     0.0042    0.0079   2077.3376    0.9999      153.4336
               capital_gain   9779.7926   4291.9553    42.9196   90.0528   2065.8181    1.0002      152.5828
                        age     40.0510     11.9233     0.1192    0.2871   1684.2237    1.0000      124.3979
               relationship      2.0305      0.8788     0.0088    0.0203   1820.6243    0.9999      134.4726
                          t      0.0000      0.0000     0.0000    0.0000         NaN       NaN           NaN
                  sex_prior      1.6771      0.4676     0.0047    0.0062   6100.1244    1.0000      450.5594
           sex_female_prior      0.3229      0.4676     0.0047    0.0062   6100.1244    1.0000      450.5594
       sex_female_posterior      0.2230      0.4163     0.0042    0.0079   2077.3376    0.9999      153.4336

        Quantiles
                    parameters        2.5%       25.0%       50.0%        75.0%        97.5%
                        Symbol     Float64     Float64     Float64      Float64      Float64

                       sex      1.0000      2.0000      2.0000       2.0000       2.0000
              capital_gain   4998.2887   6494.5706   8685.1889   11816.3482   20384.3524
                       age     19.9492     30.9827     39.3438      48.0846      64.6989
              relationship      0.0000      2.0000      2.0000       3.0000       3.0000
                         t      0.0000      0.0000      0.0000       0.0000       0.0000
                 sex_prior      1.0000      1.0000      2.0000       2.0000       2.0000
          sex_female_prior      0.0000      0.0000      0.0000       1.0000       1.0000
      sex_female_posterior      0.0000      0.0000      0.0000       0.0000       1.0000

        Distributions of variable sex
        male       =>    7770  (0.777000)
        female     =>    2230  (0.223000)

        100*(sex_female_posterior) / sex_female_prior - 1): -30.93837101269743

=#

using Turing
include("jl_utils.jl")

# adjust_std == true: use sqrt'ed value of Normal
@model function fairness_income_model(adjust_std=true)
    female = 1
    male   = 2
    sex ~ Categorical([0.3307, 0.6693]) # [female, male]
    if sex == female
        if adjust_std
            capital_gain ~ Normal(568.4105, sqrt(24248365.5428))
        else
            capital_gain ~ Normal(568.4105, 24248365.5428)
        end
        if capital_gain < 7298.0000
            if adjust_std
                age ~ truncated(Normal(38.4208, sqrt(184.9151)),18.001,Inf)
            else
                age ~ truncated(Normal(38.4208, 184.9151),18.001,Inf)
            end
            relationship ~ DiscreteNonParametric([0,1,2,3,4,5],[0.0491, 0.1556, 0.4012,0.2589, 0.0294, 0.1058] )
        else
            if adjust_std
                age ~ truncated(Normal(38.8125, sqrt(193.4918)),18.001,Inf)
            else
                age ~ truncated(Normal(38.8125, 193.4918),18.001,Inf)
            end
            relationship ~ DiscreteNonParametric([0,1,2,3,4,5],[0.0416, 0.1667, 0.4583,0.2292, 0.0166, 0.0876])
        end
    else
        if adjust_std
            capital_gain ~ Normal(1329.3700, sqrt(69327473.1006))
        else
            capital_gain ~ Normal(1329.3700, 69327473.1006)
        end
        if capital_gain < 5178.0000
            if adjust_std
                age ~ truncated(Normal(38.6361, sqrt(187.2435)),18.001,Inf)
            else
                age ~ truncated(Normal(38.6361, 187.2435),18.001,Inf)
            end
            relationship ~ DiscreteNonParametric([0,1,2,3,4,5],[0.0497, 0.1545, 0.4021,0.2590, 0.0294, 0.1053])
        else
            if adjust_std
                age ~ truncated(Normal(38.2668, sqrt(187.2747)),18.001,Inf)
            else
                age ~ truncated(Normal(38.2668, 187.2747),18.001,Inf)
            end
            relationship ~ DiscreteNonParametric([0,1,2,3,4,5],[0.0417, 0.1624, 0.3976,0.2606, 0.0356, 0.1021])
        end
    end

    # true ~ Dirac(age > 18) # Used truncated(.) instead

    # Decision model.
    if relationship == 1
        if capital_gain < 5095.5
            t ~ Dirac(1)
        else
            t ~ Dirac(0)
        end
    elseif relationship == 2
        if capital_gain < 4718.5
            t ~ Dirac(1)
        else
            t ~ Dirac(0)
        end
    elseif relationship == 3
        if capital_gain < 5095.5
            t ~ Dirac(1)
        else
            t ~ Dirac(0)
        end
    elseif relationship == 4
        if capital_gain < 8296
            t ~ Dirac(1)
        else
            t ~ Dirac(0)
        end
    elseif relationship == 5
        t ~ Dirac(1)
    else
        if capital_gain < 4668.5
            t ~ Dirac(1)
        else
            t ~ Dirac(0)
        end
    end

    # Observation:
    true ~ Dirac(t == 0)

    # Prior and posterior
    sex_prior ~ Categorical([0.3307, 0.6693])
    sex_female_prior ~ Dirac(sex_prior == female)
    sex_female_posterior ~ Dirac(sex == female)

end

# adjust_std = false # Original version of the SPPL model
adjust_std = true # Adjust for Normal by sqrt'ing the scale parameter
model = fairness_income_model(adjust_std)

# chns = sample(model, Prior(), 10_000)
# chns = sample(model, MH(), 1_000)
chns = sample(model, PG(5), 10_000)
# chns = sample(model, SMC(10), 1_000)
# chns = sample(model, IS(), 1_000)

display(chns)

show_var_dist_pct(chns,:sex,["female","male"])

sex_female_posterior = mean(chns[:sex_female_posterior])
sex_female_prior = mean(chns[:sex_female_prior])
println("sex_female_prior:$sex_female_prior sex_female_posterior:$sex_female_posterior")
println("100*(sex_female_posterior) / sex_female_prior - 1): $(100*(sex_female_posterior / sex_female_prior - 1))")

# gender = group(chns,:is_male).value.data |> make_hash
# println("gender: $gender")

# println("gender: 1 female, 2: male:")
# chains_params = Turing.MCMCChains.get_sections(chns, :parameters)
# genq = generated_quantities(model, chains_params)
# show_var_dist_pct(genq)

# hhh = genq[:,1] |> make_hash
# hire_male = get(hhh,[1,1],0) / gender[1]
# hire_female = get(hhh,[0,1],0) / gender[0]
# println("hire | male: $hire_male")
# println("hire | female: $hire_female")
# println("female / male: $(hire_male > 0 ? hire_female / hire_male : 0)")

max_stars_repo_name: tias/hakank

max_issues_repo_name: tias/hakank

max_forks_repo_name: tias/hakank


text:
@testset "ASH Model" begin
    @testset "1-var" begin
        @test @timev !isnothing(one_var_test((n_vars, dist) -> ASHModel()))
    end
    @testset "2-var" begin
        @test_broken @timev !isnothing(multi_var_test((n_vars, dist) -> ASHModel()))
    end
    @testset "3-var" begin
        @test_broken @timev !isnothing(multi_var_test((n_vars, dist) -> ASHModel(), n_vars=3))
    end
end


text:
# Learn Polynomials

using Logging
logger = SimpleLogger(stdout, Logging.Info)
global_logger(logger)

import Polynomials
Poly = Polynomials

poly = Poly.Polynomial([1, 0, 3, 4])
using Plots
plotly()
xs = range(0, 10, length=10)
ys = @. exp(-xs)
f = Poly.fit(xs,ys) # degree = length(xs) - 1
f2 = Poly.fit(xs,ys, 2) # 2
f3 = Poly.fit(xs,ys, 1)

#pt = scatter(xs, ys, markerstrokewidth=0, label="Data")
pt = plot(xs, ys, seriestype=:scatter, markerstrokewidth=0)

plot!(pt, f, extrema(xs)..., label="Fit")
plot!(pt, f2, extrema(xs)..., label="Quadratic Fit")
plot!(pt, f3, extrema(xs)..., label="Linear Fit")

gui(pt)
nothing


text:
function Δ(model::LogReturnComputationModel; multiplier::Float64 = 1.0)::DataFrame

    # get stuff from the computational model -
    raw_data = model.data
    from = model.from
    to = model.to
    map = model.map

    # ok, so need to filter the data (if we have a from and to date set)
    data = raw_data
    if (isnothing(to) == false && isnothing(from) == false)

        # get the date key from the map -
        date_key = map.first

        # filter to the specified date range -
        data = filter(date_key => d -> (d >= from && d <= to), raw_data)
    end

    # initialize -
    (number_of_rows, _) = size(data)
    return_table = DataFrame(date = Date[], P₁ = Float64[], P₂ = Float64[], Δ = Float64[], Δ₍μ₎ = Float64[], Z = Float64[])

    # main loop -
    for row_index ∈ 2:number_of_rows

        # grab the date -
        tmp_date = data[row_index, map.first]

        # grab the price data -
        yesterday_close_price = data[row_index-1, map.second]
        today_close_price = data[row_index, map.second]

        # compute the diff -
        δ_value = multiplier * log(today_close_price / yesterday_close_price)

        # push! -
        push!(return_table, (tmp_date, yesterday_close_price, today_close_price, δ_value, 0.0, 0.0))
    end

    # compute the mean -
    μ = mean(return_table[!, :Δ])

    # compute the std -
    σ = std(return_table[!, :Δ])

    # add values to the δ - μ col -
    (number_of_rows, _) = size(return_table)
    for row_index ∈ 1:number_of_rows
        value = return_table[row_index, :Δ] - μ
        return_table[row_index, :Δ₍μ₎] = value
    end

    # add the Z column -
    for row_index ∈ 1:number_of_rows
        value = (1 / σ) * (return_table[row_index, :Δ] - μ)
        return_table[row_index, :Z] = value
    end

    # return -
    return return_table
end

function Δ(model::LinearReturnComputationModel; multiplier::Float64 = 1.0)::DataFrame

    # get stuff from the computational model -
    raw_data = model.data
    from = model.from
    to = model.to
    map = model.map

    # ok, so need to filter the data (if we have a from and to date set)
    data = raw_data
    if (isnothing(to) == false && isnothing(from) == false)

        # get the date key from the map -
        date_key = map.first

        # filter to the specified date range -
        data = filter(date_key => d -> (d >= from && d <= to), raw_data)
    end

    # initialize -
    (number_of_rows, _) = size(data)
    return_table = DataFrame(date = Date[], P₁ = Float64[], P₂ = Float64[], Δ = Float64[], Δ₍μ₎ = Float64[], Z = Float64[])

    # main loop -
    for row_index ∈ 2:number_of_rows

        # grab the date -
        tmp_date = data[row_index, map.first]

        # grab the price data -
        yesterday_close_price = data[row_index-1, map.second]
        today_close_price = data[row_index, map.second]

        # compute the diff -
        δ_value = multiplier * ((today_close_price - yesterday_close_price) / (yesterday_close_price))

        # push! -
        push!(return_table, (tmp_date, yesterday_close_price, today_close_price, δ_value, 0.0, 0.0))
    end

    # compute the mean -
    μ = mean(return_table[!, :Δ])

    # compute the std -
    σ = std(return_table[!, :Δ])

    # add values to the δ - μ col -
    (number_of_rows, _) = size(return_table)
    for row_index ∈ 1:number_of_rows
        value = return_table[row_index, :Δ] - μ
        return_table[row_index, :Δ₍μ₎] = value
    end

    # add the Z column -
    for row_index ∈ 1:number_of_rows
        value = (1 / σ) * (return_table[row_index, :Δ] - μ)
        return_table[row_index, :Z] = value
    end

    # return -
    return return_table
end

function Δ(models::Array{T,1};
    multiplier::Float64 = 1.0)::Dict{String,DataFrame} where {T<:AbstractBaseCampComputation}

    # initialize -
    Δ_dictionary = Dict{String,DataFrame}()

    # compute the returns for each model in the array -
    for model ∈ models
        ticker = model.ticker
        Δ_dictionary[ticker] = Δ(model; multiplier = multiplier)
    end

    # return -
    return Δ_dictionary
end

function 𝒟(distribution::Type{T}, ticker_symbol_array::Array{String,1}, data::Dict{String, DataFrame};
    colkey::Symbol = :Δ)::ContinuousMultivariateDistribution where {T <: ContinuousMultivariateDistribution}

    # how many keys and rows do we have?
    number_of_ticker_symbols = length(ticker_symbol_array)
    number_of_rows = length(data[first(ticker_symbol_array)][!,colkey])

    # initialize -
    tmp_array = Array{Float64,2}(undef, number_of_rows, number_of_ticker_symbols)

    # build the data array -
    for (ticker_index, ticker_symbol) ∈ enumerate(ticker_symbol_array)

        # grab the data for this ticker -
        tmp_data_col = data[ticker_symbol][!,colkey]

        # copy into the tmp array -
        for row_index ∈ 1:number_of_rows
            tmp_array[row_index, ticker_index] = tmp_data_col[row_index]
        end
    end

    # fit the distribution -
    return fit(distribution, transpose(tmp_array))
end

function 𝒟(distribution::Type{T}, data::DataFrame;
    colkey::Symbol = :Δ)::UnivariateDistribution where {T<:ContinuousUnivariateDistribution}

    # get the array of data from the data frame -
    data_array = data[!, colkey]

    # do the fit -
    return fit(distribution, data_array)
end

function 𝒟(distribution::Type{T}, data::DataFrame, weights::Union{Nothing,Array{Float64,1}};
    colkey::Symbol = :Δ)::UnivariateDistribution where {T<:ContinuousUnivariateDistribution}

    # get the array of data from the data frame -
    data_array = data[!, colkey]

    # check: do we have a weight array?
    if (isnothing(weights) == false)

        # do the fit w/weights -
        return fit(distribution, data_array, weights)
    else
         # do the fit w/o weights -
        return fit(distribution, data_array)
    end
end

function 𝒟(distribution::Type{T}, data::Dict{String, DataFrame};
    colkey::Symbol = :Δ, weights::Union{Nothing, Dict{String, Union{Nothing,Array{Float64,1}}}} = nothing)::Dict{String, T} where {T<:ContinuousUnivariateDistribution}

    # initialize -
    distribution_dictionary = Dict{String, T}()

    # call the single 𝒟 -
    for (key,value) ∈ data

        # get the array of data from the data frame -
        data_array = value[!, colkey]

        # check: do we have weights?
        d = nothing
        if (isnothing(weights) == false &&
            isnothing(weights[key]) == false)

            # we have a weights -
            w = weights[key]

            # fit -
            d = fit(distribution, data_array, w);
        else
            # fit a distribution -
            d = fit(distribution, data_array);
        end

        # capture -
        distribution_dictionary[key] = d;
    end

    # return data -
    return distribution_dictionary
end

function 𝒫(compare::Function, samples::Array{Float64})::Float64

    # initialize -
    number_of_samples = length(samples)
    tmp_array = BitArray(undef, (number_of_samples, 1))

    # main -
    for sample_index ∈ 1:number_of_samples

        # get the sample price -
        sample_price = samples[sample_index]

        # check: which is larger, sample or target price?
        compare(sample_price) ? tmp_array[sample_index] = 1 : tmp_array[sample_index] = 0
    end

    # sum the tmp_array -
    number_of_larger_values = sum(tmp_array)

    # compute the probability -
    return (number_of_larger_values / number_of_samples)
end

function covariance(tickers::Array{String,1}, data::Dict{String,DataFrame};
    key::Symbol = :Δ)::Array{Float64,2}

    # build a return matrix -
    number_of_tickers = length(tickers)

    # get the first data table so we can get the number of rows -
    (number_of_rows, _) = size(data[first(tickers)])

    # initialize -
    price_return_array = Array{Float64,2}(undef, number_of_rows, number_of_tickers)

    # populate the price return array -
    for col_index ∈ 1:number_of_tickers

        # get the ticker -
        ticker_symbol = tickers[col_index]

        # get data -
        df = data[ticker_symbol]

        for row_index ∈ 1:number_of_rows
            price_return_array[row_index, col_index] = df[row_index, key]
        end
    end

    # compute the cov array -
    return Statistics.cov(price_return_array)
end

function β(tickers::Array{String,1}, data::Dict{String,DataFrame};
    key::Symbol = :Δ, base::String = "SPY")::Array{Float64,1}

    # initialize -
    number_of_tickers = length(tickers)
    β_array = Array{Float64,1}(undef, number_of_tickers)

    # get the return data for the base -
    base_return_array = data[base][!,key]
    var_base = var(base_return_array)

    # compute β -
    for ticker_index ∈ 1:number_of_tickers

        # what ticker?
        ticker_value = tickers[ticker_index]
        return_array_ticker = data[ticker_value][!,key]

        # compute the β_value -
        β_value = cov(return_array_ticker, base_return_array) * (1 / var_base)

        # capture -
        β_array[ticker_index] = β_value
    end

    # return -
    return β_array
end

function sample(model::T, number_of_steps::Int64;
    number_of_sample_paths = 100)::Array{Array{Float64,2},1} where {T<:ContinuousMultivariateDistribution}

    # initialize -
    number_of_steps = number_of_steps + 1
    number_of_dimensions = length(model)
    sample_return_data = Array{Array{Float64,2},1}(undef, number_of_steps)

    for time_step_index ∈ 1:number_of_steps

        # generate a random block -
        B = rand(model, number_of_sample_paths)

        # capture -
        sample_return_data[time_step_index] = B
    end

    # return -
    return sample_return_data
end

function sample(model::T, number_of_steps::Int64;
    number_of_sample_paths = 100, number_of_strata = 1)::Array{Float64,2} where {T<:ContinuousUnivariateDistribution}

    # initialize -
    number_of_steps = number_of_steps + 1
    sample_return_data = Array{Array{Float64,1},1}(undef, number_of_steps)

	# Let's use stratefied sampling to generate the return samples -
    for time_step_index ∈ 1:number_of_steps

		tmp_vector = Array{Float64,1}()

        # sample the strata ...
        for strata_index ∈ 1:number_of_strata

            # compute a number_of_sample_paths draws from tis strata?
            for _ ∈ 1:number_of_sample_paths

                # role a random number -
                r = rand()

                # compute V -
                V₁ = (strata_index - 1)/number_of_strata + r/number_of_strata
                V₂ = (strata_index - 1)/number_of_strata + (1 - r)/number_of_strata

                # compute the quantile for this V -
                q₁ = quantile(model, V₁)
                q₂ = quantile(model, V₂)

                # grab this value -
                push!(tmp_vector, q₁)
                push!(tmp_vector, q₂)
            end
        end

        sample_return_data[time_step_index] = tmp_vector
    end

    # crunch the data together -
    sample_return_array = transpose(hcat(sample_return_data...))

    # return -
    return sample_return_array
end

function sample(models::Dict{String,T}, number_of_steps::Int64;
    number_of_sample_paths = 100, number_of_strata = 1)::Dict{String,Array{Float64,2}} where {T<:ContinuousUnivariateDistribution}

    # initialize -
    sample_dictionary = Dict{String,Array{Float64,2}}()

    # compute -
    for (ticker_symbol, model) ∈ models

        # sample this model -
        tmp_array = sample(model, number_of_steps;
            number_of_sample_paths = number_of_sample_paths, number_of_strata = number_of_strata)

        # store the samples -
        sample_dictionary[ticker_symbol] = tmp_array
    end

    # return -
    return sample_dictionary
end


text:
module DRAGO

using LinearAlgebra
using Plots
using Distributions
using BandedMatrices

include("utility.jl")

export drago, simulate_data, find_gaps, get_present

"""
    drago(y, λ1, λ2, S = I; ΔFmin = 0.01, maxiter = 10, verbose = false)

Nonlinear Smoothing of Data with Random Gaps and Outliers (DRAGO). A detailed description of the algorithm can be found in [1]

[1] Parekh, A., et al. "Nonlinear Smoothing of Core Body Temperature Data with Random Gaps and Outliers (DRAGO)." Biomedical Signal Processing. Springer, Cham, 2021. 63-84.
# Arguments

* y:    Signal with gaps and outlier
* λ1:   Influences the sparsity of the estimated outliers `x`. As a reasonable heuristic is λ1 = 3σ, where σ is the standard deviation of the noise.
* λ2:   Influences the smoothness of the estimate `f`
* S:    A matrix encoding gaps defaulting to `I` if none are present. Otherwise it can be created by starting with `I` and deleting all rows corresponding to a gap.

# Keyword Arguments

* ΔFmin:    The minimal change in the objective function F that defines convergence.
* maxiter:  Maximum number of iterations
* verbose:  Print some information

# Returns

* fRes:     The smoothed signal
* xRes:     The estimated outliers
* residual: The residual signal y - (f + x)
"""
function drago(y, λ1, λ2, S = I(length(y)); ΔFmin = 0.01, maxiter = 10, verbose = false)
    # Initialize
    N = length(y)
    xRes = S * y
    present = [1:N;][diag(S' * S) .== 1]
    Dn = Tridiagonal(ones(N-1), -2ones(N), ones(N-1))
    Dn = Dn[2:end-1, 1:end] # make it N-2 x N
    λ2DDn = λ2 .* Dn' * Dn

    # Objective function
    F(f, x) = 0.5norm2(S * y .- S * f .- x)^2 + 0.5λ1 * norm1(x) + .5λ2 *  norm2(Dn * f)^2

    # Iterate
    iter = 1
    fRes = similar(y)
    Fold = ΔF = Inf
    while ΔF > ΔFmin && iter <= maxiter
        A = Diagonal(λ1 ./ (abs.(xRes) .+ λ1))
        # B = Matrix(S' * A * S .+ λ2 .* Dn' * Dn) # faster for low N but more Memory gets allocated
        B = BandedMatrix(S' * A * S .+ λ2DDn) # less Memory but apparently slower
        fRes = inv(B) * S' * A * S * y
        xRes = abs.(xRes) .* (S * (y .- fRes)) ./ (abs.(xRes) .+ λ1)
        Fnow = F(fRes, xRes)
        verbose && @info "Iteration: $iter with F = $Fnow"
        ΔF = Fold .- Fnow
        Fold = Fnow
        iter += 1
    end
    residual = y .- (S' * xRes .+ fRes)

    return fRes, xRes, residual
end
end #module


text:
using LinearAlgebra, FFTW
import BSON, Statistics, PyPlot, Random

import NMRSpectraSimulator

include("../src/NMRCalibrate.jl")
import .NMRCalibrate

# for loading something with Interpolations.jl
import OffsetArrays
import Interpolations

import MultistartOptimization, NLopt

#import Clustering

PyPlot.close("all")
fig_num = 1

Random.seed!(25)
PyPlot.matplotlib["rcParams"][:update](["font.size" => 22, "font.family" => "serif"])

# load_path = "/home/roy/MEGAsync/inputs/NMR/debug/test_As.bson"
# dict = BSON.load(load_path)
# As = collect( dict[:As][i] for i = 1:length(dict[:As]) )

projects_dir = "/home/roy/MEGAsync/outputs/NMR/calibrate/final/test_glucose1/"
#projects_dir = "/home/roy/MEGAsync/outputs/NMR/calibrate/final/D-(+)-Glucose-700-r3/"
r = 3

T = Float64

### load block.
#load_path = joinpath(joinpath(projects_dir, project_name), "results_full.bson")
load_path = joinpath(projects_dir, "results_$(r).bson")
dict = BSON.load(load_path)

Es = collect( dict[:Es][i] for i = 1:length(dict[:Es]) )
As = collect( Es[n].core for n = 1:length(Es))

Δsys_cs = convert(Vector{Vector{Float64}}, dict[:Δsys_cs])
y = convert(Vector{Complex{Float64}}, dict[:y])
U_y = convert(Vector{Float64}, dict[:U_y])
SW = dict[:SW]
fs = dict[:fs]
ν_0ppm = dict[:ν_0ppm]

hz2ppmfunc = uu->(uu - ν_0ppm)*SW/fs
ppm2hzfunc = pp->(ν_0ppm + pp*fs/SW)
P_y = hz2ppmfunc.(U_y)

ΩS_ppm = NMRSpectraSimulator.getPsnospininfo(As, hz2ppmfunc)
ΩS_ppm_sorted = sort(NMRSpectraSimulator.combinevectors(ΩS_ppm))
u_offset = 0.5
u_min = ppm2hzfunc(ΩS_ppm_sorted[1] - u_offset)
u_max = ppm2hzfunc(ΩS_ppm_sorted[end] + u_offset)

P = LinRange(hz2ppmfunc(u_min), hz2ppmfunc(u_max), 50000)
U = ppm2hzfunc.(P)
U_rad = U .* (2*π)

p_star = dict[:p_star]
κ_lb_default = dict[:κ_lb_default]
κ_ub_default = dict[:κ_ub_default]
κ_star = dict[:κ_star]
d_star = dict[:d_star]
β_star = dict[:β_star]
λ_star = dict[:λ_star]
cost_inds_set = dict[:cost_inds_set]
w = dict[:w]

### end load block.

Δ_shifts = NMRSpectraSimulator.combinevectors(Δsys_cs)

y_cost = y[cost_inds_set[r]]
U_cost = U_y[cost_inds_set[r]]
P_cost = P_y[cost_inds_set[r]]

U_rad_cost = U_cost .* (2*π)

LS_inds = 1:length(U_cost)
# need to speed up eval design matrix.
N_κ, N_κ_singlets = NMRCalibrate.countκ(Es)

q, updatedfunc, updateβfunc, updateλfunc, updateκfunc,
E_BLS, κ_BLS, b_BLS, getshiftfunc, getβfunc, getλfunc,
N_vars_set = NMRCalibrate.setupcostcLshiftLS(Es, As, fs, SW,
LS_inds, U_rad_cost, y_cost, Δ_shifts;
w = w, κ_lb_default = κ_lb_default, κ_ub_default = κ_ub_default)

obj_func = pp->NMRCalibrate.costcLshift(U_rad_cost, y_cost,
updatedfunc, updateβfunc, updateλfunc, updateκfunc, pp, Es,
E_BLS, κ_BLS, b_BLS, q)

last_version_cost = obj_func(p_star)
q_star_U = q.(U_rad)

using BenchmarkTools

# ### BLS timings.
# println("Timing: evaldesignmatrixκ!")
# Q = zeros(Complex{Float64}, length(U_cost), N_κ + N_κ_singlets)
# @btime NMRCalibrate.evaldesignmatrixκ!(Q, U_rad_cost, Es, w);
# # Q = zeros(Float64, 2*length(U_cost), N_κ + N_κ_singlets)
# # @btime NMRCalibrate.evaldesignmatrixκ!(Q, U_rad_cost, Es, w);
#
# # println("Timing: evaldesignmatrixκ!")
# # C = zeros(Float64, 2*length(U_cost), N_κ + N_κ_singlets)
# # @btime NMRCalibrate.evaldesignmatrixκ!(C, U_rad_cost, Es, w);
# #
# # println("descrepancy: ", norm([real.(Q); imag.(Q)] - C))
# # println()
#
#
# println("q.(U_rad_cost)")
# @btime q.(U_rad_cost);
#
# println("obj_func.(p_star)")
# @btime obj_func(p_star);
#
# obj_func(p_star)
#
# q_U = q.(U_rad_cost)
#
# b_BLS = b = collect(reinterpret(Float64, y_cost))
#
# @btime B = reinterpret(Float64, Q);
# B = reinterpret(Float64, Q)
# @btime c = B*κ_BLS - b_BLS;
# c = B*κ_BLS - b_BLS
#
# norm(q.(U_rad_cost) - y_cost)^2 - obj_func(p_star)
#
# L = 50
# a = randn(Complex{Float64}, L)
# b = randn(Complex{Float64}, L)
#
# sum( abs2(a[i]-b[i]) for i in eachindex(a))
#
# sum( real(a[i]-b[i])^2 + imag(a[i]-b[i])^2 for i in eachindex(a) )

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

### reference.

# reference, zero shift, phase.
N_d = sum( NMRCalibrate.getNd(As[n]) for n = 1:length(As) )
#N_β = sum( NMRCalibrate.getNβ(As[n]) for n = 1:length(As) )
N_λ = sum( NMRCalibrate.getNλ(As[n]) for n = 1:length(As) )
#shift_manual = zeros(T, N_d)
#β_manual = zeros(T, N_β)
#λ_manual = ones(T, N_λ)

#shift_manual = [-0.08; -0.1]
shift_manual = [-0.11; -0.11]
β_manual = copy(β_star)
λ_manual = copy(λ_star)

p_manual = [shift_manual; β_manual; λ_manual]

manual_cost = obj_func(p_manual)
#NMRCalibrate.parseκ!(Es, ones(T, length(κ_BLS)))
fill!(w, 1.0)
q_manual_U = q.(U_rad)
println("norm(q_manual_U) = ", norm(q_manual_U))

final_cost = obj_func(p_star)
q_star_U = q.(U_rad)

PyPlot.figure(fig_num)
fig_num += 1

PyPlot.plot(P, real.(q_manual_U), label = "q manual")
PyPlot.plot(P_y, real.(y), label = "y")
PyPlot.plot(P, real.(q_star_U), "--", label = "q star")
#PyPlot.plot(P_cost, real.(y_cost), "x")

PyPlot.legend()
PyPlot.xlabel("ppm")
PyPlot.ylabel("real")
PyPlot.title("r = $(r). data vs. manual")

### z.
Gs = collect( NMRSpectraSimulator.zCompoundFIDType(As[i]) for i = 1:length(As) )

g, updateβfunc5, updatezfunc5,
E_LS5, z_LS, b_LS5,
getβfunc5 = NMRCalibrate.setupcostβLS(Gs, As, LS_inds, U_rad_cost, y_cost)

println("Timing: updatezfunc() and parsez!()")
@time updatezfunc5(0.0)
@time NMRCalibrate.parsez!(Gs, z_LS)

g_star_U = g.(U_rad)

PyPlot.figure(fig_num)
fig_num += 1

PyPlot.plot(P, real.(q_manual_U), label = "q manual")
PyPlot.plot(P_y, real.(y), label = "y")
PyPlot.plot(P, real.(g_star_U), "--", label = "g star")

PyPlot.legend()
PyPlot.xlabel("ppm")
PyPlot.ylabel("real")
PyPlot.title("r = $(r). LS z vs data")

### fit β.

#@assert 1==2
#β_initial = ones(length(getβfunc(p_star)))
β_initial = copy(getβfunc(p_star))

# optim_algorithm = :LN_BOBYQA # good.
# β0 = copy(β_initial)

# optim_algorithm = :GN_ESCH
# β0 = copy(β_initial) # ok
#
# optim_algorithm = :GN_ESCH
# β0 = copy(β_initial)

# optim_algorithm = :GN_ISRES
# β0 = copy(β_initial) # not good.

optim_algorithm = :GN_DIRECT_L
β0 = copy(β_initial) # good.

# ## packaged up.
# run_optim, obj_func3, E_BLS3, κ_BLS3, b_BLS3, updateβfunc3,
# q3 = NMRCalibrate.setupβLSsolver(optim_algorithm,
#     Es, As, LS_inds, U_rad_cost, y_cost;
#     κ_lb_default = 0.2,
#     κ_ub_default = 5.0,
#     max_iters = 500,
#     xtol_rel = 1e-9,
#     ftol_rel = 1e-9,
#     maxtime = Inf);

optim_algorithm = NLopt.LN_BOBYQA
#optim_algorithm = NLopt.GN_ESCH
run_optim, obj_func3, E_BLS3, κ_BLS3, b_BLS3, updateβfunc3,
q3 = NMRCalibrate.setupβLSsolverMultistartoptim(optim_algorithm,
    Es, As, LS_inds, U_rad_cost, y_cost;
    κ_lb_default = 0.2,
    κ_ub_default = 5.0,
    max_iters = 50,
    xtol_rel = 1e-9,
    ftol_rel = 1e-9,
    maxtime = Inf,
    N_starts = 10,
    inner_xtol_rel = 1e-12,
    inner_maxeval = 1000,
    inner_maxtime = Inf);

println("obj_func3(β0) = ", obj_func3(β0))

updatedfunc(zeros(length(p_manual)))
updatedfunc(p_manual)
updateλfunc(p_manual)

# @btime run_optim(β0);
# @assert 5==3

println("Timing: run_optim")
p_β = copy(β0)

minf = NaN
minx = []
ret = :NaN
N_evals = -1
if typeof(optim_algorithm) == Symbol
    # using NLopt.
    @time minf, minx, ret, N_evals = run_optim(p_β)
else
    @time p = run_optim(p_β)

    minf = p.value
    minx = p.location
    ret = p.ret
end
println()

println("obj_func3(minx) = ", obj_func3(minx))
minx3 = copy(minx)
q3_star_U = q3.(U_rad)

PyPlot.figure(fig_num)
fig_num += 1

PyPlot.plot(P_y, real.(y), label = "y")
#PyPlot.plot(P, real.(q_manual_U), label = "q manual")
PyPlot.plot(P, real.(q3_star_U), "--", label = "run_optim")

PyPlot.legend()
PyPlot.xlabel("ppm")
PyPlot.ylabel("real")
PyPlot.title("r = $(r). refined vs data")

PyPlot.figure(fig_num)
fig_num += 1

PyPlot.plot(P_y, real.(y), label = "y")
PyPlot.plot(P, real.(g_star_U), "--", label = "z LS")
PyPlot.plot(P, real.(q3_star_U), "--", label = "run_optim")

PyPlot.legend()
PyPlot.xlabel("ppm")
PyPlot.ylabel("real")
PyPlot.title("r = $(r). refined vs LS z")

st_ind_d = 1
fin_ind_d = st_ind_d + N_d - 1

st_ind_β = fin_ind_d + 1
fin_ind_β = st_ind_β + N_β - 1

p3 = copy(p_manual)
p3[st_ind_β:fin_ind_β] = minx
refined_manual_cost = obj_func(p3)

println("last_version_cost   = ", last_version_cost)
println("refined_manual_cost = ", refined_manual_cost)

PyPlot.figure(fig_num)
fig_num += 1

#PyPlot.plot(P, real.(q_manual_U), label = "q manual")
PyPlot.plot(P_y, real.(y), label = "data")
PyPlot.plot(P, real.(q_star_U), "--", label = "last version fit")
PyPlot.plot(P, real.(q3_star_U), "--", label = "refined manual")

PyPlot.legend()
PyPlot.xlabel("ppm")
PyPlot.ylabel("real")
PyPlot.title("r = $(r). refined vs last version (faulty) fit")

### unused.
# ### packaged up.
# run_optim, obj_func3, E_BLS, κ_BLS, b_BLS, updateβfunc,
# q3 = NMRCalibrate.setupβLSsolver(optim_algorithm,
#     Es, As, LS_inds, U_rad_cost, y_cost;
#     κ_lb_default = 0.2,
#     κ_ub_default = 5.0,
#     max_iters = 50,
#     xtol_rel = 1e-9,
#     ftol_rel = 1e-9,
#     maxtime = Inf);
#
# println("Timing: run_optim")
# p_β = copy(β0)
# @time minf, minx, ret, N_evals = run_optim(p_β)
# println()
#
#
# #@assert 1==44
#
# # force eval to update q2.
# println("f(minx) = ", f(minx))
# minx1 = copy(minx)
# q3_star_U = q3.(U_rad)


text:
struct DiscreteModelMock <: DiscreteModel{2,2}
  grid::GridMock
  topo::GridTopologyMock
  labels::FaceLabeling
  function DiscreteModelMock()
    grid = GridMock()
    topo = GridTopologyMock()
    labels = _init_labeling(topo)
    new(grid,topo,labels)
  end
end

function _init_labeling(topo)
  d_to_num_dfaces = [ num_faces(topo,d) for d in 0:num_dims(topo)]
  labels = FaceLabeling(d_to_num_dfaces)
  get_face_entity(labels,0) .= get_isboundary_face(topo,0) .+ 1
  get_face_entity(labels,1) .= get_isboundary_face(topo,1) .+ 1
  get_face_entity(labels,2) .= get_isboundary_face(topo,2) .+ 1
  add_tag!(labels,"interior",[1,])
  add_tag!(labels,"boundary",[2,])
  add_tag_from_tags!(labels,"all",["interior","boundary"])
  labels
end

get_grid(model::DiscreteModelMock) = model.grid

get_grid_topology(model::DiscreteModelMock) = model.topo

get_face_labeling(model::DiscreteModelMock) = model.labels


text:
module HelloWorld
    export hello
    function hello(n)
        println()
        return n
    end
end


text:
#=
TODO: This partial derivative extraction should be doable without the extra
temporaries utilized here, but AFAICT such an approach is hard to write
without relying on inference hacks unless we have something akin to
https://github.com/JuliaLang/julia/issues/22129.
=#
function _cast_diff(f, x)
    function element_rule(u)
        fu, du = frule(f, u)
        fu, extern(du(NamedTuple(), One()))
    end
    results = broadcast(element_rule, x)
    return first.(results), last.(results)
end

function frule(::typeof(broadcast), f, x)
    Ω, ∂x = _cast_diff(f, x)
    function broadcast_pushforward(_, Δf, Δx)
        return Δx * cast(∂x)
    end
    return Ω, broadcast_pushforward
end

function rrule(::typeof(broadcast), f, x)
    values, derivs = _cast_diff(f, x)
    function broadcast_pullback(ΔΩ)
        return (NO_FIELDS, DNE(), @thunk(ΔΩ * cast(derivs)))
    end
    return values, broadcast_pullback
end


text: include("mnist.jl")

max_stars_repo_name: HamletWantToCode/Generative

max_issues_repo_name: HamletWantToCode/Generative

max_forks_repo_name: HamletWantToCode/Generative