text:
#=##############################################################################
# DESCRIPTION
    Creation and processing of WAV audio files.

# AUTHORSHIP
  * Author    : Eduardo J. Alvarez and Tyler Critchfield
  * Email     : Edo.AlvarezR@gmail.com
  * Created   : Nov 2019
  * License   : MIT
=###############################################################################

"""
# ARGUMENTS
* `in_samplerate::Real`         : Sample rate of the input pressure data WAV
                                    file in Hz.

# OPTIONAL ARGUMENTS
* `out_samplerate::Real`        : Sample rate of the output WAV file in Hz.
* `secs::Real`                  : Total length of WAV file in seconds.
* `usefreqs::Real`              : Frequencies to use (see `sampleiFFT`).
"""
function generate_WAV(pressuredata::Array{T, 1}, in_samplerate::Real,
                        filename::String; path="", out_samplerate::Real=32000,
                        secs::Real=4.0, usefreqs::Real=0.75, plot_verif=false
                            ) where {T<:Real}

    # Calculate FFT coefficients
    fftdata = fft(pressuredata)
    # freqs = in_samplerate * (0:size(fftdata, 1)-1)/size(fftdata, 1)

    samples = out_samplerate*secs      # Number of samples on WAV file

    # Generate sound wave
    y = [sampleiFFT(i/out_samplerate, fftdata, in_samplerate;
                                    usefreqs=usefreqs) for i in 0:(samples-1)]

    # Normalizes the sound wave
    maxy = max(abs(maximum(y)), abs(minimum(y)))
    y[:] .= y/maxy

    # Write wave to WAV file
    WAV.wavwrite(y, joinpath(path, filename), Fs=out_samplerate)

    if plot_verif
        period = length(pressuredata)/in_samplerate  # Period of input wave
        maxt = 4*period                             # Maximum time to plot
        maxi = ceil(Int, maxt*out_samplerate)       # Max index to plot
        plt.plot((0:length(pressuredata)-1)/in_samplerate, pressuredata, "-r",
                                                                  label="Input")
        plt.plot((0:maxi-1)/out_samplerate-period, maxy*y[1:maxi], ",k",
                                                              label="Resampled")
        plt.xlabel("Time (s)")
        plt.ylabel("Amplitude")
        plt.legend(loc="center left", bbox_to_anchor=(1, 0.5), frameon=false)
    end

    return y, out_samplerate
end

"""
    `sampleiFFT(t, fftdata, fftsamplerate; usefreqs=1/3)`

Given the output `fftdata = FFTW.fft(data)` and the samplerate of `data`,
it will sample the inverse Fourier transform at the time `t` and return
the value in time domain.

`usefreqs` indicates what portion of the fft frequencies to sample, with
0 indicating none, 0.5. indicating the first half of them, and 1
indicating all of them.
"""
function sampleiFFT(t::Real, fftdata::Array{C, 1}, fftsamplerate::Real;
                        usefreqs::Real=1/3) where {C<:Complex}
    val = 0

    for (i, amp) in enumerate(fftdata)

        # This condition avoids including under-calculated freqs
        if i >= length(fftdata)*usefreqs
            break
        end

        # Sample this frequency
        freq = fftsamplerate*(i-1)/length(fftdata)
        val += real(amp)*cos(2*pi*t*freq) - imag(amp)*sin(2*pi*t*freq)

    end

    # NOTE: I'm not sure why I need to multiply by 2 here, but if I don't then
    # I get half the amplitude of the input
    return 2*val/length(fftdata)
end

max_stars_repo_name: byuflowlab/FLOWNoise

max_issues_repo_name: byuflowlab/FLOWNoise

max_forks_repo_name: byuflowlab/FLOWNoise


text:
using ElasticArrays

Base.push!(a::ElasticArray, x) = append!(a, x)
Base.push!(a::ElasticArray{T,1}, x) where {T} = append!(a, [x])
Base.empty!(a::ElasticArray) = ElasticArrays.resize_lastdim!(a, 0)

function Base.pop!(a::ElasticArray)
    if length(a) > 0
        last_frame_inds = length(a.data)-a.kernel_length.divisor+1:length(a.data)
        d = reshape(view(a.data, last_frame_inds), a.kernel_size)
        ElasticArrays.resize!(a.data, length(a.data) - a.kernel_length.divisor)
        d
    else
        @error "can not pop! from an empty ElasticArray"
    end
end

ext: jl


text:
include("temperature_converter.jl")

println("$(TemperatureConverter.as_celsius(100, :Celsius))") #> 100
println("$(TemperatureConverter.as_celsius(100, :Kelvin))") #> 373
println("$(TemperatureConverter.kelvin_to_celsius(0))") #> 273


text:
#
# Self - TMAO
#

using MDDF
using PDBTools
using Plots

# Here we use the PDBTools package to read the pdb file (from http://github.com/m3g/PDBTools)
atoms = PDBTools.readPDB("./structure.pdb")

# Self correlation, thus the solute and solvent indexes are identical
solute_indexes = PDBTools.select(atoms,"resname TMAO")
solute = MDDF.Selection( solute_indexes, natomspermol=14 )

# Input options for the calcualtion
options = MDDF.Options(binstep=0.2,n_random_samples=100)

# Run MDDF calculation, and get the resutls in the R structure
nlabel="lcP"
trajectory = MDDF.Trajectory("./trajectory.dcd",solute)
N = MDDF.mddf_linkedcells_parallel(trajectory,options)

trajectory = MDDF.Trajectory("./trajectory.dcd",solute)
R = MDDF.mddf_linkedcells_self(trajectory,options)

plot(layout=(4,1))

x = [0,10]
y = [1, 1]

sp=1
plot!(ylabel="MDDF or RDF",subplot=sp)
scatter!(R.d,R.mddf,subplot=sp,label="mddf")
scatter!(R.d,R.rdf,subplot=sp,label="rdf")
plot!(N.d,N.mddf,subplot=sp,label="mddf - $nlabel")
plot!(N.d,N.rdf,subplot=sp,label="rdf - $nlabel")
plot!(x,y)
plot!(legend=:topright,subplot=sp)

sp=2
plot!(ylabel="KB",subplot=sp)
scatter!(R.d,R.kb,subplot=sp,label="mddf")
scatter!(R.d,R.kb_rdf,subplot=sp,label="rdf")
plot!(N.d,N.kb,subplot=sp,label="mddf - $nlabel")
plot!(N.d,N.kb_rdf,subplot=sp,label="rdf - $nlabel")
plot!(legend=:topright,subplot=sp)

sp=3
plot!(ylabel="atom contrib",subplot=sp)
i = 5
plot!(R.d,R.solute_atom[:,i],subplot=sp,label="new",linewidth=2)
plot!(R.d,R.solvent_atom[:,i],subplot=sp,label="new",linewidth=2)
scatter!(N.d,N.solute_atom[:,i],subplot=sp,label="$nlabel")
scatter!(N.d,N.solvent_atom[:,i],subplot=sp,label="$nlabel")
y1 = similar(R.d)
y2 = similar(R.d)
for i in 1:size(R.d,1)
  y1[i] = sum(R.solvent_atom[i,:])
  y2[i] = sum(N.solvent_atom[i,:])
end
plot!(R.d,y1,subplot=sp,label="sum")
scatter!(R.d,y2,subplot=sp,label="sum")
plot!(legend=:topright,subplot=sp)

sp=4
plot!(ylabel="count",subplot=sp)
scatter!(R.d,R.md_count,subplot=sp,label="R md")
#scatter!(R.d,R.md_count_random,subplot=sp,label="R rand")
plot!(N.d,N.md_count,subplot=sp,label="N md")
#plot!(N.d,N.md_count_random,subplot=sp,label="N rand")
plot!(legend=:topleft,subplot=sp)

plot!(size=(600,800))
savefig("./example6.pdf")
savefig("./plots.pdf")


text:
# Tests for core functionality

using NonlinearEigenproblemsTest
using NonlinearEigenproblems
using LinearAlgebra
using Test,Random

struct TestNEP <: NEP
   dummy
end

@bench @testset "Core" begin

nep=nep_gallery("dep0");
n=size(nep,1);

## Mlincomb_tests
Random.seed!(0);
V=complex(randn(n,3));
λ=0.3+1im;
T=typeof(λ);
z1=compute_Mlincomb(nep,λ,V)
z2=compute_Mlincomb(nep,λ,V,[T(1),T(1),T(1)])
@test z1==z2
z3=compute_Mder(nep,λ,0)*V[:,1]+ compute_Mder(nep,λ,1)*V[:,2]+ compute_Mder(nep,λ,2)*V[:,3]
@test z1 ≈ z3

## Test Mlincomb starting counting with kth derivative
z2=compute_Mlincomb(nep,λ,V,[T(0),T(0),T(1)])
z3=compute_Mder(nep,λ,2)*V[:,3]
@test z2≈z3
z4=compute_Mlincomb(nep,λ,V[:,3],[T(1)], 2);
@test z3≈z4
#

## Simple coverage. Test that methods throw error if not defined
my_test_NEP = TestNEP([1 2; 3 4])
@test_throws ErrorException compute_Mder(my_test_NEP, 1+1im, 2)
#@test_throws MethodError compute_Mlincomb(my_test_NEP , 1+1im, [1 2; 1 4])
@test_throws ErrorException compute_MM(my_test_NEP, [1 2; 1 4], diagm(0 => [1,2]))
@test_throws ErrorException size(my_test_NEP,1)

end

@bench @testset "Types (sumnep)" begin
    A0 = ones(3, 3)
    A1 = Matrix(1.0I, 3, 3)
    nep1 = DEP([A0,A1])
    B0 = reverse(Matrix(1.0I, 3, 3), dims = 1)
    B1 = Matrix(3.0I, 3, 3) + ones(3,3)
    nep2 = PEP([B0,B1])
    λ = 1+1im
    sumnep = SumNEP(nep1, nep2)
    M = compute_Mder(sumnep, λ)
    M1 = compute_Mder(nep1, λ)
    M2 = compute_Mder(nep2, λ)
    @test (M1+M2) ≈ M
end

# compute_Mlincomb for PEPs
@bench @testset "compute_Mlincomb PEP" begin
    nep=nep_gallery("pep0");
    n=size(nep,1);
    Random.seed!(0);
    λ = rand()+rand()*im; V=randn(n,3); a=rand(3);
    # test against another way to compute Mlincomb
    z=compute_Mlincomb(nep,λ,V,a)
    z2=compute_Mlincomb_from_MM(nep,λ,V,a)
    @test norm(z-z2)<sqrt(eps())*100
    # test that the function compute_Mlincomb does not overwrite the input
    λ = 0; V=randn(n,3); W=copy(V); a=rand(3);
    z=compute_Mlincomb(nep,λ,V,a);
    # test against another way to compute Mlincomb
    z2=compute_Mlincomb_from_MM(nep,λ,V,a)
    @test norm(z-z2)<sqrt(eps())*100
end

# compute_Mlincomb for DEPs
@bench @testset "compute_Mlincomb DEP" begin
    Random.seed!(0);
    n=100; A1=rand(n,n); A2=rand(n,n); A3=rand(n,n);
    tau1=0; tau2=1.3; tau3=.1;
    nep=DEP([A1,A2,A3],[tau1,tau2,tau3])
    n=size(nep,1);
    λ = rand()+rand()*im; V=randn(n,3); a=rand(3);
    # test against another way to compute Mlincomb
    z=compute_Mlincomb(nep,λ,V,a)
    z2=compute_Mlincomb_from_MM(nep,λ,V,a)
    @test norm(z-z2)<sqrt(eps())*100
    # test that the function compute_Mlincomb does not overwrite the input
    λ = 0; V=randn(n,3); W=copy(V); a=rand(3);
    z=compute_Mlincomb(nep,λ,V,a);
    # test against another way to compute Mlincomb
    z2=compute_Mlincomb_from_MM(nep,λ,V,a)
    @test norm(z-z2)<sqrt(eps())*100
end

# compute_Mlincomb for DerSPMF
@bench @testset "compute_Mlincomb DerSPMF" begin
    A0=[1 3; 4 5]; A1=[3 4; 5 6];
    id_op=S -> one(S) # Note: We use one(S) to be valid both for matrices and scalars
    exp_op=S -> exp(S)
    nep=SPMF_NEP([A0,A1],[id_op,exp_op]);
    m=5; σ1=3.5; σ2=9;
    Dnep=DerSPMF(nep,σ1,m)
    DDnep=DerSPMF(Dnep,σ2,m)
    Random.seed!(0);
    V=randn(2,5);
    @test norm( compute_Mlincomb(nep,0,V)-compute_Mlincomb(Dnep,0,V) )<sqrt(eps())*100
    @test norm( compute_Mlincomb(nep,σ1,V)-compute_Mlincomb(Dnep,σ1,V) )<sqrt(eps())*100
    @test norm( compute_Mlincomb(nep,σ2,V)-compute_Mlincomb(Dnep,σ2,V) )<sqrt(eps())*100
    @test norm( compute_Mlincomb(nep,0,V)-compute_Mlincomb(DDnep,0,V) )<sqrt(eps())*100
    @test norm( compute_Mlincomb(nep,σ1,V)-compute_Mlincomb(DDnep,σ1,V) )<sqrt(eps())*100
    @test norm( compute_Mlincomb(nep,σ2,V)-compute_Mlincomb(DDnep,σ2,V) )<sqrt(eps())*100

    n=10;
    nep=nep_gallery("dep0",n)
    m=5; σ1=rand()+rand()*im; σ2=rand()+rand()*im;
    Dnep=DerSPMF(nep,σ1,m)
    DDnep=DerSPMF(Dnep,σ2,m)
    V=randn(n,5);
    @test norm( compute_Mlincomb(nep,0,V)-compute_Mlincomb(Dnep,0,V) )<sqrt(eps())*100
    @test norm( compute_Mlincomb(nep,σ1,V)-compute_Mlincomb(Dnep,σ1,V) )<sqrt(eps())*100
    @test norm( compute_Mlincomb(nep,σ2,V)-compute_Mlincomb(Dnep,σ2,V) )<sqrt(eps())*100
    @test norm( compute_Mlincomb(nep,0,V)-compute_Mlincomb(DDnep,0,V) )<sqrt(eps())*100
    @test norm( compute_Mlincomb(nep,σ1,V)-compute_Mlincomb(DDnep,σ1,V) )<sqrt(eps())*100
    @test norm( compute_Mlincomb(nep,σ2,V)-compute_Mlincomb(DDnep,σ2,V) )<sqrt(eps())*100
end


text:
include("help.jl")
function find_passage(r::Robot)
    side=Ost
    door_locator!(r,side)
end

inverse(side::HorizonSide) = HorizonSide(mod(Int(side)+2,4))
function door_locator!(r::Robot,side::HorizonSide)
    n = 1
    while (isborder(r,Nord) == true)
    m = 0
            while (m != n)
                move!(r,side)
                m+=1
            end
            n+=1
            side = invers(side)
    end

