text:
# In this file, we setup the `gen_download_cmd()`, `gen_unpack_cmd()` and
# `gen_package_cmd()` functions by providing methods to probe the environment
# and determine the most appropriate platform binaries to call.

export gen_download_cmd, gen_unpack_cmd, gen_package_cmd, gen_list_tarball_cmd,
       parse_tarball_listing, gen_sh_cmd, parse_7z_list, parse_tar_list,
       download_verify_unpack, download_verify, unpack

"""
    gen_download_cmd(url::AbstractString, out_path::AbstractString)

Return a `Cmd` that will download resource located at `url` and store it at
the location given by `out_path`.

This method is initialized by `probe_platform_engines()`, which should be
automatically called upon first import of `BinaryProvider`.
"""
gen_download_cmd = (url::AbstractString, out_path::AbstractString) ->
    error("Call `probe_platform_engines()` before `gen_download_cmd()`")

"""
    gen_unpack_cmd(tarball_path::AbstractString, out_path::AbstractString; excludelist::Union{AbstractString, Nothing} = nothing)

Return a `Cmd` that will unpack the given `tarball_path` into the given
`out_path`.  If `out_path` is not already a directory, it will be created.
excludlist is an optional file which contains a list of files that is not unpacked
This option is mainyl used to exclude symlinks from extraction (see: `copyderef`)

This method is initialized by `probe_platform_engines()`, which should be
automatically called upon first import of `BinaryProvider`.
"""
gen_unpack_cmd = (tarball_path::AbstractString, out_path::AbstractString; excludelist::Union{AbstractString, Nothing} = nothing) ->
    error("Call `probe_platform_engines()` before `gen_unpack_cmd()`")

"""
    gen_package_cmd(in_path::AbstractString, tarball_path::AbstractString)

Return a `Cmd` that will package up the given `in_path` directory into a
tarball located at `tarball_path`.

This method is initialized by `probe_platform_engines()`, which should be
automatically called upon first import of `BinaryProvider`.
"""
gen_package_cmd = (in_path::AbstractString, tarball_path::AbstractString) ->
    error("Call `probe_platform_engines()` before `gen_package_cmd()`")

"""
    gen_list_tarball_cmd(tarball_path::AbstractString)

Return a `Cmd` that will list the files contained within the tarball located at
`tarball_path`.  The list will not include directories contained within the
tarball.

This method is initialized by `probe_platform_engines()`, which should be
automatically called upon first import of `BinaryProvider`.
"""
gen_list_tarball_cmd = (tarball_path::AbstractString) ->
    error("Call `probe_platform_engines()` before `gen_list_tarball_cmd()`")

"""
    parse_tarball_listing(output::AbstractString)

Parses the result of `gen_list_tarball_cmd()` into something useful.

This method is initialized by `probe_platform_engines()`, which should be
automatically called upon first import of `BinaryProvider`.
"""
parse_tarball_listing = (output::AbstractString) ->
    error("Call `probe_platform_engines()` before `parse_tarball_listing()`")

"""
    gen_sh_cmd(cmd::Cmd)

Runs a command using `sh`.  On Unices, this will default to the first `sh`
found on the `PATH`, however on Windows if that is not found it will fall back
to the `sh` provided by the `busybox.exe` shipped with Julia.

This method is initialized by `probe_platform_engines()`, which should be
automatically called upon first import of `BinaryProvider`.
"""
gen_sh_cmd = (cmd::Cmd) ->
    error("Call `probe_platform_engines()` before `gen_sh_cmd()`")

"""
    probe_cmd(cmd::Cmd; verbose::Bool = false)

Returns `true` if the given command executes successfully, `false` otherwise.
"""
function probe_cmd(cmd::Cmd; verbose::Bool = false)
    if verbose
        @info("Probing $(cmd.exec[1]) as a possibility...")
    end
    try
        success(cmd)
        if verbose
            @info("  Probe successful for $(cmd.exec[1])")
        end
        return true
    catch
        return false
    end
end

"""
    probe_symlink_creation(dest::AbstractString)

Probes whether we can create a symlink within the given destination directory,
to determine whether a particular filesystem is "symlink-unfriendly".
"""
function probe_symlink_creation(dest::AbstractString)
    while !isdir(dest)
        dest = dirname(dest)
    end

    # Build arbitrary (non-existent) file path name
    link_path = joinpath(dest, "binaryprovider_symlink_test")
    while ispath(link_path)
        link_path *= "1"
    end

    loglevel = Logging.min_enabled_level(current_logger())
    try
        disable_logging(Logging.Warn)
        symlink("foo", link_path)
        return true
    catch e
        if isa(e, Base.IOError)
            return false
        end
        rethrow(e)
    finally
        disable_logging(loglevel-1)
        rm(link_path; force=true)
    end
end

"""
    probe_platform_engines!(;verbose::Bool = false)

Searches the environment for various tools needed to download, unpack, and
package up binaries.  Searches for a download engine to be used by
`gen_download_cmd()` and a compression engine to be used by `gen_unpack_cmd()`,
`gen_package_cmd()`, `gen_list_tarball_cmd()` and `parse_tarball_listing()`, as
well as a `sh` execution engine for `gen_sh_cmd()`.  Running this function
will set the global functions to their appropriate implementations given the
environment this package is running on.

This probing function will automatically search for download engines using a
particular ordering; if you wish to override this ordering and use one over all
others, set the `BINARYPROVIDER_DOWNLOAD_ENGINE` environment variable to its
name, and it will be the only engine searched for. For example, put:

    ENV["BINARYPROVIDER_DOWNLOAD_ENGINE"] = "fetch"

within your `~/.juliarc.jl` file to force `fetch` to be used over `curl`.  If
the given override does not match any of the download engines known to this
function, a warning will be printed and the typical ordering will be performed.

Similarly, if you wish to override the compression engine used, set the
`BINARYPROVIDER_COMPRESSION_ENGINE` environment variable to its name (e.g. `7z`
or `tar`) and it will be the only engine searched for.  If the given override
does not match any of the compression engines known to this function, a warning
will be printed and the typical searching will be performed.

If `verbose` is `true`, print out the various engines as they are searched.
"""
probe_platform_engines!(;verbose::Bool = false) = nothing

"""
    parse_7z_list(output::AbstractString)

Given the output of `7z l`, parse out the listed filenames.  This funciton used
by  `list_tarball_files`.
"""
function parse_7z_list(output::AbstractString)
    lines = [chomp(l) for l in split(output, "\n")]

    # If we didn't get anything, complain immediately
    if isempty(lines)
        return []
    end

    # Remove extraneous "\r" for windows platforms
    for idx in 1:length(lines)
        if endswith(lines[idx], '\r')
            lines[idx] = lines[idx][1:end-1]
        end
    end

    # Find index of " Name".  Have to `collect()` as `findfirst()` doesn't work with
    # generators: https://github.com/JuliaLang/julia/issues/16884
    header_row = findfirst(collect(occursin(" Name", l) && occursin(" Attr", l) for l in lines))
    name_idx = findfirst("Name", lines[header_row])[1]
    attr_idx = findfirst("Attr", lines[header_row])[1] - 1

    # Filter out only the names of files, ignoring directories
    lines = [l[name_idx:end] for l in lines if length(l) > name_idx && l[attr_idx] != 'D']
    if isempty(lines)
        return []
    end

    # Extract within the bounding lines of ------------
    bounds = [i for i in 1:length(lines) if all([c for c in lines[i]] .== Ref('-'))]
    lines = lines[bounds[1]+1:bounds[2]-1]

    # Eliminate `./` prefix, if it exists
    for idx in 1:length(lines)
        if startswith(lines[idx], "./") || startswith(lines[idx], ".\\")
            lines[idx] = lines[idx][3:end]
        end
    end

    return lines
end

"""
    parse_tar_list(output::AbstractString)

Given the output of `tar -t`, parse out the listed filenames.  This funciton
used by `list_tarball_files`.
"""
function parse_tar_list(output::AbstractString)
    lines = [chomp(l) for l in split(output, "\n")]
    for idx in 1:length(lines)
        if endswith(lines[idx], '\r')
            lines[idx] = lines[idx][1:end-1]
        end
    end

    # Drop empty lines and and directories
    lines = [l for l in lines if !isempty(l) && !endswith(l, '/')]

    # Eliminate `./` prefix, if it exists
    for idx in 1:length(lines)
        if startswith(lines[idx], "./") || startswith(lines[idx], ".\\")
            lines[idx] = lines[idx][3:end]
        end
    end

    # make sure paths are always returned in the system's default way
    return Sys.iswindows() ? replace.(lines, ['/' => '\\']) : lines
end

"""
    download(url::AbstractString, dest::AbstractString;
             verbose::Bool = false)

Download file located at `url`, store it at `dest`, continuing if `dest`
already exists and the server and download engine support it.
"""
function download(url::AbstractString, dest::AbstractString;
                  verbose::Bool = false)
    download_cmd = gen_download_cmd(url, dest)
    if verbose
        @info("Downloading $(url) to $(dest)...")
    end
    oc = OutputCollector(download_cmd; verbose=verbose)
    try
        if !wait(oc)
            error()
        end
    catch e
        if isa(e, InterruptException)
            rethrow()
        end
        error("Could not download $(url) to $(dest):\n$(e)")
    end
end

"""
    download_verify(url::AbstractString, hash::AbstractString,
                    dest::AbstractString; verbose::Bool = false,
                    force::Bool = false, quiet_download::Bool = false)

Download file located at `url`, verify it matches the given `hash`, and throw
an error if anything goes wrong.  If `dest` already exists, just verify it. If
`force` is set to `true`, overwrite the given file if it exists but does not
match the given `hash`.

This method returns `true` if the file was downloaded successfully, `false`
if an existing file was removed due to the use of `force`, and throws an error
if `force` is not set and the already-existent file fails verification, or if
`force` is set, verification fails, and then verification fails again after
redownloading the file.

If `quiet_download` is set to `false` (the default), this method will print to
stdout when downloading a new file.  If it is set to `true` (and `verbose` is
set to `false`) the downloading process will be completely silent.  If
`verbose` is set to `true`, messages about integrity verification will be
printed in addition to messages regarding downloading.
"""
function download_verify(url::AbstractString, hash::AbstractString,
                         dest::AbstractString; verbose::Bool = false,
                         force::Bool = false, quiet_download::Bool = false)
    # Whether the file existed in the first place
    file_existed = false

    if isfile(dest)
        file_existed = true
        if verbose
            info_onchange(
                "Destination file $(dest) already exists, verifying...",
                "download_verify_$(dest)",
                @__LINE__,
            )
        end

        # verify download, if it passes, return happy.  If it fails, (and
        # `force` is `true`, re-download!)
        try
            verify(dest, hash; verbose=verbose)
            return true
        catch e
            if isa(e, InterruptException)
                rethrow()
            end
            if !force
                rethrow()
            end
            if verbose
                info_onchange(
                    "Verification failed, re-downloading...",
                    "download_verify_$(dest)",
                    @__LINE__,
                )
            end
        end
    end

    # Make sure the containing folder exists
    mkpath(dirname(dest))

    try
        # Download the file, optionally continuing
        download(url, dest; verbose=verbose || !quiet_download)

        verify(dest, hash; verbose=verbose)
    catch e
        if isa(e, InterruptException)
            rethrow()
        end
        # If the file already existed, it's possible the initially downloaded chunk
        # was bad.  If verification fails after downloading, auto-delete the file
        # and start over from scratch.
        if file_existed
            if verbose
                @info("Continued download didn't work, restarting from scratch")
            end
            rm(dest; force=true)

            # Download and verify from scratch
            download(url, dest; verbose=verbose || !quiet_download)
            verify(dest, hash; verbose=verbose)
        else
            # If it didn't verify properly and we didn't resume, something is
            # very wrong and we must complain mightily.
            rethrow()
        end
    end

    # If the file previously existed, this means we removed it (due to `force`)
    # and redownloaded, so return `false`.  If it didn't exist, then this means
    # that we successfully downloaded it, so return `true`.
    return !file_existed
end

"""
    package(src_dir::AbstractString, tarball_path::AbstractString;
            verbose::Bool = false)

Compress `src_dir` into a tarball located at `tarball_path`.
"""
function package(src_dir::AbstractString, tarball_path::AbstractString;
                  verbose::Bool = false)
    # For now, use environment variables to set the gzip compression factor to
    # level 9, eventually there will be new enough versions of tar everywhere
    # to use -I 'gzip -9', or even to switch over to .xz files.
    withenv("GZIP" => "-9") do
        oc = OutputCollector(gen_package_cmd(src_dir, tarball_path); verbose=verbose)
        try
            if !wait(oc)
                error()
            end
        catch e
            if isa(e, InterruptException)
                rethrow()
            end
            error("Could not package $(src_dir) into $(tarball_path)")
        end
    end
end

"""
    unpack(tarball_path::AbstractString, dest::AbstractString;
           verbose::Bool = false)

Unpack tarball located at file `tarball_path` into directory `dest`.
"""
function unpack(tarball_path::AbstractString, dest::AbstractString;
                verbose::Bool = false)

    # unpack into dest
    mkpath(dest)

    # The user can force usage of our dereferencing workarounds for filesystems
    # that don't support symlinks, but it is also autodetected.
    copyderef = (get(ENV, "BINARYPROVIDER_COPYDEREF", "") == "true") || !probe_symlink_creation(dest)

    # If we should "copyderef" what we do is to unpack everything except symlinks
    # then copy the sources of the symlinks to the destination of the symlink instead.
    # This is to work around filesystems that are mounted (such as SMBFS filesystems)
    # that do not support symlinks.

    excludelist = nothing

    if copyderef
        symlinks = list_tarball_symlinks(tarball_path)
        if length(symlinks) > 0
            (excludelist, io) = mktemp()
            write(io, join([s[1] for s in symlinks], "\n"))
            close(io)
        end
    end

    oc = OutputCollector(gen_unpack_cmd(tarball_path, dest, excludelist); verbose=verbose)
    try
        if !wait(oc)
            error()
        end
    catch e
        if isa(e, InterruptException)
            rethrow()
        end
        error("Could not unpack $(tarball_path) into $(dest)")
    end

    if copyderef && length(symlinks) > 0
        @info("Replacing symlinks in tarball by their source files ...\n" * join(string.(symlinks),"\n"))
        for s in symlinks
            sourcefile = normpath(joinpath(dest, s[2]))
            destfile   = normpath(joinpath(dest, s[1]))

            if isfile(sourcefile)
                cp(sourcefile, destfile, force = true)
            else
                @warn("Symlink source '$sourcefile' does not exist!")
            end
        end
        rm(excludelist; force = true)
    end