end

max_stars_repo_name: MCPavlo/mirea-progs

max_issues_repo_name: MCPavlo/mirea-progs

max_forks_repo_name: MCPavlo/mirea-progs


text:
### A Pluto.jl notebook ###
# v0.14.5

using Markdown
using InteractiveUtils

# ╔═╡ 7d1ac287-43d9-4921-bade-5658899648e6
using PlutoTest

# ╔═╡ 7728c7b0-b022-11eb-363f-5f51427038cb
md"""
# [Day 8: Matchsticks](https://adventofcode.com/2015/day/8)

Space on the sleigh is limited this year, and so Santa will be bringing his list as a digital copy. He needs to know how much space it will take up when stored.

It is common in many programming languages to provide a way to escape special characters in strings. For example, [C](https://en.wikipedia.org/wiki/Escape_sequences_in_C), [JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String), [Perl](http://perldoc.perl.org/perlop.html#Quote-and-Quote-like-Operators), [Python](https://docs.python.org/2.0/ref/strings.html), and even [PHP](http://php.net/manual/en/language.types.string.php#language.types.string.syntax.double) handle special characters in very similar ways.

However, it is important to realize the difference between the number of characters in the code representation of the string literal and the number of characters in the in-memory string itself.

For example:

-   `""` is `2` characters of code (the two double quotes), but the string contains zero characters.
-   `"abc"` is `5` characters of code, but `3` characters in the string data.
-   `"aaa\\"aaa"` is `10` characters of code, but the string itself contains six "a" characters and a single, escaped quote character, for a total of `7` characters in the string data.
-   `"\x27"` is `6` characters of code, but the string itself contains just one - an apostrophe (`'`), escaped using hexadecimal notation.

Santa's list is a file that contains many double-quoted string literals, one on each line. The only escape sequences used are `\\` (which represents a single backslash), `\"` (which represents a lone double-quote character), and `\x` plus two hexadecimal characters (which represents a single character with that ASCII code).

Disregarding the whitespace in the file, **what is the number of characters of code for string literals minus the number of characters in memory for the values of the strings in total for the entire file?**

For example, given the four strings above, the total number of characters of string code (`2 + 5 + 10 + 6 = 23`) minus the total number of characters in memory for string values (`0 + 3 + 7 + 1 = 11`) is `23 - 11 = 12`.
"""

# ╔═╡ 1e37e13a-5f56-478c-8d50-d96ea1e32cd3
puzzle_input2 = raw"""
"qxfcsmh"
"ffsfyxbyuhqkpwatkjgudo"
"byc\x9dyxuafof\\\xa6uf\\axfozomj\\olh\x6a"
"jtqvz"
"uzezxa\"jgbmojtwyfbfguz"
"vqsremfk\x8fxiknektafj"
"wzntebpxnnt\"vqndz\"i\x47vvjqo\""
"higvez\"k\"riewqk"
"dlkrbhbrlfrp\\damiauyucwhty"
"d\""
"qlz"
"ku"
"yy\"\"uoao\"uripabop"
"saduyrntuswlnlkuppdro\\sicxosted"
"tj"
"zzphopswlwdhebwkxeurvizdv"
"xfoheirjoakrpofles\"nfu"
"q\xb7oh\"p\xce\"n"
"qeendp\"ercwgywdjeylxcv"
"dcmem"
"\"i\x13r\"l"
"ikso\xdcbvqnbrjduh\"uqudzki\xderwk"
"wfdsn"
"pwynglklryhtsqbno"
"hcoj\x63iccz\"v\"ttr"
"zf\x23\\hlj\\kkce\\d\\asy\"yyfestwcdxyfj"
"xs"
"m\"tvltapxdvtrxiy"
"bmud"
"k\"a"
"b\"oas"
"\"yexnjjupoqsxyqnquy\"uzfdvetqrc"
"vdw\xe3olxfgujaj"
"qomcxdnd\"\\cfoe\""
"fpul"
"m\"avamefphkpv"
"vvdnb\\x\\uhnxfw\"dpubfkxfmeuhnxisd"
"hey\\"
"ldaeigghlfey"
"eure\"hoy\xa5iezjp\\tm"
"yygb\"twbj\\r\"\x10gmxuhmp\""
"weirebp\x39mqonbtmfmd"
"ltuz\\hs\"e"
"ysvmpc"
"g\x8amjtt\"megl\"omsaihifwa"
"yimmm"
"iiyqfalh"
"cwknlaaf"
"q\x37feg\xc6s\"xx"
"uayrgeurgyp\\oi"
"xhug\"pt\"axugllbdiggzhvy"
"kdaarqmsjfx\xc3d"
"\"vkwla"
"d\""
"tmroz\"bvfinxoe\\mum\"wmm"
"\"n\"bbswxne\\p\\yr\"qhwpdd"
"skzlkietklkqovjhvj\xfe"
"pbg\\pab\"bubqaf\"obzcwxwywbs\\dhtq"
"xxjidvqh\"lx\\wu\"ij"
"daef\x5fe\x5b\\kbeeb\x13qnydtboof"
"ogvazaqy\"j\x73"
"y"
"n\"tibetedldy\\gsamm\"nwu"
"wldkvgdtqulwkad"
"dpmxnj"
"twybw\"cdvf\"mjdajurokbce"
"ru\"\\lasij\"i"
"roc\\vra\\lhrm"
"pbkt\x60booz\"fjlkc"
"j\x4dytvjwrzt"
"\\uiwjkniumxcs"
"cbhm\"nexccior\"v\"j\"nazxilmfp\x47"
"qdxngevzrlgoq"
"\"lrzxftytpobsdfyrtdqpjbpuwmm\x9e"
"mdag\x0asnck\xc2ggj\"slb\"fjy"
"wyqkhjuazdtcgkcxvjkpnjdae"
"aixfk\xc0iom\x21vueob"
"dkiiakyjpkffqlluhaetires"
"ysspv\"lysgkvnmwbbsy"
"gy\"ryexcjjxdm\"xswssgtr"
"s"
"ddxv"
"qwt\"\x27puilb\"pslmbrsxhrz"
"qdg\xc9e\\qwtknlvkol\x54oqvmchn\\"
"lvo"
"b"
"fk\"aa\"\"yenwch\\\\on"
"srig\x63hpwaavs\\\x80qzk\"xa\"\xe6u\\wr"
"yxjxuj\"ghyhhxfj\"\xa6qvatre"
"yoktqxjxkzrklkoeroil"
"\"jfmik\""
"smgseztzdwldikbqrh\""
"jftahgctf\"hoqy"
"tcnhicr\"znpgckt\"ble"
"vqktnkodh\"lo\"a\\bkmdjqqnsqr"
"ztnirfzqq"
"s"
"xx"
"iqj\"y\\hqgzflwrdsusasekyrxbp\\ad"
"\\xzjhlaiynkioz\"\"bxepzimvgwt"
"s\x36rbw"
"mniieztwrisvdx"
"atyfxioy\x2b\\"
"irde\x85\x5cvbah\\jekw\"ia"
"bdmftlhkwrprmpat\"prfaocvp"
"w\\k"
"umbpausy"
"zfauhpsangy"
"p\"zqyw"
"wtztypyqvnnxzvlvipnq\"zu"
"deicgwq\\oqvajpbov\\or\"kgplwu"
"mbzlfgpi\\\\zqcidjpzqdzxityxa"
"lfkxvhma"
"\xf2yduqzqr\"\\fak\"p\"n"
"mpajacfuxotonpadvng"
"anb\\telzvcdu\\a\xf2flfq"
"lrs\"ebethwpmuuc\"\x86ygr"
"qmvdbhtumzc\"ci"
"meet"
"yopg\x0fdxdq\"h\\ugsu\xffmolxjv"
"uhy"
"fzgidrtzycsireghazscvmwcfmw\\t"
"cqohkhpgvpru"
"bihyigtnvmevx\"xx"
"xz"
"zofomwotzuxsjk\"q\"mc\"js\"dnmalhxd"
"\\ktnddux\\fqvt\"ibnjntjcbn"
"ia"
"htjadnefwetyp\xd5kbrwfycbyy"
"\"\\hkuxqddnao"
"meqqsz\x83luecpgaem"
"cvks\x87frvxo\"svqivqsdpgwhukmju"
"sgmxiai\\o\"riufxwjfigr\xdf"
"fgywdfecqufccpcdn"
"faghjoq\x28abxnpxj"
"zuppgzcfb\"dctvp\"elup\"zxkopx"
"xqs\x45xxdqcihbwghmzoa"
"anbnlp\\cgcvm\"hc"
"xf\"fgrngwzys"
"nrxsjduedcy\x24"
"\x71sxl\"gj\"sds\"ulcruguz\\t\\ssvjcwhi"
"jhj\"msch"
"qpovolktfwyiuyicbfeeju\x01"
"nkyxmb\"qyqultgt\"nmvzvvnxnb"
"ycsrkbstgzqb\"uv\\cisn"
"s"
"ueptjnn\"\"sh"
"lp\"z\"d\"mxtxiy"
"yzjtvockdnvbubqabjourf\"k\"uoxwle"
"\x82\"wqm\""
"\xb5cwtuks\x5fpgh"
"wd"
"tbvf"
"ttbmzdgn"
"vfpiyfdejyrlbgcdtwzbnm"
"uc"
"otdcmhpjagqix"
"\\\xb1qso\"s"
"scowax"
"behpstjdh\xccqlgnqjyz\"eesn"
"r\xe1cbnjwzveoomkzlo\\kxlfouhm"
"jgrl"
"kzqs\\r"
"ctscb\x7fthwkdyko\"\x62pkf\"d\xe6knmhurg"
"tc\"kw\x3ftt"
"bxb\x5ccl"
"jyrmfbphsldwpq"
"jylpvysl\"\"juducjg"
"en\\m\"kxpq\"wpb\\\""
"madouht\"bmdwvnyqvpnawiphgac\""
"vuxpk\"ltucrw"
"aae\x60arr"
"ttitnne\"kilkrgssnr\xfdurzh"
"oalw"
"pc\"\"gktkdykzbdpkwigucqni\"nxiqx"
"dbrsaj"
"bgzsowyxcbrvhtvekhsh\"qgd"
"kudfemvk\"\"\"hkbrbil\"chkqoa"
"zjzgj\\ekbhyfzufy"
"\\acos\"fqekuxqzxbmkbnn\x1ejzwrm"
"elxahvudn\"txtmomotgw"
"\x2eoxmwdhelpr\"cgi\xf7pzvb"
"eapheklx"
"hfvma\"mietvc\"tszbbm\"czex"
"h\"iiockj\\\xc1et"
"d\"rmjjftm"
"qlvhdcbqtyrhlc\\"
"yy\"rsucjtulm\"coryri\"eqjlbmk"
"tv"
"r\"bfuht\\jjgujp\""
"kukxvuauamtdosngdjlkauylttaokaj"
"srgost\"\"rbkcqtlccu\x65ohjptstrjkzy"
"yxwxl\\yjilwwxffrjjuazmzjs"
"dxlw\\fkstu\"hjrtiafhyuoh\"sewabne"
"\x88sj\"v"
"rfzprz\xec\"oxqclu\"krzefp\\q"
"cfmhdbjuhrcymgxpylllyvpni"
"ucrmjvmimmcq\x88\xd9\"lz"
"lujtt\""
"gvbqoixn\"pmledpjmo\"flydnwkfxllf"
"dvxqlbshhmelsk\x8big\"l"
"mx\x54lma\x8bbguxejg"
"\x66jdati\xeceieo"
"\"iyyupixei\x54ff"
"xohzf\"rbxsoksxamiu"
"vlhthspeshzbppa\x4drhqnohjop\"\"mfjd"
"f\"tvxxla\"vurian\"\"idjq\x3aptm\xc3olep"
"gzqz"
"kbq\\wogye\\altvi\\hbvmodny"
"j\xd8"
"ofjozdhkblvndl"
"hbitoupimbawimxlxqze"
"ypeleimnme"
"xfwdrzsc\\oxqamawyizvi\\y"
"enoikppx\xa1ixe\"yo\"gumye"
"fb"
"vzf"
"zxidr"
"cu\x31beirsywtskq"
"lxpjbvqzztafwezd"
"\\jyxeuo\x18bv"
"b\"vawc\"p\\\\giern\"b"
"odizunx\"\"t\\yicdn\"x\"sdiz"
"\"\"tebrtsi"
"ctyzsxv\xa6pegfkwsi\"tgyltaakytccb"
"htxwbofchvmzbppycccliyik\xe5a"
"ggsslefamsklezqkrd"
"rcep\"fnimwvvdx\"l"
"zyrzlqmd\x12egvqs\\llqyie"
"\x07gsqyrr\\rcyhyspsvn"
"butg\""
"gb"
"gywkoxf\"jsg\\wtopxvumirqxlwz"
"rj\"ir\"wldwveair\x2es\"dhjrdehbqnzl"
"ru\"elktnsbxufk\\ejufjfjlevt\\lrzd"
"\"widsvok"
"oy\"\x81nuesvw"
"ay"
"syticfac\x1cfjsivwlmy\"pumsqlqqzx"
"m"
"rjjkfh\x78cf\x2brgceg\"jmdyas\"\\xlv\xb6p"
"tmuvo\"\x3ffdqdovjmdmkgpstotojkv\"as"
"jd\\ojvynhxllfzzxvbn\"wrpphcvx"
"pz"
"\"twr"
"n\\hdzmxe\"mzjjeadlz"
"fb\"rprxuagvahjnri"
"rfmexmjjgh\\xrnmyvnatrvfruflaqjnd"
"obbbde\"co\"qr\"qpiwjgqahqm\\jjp\""
"vpbq\"\"y\"czk\\b\x52ed\"lnzepobp"
"syzeajzfarplydipny\"y\"\xe8ad"
"mpyodwb"
"\x47rakphlqqptd"
"wa\"oj\"aiy"
"a"
"ropozx"
"q\x51nbtlwa"
"etukvgx\\jqxlkq"
"\"tp\"rah\"pg\"s\"bpdtes\\tkasdhqd"
"dn\"qqpkikadowssb\xcah\"dzpsf\\ect\"jdh"
"pxunovbbrrn\\vullyn\"bno\"\"\"myfxlp\""
"qaixyazuryvkmoulhcqaotegfj\\mpzm"
"bvfrbicutzbjwn\\oml\"cf\"d\"ezcpv\"j"
"rmbrdtneudemigdhelmb"
"aq\\aurmbhy"
"wujqvzw"
"gf\"tssmvm\"gm\"hu\x9a\xb7yjawsa"
"hrhqqxow\xe2gsydtdspcfqy\"zw\\ou"
"ianwwf\\yko\\tdujhhqdi"
"xylz\"zpvpab"
"lwuopbeeegp"
"aoop\x49jhhcexdmdtun"
"\\\\mouqqcsgmz"
"tltuvwhveau\x43b\"ymxjlcgiymcynwt"
"gsugerumpyuhtjljbhrdyoj"
"lnjm\xb8wg\"ajh"
"zmspue\"nfttdon\\b\"eww"
"\"w\x67jwaq\x7ernmyvs\\rmdsuwydsd\"th"
"ogtgvtlmcvgllyv"
"z\"fqi\"rvddoehrciyl"
"yustxxtot\"muec\"xvfdbzunzvveq"
"mqslw"
"txqnyvzmibqgjs\xb6xy\x86nfalfyx"
"kzhehlmkholov"
"plpmywcnirrjutjguosh\\"
"pydbnqofv\"dn\\m"
"aegqof"
"eambmxt\\dxagoogl\\zapfwwlmk"
"afbmqitxxqhddlozuxcpjxgh"
"vgts"
"bfdpqtoxzzhmzcilehnflna"
"s\"idpz"
"\xcfhgly\"nlmztwybx\"ecezmsxaqw"
"aackfgndqcqiy"
"\x22unqdlsrvgzfaohoffgxzfpir\"s"
"abh\"ydv\"kbpdhrerl"
"bdzpg"
"ekwgkywtmzp"
"wtoodejqmrrgslhvnk\"pi\"ldnogpth"
"njro\x68qgbx\xe4af\"\\suan"
"""