end

"""
    download_verify_unpack(url::AbstractString, hash::AbstractString,
                           dest::AbstractString; tarball_path = nothing,
                           verbose::Bool = false, ignore_existence::Bool = false,
                           force::Bool = false)

Helper method to download tarball located at `url`, verify it matches the
given `hash`, then unpack it into folder `dest`.  In general, the method
`install()` should be used to download and install tarballs into a `Prefix`;
this method should only be used if the extra functionality of `install()` is
undesired.

If `tarball_path` is specified, the given `url` will be downloaded to
`tarball_path`, and it will not be removed after downloading and verification
is complete.  If it is not specified, the tarball will be downloaded to a
temporary location, and removed after verification is complete.

If `force` is specified, a verification failure will cause `tarball_path` to be
deleted (if it exists), the `dest` folder to be removed (if it exists) and the
tarball to be redownloaded and reverified.  If the verification check is failed
a second time, an exception is raised.  If `force` is not specified, a
verification failure will result in an immediate raised exception.

If `ignore_existence` is set, the tarball is unpacked even if the destination
directory already exists.

Returns `true` if a tarball was actually unpacked, `false` if nothing was
changed in the destination prefix.
"""
function download_verify_unpack(url::AbstractString,
                                hash::AbstractString,
                                dest::AbstractString;
                                tarball_path = nothing,
                                ignore_existence::Bool = false,
                                force::Bool = false,
                                verbose::Bool = false)
    # First, determine whether we should keep this tarball around
    remove_tarball = false
    if tarball_path === nothing
        remove_tarball = true

        function url_ext(url)
            url = basename(url)

            # Chop off urlparams
            qidx = findfirst(isequal('?'), url)
            if qidx !== nothing
                url = url[1:qidx]
            end

            # Try to detect extension
            dot_idx = findlast(isequal('.'), url)
            if dot_idx === nothing
                return nothing
            end

            return url[dot_idx+1:end]
        end

        # If extension of url contains a recognized extension, use it, otherwise use ".gz"
        ext = url_ext(url)
        if !(ext in ["tar", "gz", "tgz", "bz2", "xz"])
            ext = "gz"
        end

        tarball_path = "$(tempname())-download.$(ext)"
    end

    # Download the tarball; if it already existed and we needed to remove it
    # then we should remove the unpacked path as well
    should_delete = !download_verify(url, hash, tarball_path;
                                     force=force, verbose=verbose)
    if should_delete
        if verbose
            @info("Removing dest directory $(dest) as source tarball changed")
        end
        rm(dest; recursive=true, force=true)
    end

    # If the destination path already exists, don't bother to unpack
    if !ignore_existence && isdir(dest)
        if verbose
            @info("Destination directory $(dest) already exists, returning")
        end

        # Signify that we didn't do any unpacking
        return false
    end

    try
        if verbose
            @info("Unpacking $(tarball_path) into $(dest)...")
        end
        unpack(tarball_path, dest; verbose=verbose)
    finally
        if remove_tarball
            rm(tarball_path)
        end
    end

    # Signify that we did some unpacking!
    return true
end


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

## Create an extractor that extracts the modified original matrix, e.g.
## LD for BunchKaufman, UL for CholeskyDense, LU for LUDense and
## define size methods for Factorization types using it.

"""
    BunchKaufman <: Factorization

Matrix factorization type of the Bunch-Kaufman factorization of a symmetric or
Hermitian matrix `A` as `P'UDU'P` or `P'LDL'P`, depending on whether the upper
(the default) or the lower triangle is stored in `A`. If `A` is complex symmetric
then `U'` and `L'` denote the unconjugated transposes, i.e. `transpose(U)` and
`transpose(L)`, respectively. This is the return type of [`bunchkaufman`](@ref),
the corresponding matrix factorization function.

If `S::BunchKaufman` is the factorization object, the components can be obtained
via `S.D`, `S.U` or `S.L` as appropriate given `S.uplo`, and `S.p`.

Iterating the decomposition produces the components `S.D`, `S.U` or `S.L`
as appropriate given `S.uplo`, and `S.p`.

# Examples
```jldoctest
julia> A = [1 2; 2 3]
2×2 Matrix{Int64}:
 1  2
 2  3

julia> S = bunchkaufman(A) # A gets wrapped internally by Symmetric(A)
BunchKaufman{Float64,Matrix{Float64}}
D factor:
2×2 Tridiagonal{Float64,Vector{Float64}}:
 -0.333333  0.0
  0.0       3.0
U factor:
2×2 UnitUpperTriangular{Float64,Matrix{Float64}}:
 1.0  0.666667
  ⋅   1.0
permutation:
2-element Vector{Int64}:
 1
 2

julia> d, u, p = S; # destructuring via iteration

julia> d == S.D && u == S.U && p == S.p
true

julia> S = bunchkaufman(Symmetric(A, :L))
BunchKaufman{Float64,Matrix{Float64}}
D factor:
2×2 Tridiagonal{Float64,Vector{Float64}}:
 3.0   0.0
 0.0  -0.333333
L factor:
2×2 UnitLowerTriangular{Float64,Matrix{Float64}}:
 1.0        ⋅
 0.666667  1.0
permutation:
2-element Vector{Int64}:
 2
 1
```
"""
struct BunchKaufman{T,S<:AbstractMatrix} <: Factorization{T}
    LD::S
    ipiv::Vector{BlasInt}
    uplo::Char
    symmetric::Bool
    rook::Bool
    info::BlasInt

    function BunchKaufman{T,S}(LD, ipiv, uplo, symmetric, rook, info) where {T,S<:AbstractMatrix}
        require_one_based_indexing(LD)
        new(LD, ipiv, uplo, symmetric, rook, info)
    end
end
BunchKaufman(A::AbstractMatrix{T}, ipiv::Vector{BlasInt}, uplo::AbstractChar, symmetric::Bool,
             rook::Bool, info::BlasInt) where {T} =
        BunchKaufman{T,typeof(A)}(A, ipiv, uplo, symmetric, rook, info)

# iteration for destructuring into components
Base.iterate(S::BunchKaufman) = (S.D, Val(:UL))
Base.iterate(S::BunchKaufman, ::Val{:UL}) = (S.uplo == 'L' ? S.L : S.U, Val(:p))
Base.iterate(S::BunchKaufman, ::Val{:p}) = (S.p, Val(:done))
Base.iterate(S::BunchKaufman, ::Val{:done}) = nothing

"""
    bunchkaufman!(A, rook::Bool=false; check = true) -> BunchKaufman

`bunchkaufman!` is the same as [`bunchkaufman`](@ref), but saves space by overwriting the
input `A`, instead of creating a copy.
"""
function bunchkaufman!(A::RealHermSymComplexSym{T,S} where {T<:BlasReal,S<:StridedMatrix},
                       rook::Bool = false; check::Bool = true)
    LD, ipiv, info = rook ? LAPACK.sytrf_rook!(A.uplo, A.data) : LAPACK.sytrf!(A.uplo, A.data)
    check && checknonsingular(info)
    BunchKaufman(LD, ipiv, A.uplo, true, rook, info)
end
function bunchkaufman!(A::Hermitian{T,S} where {T<:BlasComplex,S<:StridedMatrix{T}},
                       rook::Bool = false; check::Bool = true)
    LD, ipiv, info = rook ? LAPACK.hetrf_rook!(A.uplo, A.data) : LAPACK.hetrf!(A.uplo, A.data)
    check && checknonsingular(info)
    BunchKaufman(LD, ipiv, A.uplo, false, rook, info)
end
function bunchkaufman!(A::StridedMatrix{<:BlasFloat}, rook::Bool = false; check::Bool = true)
    if ishermitian(A)
        return bunchkaufman!(Hermitian(A), rook; check = check)
    elseif issymmetric(A)
        return bunchkaufman!(Symmetric(A), rook; check = check)
    else
        throw(ArgumentError("Bunch-Kaufman decomposition is only valid for symmetric or Hermitian matrices"))
    end
end

"""
    bunchkaufman(A, rook::Bool=false; check = true) -> S::BunchKaufman

Compute the Bunch-Kaufman [^Bunch1977] factorization of a symmetric or
Hermitian matrix `A` as `P'*U*D*U'*P` or `P'*L*D*L'*P`, depending on
which triangle is stored in `A`, and return a [`BunchKaufman`](@ref) object.
Note that if `A` is complex symmetric then `U'` and `L'` denote
the unconjugated transposes, i.e. `transpose(U)` and `transpose(L)`.

Iterating the decomposition produces the components `S.D`, `S.U` or `S.L`
as appropriate given `S.uplo`, and `S.p`.

If `rook` is `true`, rook pivoting is used. If `rook` is false,
rook pivoting is not used.

When `check = true`, an error is thrown if the decomposition fails.
When `check = false`, responsibility for checking the decomposition's
validity (via [`issuccess`](@ref)) lies with the user.

The following functions are available for `BunchKaufman` objects:
[`size`](@ref), `\\`, [`inv`](@ref), [`issymmetric`](@ref),
[`ishermitian`](@ref), [`getindex`](@ref).

[^Bunch1977]: J R Bunch and L Kaufman, Some stable methods for calculating inertia and solving symmetric linear systems, Mathematics of Computation 31:137 (1977), 163-179. [url](http://www.ams.org/journals/mcom/1977-31-137/S0025-5718-1977-0428694-0/).

# Examples
```jldoctest
julia> A = [1 2; 2 3]
2×2 Matrix{Int64}:
 1  2
 2  3

julia> S = bunchkaufman(A) # A gets wrapped internally by Symmetric(A)
BunchKaufman{Float64,Matrix{Float64}}
D factor:
2×2 Tridiagonal{Float64,Vector{Float64}}:
 -0.333333  0.0
  0.0       3.0
U factor:
2×2 UnitUpperTriangular{Float64,Matrix{Float64}}:
 1.0  0.666667
  ⋅   1.0
permutation:
2-element Vector{Int64}:
 1
 2

julia> d, u, p = S; # destructuring via iteration

julia> d == S.D && u == S.U && p == S.p
true

julia> S = bunchkaufman(Symmetric(A, :L))
BunchKaufman{Float64,Matrix{Float64}}
D factor:
2×2 Tridiagonal{Float64,Vector{Float64}}:
 3.0   0.0
 0.0  -0.333333
L factor:
2×2 UnitLowerTriangular{Float64,Matrix{Float64}}:
 1.0        ⋅
 0.666667  1.0
permutation:
2-element Vector{Int64}:
 2
 1
```
"""
bunchkaufman(A::AbstractMatrix{T}, rook::Bool=false; check::Bool = true) where {T} =
    bunchkaufman!(copy_oftype(A, typeof(sqrt(oneunit(T)))), rook; check = check)

convert(::Type{BunchKaufman{T}}, B::BunchKaufman{T}) where {T} = B
convert(::Type{BunchKaufman{T}}, B::BunchKaufman) where {T} =
    BunchKaufman(convert(Matrix{T}, B.LD), B.ipiv, B.uplo, B.symmetric, B.rook, B.info)
convert(::Type{Factorization{T}}, B::BunchKaufman{T}) where {T} = B
convert(::Type{Factorization{T}}, B::BunchKaufman) where {T} = convert(BunchKaufman{T}, B)

size(B::BunchKaufman) = size(getfield(B, :LD))
size(B::BunchKaufman, d::Integer) = size(getfield(B, :LD), d)
issymmetric(B::BunchKaufman) = B.symmetric
ishermitian(B::BunchKaufman) = !B.symmetric

function _ipiv2perm_bk(v::AbstractVector{T}, maxi::Integer, uplo::AbstractChar, rook::Bool) where T
    require_one_based_indexing(v)
    p = T[1:maxi;]
    uploL = uplo == 'L'
    i = uploL ? 1 : maxi
    # if uplo == 'U' we construct the permutation backwards
    @inbounds while 1 <= i <= length(v)
        vi = v[i]
        if vi > 0 # the 1x1 blocks
            p[i], p[vi] = p[vi], p[i]
            i += uploL ? 1 : -1
        else # the 2x2 blocks
            if rook
                p[i], p[-vi] = p[-vi], p[i]
            end
            if uploL
                vp = rook ? -v[i+1] : -vi
                p[i + 1], p[vp] = p[vp], p[i + 1]
                i += 2
            else # 'U'
                vp = rook ? -v[i-1] : -vi
                p[i - 1], p[vp] = p[vp], p[i - 1]
                i -= 2
            end
        end
    end
    return p
end