# ╔═╡ 1e28516d-8dc3-43c6-bd9d-b2afd93cf87e
puzzle_input = String(read("Day 08.txt"))

# ╔═╡ 6a6cb1c1-6b76-4ae6-91f1-0a9f523106a5
function decode(input)
	if input[1] != '"'
		throw(ErrorException("invalid syntax1: " * input))
	end
	if input[length(input)] != '"'
		throw(ErrorException("invalid syntax2: " * input))
	end
	content = input[2:end-1]
	content = replace(content, "\\\"" => "\"")
	content = replace(content, "\\\\" => "\\")

	while true
		m = match(r"\\x([0-9a-fA-F]{2})", content)
		if m == nothing
			break
		end
		code = m[1]
		content = replace(content, "\\x" * code  => Char(parse(Int, code, base = 16)))
	end

	return content
end

# ╔═╡ 8b3fb62a-ebdc-429e-83b8-d89deda32282
function count_diff(arr, func)
	before = arr           |> Base.Fix1(map, length)
	after = map(func, arr) |> Base.Fix1(map, length)
	sum(before .- after)
end

# ╔═╡ c1b22b74-c875-40e9-911a-9b0ac762e887
example_input = raw"""
""
"abc"
"aaa\\"aaa"
"\x27"
"""

# ╔═╡ 94efc347-bb75-498e-b132-c08b7b0d913f
@assert decode(raw"\"\"") == ""

# ╔═╡ 0a077daf-0e6f-487d-b7e3-ed3a39ffcc57
@assert decode(raw"\"abc\"") == "abc"

# ╔═╡ 5a12de6b-2a06-4a83-8aff-6adcaca2becf
@assert decode(raw"\"aaa\\\"aaa\"") == "aaa\"aaa"

# ╔═╡ 6bbf3051-3577-48e3-830f-d0a0db92d44a
@assert decode(raw"\"\x27\"") == "'"

# ╔═╡ fdcf3180-be09-4941-8b63-16ea1c4b86ae
@assert count_diff(split(strip(example_input), "\n"), decode) == 12

# ╔═╡ b347bd98-aff4-4390-9f12-fc37ab8d88c6
part1 = count_diff(split(strip(puzzle_input), "\n"), decode)

# ╔═╡ 00c3f519-2637-432a-8c5e-28fafd9020f5
@test part1 == 1350

# ╔═╡ 79bdbb26-ae3e-4311-a4f9-cfdd95a2e29f
md"Your puzzle answer was `1350`."

# ╔═╡ 5059f712-1e4f-4caa-9f2f-4f056d2928f0
md"""
# Part Two

Now, let's go the other way. In addition to finding the number of characters of code, you should now encode each code representation as a new string and find the number of characters of the new encoded representation, including the surrounding double quotes.

For example:

-   `""` encodes to `"\\"\\""`, an increase from `2` characters to `6`.
-   `"abc"` encodes to `"\\"abc\\""`, an increase from `5` characters to `9`.
-   `"aaa\\"aaa"` encodes to `"\\"aaa\\\\\\"aaa\\""`, an increase from `10` characters to `16`.
-   `"\x27"` encodes to `"\\"\\x27\\""`, an increase from `6` characters to `11`.

Your task is to find the total number of characters to represent the newly encoded strings minus the number of characters of code in each original string literal. For example, for the strings above, the total encoded length (`6 + 9 + 16 + 11 = 42`) minus the characters in the original code representation (`23`, just like in the first part of this puzzle) is `42 - 23 = 19`.

"""

# ╔═╡ 5f5f55f9-216f-42e9-a26c-71139e1f9198
function encode(str)
	str = replace(str, "\\" => "\\\\")
	str = "\"" * replace(str, "\"" => "\\\"") * "\""

	str
end

# ╔═╡ e4e67539-b9e0-4837-b8ea-f9d81404d137
@assert encode(raw"\"\"") == "\"\\\"\\\"\""

# ╔═╡ ba4b0f4a-db22-487f-80b9-40ff7a303734
@assert encode(raw"\"abc\"") == "\"\\\"abc\\\"\""

# ╔═╡ 6461a1a4-86ea-480b-9428-481c7378d731
@assert encode(raw"\"aaa\\\"aaa\"") == "\"\\\"aaa\\\\\\\"aaa\\\"\""

# ╔═╡ fc2681b1-04e2-4b03-b77a-0981807a8061
@assert encode(raw"\"\\x27\"") == "\"\\\"\\\\\\\\x27\\\"\""

# ╔═╡ 028f30af-d191-4313-932d-f75d818b304a
@assert -count_diff(split(strip(example_input), "\n"), encode) == 19

# ╔═╡ bb73ce4a-9d41-426e-bf13-687acf5d532f
part2 = -count_diff(split(strip(puzzle_input), "\n"), encode)

# ╔═╡ 67b06ec6-3835-44e7-906e-3ed393110dc3
@test part2 == 2085

# ╔═╡ 60c68ada-5a88-4d86-aa59-d6709afabf4c
md"Your puzzle answer was `2085`."

# ╔═╡ Cell order:
# ╠═7d1ac287-43d9-4921-bade-5658899648e6
# ╟─7728c7b0-b022-11eb-363f-5f51427038cb
# ╟─1e37e13a-5f56-478c-8d50-d96ea1e32cd3
# ╠═1e28516d-8dc3-43c6-bd9d-b2afd93cf87e
# ╠═6a6cb1c1-6b76-4ae6-91f1-0a9f523106a5
# ╠═8b3fb62a-ebdc-429e-83b8-d89deda32282
# ╠═c1b22b74-c875-40e9-911a-9b0ac762e887
# ╠═94efc347-bb75-498e-b132-c08b7b0d913f
# ╠═0a077daf-0e6f-487d-b7e3-ed3a39ffcc57
# ╠═5a12de6b-2a06-4a83-8aff-6adcaca2becf
# ╠═6bbf3051-3577-48e3-830f-d0a0db92d44a
# ╠═fdcf3180-be09-4941-8b63-16ea1c4b86ae
# ╠═b347bd98-aff4-4390-9f12-fc37ab8d88c6
# ╠═00c3f519-2637-432a-8c5e-28fafd9020f5
# ╟─79bdbb26-ae3e-4311-a4f9-cfdd95a2e29f
# ╟─5059f712-1e4f-4caa-9f2f-4f056d2928f0
# ╠═5f5f55f9-216f-42e9-a26c-71139e1f9198
# ╠═e4e67539-b9e0-4837-b8ea-f9d81404d137
# ╠═ba4b0f4a-db22-487f-80b9-40ff7a303734
# ╠═6461a1a4-86ea-480b-9428-481c7378d731
# ╠═fc2681b1-04e2-4b03-b77a-0981807a8061
# ╠═028f30af-d191-4313-932d-f75d818b304a
# ╠═bb73ce4a-9d41-426e-bf13-687acf5d532f
# ╠═67b06ec6-3835-44e7-906e-3ed393110dc3
# ╟─60c68ada-5a88-4d86-aa59-d6709afabf4c


text:
export BrickletGPSCoordinates
struct BrickletGPSCoordinates
    latitude::Integer
    ns::Char
    longitude::Integer
    ew::Char
    pdop::Integer
    hdop::Integer
    vdop::Integer
    epe::Integer
end

export BrickletGPSStatus
struct BrickletGPSStatus
    fix::Integer
    satellites_view::Integer
    satellites_used::Integer
end

export BrickletGPSAltitude
struct BrickletGPSAltitude
    altitude::Integer
    geoidal_separation::Integer
end

export BrickletGPSMotion
struct BrickletGPSMotion
    course::Integer
    speed::Integer
end

export BrickletGPSDateTime
struct BrickletGPSDateTime
    date::Integer
    time::Integer
end

export BrickletGPSIdentity
struct BrickletGPSIdentity
    uid::String
    connected_uid::String
    position::Char
    hardware_version::Vector{Integer}
    firmware_version::Vector{Integer}
    device_identifier::Integer
end

export BrickletGPS
"""
Determine position, velocity and altitude using GPS
"""
mutable struct BrickletGPS <: TinkerforgeDevice
    ipcon::IPConnection
    deviceInternal::PyObject

    """
    Creates an object with the unique device ID *uid* and adds it to
    the IP Connection *ipcon*.
    """
    function BrickletGPS(uid::String, ipcon::IPConnection)
        package = pyimport("tinkerforge.bricklet_gps")
        deviceInternal = package.BrickletGPS(uid, ipcon.ipconInternal)

        return new(ipcon, deviceInternal)
    end
end

export get_coordinates
"""
    $(SIGNATURES)

Returns the GPS coordinates. Latitude and longitude are given in the
``DD.dddddd°`` format, the value 57123468 means 57.123468°.
The parameter ``ns`` and ``ew`` are the cardinal directions for
latitude and longitude. Possible values for ``ns`` and ``ew`` are 'N', 'S', 'E'
and 'W' (north, south, east and west).

PDOP, HDOP and VDOP are the dilution of precision (DOP) values. They specify
the additional multiplicative effect of GPS satellite geometry on GPS
precision. See
`here <https://en.wikipedia.org/wiki/Dilution_of_precision_(GPS)>`__
for more information.

EPE is the "Estimated Position Error". This is not the
absolute maximum error, it is the error with a specific confidence. See
`here <https://www.nps.gov/gis/gps/WhatisEPE.html>`__ for more information.

This data is only valid if there is currently a fix as indicated by
:func:`Get Status`.
"""
function get_coordinates(device::BrickletGPS)
    return device.deviceInternal.get_coordinates()
end

export get_status
"""
    $(SIGNATURES)

Returns the current fix status, the number of satellites that are in view and
the number of satellites that are currently used.

Possible fix status values can be:

.. csv-table::
 :header: "Value", "Description"
 :widths: 10, 100

 "1", "No Fix, :func:`Get Coordinates`, :func:`Get Altitude` and :func:`Get Motion` return invalid data"
 "2", "2D Fix, only :func:`Get Coordinates` and :func:`Get Motion` return valid data"
 "3", "3D Fix, :func:`Get Coordinates`, :func:`Get Altitude` and :func:`Get Motion` return valid data"

There is also a :ref:`blue LED <gps_bricklet_fix_led>` on the Bricklet that
indicates the fix status.
"""
function get_status(device::BrickletGPS)
    return device.deviceInternal.get_status()
end

export get_altitude
"""
    $(SIGNATURES)

Returns the current altitude and corresponding geoidal separation.

This data is only valid if there is currently a fix as indicated by
:func:`Get Status`.
"""
function get_altitude(device::BrickletGPS)
    return device.deviceInternal.get_altitude()
end

export get_motion
"""
    $(SIGNATURES)

Returns the current course and speed. A course of 0° means the Bricklet is
traveling north bound and 90° means it is traveling east bound.

Please note that this only returns useful values if an actual movement
is present.

This data is only valid if there is currently a fix as indicated by
:func:`Get Status`.
"""
function get_motion(device::BrickletGPS)
    return device.deviceInternal.get_motion()
end

export get_date_time
"""
    $(SIGNATURES)

Returns the current date and time. The date is
given in the format ``ddmmyy`` and the time is given
in the format ``hhmmss.sss``. For example, 140713 means
14.07.13 as date and 195923568 means 19:59:23.568 as time.
"""
function get_date_time(device::BrickletGPS)
    return device.deviceInternal.get_date_time()
end

export restart
"""
    $(SIGNATURES)

Restarts the GPS Bricklet, the following restart types are available:

.. csv-table::
 :header: "Value", "Description"
 :widths: 10, 100

 "0", "Hot start (use all available data in the NV store)"
 "1", "Warm start (don't use ephemeris at restart)"
 "2", "Cold start (don't use time, position, almanacs and ephemeris at restart)"
 "3", "Factory reset (clear all system/user configurations at restart)"
"""
function restart(device::BrickletGPS, restart_type)
    device.deviceInternal.restart(restart_type)
end

export set_coordinates_callback_period
"""
    $(SIGNATURES)

Sets the period with which the :cb:`Coordinates` callback is triggered
periodically. A value of 0 turns the callback off.

The :cb:`Coordinates` callback is only triggered if the coordinates changed
since the last triggering.
"""
function set_coordinates_callback_period(device::BrickletGPS, period)
    device.deviceInternal.set_coordinates_callback_period(period)
end

export get_coordinates_callback_period
"""
    $(SIGNATURES)

Returns the period as set by :func:`Set Coordinates Callback Period`.
"""
function get_coordinates_callback_period(device::BrickletGPS)
    return device.deviceInternal.get_coordinates_callback_period()
end

export set_status_callback_period
"""
    $(SIGNATURES)

Sets the period with which the :cb:`Status` callback is triggered
periodically. A value of 0 turns the callback off.

The :cb:`Status` callback is only triggered if the status changed since the
last triggering.
"""
function set_status_callback_period(device::BrickletGPS, period)
    device.deviceInternal.set_status_callback_period(period)
end

export get_status_callback_period
"""
    $(SIGNATURES)

Returns the period as set by :func:`Set Status Callback Period`.
"""
function get_status_callback_period(device::BrickletGPS)
    return device.deviceInternal.get_status_callback_period()
end

export set_altitude_callback_period
"""
    $(SIGNATURES)

Sets the period with which the :cb:`Altitude` callback is triggered
periodically. A value of 0 turns the callback off.

The :cb:`Altitude` callback is only triggered if the altitude changed since
the last triggering.
"""
function set_altitude_callback_period(device::BrickletGPS, period)
    device.deviceInternal.set_altitude_callback_period(period)
end

export get_altitude_callback_period
"""
    $(SIGNATURES)

Returns the period as set by :func:`Set Altitude Callback Period`.
"""
function get_altitude_callback_period(device::BrickletGPS)
    return device.deviceInternal.get_altitude_callback_period()
end

export set_motion_callback_period
"""
    $(SIGNATURES)

Sets the period with which the :cb:`Motion` callback is triggered
periodically. A value of 0 turns the callback off.

The :cb:`Motion` callback is only triggered if the motion changed since the
last triggering.
"""
function set_motion_callback_period(device::BrickletGPS, period)
    device.deviceInternal.set_motion_callback_period(period)
end

export get_motion_callback_period
"""
    $(SIGNATURES)

Returns the period as set by :func:`Set Motion Callback Period`.
"""
function get_motion_callback_period(device::BrickletGPS)
    return device.deviceInternal.get_motion_callback_period()
end

export set_date_time_callback_period
"""
    $(SIGNATURES)

Sets the period with which the :cb:`Date Time` callback is triggered
periodically. A value of 0 turns the callback off.

The :cb:`Date Time` callback is only triggered if the date or time changed
since the last triggering.
"""
function set_date_time_callback_period(device::BrickletGPS, period)
    device.deviceInternal.set_date_time_callback_period(period)
end

export get_date_time_callback_period
"""
    $(SIGNATURES)

Returns the period as set by :func:`Set Date Time Callback Period`.
"""
function get_date_time_callback_period(device::BrickletGPS)
    return device.deviceInternal.get_date_time_callback_period()
end

export get_identity
"""
    $(SIGNATURES)

Returns the UID, the UID where the Bricklet is connected to,
the position, the hardware and firmware version as well as the
device identifier.

The position can be 'a', 'b', 'c', 'd', 'e', 'f', 'g' or 'h' (Bricklet Port).
A Bricklet connected to an :ref:`Isolator Bricklet <isolator_bricklet>` is always at
position 'z'.

The device identifier numbers can be found :ref:`here <device_identifier>`.
|device_identifier_constant|
"""
function get_identity(device::BrickletGPS)
    return device.deviceInternal.get_identity()
end

export register_callback
"""
Registers the given *function* with the given *callback_id*.
"""
function register_callback(device::BrickletGPS, callback_id, function_)
    if isnothing(function_)
        device.registered_callbacks.pop(callback_id, None)
    else
        device.registered_callbacks[callback_id] = function_
    end
end


text:
@testset "population initialization" begin
    for clonalmutations in [0,100]
        pop1 = SomaticEvolution.initialize_population(100, clonalmutations=clonalmutations)
        pop2 = SomaticEvolution.initialize_population(clonalmutations=clonalmutations)
        for pop in (pop1, pop2)
            @test length(pop) == 1
            @test pop[1].Nvec[1] == 1
            @test length(pop[1].Nvec) == length(pop[1].tvec) == 1
            @test pop[1].tvec[1] == 0.0
            @test length(pop[1].cells) == 1
            @test length(pop[1].cells[1].mutations) == clonalmutations
            @test pop[1].cells[1].clonetype == 1
            @test length(pop[1].subclones) == 0
            @test pop[1].id == 1
        end
    end
end

@testset "module initialization" begin
    @testset "branching" begin
        for clonalmutations in [0,100]
            module1 = SomaticEvolution.initializesim_branching(100, clonalmutations=clonalmutations)
            module2 = SomaticEvolution.initializesim_branching(clonalmutations=clonalmutations)
            for initmodule in (module1, module2)
                @test initmodule.Nvec[1] == 1
                @test length(initmodule.Nvec) == length(initmodule.tvec) == 1
                @test initmodule.tvec[1] == 0.0
                @test length(initmodule.cells) == 1
                @test length(initmodule.cells[1].mutations) == clonalmutations
                @test initmodule.cells[1].clonetype == 1
                @test length(initmodule.subclones) == 0
                @test initmodule.id == 1
            end
        end

        @testset "moran" begin
            for clonalmutations in [0,100]
                N = 100
                initmodule = SomaticEvolution.initializesim_moran(N, clonalmutations=clonalmutations)
                @test initmodule.Nvec[1] == N
                @test length(initmodule.Nvec) == length(initmodule.tvec) == 1
                @test initmodule.tvec[1] == 0.0
                @test length(initmodule.cells) == N
                allmuts = [cell.mutations for cell in initmodule.cells]
                @test all(x -> x == allmuts[1], allmuts)
                @test length(initmodule.cells[1].mutations) == clonalmutations
                @test initmodule.cells[1].clonetype == 1
                @test length(initmodule.subclones) == 0
                @test initmodule.id == 1
            end
        end

        @testset "sampled" begin
            subclones = SomaticEvolution.CloneTracker[]
            cells = Cell[]
            push!(cells, Cell([1,2,3,4], 1))
            push!(cells, Cell([1,2,3], 1))
            id = 1
            parentid = 0
            inittime = 5
            initmodule = SomaticEvolution.initializesim_from_cells(cells, subclones, id,
                parentid, inittime=inittime)
            @test initmodule.Nvec[1] == length(cells)
            @test length(initmodule.Nvec) == length(initmodule.tvec) == 1
            @test initmodule.tvec[1] == inittime
            @test length(initmodule.cells) == length(cells)
            @test initmodule.cells[1].mutations == [1,2,3,4]
            @test initmodule.cells[1].clonetype == initmodule.cells[2].clonetype == 1
            @test length(initmodule.subclones) == length(subclones)
            @test initmodule.id == id
        end
    end
end


text:
# m = module
# f = function
# `no_prop` = do not propagate properties/metadata
# `first` means use parent(x)
# `last` means parent(y)
macro _define_function_no_prop(m, f, T1)
    esc(:($m.$f(@nospecialize(x::$(T1))) = $m.$f(parent(x))))
end
macro _define_function_no_prop(m, f, T1, T2)
    esc(:($m.$f(x::$(T1), y::$(T2)) = $m.$f(parent(x), parent(y))))
end

macro _define_function_no_prop_first(m, f, T1, T2)
    esc(:($m.$f(@nospecialize(x::$T1), y::$T2) = $m.$f(parent(x), y)))
end

macro _define_function_no_prop_last(m, f, T1, T2)
    esc(:($m.$f(x::$T1, @nospecialize(y::$T2)) = $m.$f(x, parent(y))))
end

macro defproperties(T)
    esc(quote
        Base.getproperty(x::$T, k::String) = Metadata.metadata(x, k)
        @inline function Base.getproperty(x::$T, k::Symbol)
            if hasproperty(parent(x), k)
                return getproperty(parent(x), k)
            else
                return Metadata.metadata(x, k)
            end
        end

        Base.setproperty!(x::$T, k::String, v) = Metadata.metadata!(x, k, v)
        @inline function Base.setproperty!(x::$T, k::Symbol, val)
            if hasproperty(parent(x), k)
                return setproperty!(parent(x), k, val)
            else
                return Metadata.metadata!(x, k, val)
            end
        end

        @inline Base.propertynames(x::$T) = Metadata.metadata_keys(x)
    end)
end


text:
using ErgodicControl
using Base.Test

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

em = ErgodicManagerR2("single gaussian", K=5, bins=100)
x0 = [0.4,0.1]
N = 40
h = 0.1

tm = TrajectoryManager(x0, h, N, ConstantInitializer([0.0,0.0]))
xd, ud = pto_trajectory(em, tm)

include("dubins.jl")

include("r2t.jl")

include("multi.jl")

include("r3.jl")

include("smc.jl")

include("se2.jl")

include("linear.jl")

include("max.jl")


text:
"""
    This example shows how to use TextBox

Sometimes, you have a very long piece of text to print to the console.
But you don't want to just print one long string, you want your text to e.g., have a fixed width.
Maybe you want it to fit within a larger layout of Term Renderables (see layout example).
Or maybe you just want to control the text's appeareance.

Whatever the reason, TextBox is here for you.
"""

import Term: @green, TextBox

my_long_text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."

println(@green "This is what it looks like if you just print it out:")
println(my_long_text)

println(@green "\n\nAnd now in a textbox:")
print(TextBox(my_long_text))

"""
note that textbox is really printing a `Term.Panel` object, so all the arguments
you'd use in a panel can be used here for more creating control (see panels example)
"""

print(
    TextBox(
        my_long_text;
        title = "This is my long text",
        title_style = "bold red",
        title_justify = :center,
        subtitle = "styled by Term.jl",
        subtitle_justify = :right,
        subtitle_style = "dim",
    ),
)

"""
And of course it works well with markup styles too
"""
another_long_one =
    "This is a {red bold}long{/red bold} piece of {green italic}content{/green italic}. But TextBox can handle {underline}anything{/underline}!! "^5

print(TextBox(another_long_one; width = 44))

"""
And of course you can use Term.jl's layout syntax to compose TextBox objects,
see layout example.

ps: this might not print correctly on smaller screens since you're asking for a
very wide piece of text
"""

tb1 = TextBox(
    my_long_text;
    title = "This is my long text",
    title_style = "bold red",
    title_justify = :center,
    subtitle = "styled by Term.jl",
    subtitle_justify = :right,
    subtitle_style = "dim",
    width = 44,
)

tb2 = TextBox(
    another_long_one;
    width = 40,
    title = "second column",
    title_style = "blue bold",
    title_justify = :center,
)
print(tb1 * tb2)

""" or even

ps: this might not print correctly on smaller screens since you're asking for a
very wide piece of text
"""

import Term: Panel
print(Panel(tb1 * tb2; width = 100, justify = :center, title = "My Panel"))

"""
For comparison, we can try to use RenderableText instead of TextBox
"""

import Term: RenderableText
print(
    Panel(
        RenderableText(my_long_text; width = 44) *
        RenderableText(another_long_one; width = 40);
        width = 100,
        justify = :center,
        title = "Bad idea",
    ),
)

"""
Ooopss, that doesn't look great. We can remedy this with a Spacer layout object.
However you get the idea, textbox behave more intutively. We can use
padding to control how much space we want around our text
"""
tb1 = TextBox(my_long_text; width = 38, padding = (0, 10, 0, 0))
tb2 = TextBox(my_long_text; width = 30, padding = (0, 0, 1, 1))
print(tb1 * tb1)

print(tb2 * tb2)


text:
using Images

function getbrightframes(stack, meanvalues, threshold::Float64=(mean(meanvalues)+0.005))
    indices = findall(x -> x > threshold, meanvalues)
    return stack[:,:,indices]

end

newstack = getbrightframes(stack, meanvals)

save("newstack.tif", newstack)


text:
using Documenter, MaterialModels

makedocs(
    sitename="MaterialModels.jl",
    format = Documenter.HTML(prettyurls = false),
    pages = Any[
        "Home" => "index.md",
        "Interface" => "interface.md",
        "Materials" => [
            "materials/LinearElastic.md",
            "materials/Plastic.md"
        ]
    ]
)

deploydocs(
    repo = "github.com/kimauth/MaterialModels.jl.git",
    devbranch = "main",
    push_preview=true,
)


text:
# Set the properties of a particle.
# It generates a random direction from 0 to 360 degrees.
mutable struct Activator360 <: AbstractParticleActivator
    max_life::Float64
    max_speed::Float64

    function Activator360()
        o = new()
        o.max_life = MAX_PARTICLE_LIFETIME
        o.max_speed = MAX_PARTICLE_SPEED
        o
    end
end

function activate!(activator::Activator360, particle::AbstractParticle, x::Float64, y::Float64)
    # Create new velocity components
    direction = Float64(round(rand(1)[1] * 360.0))
    speed = rand(1)[1] * activator.max_speed

    Math.set_velocity!(particle.velocity, direction, speed)

    # The location of where the particle is emitted
    set_position!(particle, x, y)

    # A random lifetime ranging from 0.0 to max_life
    lifespan = rand(1)[1] * (activator.max_life * 1000.0)
    particle.lifespan = lifespan

    shade = clamp(Int64(round(rand(1)[1] * 32.0 * speed)), 0, 255)

    particle.visual.color.r = shade

    # Reset counter for this lifespan.
    particle.elapsed = 0.0

    activate!(particle)
end

max_stars_repo_name: wdevore/Ranger-Julia-SDL

max_issues_repo_name: wdevore/Ranger-Julia-SDL

max_forks_repo_name: wdevore/Ranger-Julia-SDL


text:
"""
`setupHTMexperiment(; inputDims,spDims,k, spParams,tmParams, dataFilepath)` initializes an HTM system
for a quick experiment with reasonable defaults.
It assumes the power/day/wkend data encoding.

# Returns

- `SpatialPooler`
- `TemporalMemory`
- `data`
- `z,bucket = encode(data,t)`
- `decoder`
"""
function setupHTMexperiment(;
      inputDims= ((15,6,3).*25,),
      spDims= (1800,),
      k= 8,
      spParams= SPParams(
            szᵢₙ= (inputDims.|> sum), szₛₚ=spDims,
            γ=600,
            s=0.03,
            prob_synapse=0.85,
            θ_stimulus_activate=5,
            p⁺_01= 0.20,
            p⁻_01= 0.12,
            β=3,
            Tboost=350,
            enable_local_inhibit=false,
            enable_boosting=true),
      tmParams= TMParams(
            Nc=prod(spDims),
            k=k,
            θ_stimulus_activate=14,
            θ_stimulus_learn=12,
            synapseSampleSize=35,
            p⁺_01=0.24,
            p⁻_01=0.08,
            LTD_p⁻_01= 0.012
          ),
      dataFilepath= "test/test_data/gym_power_benchmark-extended.csv")
  data= CSV.read(dataFilepath, DataFrame)
  encParams= initenc_powerDay(data.power_hourly_kw, data.hour, data.is_weekend,
                  encoder_size=inputDims[1], w=(34,35,35))
  decoder= SDRClassifier(tmParams.Nₙ,encParams.power_p.buckets,
                    α=0.09, buffer_length=1)

  encode= (data,t) -> encode_powerDay(data.power_hourly_kw[t], data.hour[t], data.is_weekend[t];
                       encParams...)
  (SpatialPooler(spParams),
  TemporalMemory(tmParams),
  data, encode, decoder)
end


text:
n_draws = 100
grid = 0:10:500
wts = Weights(rand(10))
resampling = BinnedWeightedResampling(grid, wts, n_draws)

@test resampling isa BinnedWeightedResampling
@test typeof(resampling) <: AbstractBinnedUncertainValueResampling

# Create the resampling scheme. Use kernel density estimates to distribution
# in each bin.
resampling = BinnedWeightedResampling(grid, wts, n_draws, bin_repr = UncertainScalarKDE)
@test resampling isa BinnedWeightedResampling{UncertainScalarKDE}
resampling = BinnedWeightedResampling(UncertainScalarKDE, grid, wts, n_draws)
@test resampling isa BinnedWeightedResampling{UncertainScalarKDE}