"""
    getproperty(B::BunchKaufman, d::Symbol)

Extract the factors of the Bunch-Kaufman factorization `B`. The factorization can take the
two forms `P'*L*D*L'*P` or `P'*U*D*U'*P` (or `L*D*transpose(L)` in the complex symmetric case)
where `P` is a (symmetric) permutation matrix, `L` is a [`UnitLowerTriangular`](@ref) matrix, `U` is a
[`UnitUpperTriangular`](@ref), and `D` is a block diagonal symmetric or Hermitian matrix with
1x1 or 2x2 blocks. The argument `d` can be

- `:D`: the block diagonal matrix
- `:U`: the upper triangular factor (if factorization is `U*D*U'`)
- `:L`: the lower triangular factor (if factorization is `L*D*L'`)
- `:p`: permutation vector
- `:P`: permutation matrix

# Examples
```jldoctest
julia> A = [1 2 3; 2 1 2; 3 2 1]
3×3 Matrix{Int64}:
 1  2  3
 2  1  2
 3  2  1

julia> F = bunchkaufman(Symmetric(A, :L))
BunchKaufman{Float64,Matrix{Float64}}
D factor:
3×3 Tridiagonal{Float64,Vector{Float64}}:
 1.0  3.0    ⋅
 3.0  1.0   0.0
  ⋅   0.0  -1.0
L factor:
3×3 UnitLowerTriangular{Float64,Matrix{Float64}}:
 1.0   ⋅    ⋅
 0.0  1.0   ⋅
 0.5  0.5  1.0
permutation:
3-element Vector{Int64}:
 1
 3
 2

julia> F.L*F.D*F.L' - A[F.p, F.p]
3×3 Matrix{Float64}:
 0.0  0.0  0.0
 0.0  0.0  0.0
 0.0  0.0  0.0

julia> F = bunchkaufman(Symmetric(A));

julia> F.U*F.D*F.U' - F.P*A*F.P'
3×3 Matrix{Float64}:
 0.0  0.0  0.0
 0.0  0.0  0.0
 0.0  0.0  0.0
```
"""
function getproperty(B::BunchKaufman{T}, d::Symbol) where {T<:BlasFloat}
    n = size(B, 1)
    if d === :p
        return _ipiv2perm_bk(getfield(B, :ipiv), n, getfield(B, :uplo), B.rook)
    elseif d === :P
        return Matrix{T}(I, n, n)[:,invperm(B.p)]
    elseif d === :L || d === :U || d === :D
        if getfield(B, :rook)
            LUD, od = LAPACK.syconvf_rook!(getfield(B, :uplo), 'C', copy(getfield(B, :LD)), getfield(B, :ipiv))
        else
            LUD, od = LAPACK.syconv!(getfield(B, :uplo), copy(getfield(B, :LD)), getfield(B, :ipiv))
        end
        if d === :D
            if getfield(B, :uplo) == 'L'
                odl = od[1:n - 1]
                return Tridiagonal(odl, diag(LUD), getfield(B, :symmetric) ? odl : conj.(odl))
            else # 'U'
                odu = od[2:n]
                return Tridiagonal(getfield(B, :symmetric) ? odu : conj.(odu), diag(LUD), odu)
            end
        elseif d === :L
            if getfield(B, :uplo) == 'L'
                return UnitLowerTriangular(LUD)
            else
                throw(ArgumentError("factorization is U*D*transpose(U) but you requested L"))
            end
        else # :U
            if B.uplo == 'U'
                return UnitUpperTriangular(LUD)
            else
                throw(ArgumentError("factorization is L*D*transpose(L) but you requested U"))
            end
        end
    else
        getfield(B, d)
    end
end

Base.propertynames(B::BunchKaufman, private::Bool=false) =
    (:p, :P, :L, :U, :D, (private ? fieldnames(typeof(B)) : ())...)

issuccess(B::BunchKaufman) = B.info == 0

function Base.show(io::IO, mime::MIME{Symbol("text/plain")}, B::BunchKaufman)
    if issuccess(B)
        summary(io, B); println(io)
        println(io, "D factor:")
        show(io, mime, B.D)
        println(io, "\n$(B.uplo) factor:")
        show(io, mime, B.uplo == 'L' ? B.L : B.U)
        println(io, "\npermutation:")
        show(io, mime, B.p)
    else
        print(io, "Failed factorization of type $(typeof(B))")
    end
end

function inv(B::BunchKaufman{<:BlasReal})
    if B.rook
        copytri!(LAPACK.sytri_rook!(B.uplo, copy(B.LD), B.ipiv), B.uplo, true)
    else
        copytri!(LAPACK.sytri!(B.uplo, copy(B.LD), B.ipiv), B.uplo, true)
    end
end

function inv(B::BunchKaufman{<:BlasComplex})
    if issymmetric(B)
        if B.rook
            copytri!(LAPACK.sytri_rook!(B.uplo, copy(B.LD), B.ipiv), B.uplo)
        else
            copytri!(LAPACK.sytri!(B.uplo, copy(B.LD), B.ipiv), B.uplo)
        end
    else
        if B.rook
            copytri!(LAPACK.hetri_rook!(B.uplo, copy(B.LD), B.ipiv), B.uplo, true)
        else
            copytri!(LAPACK.hetri!(B.uplo, copy(B.LD), B.ipiv), B.uplo, true)
        end
    end
end

function ldiv!(B::BunchKaufman{T}, R::StridedVecOrMat{T}) where T<:BlasReal
    if B.rook
        LAPACK.sytrs_rook!(B.uplo, B.LD, B.ipiv, R)
    else
        LAPACK.sytrs!(B.uplo, B.LD, B.ipiv, R)
    end
end
function ldiv!(B::BunchKaufman{T}, R::StridedVecOrMat{T}) where T<:BlasComplex
    if B.rook
        if issymmetric(B)
            LAPACK.sytrs_rook!(B.uplo, B.LD, B.ipiv, R)
        else
            LAPACK.hetrs_rook!(B.uplo, B.LD, B.ipiv, R)
        end
    else
        if issymmetric(B)
            LAPACK.sytrs!(B.uplo, B.LD, B.ipiv, R)
        else
            LAPACK.hetrs!(B.uplo, B.LD, B.ipiv, R)
        end
    end
end
# There is no fallback solver for Bunch-Kaufman so we'll have to promote to same element type
function ldiv!(B::BunchKaufman{T}, R::StridedVecOrMat{S}) where {T,S}
    TS = promote_type(T,S)
    return ldiv!(convert(BunchKaufman{TS}, B), convert(AbstractArray{TS}, R))
end

function logabsdet(F::BunchKaufman)
    M = F.LD
    p = F.ipiv
    n = size(F.LD, 1)

    if !issuccess(F)
        return eltype(F)(-Inf), zero(eltype(F))
    end
    s = one(real(eltype(F)))
    i = 1
    abs_det = zero(real(eltype(F)))
    while i <= n
        if p[i] > 0
            elm = M[i,i]
            s *= sign(elm)
            abs_det += log(abs(elm))
            i += 1
        else
            # 2x2 pivot case. Make sure not to square before the subtraction by scaling
            # with the off-diagonal element. This is safe because the off diagonal is
            # always large for 2x2 pivots.
            if F.uplo == 'U'
                elm = M[i, i + 1]*(M[i,i]/M[i, i + 1]*M[i + 1, i + 1] -
                    (issymmetric(F) ? M[i, i + 1] : conj(M[i, i + 1])))
                s *= sign(elm)
                abs_det += log(abs(elm))
            else
                elm = M[i + 1,i]*(M[i, i]/M[i + 1, i]*M[i + 1, i + 1] -
                    (issymmetric(F) ? M[i + 1, i] : conj(M[i + 1, i])))
                s *= sign(elm)
                abs_det += log(abs(elm))
            end
            i += 2
        end
    end
    return abs_det, s
end

## reconstruct the original matrix
## TODO: understand the procedure described at
## http://www.nag.com/numeric/FL/nagdoc_fl22/pdf/F07/f07mdf.pdf

ext: jl


text:
using Documenter, AppliGeneralLedger

makedocs(;
    modules=[AppliGeneralLedger],
    format=Documenter.HTML(),
    pages=[
        "General Ledger" => "index.md",
        "1 - API" => "chapter1.md",
        "2 - Infrastructure" => "chapter2.md",
        "3 - Examples" => "chapter3.md",
    ],
    repo="https://github.com/rbontekoe/AppliGeneralLedger.jl/blob/{commit}{path}#L{line}",
    sitename="AppliGeneralLedger.jl",
    authors="Rob Bontekoe",
    assets=String[],
)

deploydocs(;
    repo="github.com/rbontekoe/AppliGeneralLedger.jl",
)


text:
# * Densities

function Base.Broadcast.broadcasted(::typeof(*), a::FDArray{T,N}, b::FDArray{T,N}) where {T,N}
    axes(a) == axes(b) || throw(DimensionMismatch("Incompatible axes"))
    A,ca = a.args
    B,cb = b.args
    A == B || throw(DimensionMismatch("Incompatible bases"))
    # We want the first MulQuasiArray to be conjugated, if complex
    c = conj.(ca) .* cb
    A*c
end

struct FDDensity{T,B<:FiniteDifferencesOrRestricted,
                 U<:AbstractVecOrMat{T},V<:AbstractVecOrMat{T}}
    R::B
    u::U
    v::V
end

function _FDDensity(Ra::FiniteDifferencesOrRestricted, ca::AbstractVecOrMat,
                    Rb::FiniteDifferencesOrRestricted, cb::AbstractVecOrMat)
    # Ra == Rb || throw(DimensionMismatch("Incompatible bases"))
    FDDensity(Ra, ca, cb)
end

function Base.copyto!(cρ::AbstractVecOrMat{T}, ld::FDDensity{T,R}, Rρ::R) where {T,R}
    # Rρ == ld.R || throw(DimensionMismatch("Incompatible bases"))
    # size(cρ) == size(ld.u) || throw(DimensionMismatch("Incompatible sizes"))
    # We want the first MulQuasiArray to be conjugated, if complex
    cρ .= conj.(ld.u) .* ld.v
    cρ
end

function Base.Broadcast.broadcasted(::typeof(⋆), a::V₁, b::V₂) where {T,B<:FiniteDifferencesOrRestricted,V₁<:FDVecOrMat{T,B},V₂<:FDVecOrMat{T,B}}
    # axes(a) == axes(b) || throw(DimensionMismatch("Incompatible axes"))
    _FDDensity(a.args..., b.args...)
end

function Base.copyto!(ρ::FDVecOrMat{T,R}, ld::FDDensity{T,R}) where {T,R}
    copyto!(ρ.args[2], ld, ρ.args[1])
    ρ
end

function Base.Broadcast.broadcasted(::typeof(⋆), a::V₁, b::V₂) where {T,B<:FiniteDifferencesOrRestricted,
                                                                      V₁<:Mul{<:Any, <:Tuple{B,<:AbstractVector{T}}},
                                                                      V₂<:Mul{<:Any, <:Tuple{B,<:AbstractVector{T}}}}
    # axes(a) == axes(b) || throw(DimensionMismatch("Incompatible axes"))
    _FDDensity(a.args..., b.args...)
end

function Base.copyto!(ρ::Mul{<:Any, <:Tuple{R,<:AbstractVector{T}}}, ld::FDDensity{T,R}) where {T,R}
    copyto!(ρ.args[2], ld, ρ.args[1])
    ρ
end


text:
export OutputContext, @rocprint, @rocprintln, @rocprintf

"Internal representation of a static string."
struct DeviceStaticString{N} end
Base.sizeof(dss::DeviceStaticString{N}) where N = N

function Base.unsafe_load(ptr::LLVMPtr{DeviceStaticString{N},AS.Global}) where N
    vec_ptr = reinterpret(Ptr{UInt8}, ptr)
    vec_raw = Base.unsafe_wrap(Vector{UInt8}, vec_ptr, (N,))
    idx = findfirst(x->x==0, vec_raw)
    idx = idx === nothing ? N : idx
    return vec_raw[1:idx-1]
end
Base.unsafe_store!(ptr::LLVMPtr{<:DeviceStaticString,1}, x) = nothing

struct OutputContext{HC}
    hostcall::HC
end
function OutputContext(io::IO=stdout; agent=get_default_agent(), buf_len=2^16, kwargs...)
    hc = HostCall(Int64, Tuple{DeviceStaticString{buf_len}}; agent=agent, continuous=true, kwargs...) do bytes
        print(io, String(bytes))
        Int64(length(bytes))
    end
    OutputContext(hc)
end

const GLOBAL_OUTPUT_CONTEXT_TYPE = OutputContext{HostCall{UInt64,Int64,Tuple{DeviceStaticString{2^16}}}}

### macros

macro rocprint(oc, str)
    rocprint(oc, str)
end
macro rocprintln(oc, str)
    rocprint(oc, str, true)
end

macro rocprint(str)
    @gensym oc_ptr oc
    ex = quote
        $(esc(oc_ptr)) = AMDGPU.get_global_pointer(Val(:__global_output_context),
                                                         $GLOBAL_OUTPUT_CONTEXT_TYPE)
        $(esc(oc)) = Base.unsafe_load($(esc(oc_ptr)))
    end
    push!(ex.args, rocprint(oc, str))
    ex
end
macro rocprintln(str)
    @gensym oc_ptr oc
    ex = quote
        $(esc(oc_ptr)) = AMDGPU.get_global_pointer(Val(:__global_output_context),
                                                         $GLOBAL_OUTPUT_CONTEXT_TYPE)
        $(esc(oc)) = Base.unsafe_load($(esc(oc_ptr)))
    end
    push!(ex.args, rocprint(oc, str, true))
    ex
end

### parse-time helpers

function rocprint(oc, str, nl::Bool=false)
    ex = Expr(:block)
    if !(str isa Expr)
        str = Expr(:string, str)
    end
    @assert str.head == :string
    # TODO: push!(ex.args, :($_hostcall_lock!($(esc(oc)).hostcall)))
    for (idx,arg) in enumerate(str.args)
        if nl && idx == length(str.args)
            arg *= '\n'
        end
        N = rocprint!(ex, 1, oc, arg)
        N = rocprint!(ex, N, oc, '\0')
        dstr = DeviceStaticString{N}()
    end
    push!(ex.args, :($_hostcall!($(esc(oc)).hostcall)))
    push!(ex.args, :(nothing))
    return ex