# Represent each bin as an equiprobably population
resampling = BinnedWeightedResampling(grid, wts, n_draws, bin_repr = UncertainScalarPopulation)
@test resampling isa BinnedWeightedResampling{UncertainScalarPopulation}
resampling = BinnedWeightedResampling(UncertainScalarPopulation, grid, wts, n_draws)
@test resampling isa BinnedWeightedResampling{UncertainScalarPopulation}

# Keep raw values for each bin (essentially the same as UncertainScalarPopulation,
# but avoids storing an additional vector of weights for the population members).
resampling = BinnedWeightedResampling(grid, wts, n_draws, bin_repr = RawValues)
@test resampling isa BinnedWeightedResampling{RawValues}
resampling = BinnedWeightedResampling(RawValues, grid, wts, n_draws)
@test resampling isa BinnedWeightedResampling{RawValues}


text:
using MolecularGraph:
    forward!, chain!, fragment!, componentquery!

@testset "smarts.molecule" begin

@testset "chain" begin
    state = SmartsParser("CCCCC", false)
    state.node = 1
    push!(state.nodeattrs, SmartsAtom(QueryFormula(:atomsymbol, :C)))
    forward!(state)
    chain!(state)
    @test length(state.edges) == 4
    @test length(state.nodeattrs) == 5
    @test length(state.edgeattrs) == 4

    state = SmartsParser("C1CCCCC1", false)
    state.node = 1
    push!(state.nodeattrs, SmartsAtom(QueryFormula(:atomsymbol, :C)))
    forward!(state)
    chain!(state)
    @test state.edges[6] == (6, 1)

    state = SmartsParser("C%10CCCCC%10", false)
    state.node = 1
    push!(state.nodeattrs, SmartsAtom(QueryFormula(:atomsymbol, :C)))
    forward!(state)
    chain!(state)
    @test state.edges[6] == (6, 1)
end

@testset "fragment" begin
    branched1 = SmartsParser("C(C)(C)(C)C", false)
    fragment!(branched1)
    @test branched1.edges[4] == (1, 5)

    branched2 = SmartsParser("CC(C)C(C)C(C)C", false)
    fragment!(branched2)
    @test branched2.edges[7] == (6, 8)

    nested1 = SmartsParser("C(C(C(C)C)C)C", false)
    fragment!(nested1)
    @test nested1.edges[5] == (2, 6)
    @test nested1.edges[6] == (1, 7)

    ring1 = SmartsParser("CC(C(C)1)CC1", false)
    fragment!(ring1)
    @test ring1.edges[6] == (6, 3)

    invalid1 = SmartsParser("CC(", false)
    @test_throws ErrorException fragment!(invalid1)

    invalid2 = SmartsParser("C()C", false)
    @test_throws ErrorException fragment!(invalid2)

    invalid3 = SmartsParser("C1CC", false)
    @test_throws ErrorException fragment!(invalid3)

    invalid4 = SmartsParser("1CCC1", false)
    @test_throws ErrorException fragment!(invalid4)

    valid5 = SmartsParser("CC(C)", false)
    fragment!(valid5)

    invalid6 = SmartsParser("(CC)CC", false)
    @test_throws ErrorException fragment!(invalid6)

    valid7 = SmartsParser("C(C(C))CC", false)
    fragment!(valid7)

    invalid8 = SmartsParser("C(1C)C1C", false)
    @test_throws ErrorException fragment!(invalid8)

    valid9 = SmartsParser("C(C(C(C(C(C(C(C(C(C(C(C(C(C(C(C(C(C(C(C(C))))))))))))))))))))C", false)
    fragment!(valid9)
    @test length(valid9.nodeattrs) == 22
    @test length(valid9.edgeattrs) == 21

end

@testset "component" begin
    conn0 = SmartsParser("C.CC.CCC", true)
    componentquery!(conn0)
    @test isempty(conn0.connectivity)

    conn1 = SmartsParser("(C.CC.CCC)", true)
    componentquery!(conn1)
    @test conn1.connectivity[1] == [1, 2, 4]

    conn2 = SmartsParser("CC.CC.(CC)", true)
    componentquery!(conn2)
    @test conn2.connectivity[1] == [5]

    conn3 = SmartsParser("CC.(CC(C)C.CC)", true)
    componentquery!(conn3)
    @test conn3.connectivity[1] == [3, 7]

    conn4 = SmartsParser("(CCC).(CCC)", true)
    componentquery!(conn4)
    @test conn4.connectivity == [[1], [4]]

    conn5 = SmartsParser("(C.C.C).(C.C).C.(C)", true)
    componentquery!(conn5)
    @test conn5.connectivity == [[1, 2, 3], [4, 5], [7]]

    invalid1 = SmartsParser("C..C", true)
    @test_throws ErrorException fragment!(invalid1)

    invalid2 = SmartsParser("CCC.", true)
    @test_throws ErrorException fragment!(invalid2)

    valid3 = SmartsParser("CC(C).C", true)
    componentquery!(conn0)
    @test isempty(valid3.connectivity)
end

end # smarts.molecule


text:
using Test, Random, AutoGrad, Statistics
using Knet.Ops21: batchnorm
using Knet.KnetArrays: KnetArray
using CUDA: CUDA, CuArray

@testset "ops21/batchnorm" begin

    # @gcheck needs Inference and Training to compute the same function
    # For this they need to use the same mean/var
    # @gcheck sums the output of non-scalar functions by default
    # This has the effect of multiplying scale with a 0 mean array
    # So we'll use getindex(y,1) instead
    function bnormtest(; dims=(1,2,4), epsilon=1e-5, momentum=0.9, atype=Array, seed=-1)
        if seed > 0; Random.seed!(seed); end
        x = randn(5,4,3,2)
        m(x) = mean(x; dims)
        v(x) = var(x; dims, corrected=false)
        bsize = size(m(x))
        b = randn(bsize...)
        s = randn(bsize...)
        ax,ab,as = atype.((x,b,s))
        px,pb,ps = Param.((ax,ab,as))
        if atype == Array
            r1 = r2 = true
        else
            r1 = isapprox(Array(batchnorm(px, m(ax), v(ax), pb, ps; epsilon, momentum)), batchnorm(x, m(x), v(x), b, s; epsilon, momentum))
            r2 = isapprox(Array(batchnorm(px, m(ax), v(ax), pb, ps; epsilon, momentum, training=true)), batchnorm(x, m(x), v(x), b, s; epsilon, momentum, training=true))
        end
        r3 = isapprox(batchnorm(px, m(ax), v(ax), pb, ps; epsilon, momentum)[1], value(@diff(batchnorm(px, m(ax), v(ax), pb, ps; epsilon, momentum)[1])))
        r4 = @gcheck(batchnorm(px, m(ax), v(ax), pb, ps; epsilon, momentum)[1])
        r1 && r2 && r3 && r4
    end

    @test bnormtest()
    @test bnormtest(; dims=4)
    @test bnormtest(; dims=(2,3,4))
    @test bnormtest(; epsilon = 0)
    @test bnormtest(; momentum = 0.99)
    if CUDA.functional()
        @test bnormtest(; atype=CuArray)
        @test bnormtest(; dims=4, atype=CuArray)
        @test bnormtest(; dims=(2,3,4), atype=CuArray)
        @test bnormtest(; atype=KnetArray)
        @test bnormtest(; dims=4, atype=KnetArray)
        @test bnormtest(; dims=(2,3,4), atype=KnetArray)
    end
end


text:
using Test

const file_dir = @__DIR__

@sync for p in workers()
  @spawnat p global file_dir = file_dir
end

@everywhere begin
  using DistributedArrays
  do_serial = myid() == 1
  const nb_procs = nprocs()
  include(joinpath(file_dir, "matmul_setup.jl"))
end

@testset "MatMul" begin

println("DArray test on $nb_procs processes")

Ad1 = distribute(As, procs=procs(), dist=(nprocs(),1))
Ad2 = distribute(As, procs=procs(), dist=(1,nprocs()))
xd = distribute(xs, procs=procs(), dist=(nprocs(),))

dres1 = Ad1*xd
dres2 = Ad2*xd
@test all(dres1 .≈ ref)
@test all(dres2 .≈ ref)

global (mintime, maxtime) = timed_mul(Ad1,xd)
write_timings("out-darray.txt", 1, mintime, maxtime)
global (mintime, maxtime) = timed_mul(Ad2,xd)
write_timings("out-darray.txt", 2, mintime, maxtime)

end


text:
using Robotlib, LinearAlgebra, Random, Statistics
using Test
import Robotlib: ad, adi, I4

@testset "Robotlib tests" begin
Random.seed!(1)
include("testCalibNAXP.jl")
include("testForce.jl")

function simulateCalibration1(N)
    n       = 6
    q       = 2π*rand(N,n)

    wn      = zeros(3,n)
    pn      = zeros(3,n)
    xin     = zeros(6,n+1)
    wn[:,1] = [0,0,1]
    pn[:,1] = [0,0,0]
    wn[:,2] = [0,1,0]
    pn[:,2] = [0,0,0.3]
    wn[:,3] = [0,1,0]
    pn[:,3] = [0,0,1]
    wn[:,4] = [1,0,0]
    pn[:,4] = [1,0,1]
    wn[:,5] = [0,1,0]
    pn[:,5] = [1,0,1]
    wn[:,6] = [1,0,0]
    pn[:,6] = [1,0,1]
    T0 = [  0 -1 0  1.2;
    0 0 -1 0;
    1 0  0  1;
    0 0  0  1]
    for i = 1:n
        xin[:,i] = [-skew(pn[:,i])*wn[:,i]; wn[:,i]]
    end
    xin[:,n+1] = twistcoords(logT(T0))
    xinmod   = deepcopy(xin)
    for i = 1:n+1
        xinmod[:,i]  += [0.001randn(3); 0.01π/180*randn(3)]
        #conformize(xinmod[:,i])
    end
    Ta  = zeros(4,4,N)
    for i = 1:N
        Ta[:,:,i] = fkinePOE(xin,q[i,:])
    end
    return q, xin, T0, xinmod, Ta
end

@testset "Calibration and kinematics" begin

    N = 100

    println("===== Testing calibPOE =====")
    # q, xin,T0, xinmod, Ta= Robotlib.Calibration.simulateCalibration_POE(N)
    # xic,et,er = Robotlib.Calibration.calibPOE(xinmod,Ta,q,maxiter=150, λ = 10000.0)
    # println("Initial error: ", norm(xin[:,1:7]-xinmod[:,1:7]))
    # println("Final error: ", norm(xin[:,1:7]-xic[:,1:7]))
    # @test et[et .!= 0][end] < 1e-12
    # @test et[et .!= 0][end] < 1e-12

    println("===== Testing calibLPOE =====")
    q, xin,Tn0,Tn0mod, Ta = Robotlib.Calibration.simulateCalibration_LPOE(N)
    @time Tn0c,xic,et,er = Robotlib.Calibration.calibLPOE(xin,Tn0mod,Ta,q,maxiter=10, λ = 100.0)
    @test et[et .!= 0][end] < 1e-12
    @test er[er .!= 0][end] < 1e-12
    println("Initial error: ", sqrt(sum((Tn0-Tn0mod).^2)))
    println("Final error: ", sqrt(sum((Tn0-Tn0c).^2)))
    @test sqrt(sum((Tn0-Tn0c).^2)) < 0.8sqrt(sum((Tn0-Tn0mod).^2))

    println("===== Testing calibLPOEdual =====")
    q, xin,Tn0,Tn0mod, Ta = Robotlib.Calibration.simulateCalibration_LPOE_dual(N)
    @time Tn0c,xic,et,er = Robotlib.Calibration.calibLPOEdual(xin,Tn0mod,q,maxiter=6, λ = 0.01)
    println("Error between Tn0 and Tn0mod: ",
        sqrt(sum((Tn0[1:3,4,:]-Tn0mod[1:3,4,:,1]).^2+(Tn0[1:3,4,:]-Tn0mod[1:3,4,:,2]).^2)/N))
    println("Error between Tn0 and Tn0c  : ",
        sqrt(sum((Tn0[1:3,4,:]-Tn0c[1:3,4,:,1]).^2+(Tn0[1:3,4,:]-Tn0c[1:3,4,:,2]).^2)/N))
    global ei = 0.0
    global ec = 0.0
    for i = 1:N
        global ei, ec
        T1 = fkineLPOE(Tn0mod[:,:,:,1],xin[:,:,1],q[i,:,1])
        T2 = fkineLPOE(Tn0mod[:,:,:,2],xin[:,:,2],q[i,:,2])
        ei += norm(twistcoords(log(Ta[:,:,i]*trinv(T1))))
        ei += norm(twistcoords(log(Ta[:,:,i]*trinv(T2))))
        T1 = fkineLPOE(Tn0c[:,:,:,1],xin[:,:,1],q[i,:,1])
        T2 = fkineLPOE(Tn0c[:,:,:,2],xin[:,:,2],q[i,:,2])
        ec += norm(twistcoords(log(Ta[:,:,i]*trinv(T1))))
        ec += norm(twistcoords(log(Ta[:,:,i]*trinv(T2))))
    end
    println("Initial error: ",round(ei/N, digits=5), " Calibrated error: ", round(ec/N, digits=5))
    @test ec < ei
end

println("===== Testing frames =====")
# @warn("Testing of Frames is inactivated since MAT.jl didn't build. Re-enable when this is resolved https://github.com/JuliaIO/MAT.jl/issues/90")
include("testFrames.jl")
end


text:
export
    DiophantineEquationLinearXY,
    diophantine_equation_linear_xy

# Dx + Ey + F = 0
struct DiophantineEquationLinearXY{T<:Integer}
    cx::T  # D
    cy::T  # E
    c0::T  # F
end

diophantine_equation_linear_xy(;cx::T=0, cy::T=0, c0::T=0) where {T<:Integer} = DiophantineEquationLinearXY(cx, cy, c0)

Base.show(io::IO, eq::DiophantineEquationLinearXY) = print(io, "$(eq.cx)x + $(eq.cy)y + $(eq.c0) = 0")

evaluate(eq::DiophantineEquationLinearXY{T}, sol::DiophantineSolutionXY{T}) where {T<:Integer} = evaluate(eq, sol.x, sol.y)
evaluate(eq::DiophantineEquationLinearXY{T}, x::T, y::T) where {T<:Integer} = eq.cx * x + eq.cy * y + eq.c0

solve(eq::DiophantineEquationLinearXY{T}) where {T<:Integer} = begin
    cx, cy, c0 = eq.cx, eq.cy, eq.c0

    cx == cy == c0 == 0 && return diophantine_anyx_anyy(T)
    cx == cy == 0 && return diophantine_nonex_noney(T)

    cx == 0 && cy != 0 && return c0 % cy == 0 ?
        diophantine_anyx_oney(-(c0 ÷ cy)) :
        diophantine_nonex_noney(T)

    cx != 0 && cy == 0 && return c0 % cx == 0 ?
        diophantine_onex_anyy(-(c0 ÷ cx)) :
        diophantine_nonex_noney(T)

    g = gcd(cx, cy)
    c0 % g == 0 || return diophantine_nonex_noney(T)

    cx, cy, c0 = cx ÷ g, cy ÷ g, c0 ÷ g
    _, u, v = gcdx(cx, cy)

    diophantine_linearx_lineary(cy, -c0*u, -cx, -c0*v)
end


text:
struct OrientedBoundingBoxDetection{T} <: AbstractObjectDetection{T}
    center::Point2{T}
    width::T
    height::T
    orientation::Float32
    core::ObjectDetectionCore

    function OrientedBoundingBoxDetection(center::Point2{T}, width::T, height::T, orientation::T, core_args...) where T
        core = ObjectDetectionCore(core_args...)
        new{T}(center, width, height, orientation, core)
    end
end

width(detection::OrientedBoundingBoxDetection) = detection.width
height(detection::OrientedBoundingBoxDetection) = detection.height
orientation(detection::OrientedBoundingBoxDetection) :: Float32 = detection.orientation

function centroid(detection::OrientedBoundingBoxDetection{T}) :: Point2{T} where T
    return detection.center
end

function bounding_box_detection(detection::OrientedBoundingBoxDetection{T}) :: BoundingBoxDetection{T} where T
    # Corners of non-oriented box
    tl = detection.center - Point2{T}(detection.width, detection.height) / 2
    tr = tl + Point2{T}(detection.width, 0)
    br = tr + Point2{T}(0, detection.height)
    bl = br - Point2{T}(detection.width, 0)

    # Calculate oriented box corners
    rotated_polygon = [rotate_point(tl, detection.center, detection.orientation),
                       rotate_point(tr, detection.center, detection.orientation),
                       rotate_point(br, detection.center, detection.orientation),
                       rotate_point(bl, detection.center, detection.orientation)]

    return BoundingBoxDetection(rotated_polygon, detection.core)
end

function rotate_point(p::Point2{T}, origin::Point2{T}, theta::AbstractFloat) :: Point2{T} where T
    x, y = p
    x0, y0 = origin

    st = sin(theta)
    ct = cos(theta)

    x2 = x0 + (x-x0)ct - (y-y0)st
    y2 = y0 + (x-x0)st + (y-y0)ct

    return Point2{T}(x2, y2)
end


text:
module Types

using Base.Random: UUID
using Pkg3.Pkg2.Types: VersionSet, Available
using Pkg3.TOML
using Pkg3.TerminalMenus

import Pkg3
import Pkg3: depots, iswindows

export SHA1, VersionRange, VersionSpec, PackageSpec, UpgradeLevel, EnvCache,
    CommandError, cmderror, has_name, has_uuid, write_env, parse_toml, find_registered!,
    project_resolve!, manifest_resolve!, registry_resolve!, ensure_resolved,
    manifest_info, registered_uuids, registered_paths, registered_uuid, registered_name,
    git_file_stream, read_project, read_manifest, pathrepr

## ordering of UUIDs ##

Base.isless(a::UUID, b::UUID) = a.value < b.value
Base.convert(::Type{String}, u::UUID) = string(u)

## SHA1 ##

struct SHA1
    bytes::Vector{UInt8}
    function SHA1(bytes::Vector{UInt8})
        length(bytes) == 20 ||
            throw(ArgumentError("wrong number of bytes for SHA1 hash: $(length(bytes))"))
        return new(bytes)
    end
end

Base.convert(::Type{SHA1}, s::String) = SHA1(hex2bytes(s))
Base.convert(::Type{Vector{UInt8}}, hash::SHA1) = hash.bytes
Base.convert(::Type{String}, hash::SHA1) = bytes2hex(Vector{UInt8}(hash))

Base.string(hash::SHA1) = String(hash)
Base.show(io::IO, hash::SHA1) = print(io, "SHA1(", String(hash), ")")
Base.isless(a::SHA1, b::SHA1) = lexless(a.bytes, b.bytes)
Base.hash(a::SHA1, h::UInt) = hash((SHA1, a.bytes), h)
Base.:(==)(a::SHA1, b::SHA1) = a.bytes == b.bytes

## VersionRange ##

struct VersionBound{n}
    t::NTuple{n,Int}
    function VersionBound{n}(t::NTuple{n,Integer}) where n
        n <= 3 || throw(ArgumentError("VersionBound: you can only specify major, minor and patch versions"))
        return new(t)
    end
end
VersionBound(t::Integer...) = VersionBound{length(t)}(t)

Base.convert(::Type{VersionBound}, v::VersionNumber) =
    VersionBound(v.major, v.minor, v.patch)

Base.getindex(b::VersionBound, i::Int) = b.t[i]

≲(v::VersionNumber, b::VersionBound{0}) = true
≲(v::VersionNumber, b::VersionBound{1}) = v.major <= b[1]
≲(v::VersionNumber, b::VersionBound{2}) = (v.major, v.minor) <= (b[1], b[2])
≲(v::VersionNumber, b::VersionBound{3}) = (v.major, v.minor, v.patch) <= (b[1], b[2], b[3])

≲(b::VersionBound{0}, v::VersionNumber) = true
≲(b::VersionBound{1}, v::VersionNumber) = v.major >= b[1]
≲(b::VersionBound{2}, v::VersionNumber) = (v.major, v.minor) >= (b[1], b[2])
≲(b::VersionBound{3}, v::VersionNumber) = (v.major, v.minor, v.patch) >= (b[1], b[2], b[3])

≳(v::VersionNumber, b::VersionBound) = v ≲ b
≳(b::VersionBound, v::VersionNumber) = b ≲ v

Base.convert(::Type{VersionBound}, s::AbstractString) =
    VersionBound(map(x->parse(Int, x), split(s, '.'))...)

struct VersionRange{m,n}
    lower::VersionBound{m}
    upper::VersionBound{n}
    # TODO: check non-emptiness of range?
end
VersionRange(b::VersionBound=VersionBound()) = VersionRange(b, b)
VersionRange(t::Integer...) = VersionRange(VersionBound(t...))

Base.convert(::Type{VersionRange}, v::VersionNumber) =
    VersionRange(VersionBound(v))

function Base.convert(::Type{VersionRange}, s::AbstractString)
    ismatch(r"^\s*\*\s*$", s) && return VersionRange()
    m = match(r"^\s*(\d+(?:\.\d+)?(?:\.\d+)?)(?:\s*-\s*(\d+(?:\.\d+)?(?:\.\d+)?))?\s*$", s)
    m == nothing && throw(ArgumentError("invalid version range: $(repr(s))"))
    lower = VersionBound(m.captures[1])
    upper = m.captures[2] != nothing ? VersionBound(m.captures[2]) : lower
    return VersionRange(lower, upper)
end

function Base.print(io::IO, r::VersionRange)
    join(io, r.lower.t, '.')
    if r.lower != r.upper
        print(io, '-')
        join(io, r.upper.t, '.')
    end
end
Base.print(io::IO, ::VersionRange{0,0}) = print(io, "*")
Base.show(io::IO, r::VersionRange) = print(io, "VersionRange(\"", r, "\")")

Base.in(v::VersionNumber, r::VersionRange) = r.lower ≲ v ≲ r.upper
Base.in(v::VersionNumber, r::VersionNumber) = v == r

struct VersionSpec
    ranges::Vector{VersionRange}
    VersionSpec(r::Vector{<:VersionRange}) = new(r)
end
VersionSpec() = VersionSpec(VersionRange())

Base.in(v::VersionNumber, s::VersionSpec) = any(v in r for r in s.ranges)

Base.convert(::Type{VersionSpec}, v::VersionNumber) = VersionSpec(VersionRange(v))
Base.convert(::Type{VersionSpec}, r::VersionRange) = VersionSpec(VersionRange[r])
Base.convert(::Type{VersionSpec}, s::AbstractString) = VersionSpec(VersionRange(s))
Base.convert(::Type{VersionSpec}, v::AbstractVector) = VersionSpec(map(VersionRange, v))

function Base.print(io::IO, s::VersionSpec)
    length(s.ranges) == 1 && return print(io, s.ranges[1])
    print(io, '[')
    for i = 1:length(s.ranges)
        1 < i && print(io, ", ")
        print(io, s.ranges[i])
    end
    print(io, ']')
end
Base.show(io::IO, s::VersionSpec) = print(io, "VersionSpec(\"", s, "\")")

Base.convert(::Type{VersionSet}, v::VersionNumber) = VersionSet(v, Base.nextpatch(v))
Base.convert(::Type{VersionSet}, r::VersionRange{0,0}) = VersionSet()
Base.convert(::Type{VersionSet}, r::VersionRange{m,1}) where {m} =
    VersionSet(VersionNumber(r.lower.t...), VersionNumber(r.upper[1]+1))
Base.convert(::Type{VersionSet}, r::VersionRange{m,2}) where {m} =
    VersionSet(VersionNumber(r.lower.t...), VersionNumber(r.upper[1], r.upper[2]+1))
Base.convert(::Type{VersionSet}, r::VersionRange{m,3}) where {m} =
    VersionSet(VersionNumber(r.lower.t...), VersionNumber(r.upper[1], r.upper[2], r.upper[3]+1))
Base.convert(::Type{VersionSet}, s::VersionSpec) = mapreduce(VersionSet, ∪, s.ranges)
Base.convert(::Type{Available}, t::Tuple{SHA1,Dict{UUID,VersionSpec}}) = Available(t...)

## command errors (no stacktrace) ##

struct CommandError <: Exception
    msg::String
end
cmderror(msg::String...) = throw(CommandError(join(msg)))

function Base.showerror(io::IO, ex::CommandError, bt; backtrace=true)
    print_with_color(Base.error_color(), io, string(ex.msg))
end

## type for expressing operations ##

@enum UpgradeLevel fixed=0 patch=1 minor=2 major=3

function Base.convert(::Type{UpgradeLevel}, s::Symbol)
    s == :fixed ? fixed :
    s == :patch ? patch :
    s == :minor ? minor :
    s == :major ? major :
    throw(ArgumentError("invalid upgrade bound: $s"))
end
Base.convert(::Type{UpgradeLevel}, s::String) = UpgradeLevel(Symbol(s))
Base.convert(::Type{Union{VersionSpec,UpgradeLevel}}, v::VersionRange) = VersionSpec(v)

const VersionTypes = Union{VersionNumber,VersionSpec,UpgradeLevel}

mutable struct PackageSpec
    name::String
    uuid::UUID
    version::VersionTypes
    mode::Symbol
    PackageSpec(name::String, uuid::UUID, version::VersionTypes) =
        new(name, uuid, version, :project)
end
PackageSpec(name::String, uuid::UUID) =
    PackageSpec(name, uuid, VersionSpec())
PackageSpec(name::AbstractString, version::VersionTypes=VersionSpec()) =
    PackageSpec(name, UUID(zero(UInt128)), version)
PackageSpec(uuid::UUID, version::VersionTypes=VersionSpec()) =
    PackageSpec("", uuid, version)

has_name(pkg::PackageSpec) = !isempty(pkg.name)
has_uuid(pkg::PackageSpec) = pkg.uuid != UUID(zero(UInt128))

function Base.show(io::IO, pkg::PackageSpec)
    print(io, "PackageSpec(")
    has_name(pkg) && show(io, pkg.name)
    has_name(pkg) && has_uuid(pkg) && print(io, ", ")
    has_uuid(pkg) && show(io, pkg.uuid)
    vstr = repr(pkg.version)
    if vstr != "VersionSpec(\"*\")"
        (has_name(pkg) || has_uuid(pkg)) && print(io, ", ")
        print(io, vstr)
    end
    print(io, ")")
end

## environment & registry loading & caching ##

function parse_toml(path::String...; fakeit::Bool=false)
    p = joinpath(path...)
    !fakeit || isfile(p) ? TOML.parsefile(p) : Dict{String,Any}()
end

const project_names = ["JuliaProject.toml", "Project.toml"]
const manifest_names = ["JuliaManifest.toml", "Manifest.toml"]
const default_envs = [
    "v$(VERSION.major).$(VERSION.minor).$(VERSION.patch)",
    "v$(VERSION.major).$(VERSION.minor)",
    "v$(VERSION.major)",
    "default",
]

struct EnvCache
    # environment info:
    env::Union{Void,String}
    git::Union{Void,LibGit2.GitRepo}

    # paths for files:
    project_file::String
    manifest_file::String

    # cache of metadata:
    project::Dict
    manifest::Dict

    # registered package info:
    uuids::Dict{String,Vector{UUID}}
    paths::Dict{UUID,Vector{String}}

    preview::Ref{Bool}

    function EnvCache(env::Union{Void,String})
        project_file, git_repo = find_project(env)
        project = read_project(project_file)
        if haskey(project, "manifest")
            manifest_file = abspath(project["manifest"])
        else
            dir = abspath(dirname(project_file))
            for name in manifest_names
                manifest_file = joinpath(dir, name)
                isfile(manifest_file) && break
            end
        end
        manifest = read_manifest(manifest_file)
        uuids = Dict{String,Vector{UUID}}()
        paths = Dict{UUID,Vector{String}}()
        preview = Ref(false)
        return new(
            env,
            git_repo,
            project_file,
            manifest_file,
            project,
            manifest,
            uuids,
            paths,
            preview,
        )
    end
end
EnvCache() = EnvCache(get(ENV, "JULIA_ENV", nothing))

function read_project(io::IO)
    project = TOML.parse(io)
    if !haskey(project, "deps")
        project["deps"] = Dict{String,Any}()
    end
    return project
end
function read_project(file::String)
    isfile(file) ? open(read_project, file) : read_project(DevNull)
end

function read_manifest(io::IO)
    manifest = TOML.parse(io)
    for (name, infos) in manifest, info in infos
        haskey(info, "deps") || continue
        info["deps"] isa AbstractVector || continue
        for dep in info["deps"]
            length(manifest[dep]) == 1 ||
                error("ambiguious dependency for $name: $dep")
        end
        info["deps"] = Dict(d => manifest[d][1]["uuid"] for d in info["deps"])
    end
    return manifest
end
function read_manifest(file::String)
    try isfile(file) ? open(read_manifest, file) : read_manifest(DevNull)
    catch err
        err isa ErrorException && startswith(err.msg, "ambiguious dependency") || rethrow(err)
        err.msg *= "In manifest file: $file"
        rethrow(err)
    end
end