end
function rocprint!(ex, N, oc, str::String)
    @gensym str_ptr
    push!(ex.args, :($str_ptr = AMDGPU.alloc_string($(Val(Symbol(str))))))
    push!(ex.args, :(AMDGPU.memcpy!($(esc(oc)).hostcall.buf_ptr+$(N-1), $str_ptr, $(length(str)))))
    return N+length(str)
end
function rocprint!(ex, N, oc, char::Char)
    @assert char == '\0' "Non-null chars not yet implemented"
    byte = UInt8(char)
    ptr = :(reinterpret(LLVMPtr{UInt8,AS.Global}, $(esc(oc)).hostcall.buf_ptr))
    push!(ex.args, :(Base.unsafe_store!($ptr, $byte, $N)))
    return N+1
end
function rocprint!(ex, N, oc, iex::Expr)
    for arg in iex.args
        N = rocprint!(ex, N, oc, arg)
    end
    return N
end
function rocprint!(ex, N, oc, sym::S) where S
    error("Dynamic printing of $S only supported via @rocprintf")
end

## @rocprintf

macro rocprintf(fmt, args...)
    ex = Expr(:block)
    @gensym device_ptr device_fmt_ptr printf_hc
    push!(ex.args, :($device_fmt_ptr = AMDGPU.alloc_string($(Val(Symbol(fmt))))))
    push!(ex.args, :($printf_hc = unsafe_load(AMDGPU.get_global_pointer(Val(:__global_printf_context),
                                                                        HostCall{UInt64,Int64,LLVMPtr{ROCPrintfBuffer,AS.Global}}))))
    push!(ex.args, :($device_ptr = reinterpret($(LLVMPtr{UInt64,AS.Global}), $printf_hc.buf_ptr)))

    #= FIXME: https://github.com/JuliaGPU/AMDGPU.jl/pull/50
    #push!(ex.args, :($device_signal_wait($printf_hc.signal, $READY_SENTINEL)))
    #push!(ex.args, :($device_signal_store!($printf_hc.signal, $DEVICE_LOCK_SENTINEL)))
    push!(ex.args, quote
        if AMDGPU.workitemIdx().x == 1
            $_hostcall_lock!($printf_hc)
        end
    end)
    =#

    push!(ex.args, :($device_ptr = AMDGPU._rocprintf_fmt($device_ptr, $device_fmt_ptr, $(sizeof(fmt)))))
    for arg in args
        push!(ex.args, :($device_ptr = AMDGPU._rocprintf_arg($device_ptr, $(esc(arg)))))
    end
    push!(ex.args, :(unsafe_store!($device_ptr, UInt64(0))))
    #= FIXME: Same as above
    push!(ex.args, :(AMDGPU.memfence!(Val(:seq_cst))))
    push!(ex.args, :($device_signal_store!($printf_hc.signal, $DEVICE_MSG_SENTINEL)))
    push!(ex.args, :($device_signal_wait($printf_hc.signal, $HOST_MSG_SENTINEL)))
    push!(ex.args, :($device_signal_store!($printf_hc.signal, $READY_SENTINEL)))
    =#
    push!(ex.args, :($_hostcall!($printf_hc)))
    push!(ex.args, :(nothing))
    ex
end

# Serializes execution of a function within a wavefront
# From implementation by @jonathanvdc in CUDAnative.jl#419
function wave_serialized(func::Function)
    # Get the current thread's ID
    thread_id = workitemIdx().x - 1

    # Get the size of a wavefront
    size = wavefrontsize()

    local result
    i = 0
    while i < size
        if thread_id % size == i
            result = func()
        end
        i += 1
    end
    return result
end

struct ROCPrintfBuffer end
Base.sizeof(::ROCPrintfBuffer) = 0
Base.unsafe_store!(::LLVMPtr{ROCPrintfBuffer,as} where as, x) = nothing
function Base.unsafe_load(ptr::LLVMPtr{ROCPrintfBuffer,as} where as)
    ptr = reinterpret(Ptr{UInt64}, ptr)
    fmt_ptr = Ptr{UInt64}(unsafe_load(ptr))
    ptr += sizeof(UInt64)
    fmt_len = unsafe_load(ptr)
    ptr += sizeof(UInt64)
    fmt_buf = Vector{UInt8}(undef, fmt_len)
    HSA.memory_copy(convert(Ptr{Cvoid}, pointer(fmt_buf)), convert(Ptr{Cvoid}, fmt_ptr), fmt_len)
    fmt = String(fmt_buf)
    args = []
    while true
        T_ptr = Ptr{UInt64}(unsafe_load(ptr))
        ptr += sizeof(UInt64)
        UInt64(T_ptr) == 0 && break
        T = unsafe_pointer_to_objref(T_ptr)
        arg = unsafe_load(convert(Ptr{T}, ptr))
        push!(args, arg)
        ptr += sizeof(arg)
    end
    return (fmt, args)
end
function _rocprintf_fmt(ptr, fmt_ptr, fmt_len)
    unsafe_store!(ptr, reinterpret(UInt64, fmt_ptr))
    ptr += sizeof(UInt64)
    unsafe_store!(ptr, UInt64(fmt_len))
    ptr += sizeof(UInt64)
    return ptr
end
function _rocprintf_arg(ptr, arg::T) where T
    T_ptr = pointer_from_objref(T)
    unsafe_store!(ptr, T_ptr)
    ptr += sizeof(UInt64)
    unsafe_store!(reinterpret(LLVMPtr{T,1}, ptr), arg)
    ptr += sizeof(arg)
    #= FIXME
    ref_arg = Ref{T}(arg)
    GC.@preserve ref_arg begin
    ptr_arg = convert(DevicePtr{UInt8,AS.Global},
                      convert(DevicePtr{T,AS.Global},
                      Base.unsafe_convert(Ptr{T}, ref_arg)))
    memcpy!(ptr, ptr_arg, sizeof(arg), Val(true))
    end
    =#
    return ptr
end
#= TODO: Not really useful until we can work with device-side strings
function _rocprintf_string(ptr, str::String)
    @gensym T_str T_str_len str_ptr
    quote
        $T_str, $T_str_len = AMDGPU._rocprintf_T_str(String)
        AMDGPU.memcpy!($ptr, $T_str, $T_str_len)
        $ptr += $T_str_len
        unsafe_store!($ptr, UInt8(0))
        $ptr += 1
        $str_ptr = Base.unsafe_convert(DevicePtr{UInt8,AS.Generic}, $str_ptr)
        $str_ptr = AMDGPU.alloc_string($(Val(Symbol(str))))
        AMDGPU.memcpy!($ptr, $str_ptr, $(length(str)))
        $ptr += $(length(str))
        $ptr
    end
end
@generated function _rocprintf_T_str(::Type{T}) where T
    quote
        (AMDGPU.alloc_string($(Val(Symbol(repr(T))))), $(sizeof(repr(T))))
    end
end
=#


text:
module WindowpaneHelperWindowpane

using ..Ahorn, Maple

@mapdef Entity "WindowpaneHelper/Windowpane" Windowpane(
  x::Integer, y::Integer,
  width::Integer=8, height::Integer=8,
  depth::Integer=11000,
  wipeColor::String="000000ff", overlayColor::String="ffffffff",
  blendState::String="alphablend",
  renderPosition::String="inLevel", punchThrough::Bool=false,
  stylegroundTag::String="",
  flags::String=""
)

const placements = Ahorn.PlacementDict(
    "Windowpane" => Ahorn.EntityPlacement(
      Windowpane, "rectangle"
    )
)

const windowFillColor = (0.6, 0.6, 0.6, 0.4)
const windowBorderColor = (0.4, 0.4, 0.4, 1.0)

Ahorn.nodeLimits(entity::Windowpane) = 0, 1

Ahorn.minimumSize(entity::Windowpane) = 1, 1
Ahorn.resizable(entity::Windowpane) = true, true

Ahorn.editingOptions(entity::Windowpane) = Dict{String, Any}(
    "blendState" => ["additive", "alphablend", "nonpremultiplied", "opaque"],
    "renderPosition" => ["inLevel", "above", "below"]
)

function Ahorn.selection(entity::Windowpane)
  x, y = Ahorn.position(entity)

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

  nodes = get(entity.data, "nodes", ())
  if isempty(nodes)
      return Ahorn.Rectangle(x, y, width, height)

  else
      nx, ny = Int.(nodes[1])
      return [Ahorn.Rectangle(x, y, width, height), Ahorn.Rectangle(nx, ny, width, height)]
  end
end

function renderWindow(ctx::Ahorn.Cairo.CairoContext, x::Number, y::Number, width::Number, height::Number)
    Ahorn.Cairo.save(ctx)

    Ahorn.set_antialias(ctx, 1)
    Ahorn.set_line_width(ctx, 1)

    Ahorn.drawRectangle(ctx, x, y, width, height, windowFillColor, windowBorderColor)

    Ahorn.restore(ctx)
end

function Ahorn.render(ctx::Ahorn.Cairo.CairoContext, entity::Windowpane, room::Maple.Room)
    x = Int(get(entity.data, "x", 0))
    y = Int(get(entity.data, "y", 0))

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

    renderWindow(ctx, 0, 0, width, height)
end

function Ahorn.renderSelectedAbs(ctx::Ahorn.Cairo.CairoContext, entity::Windowpane)
  x, y = Ahorn.position(entity)
  nodes = get(entity.data, "nodes", ())

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

  if !isempty(nodes)
      nx, ny = Int.(nodes[1])

      cox, coy = floor(Int, width / 2), floor(Int, height / 2)

      renderWindow(ctx, nx, ny, width, height)
      Ahorn.drawArrow(ctx, x + cox, y + coy, nx + cox, ny + coy, Ahorn.colors.selection_selected_fc, headLength=6)
  end
end

end

max_stars_repo_name: DemoJameson/WindowpaneHelper

max_issues_repo_name: DemoJameson/WindowpaneHelper

max_forks_repo_name: DemoJameson/WindowpaneHelper


text:
function Base.getindex(
    tr::TableResidues,
    com::ConstraintSolverModel,
    vidx::Int,
    local_vidx::Int,
    val::Int,
)
    val_idx = com.search_space[vidx].init_val_to_index[val + com.search_space[vidx].offset]
    index_shift = tr.var_start[local_vidx] - 1 + val_idx
    return tr.values[index_shift]
end

function Base.setindex!(
    tr::TableResidues,
    residue::Int,
    com::ConstraintSolverModel,
    vidx::Int,
    local_vidx::Int,
    val::Int,
)
    val_idx = com.search_space[vidx].init_val_to_index[val + com.search_space[vidx].offset]
    index_shift = tr.var_start[local_vidx] - 1 + val_idx
    tr.values[index_shift] = residue
end


text:
include("../aimajulia.jl");

using Test;
using Printf;

using Main.aimajulia;

using Main.aimajulia.utils;

#The following text tests are from the aima-python doctests

# Base.Test tests for unigram and n-gram of words
flatland = String(read("./aima-data/EN-text/flatland.txt"));
word_sequence = extract_words(flatland);
P1 = UnigramWordModel(word_sequence);
P2 = NgramWordModel(2, word_sequence);
P3 = NgramWordModel(3, word_sequence);

@test (top(P1, 5) == [(2081,"the"),(1479,"of"),(1021,"and"),(1008,"to"),(850,"a")]);

@test (top(P2, 5) == [(368,("of","the")),(152,("to","the")),(152,("in","the")),(86,("of","a")),(80,("it","is"))]);

@test (top(P3, 5) == [(30,("a","straight","line")),
                        (19,("of","three","dimensions")),
                        (16,("the","sense","of")),
                        (13,("by","the","sense")),
                        (13,("as","well","as"))]);

@test (abs(P1["the"] - 0.0611) <= (0.001 * max(abs(P1["the"]), abs(0.0611))));

@test (abs(P2[("of", "the")] - 0.0108) <= (0.01 * max(abs(P2[("of", "the")]), abs(0.0108))));

@test (abs(P3[("so", "as", "to")] - 0.000323) <= (0.001 * max(abs(P3[("so", "as", "to")]), abs(0.000323))));

@test (!haskey(P2.conditional_probabilities, ("went",)));

@test (P3.conditional_probabilities[("in", "order")].dict == Dict([Pair("to", 6)]));

test_string = "unigram";
word_sequence = extract_words(test_string);
P1 = UnigramWordModel(word_sequence);

@test (P1.dict == Dict([Pair("unigram", 1)]));

test_string = "bigram text";
word_sequence = extract_words(test_string);
P2 = NgramWordModel(2, word_sequence);

@test (P2.dict == Dict([Pair(("bigram", "text"), 1)]));

test_string = "test trigram text here";
word_sequence = extract_words(test_string);
P3 = NgramWordModel(3, word_sequence);

@test (haskey(P3.dict, ("test", "trigram", "text")));

@test (haskey(P3.dict, ("trigram", "text", "here")));

# Base.Test tests for canonicalizing text
@test (extract_words("``EGAD'' Edgar cried.") == ["egad", "edgar", "cried"]);

@test (canonicalize_text("``EGAD'' Edgar cried.") == "egad edgar cried");

# Base.Test tests for samples() methods
story = String(read("./aima-data/EN-text/flatland.txt"));
story = story*String(read("./aima-data/gutenberg.txt"));
word_sequence = extract_words(story);
P1 = UnigramWordModel(word_sequence);
P2 = NgramWordModel(2, word_sequence);
P3 = NgramWordModel(3, word_sequence);

@test (length(split(samples(P1, 10))) == 10);

@test (length(split(samples(P2, 10))) == 10);

@test (length(split(samples(P3, 10))) == 10);

# Base.Test tests for unigram and n-gram of characters/letters
test_string = "test unigram";
word_sequence = extract_words(test_string);
P1 = UnigramCharModel(word_sequence);
expected_unigrams = Dict([Pair('n',1),
                        Pair('g', 1),
                        Pair('t', 2),
                        Pair('a', 1),
                        Pair('u', 1),
                        Pair('i', 1),
                        Pair('m', 1),
                        Pair('e', 1),
                        Pair('s', 1),
                        Pair('r', 1)]);

@test (length(P1.dict) == length(expected_unigrams));

@test (all(haskey(P1.dict, character) for character in setdiff(Set(test_string), Set(" "))));

test_string = "alpha beta";
word_sequence = extract_words(test_string);
P1 = NgramCharModel(1, word_sequence);

@test (length(P1.dict) == length(Set(collect(test_string))));

@test (all((function(c::Char)
                return haskey(P1.dict, (c,));
            end),
            Set(collect(test_string))));

test_string = "bigram";
word_sequence = extract_words(test_string);
P2 = NgramCharModel(2, word_sequence);
expected_bigrams = Dict([Pair((' ', 'b'), 1),
                        Pair(('b', 'i'), 1),
                        Pair(('i', 'g'), 1),
                        Pair(('g', 'r'), 1),
                        Pair(('r', 'a'), 1),
                        Pair(('a', 'm'), 1)]);

@test (length(P2.dict) == length(expected_bigrams));

@test (all(haskey(P2.dict, key) for key in keys(expected_bigrams)));

@test (all((P2.dict[key] == expected_bigrams[key]) for key in keys(expected_bigrams)));

test_string = "trigram";
word_sequence = extract_words(test_string);
P3 = NgramCharModel(3, word_sequence);
expected_trigrams = Dict([Pair((' ', 't', 'r'), 1),
                        Pair(('t', 'r', 'i'), 1),
                        Pair(('r', 'i', 'g'), 1),
                        Pair(('i', 'g', 'r'), 1),
                        Pair(('g', 'r', 'a'), 1),
                        Pair(('r', 'a', 'm'), 1)]);

@test (length(P3.dict) == length(expected_trigrams));

@test (all(haskey(P3.dict, key) for key in keys(expected_trigrams)));

@test (all((P3.dict[key] == expected_trigrams[key]) for key in keys(expected_trigrams)));

test_string = "trigram trigram trigram";
word_sequence = extract_words(test_string);
P3 = NgramCharModel(3, word_sequence);
expected_trigrams = Dict([Pair((' ', 't', 'r'), 3),
                        Pair(('t', 'r', 'i'), 3),
                        Pair(('r', 'i', 'g'), 3),
                        Pair(('i', 'g', 'r'), 3),
                        Pair(('g', 'r', 'a'), 3),
                        Pair(('r', 'a', 'm'), 3)]);

@test (length(P3.dict) == length(expected_trigrams));

@test (all(haskey(P3.dict, key) for key in keys(expected_trigrams)));

@test (all((P3.dict[key] == expected_trigrams[key]) for key in keys(expected_trigrams)));

# Base.Test tests for encoding
@test (shift_encode("This is a secret message.", 17) == "Kyzj zj r jvtivk dvjjrxv.");

@test (rot13("Hello, world!") == "Uryyb, jbeyq!");

@test (reduce(*, map((function(c::Char)
                            if (c == ' ')
                                return String(['s', ' ', c]);
                            else
                                return String([c]);
                            end
                        end),
                        collect("orange apple lemon "))) == "oranges  apples  lemons  ");

# Base.Test tests for decoding
flatland = read("./aima-data/EN-text/flatland.txt", String);
ring = ShiftCipherDecoder(flatland);

@test (decode_text(ring, "Kyzj zj r jvtivk dvjjrxv.") == "This is a secret message.");

@test (decode_text(ring, rot13("Hello, world!")) == "Hello, world!");

gutenberg = read("./aima-data/gutenberg.txt", String);
pd = PermutationCipherDecoder(canonicalize_text(gutenberg));

@test (decode_text(pd, "aba") in ("ece", "ete", "tat", "tit", "txt"));

pd = PermutationCipherDecoder(canonicalize_text(flatland));

@test (decode_text(pd, "aba") in ("ded", "did", "ece", "ele", "eme", "ere", "eve", "eye", "iti", "mom", "ses", "tat", "tit"));

# Base.Test tests for generating arrays of bigrams
@test (bigrams("this") == [('t', 'h'), ('h', 'i'), ('i', 's')]);

@test (bigrams(["this", "is", "a", "test"]) == [("this", "is"), ("is", "a"), ("a", "test")]);

flatland = String(read("./aima-data/EN-text/flatland.txt"));
word_sequence = extract_words(flatland);
P = UnigramWordModel(word_sequence);
segmented_text, p = viterbi_text_segmentation("itiseasytoreadwordswithoutspaces", P);

@test (segmented_text == ["it", "is", "easy", "to", "read", "words", "without", "spaces"]);

# Base.Test tests for IR systems
uc = UnixConsultant();

function check_query(irs::T, results::AbstractVector, expected::AbstractVector) where {T <: AbstractInformationRetrievalSystem}
    @test (length(results) == length(expected));
    for (i, (score, id)) in enumerate(results)
        expected_score, expected_url = expected[i];
        @test (@sprintf("%.4f", score) == @sprintf("%.4f", expected_score));
        @test (basename(irs.documents[id].url) == basename(expected_url));
    end
    nothing;
end

check_query(uc,
            execute_query(uc, "how do I remove a file"),
            [(0.7683, "aima-data/MAN/rm.txt"),
            (0.6783, "aima-data/MAN/tar.txt"),
            (0.6779, "aima-data/MAN/cp.txt"),
            (0.6658, "aima-data/MAN/zip.txt"),
            (0.6458, "aima-data/MAN/gzip.txt"),
            (0.6374, "aima-data/MAN/pine.txt"),
            (0.6295, "aima-data/MAN/shred.txt"),
            (0.5746, "aima-data/MAN/pico.txt"),
            (0.4338, "aima-data/MAN/login.txt"),
            (0.4193, "aima-data/MAN/ln.txt")]);

check_query(uc,
            execute_query(uc, "how do I delete a file"),
            [(0.7547, "aima-data/MAN/diff.txt"),
            (0.6912, "aima-data/MAN/pine.txt"),
            (0.6356, "aima-data/MAN/tar.txt"),
            (0.6063, "aima-data/MAN/zip.txt"),
            (0.5746, "aima-data/MAN/pico.txt"),
            (0.5128, "aima-data/MAN/shred.txt"),
            (0.2672, "aima-data/MAN/tr.txt")]);

check_query(uc,
            execute_query(uc, "email"),
            [(0.1839, "aima-data/MAN/pine.txt"),
            (0.1201, "aima-data/MAN/info.txt"),
            (0.0989, "aima-data/MAN/pico.txt"),
            (0.0873, "aima-data/MAN/grep.txt"),
            (0.0807, "aima-data/MAN/zip.txt")]);

check_query(uc,
            execute_query(uc, "word count for files"),
            [(1.2815, "aima-data/MAN/grep.txt"),
            (0.9420, "aima-data/MAN/find.txt"),
            (0.8171, "aima-data/MAN/du.txt"),
            (0.5545, "aima-data/MAN/ps.txt"),
            (0.5342, "aima-data/MAN/more.txt"),
            (0.4200, "aima-data/MAN/dd.txt"),
            (0.1285, "aima-data/MAN/who.txt")]);

if (!Sys.iswindows())  # Windows 7/8 does not install a date executable by default
    check_query(uc, execute_query(uc, "learn: date"), []);
end

check_query(uc,
            execute_query(uc, "2003"),
            [(0.1458, "aima-data/MAN/pine.txt"),
            (0.1162, "aima-data/MAN/jar.txt")]);

max_stars_repo_name: mikhail-j/aima-julia

max_issues_repo_name: mikhail-j/aima-julia

max_forks_repo_name: mikhail-j/aima-julia


text:
function meta_walk(metapairs)
    return walker_end_point_meta(
            metapairs[1],
            metapairs[2],
            metapairs[3],
            metapairs[4])
end

# Generate a dataframe from several walks
function meta_path(wd::Vector{Any})
    totallength = size(wd,1)
    Nwalks = size(wd[1],1)
    NN = totallength*Nwalks

    completeSet = [wd[i][j] for i in 1:totallength for j in 1:Nwalks]

    arrayData = [[completeSet[i].nwalks completeSet[i].timesteps completeSet[i].rate completeSet[i].width completeSet[i].length completeSet[i].x completeSet[i].y] for i in 1:NN]

    arrDF = reduce(vcat,arrayData)

    return DataFrame(arrDF,[:nwalks, :timesteps, :rate, :width, :length, :x, :y])
end


text:
module LoadSave

using LogicCircuits
using ...ProbabilisticCircuits

include("circuit_line_compiler.jl")
include("circuit_loaders.jl")
include("circuit_savers.jl")
include("plot.jl")

end


text:
#function createCanon(u₀::Float64;hg=nothing, vg=nothing, zerotage=nothing,tc=nothing, lw=nothing,X2w=nothing, θₜ=0.0, ξₜ=0.0 )
 #    Canon(u₀,hg, vg, zerotage,tc, lw,X2w, θₜ, ξₜ )
#end
"""
    createTourelle(θ,ξ;optional arguments)

Creates an object turrets with elevation angle = θ and azimuth angle = ξ (in degrees).
optional arguments are angles (in degrees): Φ, Ψ, χ and rwY.
"""
function createTourelle(θ::Float64, ξ::Float64; Φ=nothing, ψ=nothing, χ=nothing, rWY=nothing)
    Tourelle(θ, ξ, Φ, ψ, χ, rWY)

end

#function createTarget(ρ::Float64)
#    Target(ρ)
#end
"""
    createProjectile(mass,Calibre;optional arguments)

Creates an object projectile with mass (kg), calibre (m).
optional arguments are: position, velocity, time of flight, moment of inertia, distance to the center of gravity.
"""
function createProjectile(mass::Float64, calibre::Float64; position=nothing, velocity =nothing, tof =nothing, Ix=nothing, Iy = nothing, Xcg=nothing)
    Projectile(mass, calibre, position, velocity, tof, Ix,Iy, Xcg)
end


text:
function index(x::NamedTuple{L}) where L
    indexes = ntuple(identity, length(L))
    return NamedTuple{L, typeof(indexes)}(indexes)
end

function adjancy_matrix(proj::Project)
    task_index = index(proj.task_durations)
    n = length(proj.task_durations)
    adjmat = zeros(n, n)
    for (src, dest) in proj.links
        ii = task_index[src]
        jj = task_index[dest]
        adjmat[ii, jj] = 1
    end
    return adjmat
end

chart_duration_string(duration) = string(duration)
function chart_duration_string(duration::Tuple{<:Any})
    return if iszero(only(duration))
        ""
    else
        string(only(duration))
    end
end
function chart_duration_string(dur::PertBeta)
    return string(
        "(", minimum(dur), "|", mode(dur), "|", maximum(dur), ")",
        "\n", round(mean(dur); digits=2),
    )
end

function chart_task(name, duration; fontsize=2)
    str = "$name\n" * chart_duration_string(duration)
    return compose(
        context(),
        (
            context(;order=1),
            stroke("blue"),
            # flattened hexegon
            polygon([(0, 0), (1, 1.5), (4, 1.5), (5, 0), (4, -1.5), (1, -1.5),]),
            fill("white"), fillopacity(1.0)
        ),
        (
             # rectanle in the hexegon
            context(1, -1, 3, 2; order=2, units=UnitBox(0, 0, 10, 10)),
            text(5, 2, str, hcenter, vtop),
            Compose.fontsize(fontsize),
            #rectangle(), fill("red"),
        ),
    )
end

"""
    visualize_chart(proj::Project; fontsize=2)

Visualizes the project as a PERT Chart.
Each task is places to the right of the tasks that much be completed first.
"""
function visualize_chart(proj; fontsize=2)
    graph = SimpleDiGraph(ProjectManagement.adjancy_matrix(proj))
    xs_original, ys_original = solve_positions(Zarate(), graph)
    x_factor = 9
    y_factor = 6
    xs = x_factor .* xs_original
    ys = y_factor .* ys_original
    x_min, x_max = extrema(xs)
    y_min, y_max = extrema(ys)
    box_max = max(x_max-x_min+2x_factor, y_max-y_min+2y_factor)

    set_default_graphic_size(22cm, 22cm)
    return compose(
        context(; units=UnitBox(x_min-x_factor, y_min-y_factor, box_max, box_max)),
        (
            context(; order=2),
            map(keys(proj.task_durations), proj.task_durations, xs, ys) do name, duration, x, y
                compose(
                    context(x, y),
                    chart_task(name, duration; fontsize=fontsize),
                )
            end...,
        ),
        (
            context(2.5, 0; order=1),
            line(map(edges(graph)) do edge
                [(xs[edge.src], ys[edge.src]), (xs[edge.dst], ys[edge.dst])]
            end),
            stroke("green"),
        )
    )
end


text:
export Line, PeriodicLine

## Standard interval

# angle is π*a where a is (false==0) and (true==1)
# or ranges from (-1,1].  We use 1 as 1==true.

immutable Line{angle,T<:Number} <: IntervalDomain{T}
    center::T
    α::Float64
    β::Float64

    #TODO get this inner constructor working again.
    Line(c,α,β)=new(c,α,β)
    Line(c)=new(c,-1.,-1.)
    Line()=new(zero(T),-1.,-1.)
end

typealias RealLine{T} Union{Line{false,T},Line{true,T}}

@compat (::Type{Line{a}}){a}(c,α,β) = Line{a,typeof(c)}(c,α,β)
@compat (::Type{Line{a}}){a}(c::Number) = Line{a,typeof(c)}(c)
@compat (::Type{Line{a}}){a}() = Line{a,Float64}()

Base.angle{a}(d::Line{a}) = a*π

Base.reverse(d::Line{true}) = Line{false}(d.center,d.β,d.α)
Base.reverse(d::Line{false}) = Line{true}(d.center,d.β,d.α)
Base.reverse{a}(d::Line{a}) = Line{a-1}(d.center,d.β,d.α)