function write_env(env::EnvCache)
    # load old environment for comparison
    old_env = EnvCache(env.env)
    # update the project file
    project = deepcopy(env.project)
    isempty(project["deps"]) && delete!(project, "deps")
    if !isempty(project) || ispath(env.project_file)
        info("Updating $(pathrepr(env, env.project_file))")
        Pkg3.Display.print_project_diff(old_env, env)
        if !env.preview[]
            mkpath(dirname(env.project_file))
            open(env.project_file, "w") do io
                TOML.print(io, project, sorted=true)
            end
        end
    end
    # update the manifest file
    if !isempty(env.manifest) || ispath(env.manifest_file)
        info("Updating $(pathrepr(env, env.manifest_file))")
        Pkg3.Display.print_manifest_diff(old_env, env)
        manifest = deepcopy(env.manifest)
        uniques = sort!(collect(keys(manifest)), by=lowercase)
        filter!(name->length(manifest[name]) == 1, uniques)
        uuids = Dict(name => UUID(manifest[name][1]["uuid"]) for name in uniques)
        for (name, infos) in manifest, info in infos
            haskey(info, "deps") || continue
            deps = convert(Dict{String,UUID}, info["deps"])
            all(d in uniques && uuids[d] == u for (d, u) in deps) || continue
            info["deps"] = sort!(collect(keys(deps)))
        end
        if !env.preview[]
            open(env.manifest_file, "w") do io
                TOML.print(io, manifest, sorted=true)
            end
        end
    end
end

# finding the current project file

function git_discover(
    start_path::AbstractString = pwd();
    ceiling::Union{AbstractString,Vector} = "",
    across_fs::Bool = false,
)
    sep = @static iswindows() ? ";" : ":"
    ceil = ceiling isa AbstractString ? ceiling :
        join(convert(Vector{String}, ceiling), sep)
    buf_ref = Ref(LibGit2.Buffer())
    @LibGit2.check ccall(
        (:git_repository_discover, :libgit2), Cint,
        (Ptr{LibGit2.Buffer}, Cstring, Cint, Cstring),
        buf_ref, start_path, across_fs, ceil)
    buf = buf_ref[]
    str = unsafe_string(buf.ptr, buf.size)
    LibGit2.free(buf_ref)
    return str
end

function find_git_repo(path::String)
    while !ispath(path)
        prev = path
        path = dirname(path)
        path == prev && break
    end
    try return LibGit2.GitRepo(git_discover(path))
    catch err
        err isa LibGit2.GitError && err.code == LibGit2.Error.ENOTFOUND || rethrow(err)
    end
    return nothing
end

function git_file_stream(repo::LibGit2.GitRepo, spec::String; fakeit::Bool=false)::IO
    blob = try LibGit2.GitBlob(repo, spec)
    catch err
        err isa LibGit2.GitError && err.code == LibGit2.Error.ENOTFOUND || rethrow(err)
        fakeit && return DevNull
    end
    return IOBuffer(LibGit2.rawcontent(blob))
end

function find_local_env(start_path::String = pwd())
    path = git_discover(start_path, ceiling = homedir())
    repo = LibGit2.GitRepo(path)
    work = LibGit2.workdir(repo)
    for name in project_names
        path = abspath(work, name)
        isfile(path) && return path, repo
    end
    return abspath(work, project_names[end]), repo
end

function find_named_env()
    for depot in depots(), env in default_envs, name in project_names
        path = abspath(depot, "environments", env, name)
        isfile(path) && return path, find_git_repo(path)
    end
    env = VERSION.major == 0 ? default_envs[2] : default_envs[3]
    path = abspath(depots()[1], "environments", env, project_names[end])
    return path, find_git_repo(path)
end

function find_project(env::String)
    if isempty(env)
        cmderror("invalid environment name: \"\"")
    elseif env == "/"
        return find_named_env()
    elseif env == "."
        return find_local_env()
    elseif startswith(env, "/") || startswith(env, "./")
        # path to project file or project directory
        if splitext(env)[2] == ".toml"
            path = abspath(env)
            return path, find_git_repo(path)
        end
        for name in project_names
            path = abspath(env, name)
            isfile(path) && return path, find_git_repo(path)
        end
        path = abspath(env, project_names[end])
        return path, find_git_repo(path)
    else # named environment
        for depot in depots()
            path = abspath(depot, "environments", env, project_names[end])
            isfile(path) && return path, find_git_repo(path)
        end
        path = abspath(depots()[1], "environments", env, project_names[end])
        return path, find_git_repo(path)
    end
end

function find_project(::Void)
    try return find_local_env()
    catch err
        err isa LibGit2.GitError && err.code == LibGit2.Error.ENOTFOUND || rethrow(err)
    end
    return find_named_env()
end

## resolving packages from name or uuid ##

"""
Disambiguate name/uuid package specifications using project info.
"""
function project_resolve!(env::EnvCache, pkgs::AbstractVector{PackageSpec})
    uuids = env.project["deps"]
    names = Dict(uuid => name for (uuid, name) in uuids)
    length(uuids) < length(names) && # TODO: handle this somehow?
        warn("duplicate UUID found in project file's [deps] section")
    for pkg in pkgs
        pkg.mode == :project || continue
        if has_name(pkg) && !has_uuid(pkg) && pkg.name in keys(uuids)
            pkg.uuid = uuids[pkg.name]
        end
        if has_uuid(pkg) && !has_name(pkg) && pkg.uuid in keys(names)
            pkg.name = names[pkg.uuid]
        end
    end
    return pkgs
end

"""
Disambiguate name/uuid package specifications using manifest info.
"""
function manifest_resolve!(env::EnvCache, pkgs::AbstractVector{PackageSpec})
    uuids = Dict{String,Vector{String}}()
    names = Dict{String,String}()
    for (name, infos) in env.manifest, info in infos
        haskey(info, "uuid") || continue
        uuid = info["uuid"]
        push!(get!(uuids, name, String[]), uuid)
        names[uuid] = name # can be duplicate but doesn't matter
    end
    for pkg in pkgs
        pkg.mode == :manifest || continue
        if has_name(pkg) && !has_uuid(pkg) && pkg.name in keys(uuids)
            length(uuids[pkg.name]) == 1 && (pkg.uuid = uuids[pkg.name][1])
        end
        if has_uuid(pkg) && !has_name(pkg) && pkg.uuid in keys(names)
            pkg.name = names[pkg.uuid]
        end
    end
    return pkgs
end

"""
Disambiguate name/uuid package specifications using registry info.
"""
function registry_resolve!(env::EnvCache, pkgs::AbstractVector{PackageSpec})
    # if there are no half-specified packages, return early
    any(pkg->has_name(pkg) ⊻ has_uuid(pkg), pkgs) || return
    # collect all names and uuids since we're looking anyway
    names = [pkg.name for pkg in pkgs if has_name(pkg)]
    uuids = [pkg.uuid for pkg in pkgs if has_uuid(pkg)]
    find_registered!(env, names, uuids)
    for pkg in pkgs
        @assert has_name(pkg) || has_uuid(pkg)
        if has_name(pkg) && !has_uuid(pkg)
            pkg.uuid = registered_uuid(env, pkg.name)
        end
        if has_uuid(pkg) && !has_name(pkg)
            pkg.name = registered_name(env, pkg.uuid)
        end
    end
    return pkgs
end

"Ensure that all packages are fully resolved"
function ensure_resolved(
    env::EnvCache,
    pkgs::AbstractVector{PackageSpec},
    registry::Bool = false,
)::Void
    unresolved = Dict{String,Vector{UUID}}()
    for pkg in pkgs
        has_uuid(pkg) && continue
        uuids = UUID[]
        for (name, infos) in env.manifest, info in infos
            name == pkg.name && haskey(info, "uuid") || continue
            uuid = UUID(info["uuid"])
            uuid in uuids || push!(uuids, uuid)
        end
        sort!(uuids, by=uuid->uuid.value)
        unresolved[pkg.name] = uuids
    end
    isempty(unresolved) && return
    msg = sprint() do io
        println(io, "The following package names could not be resolved:")
        for (name, uuids) in sort!(collect(unresolved), by=lowercase∘first)
            print(io, " * $name (")
            if length(uuids) == 0
                what = ["project", "manifest"]
                registry && push!(what, "registry")
                print(io, "not found in ")
                join(io, what, ", ", " or ")
            else
                join(io, uuids, ", ", " or ")
                print(io, " in manifest but not in project")
            end
            println(io, ")")
        end
        print(io, "Please specify by known `name=uuid`.")
    end
    cmderror(msg)
end

const DEFAULT_REGISTRIES = Dict(
    "Uncurated" => "https://github.com/JuliaRegistries/Uncurated.git"
)

"Return paths of all registries in a depot"
function registries(depot::String)::Vector{String}
    d = joinpath(depot, "registries")
    ispath(d) || return String[]
    regs = filter!(readdir(d)) do r
        isfile(joinpath(d, r, "registry.toml"))
    end
    String[joinpath(depot, "registries", r) for r in regs]
end

"Return paths of all registries in all depots"
function registries()::Vector{String}
    isempty(depots()) && return String[]
    user_regs = abspath(depots()[1], "registries")
    if !ispath(user_regs)
        mkpath(user_regs)
        info("Cloning default registries into $user_regs")
        for (reg, url) in DEFAULT_REGISTRIES
            info(" [+] $reg = $(repr(url))")
            path = joinpath(user_regs, reg)
            LibGit2.clone(url, path)
        end
    end
    return [r for d in depots() for r in registries(d)]
end

const line_re = r"""
    ^ \s*
    ([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})
    \s* = \s* \{
    \s* name \s* = \s* "([^"]*)" \s*,
    \s* path \s* = \s* "([^"]*)" \s*,?
    \s* \} \s* $
"""x

"""
Lookup package names & uuids in a single pass through registries
"""
function find_registered!(
    env::EnvCache,
    names::Vector{String},
    uuids::Vector{UUID} = UUID[];
    force::Bool = false,
)::Void
    # only look if there's something new to see (or force == true)
    names = filter(name->!haskey(env.uuids, name), names)
    uuids = filter(uuid->!haskey(env.paths, uuid), uuids)
    !force && isempty(names) && isempty(uuids) && return

    # since we're looking anyway, look for everything
    save(name::String) =
        name in names || haskey(env.uuids, name) || push!(names, name)
    save(uuid::UUID) =
        uuid in uuids || haskey(env.paths, uuid) || push!(uuids, uuid)

    # lookup any dependency in the project file
    for (name, uuid) in env.project["deps"]
        save(name); save(UUID(uuid))
    end
    # lookup anything mentioned in the manifest file
    for (name, infos) in env.manifest, info in infos
        save(name)
        haskey(info, "uuid") && save(UUID(info["uuid"]))
        haskey(info, "deps") || continue
        for (n, u) in info["deps"]
            save(n); save(UUID(u))
        end
    end
    # if there's still nothing to look for, return early
    isempty(names) && isempty(uuids) && return

    # build regexs for names and uuids
    uuid_re = sprint() do io
        if !isempty(uuids)
            print(io, raw"^( ")
            for (i, uuid) in enumerate(uuids)
                1 < i && print(io, " | ")
                print(io, raw"\Q", uuid, raw"\E")
            end
            print(io, raw" )\b")
        end
    end
    name_re = sprint() do io
        if !isempty(names)
            print(io, raw"\bname \s* = \s* \"( ")
            for (i, name) in enumerate(names)
                1 < i && print(io, " | ")
                print(io, raw"\Q", name, raw"\E")
            end
            print(io, raw" )\"")
        end
    end
    regex = if !isempty(uuids) && !isempty(names)
        Regex("( $uuid_re | $name_re )", "x")
    elseif !isempty(uuids)
        Regex(uuid_re, "x")
    elseif !isempty(names)
        Regex(name_re, "x")
    else
        error("this should not happen")
    end

    # initialize env entries for names and uuids
    for name in names; env.uuids[name] = UUID[]; end
    for uuid in uuids; env.paths[uuid] = String[]; end
    # note: empty vectors will be left for names & uuids that aren't found

    # search through all registries
    for registry in registries()
        open(joinpath(registry, "registry.toml")) do io
            # skip forward until [packages] section
            for line in eachline(io)
                ismatch(r"^ \s* \[ \s* packages \s* \] \s* $"x, line) && break
            end
            # fine lines with uuid or name we're looking for
            for line in eachline(io)
                ismatch(regex, line) || continue
                m = match(line_re, line)
                m == nothing &&
                    error("misformated registry.toml package entry: $line")
                uuid = UUID(m.captures[1])
                name = Base.unescape_string(m.captures[2])
                path = abspath(registry, Base.unescape_string(m.captures[3]))
                push!(get!(env.uuids, name, typeof(uuid)[]), uuid)
                push!(get!(env.paths, uuid, typeof(path)[]), path)
            end
        end
    end
end
find_registered!(env::EnvCache, uuids::Vector{UUID}; force::Bool=false)::Void =
    find_registered!(env, String[], uuids, force=force)

"Lookup all packages in project & manifest files"
find_registered!(env::EnvCache)::Void =
    find_registered!(env, String[], UUID[], force=true)

"Get registered uuids associated with a package name"
function registered_uuids(env::EnvCache, name::String)::Vector{UUID}
    find_registered!(env, [name], UUID[])
    return env.uuids[name]
end

"Get registered paths associated with a package uuid"
function registered_paths(env::EnvCache, uuid::UUID)::Vector{String}
    find_registered!(env, String[], [uuid])
    return env.paths[uuid]
end

"Get registered names associated with a package uuid"
function registered_names(env::EnvCache, uuid::UUID)::Vector{String}
    find_registered!(env, String[], [uuid])
    String[n for (n, uuids) in env.uuids for u in uuids if u == uuid]
end

"Determine a single UUID for a given name, prompting if needed"
function registered_uuid(env::EnvCache, name::String)::UUID
    uuids = registered_uuids(env, name)
    length(uuids) == 0 && return UUID(zero(UInt128))
    length(uuids) == 1 && return uuids[1]
    # prompt for which UUID was intended:
    choices = ["$uuid – $(registered_info(env, uuid, "repo"))" for uuid in uuids]
    menu = RadioMenu(choices)
    choice = request("There are multiple registered `$name` packages, choose one:", menu)
    choice == -1 && return UUID(zero(UInt128))
    return uuids[choice]
end

"Determine current name for a given package UUID"
function registered_name(env::EnvCache, uuid::UUID)::String
    names = registered_names(env, uuid)
    length(names) == 0 && return ""
    length(names) == 1 && return names[1]
    return registered_info(env, uuid, "name")
end

"Return most current package info for a registered UUID"
function registered_info(env::EnvCache, uuid::UUID, key::String)
    paths = env.paths[uuid]
    isempty(paths) && cmderror("`$uuid` is not registered")
    values = []
    for path in paths
        info = parse_toml(paths[1], "package.toml")
        value = get(info, key, nothing)
        value in values || push!(values, value)
    end
    length(values) > 1 &&
        cmderror("package `$uuid` has multiple registered `$key` values: ", join(values, ", "))
    return values[1]