# ensure the angle is always in (-1,1]
Line(c,a,α,β) = Line{mod(a/π-1,-2)+1,typeof(c)}(c,α,β)
Line(c,a) = Line(c,a,-1.,-1.)
# true is negative orientation, false is positive orientation
# this is because false==0 and we take angle=0
Line(b::Bool) = Line{b}()
Line() = Line(false)

isambiguous(d::Line)=isnan(d.center)
Base.convert{a,T<:Number}(::Type{Line{a,T}},::AnyDomain)=Line{a,T}(NaN)
Base.convert{IT<:Line}(::Type{IT},::AnyDomain)=Line(NaN,NaN)

## Map interval

##TODO non-1 alpha,beta

function line_tocanonical(α,β,x)

    @assert α==β==-1. || α==β==-.5

    if α==β==-1.
        2x./(1+sqrt(1+4x.^2))
    elseif α==β==-.5
        x./sqrt(1 + x.^2)
    end
end

function line_tocanonicalD(α,β,x)
    @assert α==β==-1. || α==β==-.5

    if α==β==-1.
        2./(1+4x.^2+sqrt(1+4x.^2))
    elseif α==β==-.5
        (1 + x.^2).^(-3/2)
    end
end
function line_fromcanonical(α,β,x)
    #TODO: why is this consistent?
    if α==β==-1.
        x./(1-x.^2)
    else
        x.*(1 + x).^α.*(1 - x).^β
    end
end
function line_fromcanonicalD(α,β,x)
    if α==β==-1.
        (1+x.^2)./(1-x.^2).^2
    else
        (1 - (β-α)x - (β+α+1)x.^2).*(1+x).^(α-1).*(1-x).^(β-1)
    end
end

function line_invfromcanonicalD(α,β,x)
    if α==β==-1.
        (1-x.^2).^2./(1+x.^2)
    else
        1./(1 - (β-α)x - (β+α+1)x.^2).*(1+x).^(1-α).*(1-x).^(1-β)
    end
end

tocanonical(d::Line,x)=line_tocanonical(d.α,d.β,cis(-angle(d)).*(x-d.center))
tocanonical(d::Line{false},x)=line_tocanonical(d.α,d.β,x-d.center)
tocanonical(d::Line{true},x)=line_tocanonical(d.α,d.β,d.center-x)

tocanonicalD(d::Line,x)=cis(-angle(d)).*line_tocanonicalD(d.α,d.β,cis(-angle(d)).*(x-d.center))
tocanonicalD(d::Line{false},x)=line_tocanonicalD(d.α,d.β,x-d.center)
tocanonicalD(d::Line{true},x)=-line_tocanonicalD(d.α,d.β,d.center-x)

fromcanonical(d::Line,v::AbstractArray)=eltype(d)[fromcanonical(d,vk) for vk in v]
fromcanonical(d::Line{false},v::AbstractArray)=eltype(d)[fromcanonical(d,vk) for vk in v]
fromcanonical(d::Line{true},v::AbstractArray)=eltype(d)[fromcanonical(d,vk) for vk in v]
fromcanonical(d::Line,x)=cis(angle(d))*line_fromcanonical(d.α,d.β,x)+d.center
fromcanonical(d::Line{false},x)=line_fromcanonical(d.α,d.β,x)+d.center
fromcanonical(d::Line{true},x)=-line_fromcanonical(d.α,d.β,x)+d.center

fromcanonicalD(d::Line,x)=cis(angle(d))*line_fromcanonicalD(d.α,d.β,x)
fromcanonicalD(d::Line{false},x)=line_fromcanonicalD(d.α,d.β,x)
fromcanonicalD(d::Line{true},x)=-line_fromcanonicalD(d.α,d.β,x)

invfromcanonicalD(d::Line,x)=cis(-angle(d))*line_invfromcanonicalD(d.α,d.β,x)
invfromcanonicalD(d::Line{false},x)=line_invfromcanonicalD(d.α,d.β,x)
invfromcanonicalD(d::Line{true},x)=-line_invfromcanonicalD(d.α,d.β,x)

=={a}(d::Line{a},m::Line{a}) = d.center == m.center && d.β == m.β &&d.α == m.α

# algebra
*(c::Real,d::Line{false})=Line{sign(c)>0?false:true}(isapprox(d.center,0)?d.center:c*d.center,d.α,d.β)
*(c::Real,d::Line{true})=Line{sign(c)>0?true:false}(isapprox(d.center,0)?d.center:c*d.center,d.α,d.β)
*(c::Number,d::Line)=Line(isapprox(d.center,0)?d.center:c*d.center,angle(d)+angle(c),d.α,d.β)
*(d::Line,c::Number)=c*d
for OP in (:+,:-)
    @eval begin
        $OP{a}(c::Number,d::Line{a})=Line{a}($OP(c,d.center),d.α,d.β)
        $OP{a}(d::Line{a},c::Number)=Line{a}($OP(d.center,c),d.α,d.β)
    end
end

## Periodic line

# angle is (false==0) and π (true==1)
# or ranges from (-1,1]
immutable PeriodicLine{angle,T} <: PeriodicDomain{Float64}
    center::T
    L::Float64
    PeriodicLine(c,L)=new(c,L)
    PeriodicLine(c)=new(c,1.)
    PeriodicLine()=new(0.,1.)
end

Base.convert{a}(::Type{PeriodicLine{a}},c,L)=PeriodicLine{a,typeof(c)}(c,L)

PeriodicLine(c,a)=PeriodicLine{a/π,eltype(c)}(c,1.)
PeriodicLine()=PeriodicLine{false,Float64}(0.,1.)
PeriodicLine(b::Bool)=PeriodicLine{b,Float64}()

isambiguous(d::PeriodicLine)=isnan(d.center) && isnan(d.angle)
Base.convert{T<:Number,TT}(::Type{PeriodicLine{T,TT}},::AnyDomain)=PeriodicLine{T,TT}(NaN,NaN)
Base.convert{IT<:PeriodicLine}(::Type{IT},::AnyDomain)=PeriodicLine(NaN,NaN)

Base.angle{a}(d::PeriodicLine{a})=a*π

Base.reverse(d::PeriodicLine{true})=PeriodicLine{false}(d.center,d.L)
Base.reverse(d::PeriodicLine{false})=PeriodicLine{true}(d.center,d.L)
Base.reverse{a}(d::PeriodicLine{a})=PeriodicLine{a-1}(d.center,d.L)

tocanonical(d::PeriodicLine{false},x)= 2atan((x-d.center)/d.L)
fromcanonical(d::PeriodicLine{false},v::AbstractArray)=eltype(d)[fromcanonical(d,vk) for vk in v]
fromcanonical(d::PeriodicLine{false},θ)=d.L*tan(θ/2) + d.center

tocanonical{a}(d::PeriodicLine{a},x)=tocanonical(PeriodicLine{false,Float64}(0.,d.L),exp(-π*im*a)*(x-d.center))
fromcanonical{a}(d::PeriodicLine{a},v::AbstractArray)=eltype(d)[fromcanonical(d,vk) for vk in v]
fromcanonical{a}(d::PeriodicLine{a},x)=exp(π*im*a)*fromcanonical(PeriodicLine{false,Float64}(0.,d.L),x)+d.center

function invfromcanonicalD(d::PeriodicLine{false})
    @assert d.center==0  && d.L==1.0
    a=Fun([1.,0,1],PeriodicInterval())
end

mappoint(a::PeriodicLine{false},b::Circle,x)=b.radius*((a.L*im-(x-a.center))./(a.L*im+(x-a.center)))+b.center
function mappoint(b::Circle,a::PeriodicLine{false},x)
    y=(x-b.center)./b.radius
    a.center+a.L*im*(1-y)./(y+1)
end

# algebra
*(c::Number,d::PeriodicLine)=PeriodicLine(isapprox(d.center,0)?d.center:c*d.center,angle(d)+angle(c))
*(d::PeriodicLine,c::Number)=c*d
for OP in (:+,:-)
    @eval begin
        $OP{a,T}(c::Number,d::PeriodicLine{a,T})=PeriodicLine{a,promote_type(eltype(c),T)}($OP(c,d.center),d.L)
        $OP{a,T}(d::PeriodicLine{a,T},c::Number)=PeriodicLine{a,promote_type(eltype(c),T)}($OP(d.center,c),d.L)
    end
end

arclength(d::Union{Line,PeriodicLine}) = Inf
Base.first(d::Union{Line,PeriodicLine})= -Inf
Base.last(d::Union{Line,PeriodicLine})= Inf
complexlength(d::Union{Line,PeriodicLine})=Inf

## vectorized

for typ in (:Line,:PeriodicLine)
    @eval function ($typ)(d::AbstractVector)
        @assert length(d) == 2
        @assert abs(d[1]) == abs(d[2]) == Inf

        if isa(d[1],Real) && isa(d[2],Real)
            if d[1]==Inf
                @assert d[2]==-Inf
                $typ(true)
            else
                @assert d[1]==-Inf&&d[2]==Inf
                $typ(false)
            end
        elseif abs(real(d[1])) < Inf
            @assert real(d[1])==real(d[2])
            @assert sign(imag(d[1]))==-sign(imag(d[2]))

            $typ(real(d[2]),angle(d[2]))
        elseif isnan(real(d[1])) && isnan(real(d[2]))  # hack for -im*Inf
            $typ([imag(d[1])*im,imag(d[2])*im])
        elseif isnan(real(d[1]))  # hack for -im*Inf
            $typ([real(d[2])+imag(d[1])*im,d[2]])
        elseif isnan(real(d[2]))  # hack for -im*Inf
            $typ([d[1],real(d[1])+imag(d[2])*im])
        elseif abs(imag(d[1])) < Inf
            @assert imag(d[1])==imag(d[2])
            @assert sign(real(d[1]))==-sign(real(d[2]))

            $typ(imag(d[2]),angle(d[2]))
        else
            @assert angle(d[2]) == -angle(d[1])

            $typ(0.,angle(d[2]))
        end
    end
end


text:
database = MakieGallery.load_database(["short_tests.jl"]);

filter!(database) do example
    !("3d" ∈ example.tags)
end

format_save_path = joinpath(@__DIR__, "test_formats")
isdir(format_save_path) && rm(format_save_path, recursive = true)
mkpath(format_save_path)
savepath(uid, fmt) = joinpath(format_save_path, "$uid.$fmt")

@testset "Saving formats" begin
    for example in database
        scene = MakieGallery.eval_example(example)
        for fmt in ("png", "pdf", "svg")
            @test try
                save(savepath(example.unique_name, fmt), scene)
                true
            catch e
                @warn "Saving $(example.unique_name) in format `$fmt` failed!" exception=(e, Base.catch_backtrace())
                false
            end
        end
    end
end


text:
# Description of a vibronic system.

"""
The system has non-zero off-diagonal elements that couple the surfaces.
"""
struct SurfaceCouplingException <: Exception end

"""
System of `M` coupled harmonic oscillators (modes) across `S` surfaces.
"""
abstract type System{S,M} end

"""
    diag(sys::System{S,M})

Extract the diagonal part of `sys` as a `DiagonalSystem{S,M}`.
"""
function diag end

"""
    isdiag(sys::System)

Determine whether `sys` is diagonal (in content, rather than type or
structure). Returns `true` if there is no coupling between surfaces and `false`
otherwise.
"""
function isdiag end

include("system_dense.jl")
include("system_diagonal.jl")
include("system_io.jl")

function Base.:(==)(sys1::System{S,M}, sys2::System{S,M}) where {S,M}
    sys1.energy == sys2.energy || return false
    sys1.freq == sys2.freq || return false
    sys1.lin == sys2.lin || return false
    sys1.quad == sys2.quad || return false
    true
end

function Base.isapprox(sys1::System{S,M}, sys2::System{S,M}; kwargs...) where {S,M}
    isapprox(sys1.energy, sys2.energy; kwargs...) || return false
    isapprox(sys1.freq, sys2.freq; kwargs...) || return false
    isapprox(sys1.lin, sys2.lin; kwargs...) || return false
    isapprox(sys1.quad, sys2.quad; kwargs...) || return false
    true
end

"""
    DenseSystem(sys::DiagonalSystem)

Create a dense system from the diagonal system `sys`.
"""
DenseSystem(sys::DiagonalSystem) = DenseSystem(sys.energy, sys.freq, sys.lin, sys.quad)

"""
    DiagonalSystem(sys::DenseSystem)

Create a diagonal system from the dense system `sys`.

If there is any coupling between surfaces, `SurfaceCouplingException` is
thrown.
"""
function DiagonalSystem(sys::DenseSystem)
    isdiag(sys) || throw(SurfaceCouplingException())
    diag(sys)
end

"""
    isdiag(energy::AbstractMatrix{Float64}, lin::AbstractArray{Float64,3}, quad::AbstractArray{Float64,4})

Determine whether the component tensors of a system are diagonal in surfaces.
"""
function isdiag(energy::AbstractMatrix{Float64}, lin::AbstractArray{Float64,3}, quad::AbstractArray{Float64,4})
    isdiag(energy) || return false
    for m in 1:size(lin, 1)
        isdiag(lin[m, :, :]) || return false
    end
    for m1 in 1:size(quad, 2)
        for m2 in 1:size(quad, 1)
            isdiag(quad[m2, m1, :, :]) || return false
        end
    end
    true
end

"""
    check_shape(S::Int, M::Int, energy::AbstractMatrix{Float64}, freq::AbstractMatrix{Float64}, lin::AbstractArray{Float64,3}, quad::AbstractArray{Float64,4})

Throw the appropriate exception if the component tensors of a system have the
wrong shape.
"""
function check_shape(S::Int, M::Int, energy::AbstractMatrix{Float64}, freq::AbstractMatrix{Float64}, lin::AbstractArray{Float64,3}, quad::AbstractArray{Float64,4})
    S >= 1 || throw(DomainError(S, "At least 1 surface."))
    M >= 1 || throw(DomainError(M, "At least 1 mode."))
    size(energy) == (S, S) || throw(DomainError(size(energy), "Expected energy dimensions: $(S), $(S)."))
    size(freq) == (M, S) || throw(DomainError(size(freq), "Expected freq dimensions: $(M), $(S)."))
    size(lin) == (M, S, S) || throw(DomainError(size(lin), "Expected lin dimensions: $(M), $(S), $(S)."))
    size(quad) == (M, M, S, S) || throw(DomainError(size(quad), "Expected quad dimensions: $(M), $(M), $(S), $(S)."))
    nothing
end

"""
    simplify(sys::System)

Generate a simplified version of `sys` with no quadratic coupling.
"""
function simplify(sys::T) where {T<:System}
    T(sys.energy, sys.freq, sys.lin, zero(sys.quad))
end

"""
    issimple(sys::System)

Determine whether `sys` is simple (has no quadratic coupling).
"""
issimple(sys::System) = iszero(sys.quad)

"""
    potential(sys::System, qs::Vector{Float64})

Compute the potential matrix (full Hamiltonian without kinetic energy) for
`sys` at `qs`.
"""
function potential(sys::System{S,M}, qs::Vector{Float64}) where {S,M}
    V = copy(sys.energy)
    for s1 in 1:S
        for s2 in 1:S
            for m in 1:M
                if s1 == s2
                    V[s2, s1] += 0.5 * sys.freq[m] * qs[m]^2
                end
                V[s2, s1] += sys.lin[m, s2, s1] * qs[m]
            end
            for m1 in 1:M
                for m2 in 1:M
                    V[s2, s1] += 0.5 * sys.quad[m2, m1, s2, s1] * qs[m2] * qs[m1]
                end
            end
        end
    end
    Symmetric(V)
end


text:
"""
NLPSaUT.jl

Wrapper to construct JuMP model for NLPs
"""
module NLPSaUT

using JuMP
using Ipopt
import FiniteDifferences

# generator for constraints
#include("generate_append_function.jl")

# functions to build model
include("memoize.jl")
include("append_equalityconstraints.jl")
include("append_inequalityconstraints.jl")
include("model.jl")
include("examples.jl")

# export functions
export memoize_fitness, memoize_fitness_gradient
export build_model, build_model!

end

max_stars_repo_name: Yuricst/NLPSaUT

max_issues_repo_name: Yuricst/NLPSaUT

max_forks_repo_name: Yuricst/NLPSaUT


text:
using JLD2

let
    # index of all violated cases in the first 1000 images.
    violation_idx = [25, 142, 179, 183, 283, 285, 387, 392, 612, 647, 737]
    # index of selected holding cases in the first 1000 images.
    satisfied_idx = [10, 100, 200, 300, 400, 500 ,600, 700, 800, 900, 999, 213, 391, 660, 911]
    @load "$(@__DIR__)/MNIST_1000.jld2" train_x train_y mnist_net

    function test_solver(solver, test_idx)
        for i in test_idx
            input_center = reshape(train_x[:,:,i], 28*28)
            label = train_y[i]
            A = zeros(Float64, 10, 10)
            A[diagind(A)] .= 1
            A[:, label+1] = A[:, label+1] .- 1
            A = A[1:end .!= label+1,:]

            b = zeros(Float64, 9)

            Y = HPolytope(A, b)
            pred = argmax(NeuralVerification.compute_output(mnist_net, input_center))-1

            if pred != label
                continue
            end

            epsilon = 1.0/255.0
            upper = input_center .+ epsilon
            lower = input_center .- epsilon
            clamp!(upper, 0.0, 1.0)
            clamp!(lower, 0.0, 1.0)
            X = Hyperrectangle(low=lower, high=upper)

            problem_mnist = Problem(mnist_net, X, Y)

            result =  solve(solver, problem_mnist)

            if result.status == :violated
                noisy = argmax(NeuralVerification.compute_output(mnist_net, result.counter_example))-1
                @test i ∈ violation_idx && noisy != pred
            elseif result.status == :holds
                @test i ∉ violation_idx
            end
        end
    end

    # there are 1000 iamges. We are only selecting a subset of those for these tests

    # NOTE the number of tests maybe different for different solvers.
    # Because the test cases depends on the result.
    @time @testset "MNIST, ReluVal holds" begin
        test_solver(ReluVal(max_iter = 10), satisfied_idx)
    end
    @time @testset "MNIST, ReluVal violated" begin
        test_solver(ReluVal(max_iter = 10), violation_idx)
    end
    @time @testset "MNIST, Neurify holds" begin
        test_solver(Neurify(max_iter = 10), satisfied_idx)
    end
    @time @testset "MNIST, Neurify violation" begin
        test_solver(Neurify(max_iter = 10), violation_idx)
    end
end


text:
"""
    have_trace_compile

`true` if Julia is running with `--trace-compile`
"""
function have_trace_compile()
    jloptstc = Base.JLOptions().trace_compile
    jloptstc == C_NULL && return false
    return true
end

"""
    force_trace_compile(::String)

Force the trace compile to be enabled for the given file.
Note: Make sure the given string is no GCed or bad stuff
will happen.
"""
function force_trace_compile(path::String)
    # find trace-compile field offset
    trace_compile_offset = 0
    for i = 1:fieldcount(Base.JLOptions)
        if fieldname(Base.JLOptions, i) === :trace_compile
            trace_compile_offset = fieldoffset(Base.JLOptions, i)
            break
        end
    end
    unsafe_store!(cglobal(:jl_options, Ptr{UInt8})+trace_compile_offset, pointer(path))
end

trace_compile_path() = unsafe_string(Base.JLOptions().trace_compile)
current_process_sysimage_path() = unsafe_string(Base.JLOptions().image_file)


text:
using Gtk
params = IselRobotParams(stepsPermm=160, serial_port="COM3") #/dev/ttyUSB0
rob = IselRobot(deviceID="iselRob", params=params, dependencies=Dict{String, Union{Device, Missing}}())

if ask_dialog("You are trying to start the Isel robot hardware test. Please ensure that the robot is turned on, is able to reference itself and can move at least 50mm in all positive directions. Please continue only if the robot is safe to move!","Cancel","Start test")
    if isReferenced(rob)
        @test ask_dialog("The robot says it is already referenced. This usually means that the robot has not been powered off since the last reference drive. Is this the case?")
    end

    @test state(rob)==:INIT
    @test dof(rob)==3
    @test namedPositions(rob)["origin"]==[0,0,0]u"mm"
    @test collect(keys(namedPositions(rob))) == ["origin"]

    setup(rob)
    @test state(rob)==:DISABLED

    @test_throws RobotStateError moveAbs(rob, [1,1,1]u"mm")
    @test_throws RobotStateError setup(rob)

    enable(rob)
    @test state(rob)==:READY
    moveRel(rob, [10,10,10]u"mm")
    @test ask_dialog("Did the robot move 10mm in all positive directions?")
    moveRel(rob, [10,0,0]u"mm")
    @test ask_dialog("Did the robot move 10mm in the positive x direction?")
    moveRel(rob, [0,10,0]u"mm")
    @test ask_dialog("Did the robot move 10mm in the positive y direction?")
    moveRel(rob, [0,0,10]u"mm")
    @test ask_dialog("Did the robot move 10mm in the positive z direction?")

    @test_throws RobotAxisRangeError moveRel(rob,[10,10,10]u"m")
    @test_throws RobotDOFError moveRel(rob,[10u"mm",10u"mm"])
    @test_throws RobotDOFError moveAbs(rob,[10u"mm",10u"mm"])

    doReferenceDrive(rob)
    @test isReferenced(rob)

    moveAbs(rob, [20,0,0]u"mm")
    @test ask_dialog("Did the robot move to [20mm,0mm,0mm]?")
    @test getPosition(rob)==[20,0,0]u"mm"

    moveAbs(rob, [50,0,0]u"mm")
    @test ask_dialog("Did the robot move to [50mm,0mm,0mm]?")
    @test getPosition(rob)==[50,0,0]u"mm"

    teachNamedPosition(rob, "pos1")
    @test issetequal(keys(namedPositions(rob)), ["origin", "pos1"])
    @test namedPositions(rob)["pos1"] == [50,0,0]u"mm"
    @test_throws RobotTeachError teachNamedPosition(rob, "pos1")

    moveAbs(rob, [10,10,10]u"mm",20u"mm/s")
    @test ask_dialog("Did the robot move faster to [10mm,10mm,10mm]?")
    @test getPosition(rob)==[10,10,10]u"mm"

    gotoPos(rob, "pos1")
    @test ask_dialog("Did the robot move to [50mm,0mm,0mm]?")
    @test getPosition(rob)==[50,0,0]u"mm"

    gotoPos(rob, "origin", 20u"mm/s")
    @test ask_dialog("Did the robot move faster to [0mm,0mm,0mm]?")

    moveAbs(rob, [20,20,20]u"mm",20u"mm/s")
    @test ask_dialog("Did the robot move faster to [20mm,20mm,20mm]?")
    @test getPosition(rob)==[20,20,20]u"mm"

    reset(rob)
    @test state(rob)==:INIT
    setup(rob)
    enable(rob)

    #@test isReferenced(rob) #resetting of the igus robot should not lose the referencing

    disable(rob)
    @test ask_dialog("Did everything seem normal?")
else
    @test_broken 0
end


text:
# Purposely not using "using"
using Revise

import Dates
import OilData    # purposely not using "using"

od = OilData

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

function find_start_date(io::IOStream)
    for line in eachline(io)
        if !startswith(line, "SECTION  Starting the simulation on")
            continue
        end
        line = strip(line)
        #tokens = split(line, x -> x in [' ', '.'], keepempty=false)
        tokens = split(line, [' ', '.'], keepempty=false)

        start_date = tokens[6] # "01-Feb-2019"

        return start_date
    end

    nothing
end
function find_start_date(path::String)
    open(path, "r") do io
        return find_start_date(io)
    end
end

function count_row(io::IOStream)
    # 1
    readline(io)
    # 1st dash line
    readline(io)
    # Summary
    readline(io)
    # 2nd dash line
    readline(io)

    # 4 lines of header
    readline(io)
    readline(io)
    readline(io)
    readline(io)
    # 3rd dash line
    readline(io)

    # start counting
    row_count = 0
    for line in eachline(io)
        if length(line) == 1 # line has only 1 char "1"
            return row_count
        end
        if !isempty(line)
            row_count += 1
        end
    end

    return row_count
end
function read_section(io::IOStream, row_count)
    # 1
    line = readline(io)

    # 1st dash line
    line = readline(io)

    # Summary
    line = readline(io)

    # 2nd dash line
    line = readline(io)

    col_width = 15
    # 1st ID
    line = readline(io)
    line = line[2:end] # remove 1st char (space)
    col_count = length(line) ÷ col_width

    ids = Vector{String}(undef, col_count)
    values = ids
    for col = 1:col_count
        start_ind = (col-1)*col_width + 1
        end_ind   = col*col_width
        token = line[start_ind:end_ind]
        values[col] = strip(token)
    end
    #@show ids
    # Unit
    line = readline(io)
    line = line[2:end] # remove 1st char (space)
    values = units = Vector{String}(undef, col_count)
    for col = 1:col_count
        start_ind = (col-1)*col_width + 1
        end_ind   = col*col_width
        token = line[start_ind:end_ind]
        values[col] = strip(token)
    end
    #@show units
    # FIELD/Well Name
    line = readline(io)
    line = line[2:end] # remove 1st char (space)
    values = id2s = Vector{String}(undef, col_count)
    for col = 1:col_count
        start_ind = (col-1)*col_width + 1
        end_ind   = col*col_width
        token = line[start_ind:end_ind]
        values[col] = strip(token)
    end
    #@show id2s
    # Reservoir ID?
    # Block ID: 23 12 11
    line = readline(io)
    line = line[2:end] # remove 1st char (space)
    values = id3s = Vector{String}(undef, col_count)
    for col = 1:col_count
        start_ind = (col-1)*col_width + 1
        end_ind   = col*col_width
        token = line[start_ind:end_ind]
        values[col] = strip(token)
    end
    #@show id3s
    # 3rd dash line
    line = readline(io)

    table = Array{Float64}(undef, row_count, col_count)
    # Read values
    for rind = 1:row_count
        line = readline(io)
        tokens = split(line, keepempty=false)
        for (cind,token) = enumerate(tokens)
            value = parse(Float64, token)
            table[rind,cind] = value
        end
    end
    #@show table
end
function read_rsm(io::IOStream, row_count)
    # count number of rows
    # actual read
    reservoir_id = 0
    section_id = 0
    more = true
    while !eof(io)
        read_section(io, row_count)
    end
    # for line in eachline(io)
    #     line = rstrip(line)
    #     if isempty(line)
    #         continue
    #     end
    #     if length(line) > 1
    #         continue
    #     end
    #     # Is this reservoir ID?
    #     reservoir_id = parse(Int, line)
    #     #if line == "1"
    #     section_id += 1
    #     println("Section $section_id")
    #     read_section(io)
    #     #end
    # end
end

begin
    prt_file = joinpath(pwd(), "Project-2_16_158.PRT")
    start_datetime = od.find_prt_start_date(prt_file)
    @info "Start Date: $start_datetime"
    #start_datetime = Dates.DateTime(start_date, Dates.dateformat"d-u-Y")
    #@info "Start DateTime: $start_datetime"
    end_datetime = od.add_day(start_datetime, 12.5)
    @info "End DateTime: $end_datetime"
end

begin
    rsm_file = joinpath(pwd(), "Project-2_16_158.RSM")
    row_count = 0
    open(rsm_file, "r") do io
        global row_count = count_row(io)
    end

    open(rsm_file, "r") do io
        read_rsm(io, row_count)
    end
end

nothing


text:
# TODO: add support for custom T

import Base.getindex

abstract Net