end

"Find package by UUID in the manifest file"
function manifest_info(env::EnvCache, uuid::UUID)::Union{Dict{String,Any},Void}
    uuid in values(env.uuids) || find_registered!(env, [uuid])
    for (name, infos) in env.manifest, info in infos
        haskey(info, "uuid") && uuid == UUID(info["uuid"]) || continue
        return merge!(Dict{String,Any}("name" => name), info)
    end
    return nothing
end

"Give a short path string representation"
function pathrepr(env::EnvCache, path::String, base::String=pwd())
    path = abspath(base, path)
    if env.git != nothing
        repo = LibGit2.path(env.git)
        if startswith(base, repo)
            # we're in the repo => path relative to pwd()
            path = relpath(path, base)
        elseif startswith(path, repo)
            # we're not in repo but path is => path relative to repo
            path = relpath(path, repo)
        end
    end
    if !iswindows() && isabspath(path)
        home = joinpath(homedir(), "")
        if startswith(path, home)
            path = joinpath("~", path[nextind(path,endof(home)):end])
        end
    end
    return repr(path)
end

end # module


text:
export Agent
export sync!, addnode!, delnode!, addedge!, deledge!, deledges!, merge, merge!, barrier
export activate!, deactivate!
export relocate!, store!, sync!, addnode!, delnode!, addedge!, deledge!, merge, merge!, barrier
export run_edges!, run_nodes!, run!

# FIXME: Make immutable if possible
mutable struct Agent
  nodes::Dict{UUID, AbstractContainer}
  edges::Vector{Tuple{UUID, UUID, Symbol}}
end
Agent() = Agent(Dict{UUID,AbstractContainer}(), Tuple{UUID,UUID,Symbol}[])

# Gets/sets a node
getindex(agent::Agent, uuid::UUID) = agent.nodes[uuid]
getindex(agent::Agent, uuid::AbstractString) = agent[UUID(uuid)]
# FIXME: Container stuff
#setindex!(agent::Agent, node, uuid::UUID) = agent.nodes[uuid] = node
#setindex!(agent::Agent, node, uuid::AbstractString) = agent[UUID(uuid)] = node

# Adds a node
function addnode!(agent::Agent, node::AbstractNode)
  id = uuid4()
  agent.nodes[id] = CPUContainer(node)
  return id
end

# Deletes a node
# TODO: Delete associated edges
function delnode!(agent::Agent, id::UUID)
  node = agent.nodes[id]
  Base.delete!(agent.nodes, id)
end

# Adds an edge connection from a source node to a target node with a specified operation
function addedge!(agent::Agent, src::UUID, dst::UUID, op::Symbol, conf=NamedTuple())
  addedge!(agent.nodes[src], agent.nodes[dst], dst, op, conf)
  push!(agent.edges, (src, dst, op))
end
function addedge!(agent::Agent, src::UUID, pairs::Tuple)
  for pair in pairs
    conf = length(pair) == 3 ? pair[3] : NamedTuple()
    addedge!(agent, src, pair[2], pair[1], conf)
  end
end

# Deletes an edge
function deledge!(agent::Agent, src::UUID, dst::UUID, op::Symbol)
  edges = findall(edge->(edge[1]==src&&edge[2]==dst&&edge[3]==op), agent.edges)
  @assert length(edges) != 0 "No such edge found with src: $src, dst: $dst, op: $op"
  @assert length(edges) < 2 "Multiple matching edges returned"
  deledge!(agent.nodes[src], dst, op)
  deleteat!(agent.edges, edges[1])
end
deledge!(agent::Agent, edge::Tuple{UUID,UUID,Symbol}) =
  deledge!(agent, edge...)

# Deletes all edges originating from a node
function deledges!(agent::Agent, src::UUID)
  edges = findall(edge->(edge[1]==src), agent.edges)
  deledges!(agent.nodes[src])
  # TODO: Add assertion that this works as intended
  deleteat!.(Ref(agent.edges), reverse(edges))
end

# Returns the union of two agents
function merge(agent1::Agent, agent2::Agent)
  agent3 = Agent()
  agent3.nodes = Base.merge(agent1.nodes, agent2.nodes)
  agent3.edges = vcat(agent1.edges, agent2.edges)
  return agent3
end

# Merges agent2 into agent1
function merge!(agent1::Agent, agent2::Agent)
  Base.merge!(agent1.nodes, agent2.nodes)
  agent1.edges = vcat(agent1.edges, agent2.edges)
  return agent1
end

# Sets a barrier on all agent nodes
function barrier(agent::Agent)
  for node in values(agent.nodes)
    barrier(node)
  end
end

# Re-initializes transient data in an agent (such as learned weights or temporary values)
function reinit!(agent::Agent)
  for node in values(agent.nodes)
    reinit!(root(node))
  end
end

# Prints a text overview of the agent
# TODO: Use colors if enabled
# TODO: Optionally elaborate nodes, edges, groups?
function Base.show(io::IO, agent::Agent)
  print(io, "Agent($(length(agent.nodes)) nodes, $(length(agent.edges)) edges)")
end

# Runs all nodes for one iteration
function run_nodes!(agent::Agent)
  nodes = collect(values(agent.nodes))
  for node in nodes
    nupdate!(node)
  end
end

# Runs all edge-connected nodes for one iteration
function run_edges!(agent::Agent)
  # Construct edge sets
  # TODO: Support multiple nodes with the same op
  edges = Dict{UUID, Vector{Tuple{Symbol,UUID,AbstractContainer}}}()
  for edgeObj in agent.edges
    name = edgeObj[1]
    entry = (edgeObj[3], edgeObj[2], agent.nodes[edgeObj[2]])
    if haskey(edges, name)
      push!(edges[name], entry)
    else
      edges[name] = Tuple{Symbol,UUID,AbstractContainer}[entry]
    end
  end

  # Run edges
  for edgeName in keys(edges)
    eforward!(agent.nodes[edgeName], edges[edgeName])
  end
end

# Runs agent for one iteration
function run!(agent::Agent)
  # Forward pass on each node
  run_nodes!(agent)

  # Forward pass on edge-connected nodes
  run_edges!(agent)
end

==(agent1::Agent, agent2::Agent) =
  (agent1.nodes == agent2.nodes) && (agent1.edges == agent2.edges)


text:
module CopositiveAnalyticCenter

using LinearAlgebra

const solver = open(f->read(f, String), joinpath(@__DIR__, "..", "deps", "USESOLVER"))
if solver == "Gurobi"
    using Gurobi
    const SOCPsolver = Gurobi
    const MIPsolver = Gurobi
else
    using ECOS, Cbc
    const SOCPsolver = ECOS
    const MIPsolver = Cbc
end

include("./accp.jl")
include("./utils.jl")

export completely_positive_cut, is_completely_positive, testcopositive, CopositiveChecker

"""
    CopositiveChecker(sd::Int)
Initialize a copositivity checker for matrices with side dimension `sd`. Sets up
```math
minimize   -μ
subject to Ax + μ e - λ = 0
           e'x = 1
           0 ≤ x_j ≤ z_j
           0 ≤ λ_j ≤ (1-z_j)M_j
           z_j ∈ {0,1},
```
to check some matrix ``A`` for copositivity. It can be shown[^1] that for
suitable ``M_j`` this is equivalent to
```math
minimize   x'Ax
subject to e'x = 1
           x ≥ 0.
```
We can bound ``λ`` as follows: it follows from stationarity of the Lagrangian
that ``λ = μe + Ax``, such that ``e'x = 1`` and ``λ∘x = 0`` imply ``0 = μ +
x'Ax``. Then ``|μ| ≤ max_ij |A_ij|`` and ``λ_i ≤ max_ij |A_ij| + (Ax)_i ≤ max_ij
|A_ij| + max_j A_ij``.

[^1]:  Wei Xia, Juan Vera, and Luis F. Zuluaga. Globally solving Non-Convex
    Quadratic Programs via Linear Integer Programming techniques. **INFORMS
    Journal on Computing**, 2018.
"""
mutable struct CopositiveChecker
    side_dimension
    model
    x
    z
    KKT_con
    λ_bound
    λ_RHS_const
    λ
    function CopositiveChecker(sd::Int)
        model = Model(MIPsolver.Optimizer)
        set_silent(model)
        if solver == "Gurobi"
            set_optimizer_attribute(model, "NumericFocus", 3)
        end
        @variable(model, x[1:sd] >= 0)
        @variable(model, μ)
        @variable(model, λ[1:sd] >= 0)
        @variable(model, z[1:sd], Bin)
        @variable(model, λ_RHS_const[1:sd])
        @constraint(model, sum(x) == 1)
        @constraint(model, [i=1:sd], x[i] <= z[i])
        @objective(model, Min, -μ)
        @constraint(model, KKT_con, μ .- λ .== 0)
        @constraint(model, λ_bound[i=1:sd], λ[i] <= λ_RHS_const[i] )
        new(sd, model, x, z, KKT_con, λ_bound, λ_RHS_const, λ)
    end
end
"""
    testcopositive(A::AbstractMatrix, cc::CopositiveChecker; maxcoef=2^32)
Set up the CopositiveChecker `cc` to check `A`, and call `optimize_copositive!`.
If any coefficient exceeds `maxcoef`, the matrix is scaled first.

See also: [`CopositiveChecker`](@ref), [`optimize_copositive!`](@ref)
"""
function testcopositive(A::AbstractMatrix, cc::CopositiveChecker; maxcoef=2^32)
    M = maximum(abs.(A))
    if M > maxcoef
        A *= maxcoef/M
        M = maximum(abs.(A))
    end
    for i in 1:cc.side_dimension
        for j in 1:cc.side_dimension
            set_normalized_coefficient(cc.KKT_con[i], cc.x[j], A[j,i])
        end
        set_normalized_coefficient(cc.λ_bound[i], cc.z[i], M + maximum(A[:,i]))
        fix(cc.λ_RHS_const[i], M + maximum(A[:,i]))
    end
    return optimize_copositive!(cc)
end
function testcopositive(A::AbstractArray{T,2}) where T
    @assert issymmetric(A)
    cc = CopositiveChecker(size(A,1))
    return testcopositive(A, cc)
end

struct CopositiveStatusError <: Exception
    status::MOI.TerminationStatusCode
end
Base.showerror(io::IO, e::CopositiveStatusError) = println(io,
"could not complete copositivity check: a problem terminated with status $(e.status)")

"""
    optimize_copositive!(cc::CopositiveChecker, ub=Inf)
Solve the model in `cc` such that ``x`` and ``λ`` are complementary and ``z`` is
binary. Refine the solution if ``x`` and ``λ`` are not complementary and the
value of that solution is below `ub`.

See also: [`CopositiveChecker`](@ref)
"""
function optimize_copositive!(cc::CopositiveChecker, ub=Inf)
    optimize!(cc.model)
    if termination_status(cc.model) != MOI.OPTIMAL
        throw(CopositiveStatusError(termination_status(cc.model)))
    end
    if any(value(cc.x[i]) > 0 && value(cc.λ[i]) > 0 for i in 1:cc.side_dimension) &&
    objective_value(cc.model) < ub
        # Fix the values of z, and the values of x and λ if z forces them to 0
        fix!(cc)
        optimize!(cc.model)
        if termination_status(cc.model) == MOI.OPTIMAL
            # If the problem is feasible, we need to make sure there are no
            # other feasible solutions with a better objective value.
            obj, x = objective_value(cc.model), value.(cc.x)
        end
        # Cut off the current solution
        zval = fix_value.(cc.z)
        undofix!(cc)
        # Add cut ∑_{i: zval[i]=0} z[i] + ∑_{i: zval[i]=1} (1-z[i]) >= 1
        cut = @constraint(cc.model, sum(cc.z[zval.==0]) + sum(1 .-cc.z[zval.==1]) >= 1)
        if termination_status(cc.model) == MOI.OPTIMAL
            # Look for other feasible solutions until they are worse than the
            # one we already have
            local altobj, altx
            try
                (altobj, altx) = optimize_copositive!(cc, obj)
            catch e
                if e isa CopositiveStatusError
                    altobj = Inf
                else
                    delete(cc.model, cut)
                    rethrow(e)
                end
            end
            if altobj < obj
                obj = altobj
                x = altx
            end
        else
            # If the problem is infeasible, the computed basic solution was only
            # numerically feasible. We cut off this solution, and apply this
            # function again.
            try
                (obj, x) = optimize_copositive!(cc)
            catch e
                delete(cc.model, cut)
                rethrow(e)
            end
        end
        delete(cc.model, cut)
    else
        obj, x = objective_value(cc.model), value.(cc.x)
    end
    return (obj, x)
end
function fix!(cc::CopositiveChecker)
    for i in 1:cc.side_dimension
        fix(cc.z[i], round(value(cc.z[i])))
        if fix_value(cc.z[i]) == 0
            fix(cc.x[i], 0, force=true)
        else
            fix(cc.λ[i], 0, force=true)
        end
    end
end
function undofix!(cc::CopositiveChecker)
    for i in 1:cc.side_dimension
        if fix_value(cc.z[i]) == 0
            unfix(cc.x[i])
            set_lower_bound(cc.x[i], 0)
        else
            unfix(cc.λ[i])
            set_lower_bound(cc.λ[i], 0)
        end
        unfix(cc.z[i])
        set_binary(cc.z[i])
    end
end

"""
    is_completely_positive(A)
Test if the matrix `A` is completely positive through an analytic center cutting
plane method.

See also: [`completely_positive_cut`](@ref)
"""
function is_completely_positive(A::AbstractMatrix)
    X = completely_positive_cut(A)
    return dot(A, X) >= 0
end

"""
    completely_positive_cut(A)
Compute a copositive matrix `X` that approximately minimizes `dot(A,X)` subject
to the condition `norm(vec2matinv(X)) ≤ 1`.
# Arguments
- `useaccp=true`: uses an analytic center cutting plane method, otherwise an
  ellipsoid method.
- `verbose=true`: display output. If the analytic center computation fails, this
  includes the spectrum of the matrix in the linear system that should be solved.
- `trackcalls=false`: the method returns `(X, calls)` when `trackcalls==true`,
  where `calls` is the number of calls to `oracle`.
"""
function completely_positive_cut(A::AbstractMatrix; verbose=true, trackcalls=false,
useaccp=true)
    @assert issymmetric(A)
    obj = vec2matadj(A)
    cc = CopositiveChecker(size(A,1))
    function oracle(y::AbstractVector)
        val, x = testcopositive(vec2mat(y), cc)
        return val < 0 ? Halfspace(vec2matadj(-x*x'), 0.) : true
    end

    method = useaccp ? accp : ellipsoid_method
    res = method(obj, oracle, 1., verbose=verbose, trackcalls=trackcalls)
    return trackcalls ? (vec2mat(res[1]), res[2]) : vec2mat(res)
end

end # module