immutable DBN <: Net
    layers::Vector{RBM}
    layernames::Vector{AbstractString}
end

DBN{LT<:Tuple{AbstractString,RBM}}(namedlayers::Vector{LT}) =
    DBN(map(p -> p[2], namedlayers), map(p -> p[1], namedlayers))

immutable DAE <: Net
    layers::Vector{RBM}
    layernames::Vector{AbstractString}
end

function Base.show(io::IO, net::Net)
    nettype = string(typeof(net))
    layer_str = join(net.layernames, ",")
    print(io, "$nettype($layer_str)")
end

# DBN fields may change in the future, so it's worth to work through accessors
getname(net::Net, k::Int) = net.layernames[k]
getmodel(net::Net, k::Int) = net.layers[k]
function getmodel(net::Net, name::AbstractString)
    k = findfirst(lname -> lname == name, net.layernames)
    return (k != 0) ? net.layers[k] : error("No layer named '$name'")
end

# short syntax for accessing stored RBMs
getindex(net::Net, k::Int) = getmodel(net, k)
getindex(net::Net, name::AbstractString) = getmodel(net, name)
Base.length(net::Net) = length(net.layers)
Base.endof(net::Net) = length(net)

function hid_means_at_layer(net::Net, batch::Array{Float64, 2}, layer::Int)
    hiddens = Array(Array{Float64, 2}, layer)
    hiddens[1] = hid_means(net[1], batch)
    for k=2:layer
        hiddens[k] = hid_means(net[k], hiddens[k-1])
    end
    hiddens[end]
end

function transform(net::Net, X::Mat{Float64})
    return hid_means_at_layer(net, X, length(net))
end

function fit(dbn::DBN, X::Mat{Float64}; ctx = Dict{Any,Any}())
    @assert minimum(X) >= 0 && maximum(X) <= 1
    n_samples = size(X,2)
    batch_size = @get(ctx, :batch_size, 100)
    n_batches = round(Int, ceil(n_samples / batch_size))
    n_epochs = @get(ctx, :n_epochs, 10)
    reporter = @get_or_create(ctx, :reporter, TextReporter())
    for k = 1:length(dbn.layers)
        for epoch=1:n_epochs
            report(reporter, dbn, epoch, k)
            for i=1:n_batches
                batch = X[:, ((i-1)*batch_size + 1):min(i*batch_size, end)]
                input = k == 1 ? batch : hid_means_at_layer(dbn, batch, k-1)
                fit_batch!(dbn[k], input, ctx)
            end
        end
    end
end

fit{T}(dbn::DBN, X::Mat{T}; opts...) = fit(rbm, X, Dict(opts))

function invert(rbm::RBM)
    irbm = deepcopy(rbm)
    irbm.W = rbm.W'
    irbm.vbias = rbm.hbias
    irbm.hbias = rbm.vbias
    return irbm
end

function unroll(dbn::DBN)
    n = length(dbn)
    layers = Array(RBM, 2n)
    layernames = Array(String, 2n)
    layers[1:n] = dbn.layers
    layernames[1:n] = dbn.layernames
    for i=1:n
        layers[n+i] = invert(dbn[n-i+1])
        layernames[n+i] = getname(dbn, n-i+1) * "_inv"
    end
    return DAE(layers, layernames)
end


text:
include("src\\ConceptDrift.jl")

a = ConceptDrift.SineGenerator(1)
c = ConceptDrift.NaiveBayes(Dict(), Dict(), [])
for i in 1:1000000
    X,y = ConceptDrift.next_sample!(a, 1)
    ConceptDrift.partial_fit!(c, vec(X), y[1])
end

# a = ConceptDrift.SineGenerator(2)
right = 0
wrong = 0
for i in 1:100000
    X,y = ConceptDrift.next_sample!(a, 1)
    label = ConceptDrift.predict(c, vec(X))
    if label == y[1]
        right += 1
    else
        wrong += 1
    end
end

acc = right / (right + wrong)

o = ConceptDrift.NumericAttributeObserver()

ConceptDrift.update(o, 0.5, 1)
ConceptDrift.update(o, 0.75, 1)
ConceptDrift.update(o, 0.75, 0)
ConceptDrift.update(o, 1.75, 0)
ConceptDrift.update(o, 0.75, 0)
o
ConceptDrift.range(o)

ConceptDrift.get_split_point_suggestions(o)
ConceptDrift.estimated_weight_split(o.distribution_per_class[1], 0.25)

ConceptDrift.get_binary_split_class_distribution(o, 1.0)
split = ConceptDrift.get_best_split_suggestion(o, Dict(0=>3.0, 1=>2.0), 1)

ConceptDrift.branch_for_instance(split.split_test, [0.6, 1.0])

ConceptDrift.pdf(o.distribution_per_class[1], 0.625)
stdev = ConceptDrift.stdev(o.distribution_per_class[1])
pdf(Normal(o.distribution_per_class[1].mean, stdev), 0.25)

n = ConceptDrift.LearningNodeNB()
ConceptDrift.learn_one!(n, [0.5], 1)
ConceptDrift.learn_one!(n, [0.75], 1)
ConceptDrift.learn_one!(n, [0.75], 0)
ConceptDrift.learn_one!(n, [1.75], 0)
ConceptDrift.learn_one!(n, [0.75], 0)
n

ConceptDrift.get_best_split_suggestions(n)

ConceptDrift.predict_one(n, [0.5])

h = ConceptDrift.HoeffdingTree()
h
leaf = ConceptDrift.filter_instance_to_leaf(h.tree_root, [1.1, 0.1], nothing, -1)
ConceptDrift.learn_one!(leaf.node, [0.5, 1.1], 0)
ConceptDrift.partial_fit!(h, [0.5, 1.1], 0)
ConceptDrift.partial_fit!(h, [0.3, 1.1], 0)
ConceptDrift.partial_fit!(h, [0.4, 1.2], 0)
ConceptDrift.partial_fit!(h, [0.6, 0.8], 0)
ConceptDrift.partial_fit!(h, [1.5, 0.1], 1)
ConceptDrift.partial_fit!(h, [1.3, 0.1], 1)
ConceptDrift.partial_fit!(h, [1.4, 0.2], 1)
ConceptDrift.partial_fit!(h, [1.6, 0.3], 1)
ConceptDrift.predict(h, [1.4, 0.3])
ConceptDrift.predict(h, [0.4, 1.3])

a = ConceptDrift.SineGenerator(3)
c = ConceptDrift.HoeffdingTree()
for i in 1:1000000
    X,y = ConceptDrift.next_sample!(a, 1)
    ConceptDrift.partial_fit!(c, vec(X), y[1])
end

# a = ConceptDrift.SineGenerator(2)
right = 0
wrong = 0
for i in 1:100000
    X,y = ConceptDrift.next_sample!(a, 1)
    label = ConceptDrift.predict(c, vec(X))
    if label == y[1]
        right += 1
    else
        wrong += 1
    end
end

acc = right / (right + wrong)
c
leaf = ConceptDrift.filter_instance_to_leaf(h.tree_root, [1.1, 0.1], nothing, -1)
n = leaf.node
ConceptDrift.get_best_split_suggestions(n)

sort!(best_split_suggestions, by = x -> x.merit)
split_decision = best_split_suggestions[end]
split_decision.resulting_class_distribution
for (i, child_distribution) in enumerate(split_decision.resulting_class_distribution)
    child_distribution
end

ConceptDrift.hoeffding_bound(1, 0.000001, 0.3)

abstract type Asset end

abstract type Property <: Asset end
abstract type Investment <: Asset end
abstract type Cash <: Asset end

abstract type House <: Property end
abstract type Apartment <: Property end

abstract type FixedIncome <: Investment end
abstract type Equity <: Investment end
abstract type LiquidityStyle end
struct Residence <: House
    location
 end

 struct Stock <: Equity
     symbol
     name
 end

 struct TreasuryBill <: FixedIncome
     cusip
 end

 struct Money <: Cash
     currency
     amount
 end
struct IsLiquid <: LiquidityStyle end
struct IsIlliquid <: LiquidityStyle end
# Default behavior is illiquid
LiquidityStyle(::Type) = IsIlliquid()

# Cash is always liquid
LiquidityStyle(::Type{<:Cash}) = IsLiquid()

# Any subtype of Investment is liquid
LiquidityStyle(::Type{<:Investment}) = IsLiquid()
tradable(x::T) where {T} = tradable(LiquidityStyle(T), x)
tradable(::IsLiquid, x) = true
tradable(::IsIlliquid, x) = false

s = Stock(:TSLA, "tesla")
function  t(x::T) where {T}
    return LiquidityStyle(T)
end

r = t(s)
typeof(r)

include("src\\ConceptDrift.jl")
ConceptDrift.get_cat_value_from_onehot([0, 0, 1], 1, 0, 3)
ConceptDrift.get_cat_value_from_onehot([0, 1, 0], 1, 0, 3)
ConceptDrift.get_cat_value_from_onehot([1, 0, 0], 1, 0, 3)
ConceptDrift.get_cat_value_from_onehot([1, 0, 0, 0, 0, 1], 2, 0, 3)
ConceptDrift.get_cat_value_from_onehot([1, 0, 0, 0, 1, 0], 2, 0, 3)
ConceptDrift.get_cat_value_from_onehot([1, 0, 0, 1, 0, 0], 2, 0, 3)
ConceptDrift.get_cat_value_from_onehot([3, 1, 0, 0, 0, 0, 1], 3, 1, 3)
ConceptDrift.get_cat_value_from_onehot([3, 1, 0, 0, 0, 1, 0], 3, 1, 3)
ConceptDrift.get_cat_value_from_onehot([3, 1, 0, 0, 1, 0, 0], 3, 1, 3)

h = ConceptDrift.RandomTreeGenerator()

ConceptDrift.next_sample!(h, 1)


text:
using Brutus
using Test

@testset "Brutus" begin

@test Brutus.call(identity, 1) == identity(1)
@test Brutus.call(identity, 3.141592653) == identity(3.141592653)
# @test Brutus.call(identity, pi) == identity(pi)
@test Brutus.call(identity, true) == identity(true)

@test Brutus.call(+, 1, 2) == 1 + 2
@test Brutus.call(+, 1.1, 2.2) == 1.1 + 2.2
# @test Brutus.call(+, true, false) == true + false

constant1() = 1
@test Brutus.call(constant1) == constant1()
constant2() = 3.141592653
@test Brutus.call(constant2) == constant2()
constant3() = pi
@test Brutus.call(constant3) == constant3()
constant4() = true
@test Brutus.call(constant4) == constant4()

function gauss(N)
    acc = 0
    for i in 1:N
        acc += i
    end
    return acc
end
@test Brutus.call(gauss, 0) == gauss(0) == 0
@test Brutus.call(gauss, 1000) == gauss(1000) == 500500

@test Brutus.call(Brutus.call(identity, identity), identity)(identity) === identity

index(A, i) = A[i]
index(A, i, j) = A[i, j]
index(A, i, j, k) = A[i, j, k]

@testset "3D indexing" begin
    thunk = Brutus.thunk(index, Tuple{Array{Int,3}, Int, Int, Int})

    arr = Int[i*j*k for i in 1:3, j in 1:5, k in 1:7]
    for i in 1:3
        for j in 1:5
            for k in 1:7
                @test arr[i,j,k] == thunk(arr, i, j, k)
            end
        end
    end
end

function customsum(A)
    acc = 0
    for i in CartesianIndices(A)
        acc += A[i]
    end
    return acc
end
for array in [rand(Int64, 2, 3), rand(Int64, 2, 3)]
    for i in 1:length(array)
        @test Brutus.call(index, array, i) == index(array, i)
    end
    for i in CartesianIndices(array)
        i = convert(Tuple, i)
        @test Brutus.call(index, array, i...) == index(array, i...)
    end
    @test Brutus.call(customsum, array) == customsum(array)
end
end
# TODO: arrays with floating point elements

max_stars_repo_name: stephenneuendorffer/brutus

max_issues_repo_name: stephenneuendorffer/brutus

max_forks_repo_name: stephenneuendorffer/brutus


text:
using ReferenceFrameRotations
using LinearAlgebra

# x_ecef,v_ecefから軌道面座標系へのDCMを計算
function ecef_to_DCM(x_ecef,v_ecef,with_print=false)
    x_ecef_uniten = x_ecef / norm(x_ecef, 2)
    v_ecef_uniten = v_ecef / norm(v_ecef, 2)

    ez = - x_ecef_uniten
    ey = cross(v_ecef_uniten,x_ecef_uniten)
    ex = cross(ey,ez)

    C = inv_rotation(DCM([ex;ey;ez]))

    if with_print == true
        println("x_ecef_uniten:",x_ecef_uniten)
        println("v_ecef_uniten:",v_ecef_uniten)
        println("           ez:",ez)
        println("           ey:",ey)
        println("           ex:",ex)

        println("          DCM:",C)
    end
    return C
end

#=
# z軸、y軸からDCMを求める
function get_DCM_zy(z,y)
    ez = z / norm(z,2)
    ey = y / norm(y,2)

    ex = cross()
    ex = cross(ey,ez)

    C = inv_rotation(DCM([ex;ey;ez]))

    if with_print == true
        println("x_ecef_uniten:",x_ecef_uniten)
        println("v_ecef_uniten:",v_ecef_uniten)
        println("           ez:",ez)
        println("           ey:",ey)
        println("           ex:",ex)

        println("          DCM:",C)
    end
    return C

end
=#

#=
"""
# 方向余弦行列を用いてベクトルを変換
v = [2.0,0.0,0.0]
r = [1.0,1.0,1.0]

x = 1:10
@gp x x.^2 "w l tit 'Parabola'"
save("test2.gp")

C = getDCM(v,r)
println(C)
"""
=#

max_stars_repo_name: yui-project/ACSim

max_issues_repo_name: yui-project/ACSim

max_forks_repo_name: yui-project/ACSim