text:
## IO for element

# predicate for if an element contains only a single HTMLText node
isjusttext(elem) = length(elem.children) == 1 && typeof(first(elem.children)) == HTMLText

# linesof(elem) returns a task. consuming from this task
# yields returns tuples of (depth, line), where depth is tree
# depth and line is a pretty string representing the line
function linesof{T}(elem::HTMLElement{T},depth::Int)
    opentag = isempty(elem.attributes) ? "<$T" : "<$T "
    for (name,value) in elem.attributes
        opentag *= "$name=\"$value\""
    end
    opentag *= ">"
    closetag = "</$T>"
    if isempty(elem.children)
        produce((depth,opentag * closetag))
    elseif isjusttext(elem)
        produce((depth,opentag * elem.children[1].text * closetag))
    else
        produce((depth,opentag))
        for child in elem.children
            linesof(child,depth+1)
        end
        produce((depth,closetag))
    end
end

linesof{T}(elem::HTMLElement{T}) = @task linesof(elem,0)

linesof(t::HTMLText) = produce((0,t.text))
linesof(t::HTMLText,depth) = produce((depth,t.text))

function prettyprint(io::IO, elem::HTMLElement, maxlines)
    for (i,(depth, line)) in enumerate(linesof(elem))
        if i == maxlines
            write(io,". . . \n")
            return
        else
            write(io,repeat("  ",depth)*line*"\n")
        end
    end
end

prettyprint(io::IO, elem::HTMLElement) = prettyprint(io, elem, Inf)
prettyprint(elem::HTMLElement) = prettyprint(STDOUT, elem)

# TODO maybe query tty_cols for a default?
function Base.show(io::IO, elem::HTMLElement)
    write(io,summary(elem)*":\n")
    prettyprint(io, elem, 20)
end

function Base.showall(io::IO, elem::HTMLElement)
    write(io,summary(elem)*":\n")
    prettyprint(io, elem, Inf)
end

function Base.showcompact(io::IO, elem::HTMLElement)
    write(io,summary(elem))
end

# print just writes all the lines to io
function Base.print(io::IO, elem::HTMLElement; pretty=false)
    if pretty
        prettyprint(io, elem, Inf)
    else
        for (depth,line) in linesof(elem)
            write(io, line)
        end
    end
end

### IO for Text

function Base.show(io::IO, t::HTMLText)
    write(io,"HTML Text: $(t.text)")
end

function Base.print(io::IO, t::HTMLText)
    write(io,"$(t.text)")
end

### IO for Document

function Base.show(io::IO, doc::HTMLDocument)
    write(io, "HTML Document:\n")
    write(io, "<!DOCTYPE $(doc.doctype)>\n")
    Base.show(io, doc.root)
end

function Base.showall(io::IO, doc::HTMLDocument)
    write(io, "HTML Document:\n")
    write(io, "<!DOCTYPE $(doc.doctype)>\n")
    Base.showall(io, doc.root)
end

function Base.print(io::IO, doc::HTMLDocument; pretty=false)
    write(io, "<!DOCTYPE $(doc.doctype)>")
    Base.print(io, doc.root, pretty=pretty)
end

prettyprint(io::IO, doc::HTMLDocument) = Base.print(io, doc, pretty=true)
prettyprint(doc::HTMLDocument) = prettyprint(STDOUT, doc)


text:
using Revise
using AdFem
using LinearAlgebra
using PyPlot
using DelimitedFiles

mmesh = Mesh(8, 8 , 1/8)
DOF = zeros(Int64, 2mmesh.ndof)
for i = 1:mmesh.ndof
    DOF[i] = 2*i-1
    DOF[i+mmesh.ndof] = 2*i
end
u = readdlm("fenics/u.txt")[:]
u = u[DOF]
# u = ones(2mmesh.ndof)

μ = 10.0*ones(get_ngauss(mmesh))
λ = 3.0*ones(get_ngauss(mmesh))
ψ, J = neo_hookean(u, μ, λ, mmesh)

F = readdlm("fenics/F.txt")
S = readdlm("fenics/S.txt")
@info maximum(abs.(ψ - F[DOF]))
@info maximum(abs.(J - S[DOF, DOF]))

ψ, J = neo_hookean(constant(u), μ, λ, mmesh)
sess = Session(); init(sess)
@info maximum(abs.(run(sess, ψ) - F[DOF]))
@info maximum(abs.(run(sess, J) - S[DOF, DOF]))

ext: jl


text:
using ADCME
using LinearAlgebra
using AdFem
using DelimitedFiles

A = readdlm("fenics/A.txt")
mesh = Mesh(8, 8, 1/8)
K = zeros(get_ngauss(mesh), 3, 3)
for i = 1:get_ngauss(mesh)
    K[i, :, :] = [1.0 0.0 0.0
                  0.0 1.0 0.0
                  0.0 0.0 0.5]
end
B1 = compute_fem_stiffness_matrix(K, mesh)

K = constant(K)
B = compute_fem_stiffness_matrix(K, mesh)

sess = Session(); init(sess)
B0 = run(sess, B)
B0 = Array(B0)
@info norm(B0-B1)

DOF = zeros(Int64, 2*mesh.ndof)
for i = 1:mesh.ndof
    DOF[2*i-1] = i
    DOF[2*i] = i + mesh.ndof
end
B0 = B0[DOF, DOF]

@show norm(A - B0)

A = readdlm("fenics/A2.txt")
mesh = Mesh(8, 8, 1.0 / 8,degree=2)
K = zeros(get_ngauss(mesh), 3, 3)
for i = 1:get_ngauss(mesh)
    K[i, :, :] = [1.0 0.0 0.0
                  0.0 1.0 0.0
                  0.0 0.0 0.5]
end
K = constant(K)
B = compute_fem_stiffness_matrix(K, mesh)
sess = Session(); init(sess)
B0 = run(sess, B)
B0 = Array(B0)

E = Int64.(readdlm("fenics/edges.txt"))
Edof = get_edge_dof(E, mesh)
DOF = [1:mesh.nnode; Edof .+ mesh.nnode]
DOF = [DOF; DOF .+ mesh.ndof]
B0 = B0[DOF, DOF]

@show norm(A - B0)


text:
using FourierFlows, Printf, Random, FFTW

using Random: seed!

import GeophysicalFlows.TwoDTurb
import GeophysicalFlows.TwoDTurb: energy, enstrophy
import GeophysicalFlows: peakedisotropicspectrum

dev = CPU()     # Device (CPU/GPU)

# Parameters
  n = 256
  L = 2π
 nν = 2
  ν = 0.0
 dt = 1e-3
nstepsjit = 10 # call stepforward for nstepsjit to force compilation
nsteps = 5000
 nothingfunction(args...) = nothing

gr = TwoDGrid(dev, n, L, n, L; nthreads=1)
pr = TwoDTurb.Params(ν, nν)
vs = TwoDTurb.Vars(dev, gr)
eq = TwoDTurb.Equation(pr, gr)

prob = FourierFlows.Problem(eq, "FilteredAB3", dt, gr, vs, pr, dev)
filter = FourierFlows.makefilter(prob.eqn)

# some aliases
sol, cl, vs, gr = prob.sol, prob.clock, prob.vars, prob.grid

x, y = gridpoints(gr)

# Initial condition closely following pyqg barotropic example
# that reproduces the results of the paper by McWilliams (1984)
seed!(1234)
k0, E0 = 6, 0.5
zetai  = peakedisotropicspectrum(gr, k0, E0, mask=filter)
TwoDTurb.set_zeta!(prob, zetai)

for j=1:nstepsjit  #just-in-time compilation
  stepforward!(prob)
end

TwoDTurb.set_zeta!(prob, zetai)

startwalltime = time()
while cl.step < nsteps
  stepforward!(prob)
end

println(round((time()-startwalltime)/(nsteps-1)*1000, digits=3), " ms per time-step")

# using PyPlot
# TwoDTurb.updatevars!(prob)
# figure(figsize=(10, 4))
# subplot(121)
# pcolormesh(x, y, zetai)
# xlabel("x")
# ylabel("y")
# title("vorticity @ t=0")
# axis("square")
# subplot(122)
# pcolormesh(x, y, vs.zeta)
# axis("square")
# xlabel("x")
# ylabel("y")
# title("vorticity @ t="*string(round(cl.t, digits=2)))
# savefig("GeophysicalFlows_n"*string(n)*".png", dpi=400)


text:
export ExternalSamplingMCCFRPolicy

"""
    ExternalSamplingMCCFRPolicy

This implementation uses stochasticaly-weighted averaging.

Ref:

- [MONTE CARLO SAMPLING AND REGRET MINIMIZATION FOR EQUILIBRIUM COMPUTATION AND DECISION-MAKING IN LARGE EXTENSIVE FORM GAMES](http://mlanctot.info/files/papers/PhD_Thesis_MarcLanctot.pdf)
- [Monte Carlo Sampling for Regret Minimization in Extensive Games](https://papers.nips.cc/paper/3713-monte-carlo-sampling-for-regret-minimization-in-extensive-games.pdf)
"""
struct ExternalSamplingMCCFRPolicy{S,B,R<:AbstractRNG} <: AbstractCFRPolicy
    nodes::Dict{S,InfoStateNode}
    behavior_policy::B
    rng::R
end

(p::ExternalSamplingMCCFRPolicy)(env::AbstractEnv) = p.behavior_policy(env)

RLBase.prob(p::ExternalSamplingMCCFRPolicy, env::AbstractEnv) = prob(p.behavior_policy, env)

RLBase.prob(p::ExternalSamplingMCCFRPolicy, env::AbstractEnv, action) =
    prob(p.behavior_policy, env, action)

function ExternalSamplingMCCFRPolicy(; state_type = String, rng = Random.GLOBAL_RNG)
    ExternalSamplingMCCFRPolicy(
        Dict{state_type,InfoStateNode}(),
        TabularRandomPolicy(;
            rng = rng,
            table = Dict{state_type,Vector{Float64}}(),
            is_normalized = true,
        ),
        rng,
    )
end

function RLBase.update!(p::ExternalSamplingMCCFRPolicy)
    for (k, v) in p.nodes
        s = sum(v.cumulative_strategy)
        if s != 0
            m = v.mask
            strategy = zeros(length(m))
            strategy[m] .= v.cumulative_strategy ./ s
            update!(p.behavior_policy, k => strategy)
        else
            # The TabularLearner will return uniform distribution by default.
            # So we do nothing here.
        end
    end
end

"Run one interation"
function RLBase.update!(p::ExternalSamplingMCCFRPolicy, env::AbstractEnv)
    for x in players(env)
        if x != chance_player(env)
            external_sampling(copy(env), x, p.nodes, p.rng)
        end
    end
end

function external_sampling(env, i, nodes, rng)
    player = current_player(env)

    if is_terminated(env)
        reward(env, i)
    elseif player == chance_player(env)
        env(sample(rng, action_space(env), Weights(prob(env), 1.0)))
        external_sampling(env, i, nodes, rng)
    else
        I = state(env)
        legal_actions = legal_action_space(env)
        M = legal_action_space_mask(env)
        n = length(legal_actions)
        node = get!(nodes, I, InfoStateNode(M))
        regret_matching!(node; is_reset_neg_regrets = false)
        σ, rI, sI = node.strategy, node.cumulative_regret, node.cumulative_strategy

        if i == player
            u = zeros(n)
            uσ = 0
            for (aᵢ, a) in enumerate(legal_actions)
                u[aᵢ] = external_sampling(child(env, a), i, nodes, rng)
                uσ += σ[aᵢ] * u[aᵢ]
            end
            rI .+= u .- uσ
            uσ
        else
            a′ = sample(rng, Weights(σ, 1.0))
            env(legal_actions[a′])
            u = external_sampling(env, i, nodes, rng)
            sI .+= σ
            u
        end
    end
end


text:
#!/usr/bin/env julia
using Faker, HypertextLiteral, Hyperscript, BenchmarkTools

using HypertextLiteral: @htl_str

# This is going to simulate a hierarchical report that lists a set of
# companies, and for each company, a list of employees.

Faker.seed(4321)

make_employee() = (
  first_name=Faker.first_name(),
  last_name=Faker.last_name(),
  title=Faker.job(),
  main_number=Faker.phone_number(),
  email=Faker.email(),
  cell_phone=Faker.cell_phone(),
  color= Faker.hex_color(),
  comments= Faker.paragraphs()
)

make_customer() = (
   company=Faker.company(),
   url=Faker.url(),
   phrase=Faker.catch_phrase(),
   active=Faker.date_time_this_decade(before_now=true, after_now=false),
   notes= Faker.sentence(number_words=rand(2:9), variable_nb_words=true),
   employees=[make_employee() for x in 1:rand(3:18)])

database = [make_customer() for x in 1:13]

htl_database(d) = @htl("

    <title>Customers & Employees)</title>

    $((map(d) do c; htl_customer(c); end))

")

htl_customer(c) = @htl("

Company
$(c.company)

Phrase
$(c.phrase)

Active Since
$(c.active)

Employees

Last NameFirst NameTitle
              E-MailOffice PhoneCell Phone
              Comments

          $((map(c.employees) do e; htl_employee(e); end))

")

htl_employee(e) = @htl("

$(e.last_name)$(e.first_name)$(e.title)
          $(e.email)
          $(e.main_number)$(e.cell_phone)
          $((@htl("$c") for c in e.comments))
")

htl_test() = begin
   io = IOBuffer()
   ob = htl_database(database)
   show(io, MIME("text/html"), ob)
   return io
end

# very silly test using attributes rather than elements...

att_database(d) = @htl("""

    $(map(d) do c; att_customer(c); end)

""")

att_customer(c) = @htl("""

       <form>
         Company<input value=$(c.company)>
         Phrase<input value='$(c.phrase)'>
         Active Since<input value="$(c.active)">
         Employees
       </form>
          $((map(c.employees) do e; att_employee(e); end))

""")

att_employee(e) = @htl("""
       <form>
         Last Name<input value=$(e.last_name)>
         First Name<input value="$(e.first_name)">
         Title<input value='$(e.title)'>
         E-Mail<input $(:value => e.email)>
         Main<input $("value" => e.main_number))>
         Cell<input $((value=e.main_number,))>
         $((@htl("") for x in e.comments))
""")

att_test() = begin
   io = IOBuffer()
   ob = att_database(database)
   show(io, MIME("text/html"), ob)
   return io
end

ee(x) = replace(replace(x, "&" => "&"), "<" => "<")
ea(x) = replace(replace(x, "&" => "&"), "'" => "'")

reg_database(d) = """

    <title>Customers & Employees</title>

    $(join([reg_customer(c) for c in d]))

"""

reg_customer(c) = """

Company
$(ee(c.company))

Phrase
$(ee(c.phrase))

Active Since
$(ee(c.active))

Employees

Last NameFirst NameTitle
              E-MailOffice PhoneCell Phone
              Comments

          $(join([reg_employee(e) for e in c.employees]))

"""

reg_employee(e) = """

$(ee(e.last_name))$(ee(e.first_name))$(e.title)
          $(ee(e.email))
          $(ee(e.main_number))$(ee(e.cell_phone))
          $(join(["$(ee(c))" for c in e.comments]))
"""

reg_test() = begin
   io = IOBuffer()
   ob = reg_database(database)
   show(io, ob)
   return io
end

@tags html head body title dl dt dd table tr th td span

hs_database(d) =
  html(head(title("Customers & Employees")),
    body([hs_customer(c) for c in d]...))

hs_customer(c)=
  dl(dt("Company"), dd(c.company),
     dt("Phrase"), dd(c.phrase),
     dt("Active Since"), dd(c.active),
     dt("Employees"), dd(
       table(tr(th("Last Name"),th("First Name"),th("Title"),
                th("E-Mail"),th("Office Phone"),th("Cell Phone"),
                th("Comments")),
          [hs_employee(e) for e in c.employees]...)))

hs_employee(e) = tr(td(e.last_name), td(e.first_name), td(e.title),
                    td(href="mailto:$(e.email)", e.email),
                    td(e.main_number), td(e.cell_phone),
                    td([span(c) for c in e.comments]...))

hs_test() = begin
   io = IOBuffer()
   ob = hs_database(database)
   show(io, MIME("text/html"), ob)
   return io
end

function H(xs...)
    HTML() do io
        for x in xs
            show(io, MIME"text/html"(), x)
        end
    end
end

function entity(str::AbstractString)
    @assert length(str) == 1
    entity(str[1])
end

entity(ch::Char) = "&#$(Int(ch));"

HE(x) = HTML(replace(x, r"[<&]" => entity))
HA(x) = HTML(replace(x, r"[<']" => entity))

#HE(x) = HTML(replace(replace(x, "&" => "&"), "<" => "<"))
#HA(x) = HTML(replace(replace(x, "&" => "&"), "\"" => """))

cus_database(d) =
   H(HTML("<title>"), HE("Customers & Employees"),
     HTML("</title>"),
      [cus_customer(c) for c in d]...,
      HTML(""))

cus_customer(c) =
   H(HTML("

Company
"), HE(c.company),
     HTML("
Phrase
"), HE(c.phrase),
     HTML("
Active Siince
"), HE(c.active),
     HTML("""

Employees

Last NameFirst NameTitle
              E-MailOffice PhoneCell Phone
              Comments
"""),
     [cus_employee(e) for e in c.employees]...,
     HTML("

"))

cus_employee(e) =
   H(HTML("
"), HE(e.last_name),
         HTML(""), HE(e.first_name),
         HTML(""), HE(e.title),
         HTML(""), HE(e.email), HTML(""),
         HTML(""), HE(e.main_number),
         HTML(""), HE(e.cell_phone),
         HTML(""),
          [H(HTML(""), HE(c), HTML("")) for c in e.comments]...)

cus_test() = begin
   io = IOBuffer()
   ob = cus_database(database)
   show(io, MIME("text/html"), ob)
   return io
end

nest_result(d) = @htl("

    <title>Customers & Employees)</title>

    $((map(d) do c; @htl("

Company
$(c.company)

Phrase
$(c.phrase)

Active Since
$(c.active)

Employees

Last NameFirst NameTitle
              E-MailOffice PhoneCell Phone
              Comments

          $((map(c.employees) do e; @htl("

$(e.last_name)$(e.first_name)$(e.title)
          $(e.email)
          $(e.main_number)$(e.cell_phone)
          $((@htl("$c") for c in e.comments))
          "); end))

"); end))

")

nest_test() = begin
   io = IOBuffer()
   ob = nest_result(database)
   show(io, MIME("text/html"), ob)
   return io
end

BenchmarkTools.DEFAULT_PARAMETERS.seconds = 20
#println("interpolate: ", @benchmark reg_test())
#println("Custom HTML: ", @benchmark cus_test())
#println("Hyperscript: ", @benchmark hs_test())
println("HypertextLiteral: ", @benchmark htl_test())
#println("HTL (Attributes): ", @benchmark att_test())
#println("Nest Testing: ", @benchmark nest_test())

if false
    open("htl.html", "w") do f
       ob = htl_database(database)
       show(f, MIME("text/html"), ob)
    end
    open("hs.html", "w") do f
       ob = hs_database(database)
       show(f, MIME("text/html"), ob)
    end
    open("reg.html", "w") do f
       ob = reg_database(database)
       show(f, ob)
    end
    open("cus.html", "w") do f
       ob = cus_database(database)
       show(f, MIME("text/html"), ob)
    end
end


text:
using FunctionZeros
using Test

let zs = Array{Array{Float64,1}}(undef, 0)

    push!(zs,
          [2.4048255576957727686,
           5.5200781102863106496,
           8.6537279129110122170,
           11.791534439014281614,
           14.930917708487785948,
           18.071063967910922543,
           21.211636629879258959,
           24.352471530749302737,
           27.493479132040254796,
           30.634606468431975118,
           33.775820213573568684,
           36.917098353664043980,
           40.058425764628239295,
           43.199791713176730358,
           46.341188371661814019])

    push!(zs,
          [3.8317059702075123156,
           7.0155866698156187526,
           10.173468135062722077,
           13.323691936314223032,
           16.470630050877632813,
           19.615858510468242021,
           22.760084380592771898,
           25.903672087618382625,
           29.046828534916855067,
           32.189679910974403627,
           35.332307550083865103,
           38.474766234771615112,
           41.617094212814450886,
           44.759318997652821733,
           47.901460887185447121])

    push!(zs,
          [5.1356223018406825563,
           8.4172441403998648578,
           11.619841172149059427,
           14.795951782351260747,
           17.959819494987826455,
           21.116997053021845591,
           24.270112313573102610,
           27.420573549984557331,
           30.569204495516397037,
           33.716519509222699922,
           36.862856511283809818,
           40.008446733478192227,
           43.153453778371463270,
           46.297996677236919185,
           49.442164110416872731])

    push!(zs,
          [6.3801618959239835071,
           9.7610231299816696785,
           13.015200721698434420,
           16.223466160318768122,
           19.409415226435011554,
           22.582729593104442028,
           25.748166699294977635,
           28.908350780921757858,
           32.064852407097709483,
           35.218670738610114657,
           38.370472434756944364,
           41.520719670406775549,
           44.669743116617253071,
           47.817785691533301888,
           50.965029906205183304])

    push!(zs,
          [7.5883424345038043850,
           11.064709488501184882,
           14.372536671617589679,
           17.615966049804833042,
           20.826932956962387683,
           24.019019524771110046,
           27.199087765981251375,
           30.371007667117246902,
           33.537137711819223418,
           36.699001128744649500,
           39.857627302180889036,
           43.013737723354430016,
           46.167853512924374922,
           49.320360686390272118,
           52.471551398458022628])

@testset "test many zeros" begin
    for nu in 1:5
        for n in 1:15
            @test isapprox(besselj_zero(nu-1,n), zs[nu][n])
        end
    end
end
end # let

@testset "issue #2" begin
    @test abs(besselj_zero(-60, 6) - -103.87138471588182) < 1e-14
end

@testset "broadcasting" begin
    @test length(besselj_zero.([.1,.2],1)) == 2
    @test length(FunctionZeros.besselj_zero_asymptotic.([.1,.2],1)) == 2
end


text:
"""
    AbstractEvolution

Abstract base type for strategies for how to evolve a population into a new population.
"""
abstract type AbstractEvolution end

"""
    evolve(e::AbstractEvolution, population)

Return a new population evolved by `e`.

New population may or may not contain same individuals as before.
"""
evolve(e::AbstractEvolution, population) = _evolve(e, population)

"""
    NoOpEvolution <: AbstractEvolution
    NoOpEvolution()

Does not evolve the given population.
"""
struct NoOpEvolution <: AbstractEvolution end
_evolve(::NoOpEvolution, pop) = pop

"""
    AfterEvolution <: AbstractEvolution
    AfterEvolution(evo, fun)

Return `fun(newpop)` where `newpop = evolve(e.evo, pop)` where `pop` is original population to evolve.
"""
struct AfterEvolution{F, E} <: AbstractEvolution
    evo::E
    fun::F
end

function _evolve(e::AfterEvolution, pop)
    newpop = evolve(e.evo, pop)
    return e.fun(newpop)
end

"""
    EliteSelection <: AbstractEvolution
    EliteSelection(nselect::Integer, evo=NoOpEvolution())

Selects the `nselect` highest fitness candidates to be passed on to `evo`.
"""
struct EliteSelection{N, E} <: AbstractEvolution
    nselect::N
    evo::E
end
EliteSelection(n::Integer) = EliteSelection(n, NoOpEvolution())
_evolve(e::EliteSelection, pop::AbstractArray{<:AbstractCandidate}) = evolve(e.evo, partialsort(pop, 1:e.nselect, by=fitness, rev=true))

"""
    SusSelection <: AbstractEvolution
    SusSelection(nselect, evo, rng=rng_default)

Selects candidates for further evolution using stochastic universal sampling.
"""
struct SusSelection{N, R, E} <: AbstractEvolution
    nselect::N
    evo::E
    rng::R
end
SusSelection(nselect, evo) = SusSelection(nselect, evo, rng_default)

function _evolve(e::SusSelection, pop::AbstractArray{<:AbstractCandidate})
    csfitness = cumsum(fitness.(pop))

    gridspace = csfitness[end] / e.nselect
    start = rand(e.rng)*gridspace

    selected = similar(pop, e.nselect)
    for i in eachindex(selected)
        candind = findfirst(x -> x >= start + (i-1)*gridspace, csfitness)
        selected[i] = pop[candind]
    end
    return evolve(e.evo, selected)
end

"""
    TournamentSelection <: AbstractEvolution
    TournamentSelection(nselect, k, p::Real, evo, rng=rng_default)

Selects candidates for further evolution using tournament selection.

Holds `nselect` tournaments with one winner each where each tournament has `k`
random candidates from the given population.

Winner of a tournament is selected as the candidate with highest fitness with a
probability `p`, second highest fitness with a probability `p(p-1)`, third highest
fitness with a probability of `p((p-1)^2)` and so on.
"""
struct TournamentSelection{N,P,E,R} <: AbstractEvolution
    nselect::N
    k::N
    p::P
    evo::E
    rng::R
    function TournamentSelection(nselect::N, k::N, p::P, evo::E, rng::R=rng_default) where {N, P, E, R}
        @assert 0 <= p <= 1 "0 <= p <= 1 not fulfilled! p = $p"
        return new{N, Vector{P}, E, R}(nselect, k, p .* ((1 - p) .^ collect(0:k-1)), evo, rng)
    end
end

function _evolve(e::TournamentSelection, pop::AbstractArray{<:AbstractCandidate})
    # Step 1: Create a random tournament order with as little repetition as possible so that we can select
    # e.nselect candidates out of e.nselect tournaments with e.k random candidates in each tournament
    n = e.nselect * e.k
    nrem = mod(n, length(pop))
    nrep = max(0, n ÷ length(pop))
    torder = mapfoldl(i -> shuffle(e.rng, pop), vcat, 1:nrep, init = shuffle(e.rng, pop)[1:nrem])

    selected = similar(pop, e.nselect)
    for (i, cands) in enumerate(Iterators.partition(torder, e.k))
        score = rand(e.rng, e.k) .* e.p
        selected[i] = partialsort(cands, argmax(score), by=fitness, rev=true)
    end
    return evolve(e.evo, selected)
end

"""
    CombinedEvolution <: AbstractEvolution
    CombinedEvolution(evos::AbstractArray)
    CombinedEvolution(evos...)

Combines the evolved populations from several `AbstractEvolution`s into one population.
"""
struct CombinedEvolution{E<:AbstractArray} <: AbstractEvolution
    evos::E
end
CombinedEvolution(evos...) = CombinedEvolution(collect(evos))
_evolve(e::CombinedEvolution, pop) = mapfoldl(evo -> evolve(evo, pop), vcat, e.evos)

"""
    EvolutionChain <: AbstractEvolution
    EvolutionChain(evos::AbstractArray)
    EvolutionChain(evos...)

Chains multiple `AbstractEvolution`s in a sequence so that output from the first is input to the next and so on.
"""
struct EvolutionChain{E<:AbstractArray} <: AbstractEvolution
    evos::E
end
EvolutionChain(evos...) = EvolutionChain(collect(evos))
_evolve(e::EvolutionChain, pop) = foldr(evolve, reverse(e.evos); init=pop)

"""
    PairCandidates <: AbstractEvolution
    PairCandidates(evo::AbstractEvolution)

Creates pairs of candidates in a population and calls `evolve(evo, pairs)` where `pairs` is the array of pairs.
"""
struct PairCandidates{E<:AbstractEvolution} <: AbstractEvolution
    evo::E
end
function _evolve(e::PairCandidates, pop)
    padpop = iseven(length(pop)) ? pop : vcat(pop, pop[1])
    pairs = collect(zip(padpop[1:2:end], padpop[2:2:end]))
    return foldl(evolve(e.evo, pairs), init=[]) do popout, (c1,c2)::Tuple
        length(popout) < length(pop) - 1 ? vcat(popout, c1, c2) : vcat(popout, c1)
    end
end

"""
    ShuffleCandidates
    ShuffleCandidates()
    ShuffleCandidates(rng)

Shuffles the population using `rng`.

Useful with [`PairCandidates`](@ref) in case the prior selection does not shuffle the population.
"""
struct ShuffleCandidates{R} <: AbstractEvolution
    rng::R
end
ShuffleCandidates() = ShuffleCandidates(rng_default)
_evolve(e::ShuffleCandidates, pop) = shuffle(e.rng, pop)

"""
    EvolveCandidates <: AbstractEvolution
    EvolveCandidates(fun)

Applies `fun(c)` for each candidate `c` in a given population.

Useful with [`evolvemodel`](@ref).
"""
struct EvolveCandidates{F} <: AbstractEvolution
    fun::F
end
_evolve(e::EvolveCandidates, pop) = map(e.fun, pop)


text:
using DelimitedFiles
f_1 = "1_00__0__0__acopf__1_05"
f_2 = "1_00__0__0__scacopf__1_05"
f_3 = "1_00__0__0__exitrates__1e_15__1_05"
c_1 = "data/"*f_1*"/"
c_2 = "data/"*f_2*"/"
c_3 = "data/"*f_3*"/"
Vm_1 = readdlm(c_1*"Vm.csv",','); Va_1 = readdlm(c_1*"Va.csv",','); V_1 = Vm_1 .* exp.(im .* Va_1)
Vm_2 = readdlm(c_2*"Vm.csv",','); Va_2 = readdlm(c_2*"Va.csv",','); V_2 = Vm_2 .* exp.(im .* Va_2)
Vm_3 = readdlm(c_3*"Vm.csv",','); Va_3 = readdlm(c_3*"Va.csv",','); V_3 = Vm_3 .* exp.(im .* Va_3)
Im_1 = zeros(186)
Im_2 = zeros(186)
Im_3 = zeros(186)

Vm = Vm_1
Va = Va_1
Im = Im_1

for i in eachindex(Im)
f = line.from; t = line.to
f_idx = first(findall(opfmodeldata[:buses].bus_i .== line.from)); t_idx = first(findall(opfmodeldata[:buses].bus_i .== line.to))
Y_tf = Y[t_idx,f_idx]
Y_ft = Y[f_idx,t_idx]
Vm_f = Vm[f_idx]; Va_f = Va[f_idx]
Vm_t = Vm[t_idx]; Va_t = Va[t_idx]
# Yabs2 = max(abs2(Y_tf), abs2(Y_ft))
if options[:lossless] == true
  Yabs2 = abs2(1.0 / line.x)
else
  Yabs2 = abs2(line.r / (line.r^2 + line.x^2) - im * (line.x / (line.r^2 + line.x^2)))
end
if options[:remove_tap] == false
  t   = (line.ratio == 0.0 ? 1.0 : line.ratio) * exp(im * line.angle)
  a   = real(t)
  b   = imag(t)
  Tik = abs(t)
  φik = angle(t)
else
  Tik = 1.0
  φik = 0.0
  a   = 1.0
  b   = 0.0
end
## NOTE: current from Frank & Rebennack OPF primer eq 4.6; turns/tap ratios ARE accounted for; eq 5.11 + Remark 5.1 looks wrong
F_l = @NLexpression(opfmodel, current2, (Vm_f^2 + (a^2 + b^2) * Vm_t^2 - 2 * Vm_f * Vm_t * ( a * cos(Va_f - Va_t) + b * sin(Va_f - Va_t) ))*(Yabs2/(a^2 + b^2)^2) - flowmax)

max_stars_repo_name: jacob-roth/AC-CFM

max_issues_repo_name: jacob-roth/AC-CFM

max_forks_repo_name: jacob-roth/AC-CFM


text:
module juliawer

# Credits: https://martin-thoma.com/word-error-rate-calculation/
function calc_wer(h, r)::Float32
    """
    Calculation of WER with Levenshtein distance.

    Works only for iterables up to 254 elements (uint8).
    O(nm) time ans space complexity.

    Parameters
    ----------
    r : Array of true words
    h : Array of predicted words

    Returns
    -------
    float32
    """
    if length(h) == 1 && h[1] == ""
        return 1f0
    end
    d = zeros(UInt8, (length(r) + 1), (length(h) + 1))
    for i = 1:(length(r) + 1)
        for j = 1:(length(h) + 1)
            if i == 1
                d[1, j] = j - 1
            elseif j == 1
                d[i, 1] = i - 1
            end
        end
    end
    # computation
    for i = 2:(length(r) + 1)
        for j = 2:(length(h) + 1)
            if r[i - 1] == h[j - 1]
                d[i, j] = d[i - 1, j - 1]
            else
                substitution = d[i - 1, j - 1] + 1
                insertion = d[i, j - 1] + 1
                deletion = d[i - 1, j] + 1
                d[i, j] = min(substitution, insertion, deletion)
            end
        end
    end
    Float32(d[length(r)+1, length(h)+1]) / Float32(length(r))
end

function run()
    my_transcriptions = []
    original_transcript = []

    open(ARGS[1], "r") do f
        for line in readlines(f)
            push!(my_transcriptions, split(line, " "))
        end
    end

    open(ARGS[2], "r") do f
        for line in readlines(f)
            push!(original_transcript, split(line, " "))
        end
    end

    if length(my_transcriptions) != length(original_transcript)
        println("Invalid lengths")
        exit(1)
    end

    println("Calculating wers...")
    wers = [calc_wer(i[1], i[2]) for i in zip(my_transcriptions, original_transcript)]
    final_wer = sum(wers) / length(wers)
    println("Output WER: ", final_wer)
end

function julia_main()
    try
        run()
    catch
        Base.invokelatest(Base.display_error, Base.catch_stack())
        return 1
    end
    return 0
end

if abspath(PROGRAM_FILE) == @__FILE__
    run()
end

end # module


text:
export asarray
asarray(x::Number) = fill(x, ())
asarray(x::AbstractArray) = x

deleteat(a::AbstractVector, indices::Int) = deleteat!(copy(a), indices)
deleteat(a::AbstractVector, indices::Vector) = deleteat!(copy(a), indices |> sort)
deleteat(a::AbstractVector, indices::Tuple) = deleteat!(copy(a), indices |> TupleTools.sort)
function deleteat(a::AbstractMatrix, axis::Int, indices)
    Ni = length(indices)
    sizes = [size(a)...]
    sizes[axis] -= Ni
    b = similar(a, sizes...)
    inds = setdiff(1:size(a, axis), indices)
    if axis == 1
        for (i, k) in enumerate(inds)
            @inbounds b[i,:] = view(a,k,:)
        end
    else
        for (i, k) in enumerate(inds)
            @inbounds b[:,i] = view(a,:,k)
        end
    end
    b
end

insert(a::AbstractVector, i, elem) = insert!(copy(a), i, elem)


text:
################################################################################
#                                 Type recipes                                 #
################################################################################

function to_plottable(A::AbstractInterpolation; plotdensity = 10_000, denseplot = true)
  t = sort(A.t)
  start = t[1]; stop = t[end]
  if denseplot
    plott = collect(range(start,stop=stop,length=plotdensity))
  else
    plott = t
  end
  output = A.(plott)
  plott, output
end

@recipe function f(A::AbstractInterpolation; plotdensity = 10_000, denseplot = true)
    to_plottable(A; plotdensity = plotdensity, denseplot=denseplot)
end

################################################################################
#                                Series recipes                                #
################################################################################

############################################################
#                      Interpolations                      #
############################################################

########################################
#         Linear Interpolation         #
########################################

@recipe function f(::Type{Val{:linear_interp}}, x, y, z; plotdensity = 10_000, denseplot = true)

    seriestype := :path

    label --> "Linear fit"

    nx, ny = to_plottable(LinearInterpolation(y, x); plotdensity = plotdensity, denseplot = denseplot)

    x := nx
    y := ny
end

########################################
#       Quadratic Interpolation        #
########################################

@recipe function f(::Type{Val{:quadratic_interp}}, x, y, z; plotdensity = 10_000, denseplot = true)

    seriestype := :path

    label --> "Quadratic fit"

    nx, ny = to_plottable(
        QuadraticInterpolation(
            T.(y),
            T.(x)
        );
        plotdensity = plotdensity,
        denseplot = denseplot
    )
    x := nx
    y := ny
end

########################################
#           Quadratic Spline           #
########################################

@recipe function f(::Type{Val{:quadratic_spline}}, x, y, z; plotdensity = 10_000, denseplot = true)

    seriestype := :path

    label --> "Quadratic Spline"

    T = promote_type(eltype(y), eltype(x))

    nx, ny = to_plottable(
        QuadraticSpline(
            T.(y),
            T.(x)
        );
        plotdensity = plotdensity,
        denseplot = denseplot
    )

    x := nx
    y := ny
end

########################################
#        Lagrange Interpolation        #
########################################

@recipe function f(::Type{Val{:lagrange_interp}},
                    x, y, z;
                    n = length(x) - 1,
                    plotdensity = 10_000,
                    denseplot = true
                )

    seriestype := :path

    label --> "Lagrange Fit"

    T = promote_type(eltype(y), eltype(x))

    nx, ny = to_plottable(
        LagrangeInterpolation(
            T.(y),
            T.(x),
            n
        );
        plotdensity = plotdensity,
        denseplot = denseplot
    )

    x := nx
    y := ny
end

########################################
#             Cubic Spline             #
########################################

@recipe function f(::Type{Val{:cubic_spline}}, x, y, z; plotdensity = 10_000, denseplot = true)

    seriestype := :path

    label --> "Cubic Spline"

    T = promote_type(eltype(y), eltype(x))

    nx, ny = to_plottable(
        CubicSpline(
            T.(y),
            T.(x)
        );
        plotdensity = plotdensity,
        denseplot = denseplot
    )
    x := nx
    y := ny
end

@recipe function f(::Type{Val{:bspline_interp}},
                x, y, z;
                d = 5,
                pVec=:ArcLen,
                knotVec = :Average,
                plotdensity = length(x) * 6,
                denseplot = true
            )

        seriestype := :path

        label --> "B-Spline"

        @show x y eltype(x)

        # T = promote_type(eltype(y), eltype(x))

        nx, ny = to_plottable(
            BSplineInterpolation(
                T.(y),
                T.(x),
                d,
                pVec,
                knotVec
            );
            plotdensity = plotdensity,
            denseplot = denseplot
        )
        x := nx
        y := ny
end

########################################
#       B-spline (approximation)       #
########################################

@recipe function f(::Type{Val{:bspline_approx}},
                    x, y, z;
                    d = 5,
                    h = length(x) - 1,
                    pVec = :ArcLen,
                    knotVec = :Average,
                    plotdensity = length(x) * 6,
                    denseplot = true
                )

        seriestype := :path

        label --> "B-Spline"

        T = promote_type(eltype(y), eltype(x))

        nx, ny = to_plottable(
            BSplineApprox(
                T.(y),
                T.(x),
                d,
                h,
                pVec,
                knotVec
            );
            plotdensity = plotdensity,
            denseplot = denseplot
        )
        x := nx
        y := ny
end

########################################
#          Akima intepolation          #
########################################

@recipe function f(::Type{Val{:akima}}, x, y, z; plotdensity = length(x) * 6, denseplot = true)

        seriestype := :path

        label --> "Akima"

        T = promote_type(eltype(y), eltype(x))

        nx, ny = to_plottable(
            AkimaInterpolation(
                T.(y),
                T.(x)
            );
            plotdensity = plotdensity,
            denseplot = denseplot
        )
        x := nx
        y := ny
end

################################################################################
#                                  Shorthands                                  #
################################################################################

"""
    akima(u, t)
    akima!(u, t)

Plot the Akima interpolation of the given data.
"""
@shorthands akima


text:
import Base: *, /

export SuctionParameter, SuctionParameterRange, ModelParameters

const SuctionParameter = Float64

const f̃Limit = Float64

struct SuctionParameterRange
    σmin::SuctionParameter
    σmax::SuctionParameter

    function SuctionParameterRange(σ₁,σ₂)
        σsorted = sort([σ₁,σ₂])
        new(σsorted[1],σsorted[2])
    end
end

# Multiply and divide by a constant
function (*)(p::SuctionParameterRange,c::Number)
    return SuctionParameterRange(c*p.min,c*p.max)
end

(*)(c::Number,p::SuctionParameterRange) = *(p,c)

function (/)(p::SuctionParameterRange,c::Number)
    return SuctionParameterRange(p.min/c, p.max/c)
end

struct f̃Limits
    min::f̃Limit
    max::f̃Limit

    function f̃Limits(f̃₁,f̃₂)
        f̃sorted = sort([f̃₁,f̃₂])
        new(f̃sorted[1],f̃sorted[2])
    end
end

# Multiply and divide by a constant
function (*)(p::f̃Limits,c::Number)
    return f̃Limits(c*p.min,c*p.max)
end

(*)(c::Number,p::f̃Limits) = *(p,c)

function (/)(p::f̃Limits,c::Number)
    return f̃Limits(p.min/c, p.max/c)
end

function _computef̃limit(SP::SuctionParameter, plate::Plate, Γ₀)
    f̃ = -SP*2π*plate.len/Γ₀
    return f̃
end

function _computef̃limit(SP::SuctionParameter, body::Body, Γ₀)
    if iszero(SP)
        return 0.0
    else
        throw(ArgumentError("No conversion from suction parameter to limit on f̃ for $(typeof(body)) implemented"))
    end
end

function _computef̃limit(SP::SuctionParameterRange, args...)
    if SP.σmax == SP.σmin
        _computef̃limit(SP.σmax, args...)
    else
        throw(ArgumentError("Ambiguous SuctionParameterRange provided for steady state regularization"))
    end
end

function _computef̃range(SP::SuctionParameter, args...)
    return _computef̃range(SuctionParameterRange(-SP,SP), args...)
end

function _computef̃range(SP::SuctionParameterRange, args...)
    f̃min = _computef̃limit(SP.σmax, args...)
    f̃max = _computef̃limit(SP.σmin, args...)
    return f̃Limits(f̃min,f̃max)
end


text:
using DataTypesBasic
using Test
using Suppressor

@test map(Const(3)) do x
  x*x
end == Const(3)

@test isconst(Const(3))
@test !isconst(Identity(3))

@test repr(Const(4)) == "Const(4)"

@test collect(Const(4)) == []
@test length(Const(:hi)) == 0

@test @capture_out(for i in Const(true)
  println("i = $i")
end) == ""
@test @capture_out(foreach(println, Const(5))) == ""

@test eltype(Const(nothing)) == Any

@test Iterators.flatten(Const(5)) == Const(5)

@test convert(Const{Float32}, Const(1)) == Const(Float32(1))
@test convert(Const{Number}, Const(1)) == Const(1)

@test promote_type(Const{Int}, Const{Number}) == Const{Number}
@test promote_type(Const{Int}, Const{String}) == Const
@test promote_type(Const{Int}, Const) == Const
@test promote_type(Const, Const) == Const


text:
using BinaryBuilder

name = "HelloWorldRust"
version = v"1.1.0"

# No sources, we're just building the testsuite
sources = DirectorySource[
]

# Bash recipe for building across all platforms
script = raw"""
mkdir -p ${bindir}
rustc -o ${bindir}/hello_world${exeext} -g /usr/share/testsuite/rust/hello_world/hello_world.rs
install_license /usr/share/licenses/MIT
"""

# We build for a restricted set of platforms, because our rust toolchain is a little broken
platforms = supported_platforms()

# 32-bit Windows seems to be broken
# https://github.com/JuliaPackaging/BinaryBuilder.jl/issues/499
filter!(p -> !(Sys.iswindows(p) && arch(p) == "i686"), platforms)

# The products that we will ensure are always built
products = [
    ExecutableProduct("hello_world", :hello_world),
]

# Dependencies that must be installed before this package can be built
dependencies = Dependency[
]

# Build the tarballs, and possibly a `build.jl` as well.
build_tarballs(ARGS, name, version, sources, script, platforms, products, dependencies; compilers=[:c, :rust], julia_compat="1.6")

max_stars_repo_name: jakebolewski/Yggdrasil

max_issues_repo_name: jakebolewski/Yggdrasil

max_forks_repo_name: jakebolewski/Yggdrasil


text:
module WorkspaceManager
import UUIDs: UUID
import ..Pluto: Notebook, PKG_ROOT_DIR
import ..PlutoFormatter
import Distributed

mutable struct Workspace
    workspace_pid::Integer
    module_name::Symbol
    dowork_token::Channel{Nothing}
end

Workspace(workspace_pid::Integer, module_name::Symbol) = let
    t = Channel{Nothing}(1)
    put!(t, nothing)
    Workspace(workspace_pid, module_name, t)
end

"Should future workspaces be created on a separate process (`true`) or on the same one (`false`)?

Only workspaces on a separate process can be stopped during exectuion. Windows currently only supports `true` only partially: you can't stop cells on Windows."
function set_default_distributed(val::Bool)
    global default_distributed = val
end

function reset_default_distributed()
    global default_distributed = Sys.iswindows() ? false : true
end

reset_default_distributed()

"These expressions get executed whenever a new workspace is created."
workspace_preamble = [
    :(using Markdown),
    :(ENV["GKSwstype"] = "nul"),
    :(show, showable, showerror, repr, string), # https://github.com/JuliaLang/julia/issues/18181
]

process_preamble = [
    :(ccall(:jl_exit_on_sigint, Cvoid, (Cint,), 0)),
    :(include($(joinpath(PKG_ROOT_DIR, "src", "notebookserver", "FormatOutput.jl")))),
    :(import REPL.REPLCompletions: completions, complete_path, completion_text),
]

moduleworkspace_count = 0
workspaces = Dict{UUID,Workspace}()

"Create a workspace for the notebook, optionally in a separate process."
function make_workspace(notebook::Notebook, new_process = default_distributed)::Workspace
    pid = if new_process
        create_workspaceprocess()
    else
        pid = Distributed.myid()
        # for some reason the PlutoFormatter might not be available in Main unless we include the file
        # (even though this is the main process)
        if !Distributed.remotecall_eval(Main, pid, :(isdefined(Main, :PlutoFormatter) && PlutoFormatter isa Module))
            Distributed.remotecall_eval(Main, [pid], :(include($(joinpath(PKG_ROOT_DIR, "src", "notebookserver", "FormatOutput.jl")))))
        end
        pid
    end

    module_name = create_emptyworkspacemodule(pid)

    workspace = Workspace(pid, module_name)
    workspaces[notebook.uuid] = workspace
    return workspace
end

function create_emptyworkspacemodule(pid::Integer)
    global moduleworkspace_count += 1
    id = moduleworkspace_count

    new_workspace_name = Symbol("workspace", id)
    workspace_creation = :(module $(new_workspace_name) $(workspace_preamble...) end)

    Distributed.remotecall_eval(Main, [pid], workspace_creation)

    new_workspace_name
end

function create_workspaceprocess()::Integer
    pid = Distributed.addprocs(1) |> first

    for expr in process_preamble
        Distributed.remotecall_eval(Main, [pid], expr)
    end

    # so that we NEVER break the workspace with an interrupt 🤕
    @async Distributed.remotecall_eval(Main, [pid],
        :(while true
            try
                wait()
            catch end
        end))

    pid
end

"Try our best to delete the workspace. `ProcessWorkspace` will have its worker process terminated."
unmake_workspace(notebook::Notebook) = unmake_workspace(get_workspace(notebook))

function unmake_workspace(workspace::Workspace)
    token = take!(workspace.dowork_token)
    # TODO: test

    # TODO
    # for s in names(workspace.workspace_module)
    #     try
    #         Core.eval(workspace.workspace_module, :($s = nothing))
    #     catch end
    # end

    # TODO: verify that nothing is running
    if workspace.workspace_pid != Distributed.myid()
        Distributed.rmprocs([workspace.workspace_pid])
    end
    put!(workspace.dowork_token, token)
end

"Return the `Workspace` of `notebook`; will be created if none exists yet."
function get_workspace(notebook::Notebook)::Workspace
    if haskey(workspaces, notebook.uuid)
        workspaces[notebook.uuid]
    else
        workspaces[notebook.uuid] = make_workspace(notebook)
    end
end

"Evaluate expression inside the workspace - output is fetched and formatted, errors are caught and formatted. Returns formatted output and error flags."
function eval_fetch_in_workspace(notebook::Notebook, expr)::NamedTuple{(:output_formatted, :errored, :interrupted),Tuple{Tuple{String,MIME},Bool,Bool}}
    eval_fetch_in_workspace(get_workspace(notebook), expr)
end

function eval_fetch_in_workspace(workspace::Workspace, expr)::NamedTuple{(:output_formatted, :errored, :interrupted),Tuple{Tuple{String,MIME},Bool,Bool}}
    # We wrap the expression in a try-catch block, because we want to capture and format the exception on the worker itself.
    wrapped = :(ans = try
        # We want to eval `expr` in the global scope, try introduced a local scope.
        Core.eval($(workspace.module_name), $(expr |> QuoteNode))
    catch ex
        bt = stacktrace(catch_backtrace())
        CapturedException(ex, bt)
    end)

    # run the code 🏃♀
    # we use [pid] instead of pid to prevent fetching output

    # another try block to catch an InterruptException
    token = take!(workspace.dowork_token)
    try
        Distributed.remotecall_eval(Main, [workspace.workspace_pid], wrapped)
        put!(workspace.dowork_token, token)
    catch exs
        # We don't use a `finally` because the token needs to be back asap
        put!(workspace.dowork_token, token)
        try
            @assert exs isa CompositeException
            ex = exs.exceptions |> first
            @assert ex isa Distributed.RemoteException
            @assert ex.pid == workspace.workspace_pid
            @assert ex.captured.ex isa InterruptException

            return (output_formatted = PlutoFormatter.format_output(InterruptException()), errored = true, interrupted = true)
        catch assertionerr
            showerror(stderr, exs)
            return (output_formatted = PlutoFormatter.format_output(exs), errored = true, interrupted = true)
        end
    end

    # instead of fetching the output value (which might not make sense in our context, since the user can define structs, types, functions, etc), we format the cell output on the worker, and fetch the formatted output.
    # This also means that very big objects are not duplicated in RAM.
    fetcher = :((output_formatted = PlutoFormatter.format_output(ans), errored = isa(ans, CapturedException), interrupted = false))

    try
        result = Distributed.remotecall_eval(Main, workspace.workspace_pid, fetcher)
        return result
    catch ex
        rethrow(ex)
    end
end

"Evaluate expression inside the workspace - output is not fetched, errors are rethrown. For internal use."
function eval_in_workspace(notebook::Notebook, expr)
    eval_in_workspace(get_workspace(notebook), expr)
end

function eval_in_workspace(workspace::Workspace, expr)
    # token = take!(workspace.dowork_token)
    try
        Distributed.remotecall_eval(Main, [workspace.workspace_pid], :(Core.eval($(workspace.module_name), $(expr |> QuoteNode))))
        # put!(workspace.dowork_token, token)
    catch ex
        # put!(workspace.dowork_token, token)
        rethrow(ex)
    end
    nothing
end

# "Interrupt (Ctrl+C) a workspace, return whether succesful."
# function interrupt_workspace(notebook::Notebook)::Bool
#     interrupt_workspace(WorkspaceManager.get_workspace(notebook))
# end

# function interrupt_workspace(workspace::ModuleWorkspace)
#     @warn "Unfortunately, a `ModuleWorkspace` can't be interrupted. Use a `ProcessWorkspace` instead."
#     false
# end

# function interrupt_workspace(workspace::ProcessWorkspace)
#     if Sys.iswindows()
#         @warn "Unfortunately, stopping cells is currently not supported on Windows.
#         Maybe the Windows Subsystem for Linux is right for you:
#         https://docs.microsoft.com/en-us/windows/wsl"
#         return false
#     end
#     println("Sending interrupt to $(workspace.workspace_pid)")
#     Distributed.interrupt(workspace.workspace_pid)
#     true
# end

"Force interrupt (SIGINT) a workspace, return whether succesful"
function kill_workspace(notebook::Notebook)::Bool
    kill_workspace(WorkspaceManager.get_workspace(notebook))
end

function kill_workspace(workspace::Workspace)
    if Sys.iswindows()
        @warn "Unfortunately, stopping cells is currently not supported on Windows :(
        Maybe the Windows Subsystem for Linux is right for you:
        https://docs.microsoft.com/en-us/windows/wsl"
        return false
    end
    if workspace.workspace_pid == Distributed.myid()
        @warn "Cells in this workspace can't be stopped, because it is not running in a separate workspace. Use `set_default_distributed` to control whether future workspaces are generated in a separate process."
    end

    # You can force kill a julia process by pressing Ctrl+C five times 🙃
    # But this is not very consistent, so we will just keep pressing Ctrl+C until the workspace isn't running anymore.
    # TODO: this will also kill "pending" evaluations, and any evaluations started within 100ms of the kill. A global "evaluation count" would fix this.
    # TODO: listen for the final words of the remote process on stdout/stderr: "Force throwing a SIGINT"
    if isready(workspace.dowork_token)
        @info "Tried to stop idle workspace - ignoring."
        return true
    end
    @info "Sending interrupt to process $(workspace.workspace_pid)"
    Distributed.interrupt(workspace.workspace_pid)

    delay = 5.0 # seconds
    parts = 100

    for _ in 1:parts
        sleep(delay / parts)
        if isready(workspace.dowork_token)
            println("Cell interrupted!")
            return true
        end
    end

    println("Still running... starting sequence")
    while !isready(workspace.dowork_token)
        for _ in 1:5
            print(" 🔥 ")
            Distributed.interrupt(workspace.workspace_pid)
            sleep(0.2)
        end
        sleep(1.5)
    end
    println()
    println("Cell interrupted!")
    true
end

# "Delete all methods of the functions from the workspace."
# function delete_funcs(notebook::Notebook, to_delete::Set{Symbol})
#     delete_funcs(get_workspace(notebook), to_delete)
# end

# function delete_funcs(workspace::ModuleWorkspace, to_delete::Set{Symbol})
#     for funcname in to_delete
#         try
#             func = Core.eval(workspace.workspace_module, funcname)
#             for m in methods(func).ms
#                 Base.delete_method(m)
#             end
#         catch ex
#             if !(ex isa UndefVarError)
#                 rethrow(ex)
#             end
#         end
#     end
# end

# function delete_funcs(workspace::ProcessWorkspace, to_delete::Set{Symbol})
#     isempty(to_delete) && return
#     e = :(for funcname in $to_delete
#         try
#             func = Core.eval(Main, funcname)
#             for m in methods(func).ms
#                 Base.delete_method(m)
#             end
#         catch ex
#             if !(ex isa UndefVarError)
#                 rethrow(ex)
#             end
#         end
#     end)
#     eval_in_workspace(workspace, e)
# end

function move_vars(notebook::Notebook, old_workspace_name::Symbol, new_workspace_name::Symbol, vars_to_move::Set{Symbol}=Set{Symbol}(), module_imports_to_move::Set{Expr}=Set{Expr}(); invert_vars_set=false)
    move_vars(get_workspace(notebook), old_workspace_name, new_workspace_name, vars_to_move, module_imports_to_move, invert_vars_set=invert_vars_set)
end

function move_vars(workspace::Workspace, old_workspace_name::Symbol, new_workspace_name::Symbol, vars_to_move::Set{Symbol}=Set{Symbol}(), module_imports_to_move::Set{Expr}=Set{Expr}(); invert_vars_set=false)
    deleter = quote
        old_workspace_name = $(old_workspace_name |> QuoteNode)
        new_workspace_name = $(new_workspace_name |> QuoteNode)
        old_workspace = Core.eval(Main, old_workspace_name)
        new_workspace = Core.eval(Main, new_workspace_name)

        Core.eval(new_workspace, :(import ..($(old_workspace_name))))

        old_names = names(old_workspace, all=true, imported=true)
        vars_to_move = if $invert_vars_set
            setdiff(old_names, $vars_to_move)
        else
            $vars_to_move
        end

        for symbol in old_names
            if symbol in vars_to_move
                # var will not be redefined in the new workspace, move it over
                if !(symbol == :eval || symbol == :include || string(symbol)[1] == '#' || startswith(string(symbol), "workspace"))
                    try
                        val = Core.eval(old_workspace, symbol)

                        # Expose the variable in the scope of `new_workspace`
                        Core.eval(new_workspace, :(import ..($(old_workspace_name)).$(symbol)))
                    catch ex
                        @warn "Failed to move variable $(symbol) to new workspace:"
                        showerror(stderr, ex, stacktrace(backtrace()))
                    end
                end
            else
                # var will be redefined - unreference the value so that GC can snoop it

                # free memory for other variables
                # & delete methods created in the old module:
                # for example, the old module might extend an imported function:
                # `import Base: show; show(io::IO, x::Flower) = print(io, "🌷")`
                # when you delete/change this cell, you want this extension to disappear.
                if isdefined(old_workspace, symbol)
                    val = Core.eval(old_workspace, symbol)

                    if val isa Function
                        try
                            ms = methods(val).ms

                            Base.delete_method.(filter(m -> startswith(nameof(m.module) |> string, "workspace"), ms))
                        catch ex
                            @warn "Failed to delete methods for $(symbol)"
                            showerror(stderr, ex, stacktrace(backtrace()))
                        end
                    end

                    try
                        # it could be that `symbol ∈ vars_to_move`, but the _value_ has already been moved to the new reference in `new_module`.
                        # so clearing the value of this reference does not affect the reference in `new_workspace`.
                        Core.eval(old_workspace, :($(symbol) = nothing))
                    catch; end # sometimes impossible, eg. when $symbol was constant
                end
            end
        end
    end

    Distributed.remotecall_eval(Main, [workspace.workspace_pid], deleter)
end

"Fake deleting variables by adding them to the workspace's blacklist."
function delete_vars(notebook::Notebook, to_delete::Set{Symbol}, module_imports_to_move::Set{Expr}=Set{Expr}())
    delete_vars(get_workspace(notebook), to_delete)
end

function delete_vars(workspace::Workspace, to_delete::Set{Symbol}, module_imports_to_move::Set{Expr}=Set{Expr}())
    old_workspace_name = workspace.module_name
    new_workspace_name = create_emptyworkspacemodule(workspace.workspace_pid)

    workspace.module_name = new_workspace_name

    move_vars(workspace, old_workspace_name, new_workspace_name, to_delete, module_imports_to_move; invert_vars_set=true)
end

end


text:
"""
    GaussianProcessProbabilisticProgramme(fs, gpc)

Collects a group of related GPs together and interprets them as a single GP.
This type isn't part of the user-facing API -- the `@gppp` macro should be used to
construct a `GaussianProcessProbabilisticProgramme`.
"""
struct GaussianProcessProbabilisticProgramme{Tfs} <: AbstractGP
    fs::Tfs
    gpc::GPC
end

const GPPP = GaussianProcessProbabilisticProgramme

"""
    GPPPInput(p, x::AbstractVector)

An collection of inputs for a GPPP.
`p` indicates which process the vector `x` should be extracted from.
The required type of `p` is determined by the type of the keys in the `GPPP` indexed.

```jldoctest
julia> x = [1.0, 1.5, 0.3];

julia> v = GPPPInput(:a, x)
3-element GPPPInput{Symbol, Float64, Vector{Float64}}:
 (:a, 1.0)
 (:a, 1.5)
 (:a, 0.3)

julia> v isa AbstractVector{Tuple{Symbol, Float64}}
true

julia> v == map(x_ -> (:a, x_), x)
true
```
"""
struct GPPPInput{Tp, T, Tx<:AbstractVector{T}} <: AbstractVector{Tuple{Tp, T}}
    p::Tp
    x::Tx
end

Base.size(x::GPPPInput) = (length(x.x), )

Base.getindex(x::GPPPInput, idx) = map(x_ -> (x.p, x_), x.x[idx])

#
# Implementation of the AbstractGPs API for the inputs types which are valid for a GPPP.
# See `AbstractGPs.jl` for details.
#

extract_components(f::GPPP, x::GPPPInput) = f.fs[x.p], x.x

function extract_components(f::GPPP, x::BlockData)
    fs = map(v -> f.fs[v.p], x.X)
    vs = map(v -> v.x, x.X)
    return cross(fs), BlockData(vs)
end

function mean(f::GPPP, x::AbstractVector)
    fs, vs = extract_components(f, x)
    return mean(fs, vs)
end

function cov(f::GPPP, x::AbstractVector)
    fs, vs = extract_components(f, x)
    return cov(fs, vs)
end

function var(f::GPPP, x::AbstractVector)
    fs, vs = extract_components(f, x)
    return var(fs, vs)
end

function cov(f::GPPP, x::AbstractVector, x′::AbstractVector)
    fs_x, vs_x = extract_components(f, x)
    fs_x′, vs_x′ = extract_components(f, x′)
    return cov(fs_x, fs_x′, vs_x, vs_x′)
end

function var(f::GPPP, x::AbstractVector, x′::AbstractVector)
    fs_x, vs_x = extract_components(f, x)
    fs_x′, vs_x′ = extract_components(f, x′)
    return var(fs_x, fs_x′, vs_x, vs_x′)
end

function mean_and_cov(f::GPPP, x::AbstractVector)
    fs, vs = extract_components(f, x)
    return mean_and_cov(fs, vs)
end

function mean_and_var(f::GPPP, x::AbstractVector)
    fs, vs = extract_components(f, x)
    return mean_and_var(fs, vs)
end

"""
    Base.split(x::BlockData, Y::AbstractVecOrMat)

Split up the rows of `Y` according to the sizes of the data in `x`.

```jldoctest
julia> Y = vcat(randn(5, 3), randn(4, 3));

julia> x = BlockData(randn(5), randn(4));

julia> Y1, Y2 = split(x, Y);

julia> Y1 == Y[1:5, :]
true

julia> Y2 == Y[6:end, :]
true
```

Works with any `BlockData`, so blocks can e.g. be `GPPPInput`s. This is particularly helpful
for working with the output from `rand` and `marginals` from a `GPPP` indexed at
`BlockData`. For example
```julia
f = @gppp let
    f1 = GP(SEKernel())
    f2 = GP(Matern52Kernel())
    f3 = f1 + f2
end

x = BlockData(GPPPInput(:f2, randn(3)), GPPPInput(:f3, randn(4)))
y = rand(f(x))
y2, y3 = split(x, y)
```

Functionality also works for `AbstractVectors`.
"""
function Base.split(x::BlockData, Y::AbstractMatrix)
    length(x) == size(Y, 1) || throw(error("Expected length(x) == size(Y, 1)"))
    return map(idx->Y[idx, :], _get_indices(x))
end

function Base.split(x::BlockData, y::AbstractVector)
    length(x) == length(y) || throw(error("Expected length(x) == length(y)"))
    return map(idx->y[idx], _get_indices(x))
end

function _get_indices(x::BlockData)
    sz = cumsum(map(length, x.X))
    return [sz[n] - length(x.X[n]) + 1:sz[n] for n in eachindex(x.X)]
end
ChainRulesCore.@non_differentiable _get_indices(::Any)

"""
    @gppp(model_expression)

Construct a `GaussianProcessProbabilisticProgramme` (`GPPP`) from a code snippet.

```jldoctest
f = @gppp let
    f1 = GP(SEKernel())
    f2 = GP(Matern52Kernel())
    f3 = f1 + f2
end

x = GPPPInput(:f3, randn(5))

y = rand(f(x, 0.1))

logpdf(f(x, 0.1), y) ≈ elbo(f(x, 0.1), y, f(x, 1e-9))

# output

true
```
"""
macro gppp(let_block::Expr)
    # Consult the `Internals` section of the docs for info about what this macro is doing.

    # Ensure that we're dealing with a let block.
    let_block.head == :let || throw(error("gppp needs a let block."))

    # Pull out model definition from the block.
    model_expr = let_block.args[2]
    model_expr.head == :block || throw(error("Expected a block."))

    # Pull out the names of all of the variables.
    lines_declaring_variables = filter(x -> x isa Expr && x.head == :(=), model_expr.args)
    variable_names = map(x -> x.args[1], lines_declaring_variables)

    # Construct expression which specifies mappings between symbolic names and GPs.
    # The resulting expression is of the form (f1 = f1, f2 = f2).
    var_mapping = Expr(
        :tuple,
        map(variable_names) do var_name
            Expr(:(=), var_name, var_name)
        end...,
    )

    gpc_sym = gensym("gpc")

    # Construct an expression which wraps the GPs, and returns a GPPP.
    wrapped_model = Expr(:block,
        :($gpc_sym = Stheno.GPC()),
        postwalk(
            x->@capture(x, GP(xs__)) ? :(Stheno.wrap(GP($(xs...)), $gpc_sym)) : x,
            model_expr,
        ).args...,
        :(Stheno.GPPP($var_mapping, $gpc_sym)),
    )
    return esc(wrapped_model)
end


text:
"""
    unitrange_rand!(A, r::UnitRange)

Populate the array `A` with values picked randomly from `r`.

    unitrange_rand!(A, r::UnitRange, 𝕡, [method = InverseCDF(𝕡)])

Populate the array `A` with values picked randomly from `r` with weights given by the distribution `𝕡`.
If `length(𝕡) == length(r) * length(A)`, different weight vectors are used for each entry  of `A`.
"""
function unitrange_rand!(A, r::UnitRange)
    r1 = first(r)   # label of first class
    n = length(r)   # number of classes
    @. A = r1 + ((n * rand()) ÷ 1)
    return A
end
unitrange_rand!(A::Array, r::UnitRange) = rand!(A, r)

function unitrange_rand!(A, r::UnitRange, 𝕡::AbstractArray, method = InverseCDF(𝕡, length(r), length(A)))
    if length(𝕡) == length(r)
        isone(sum(𝕡)) || return error("`sum(𝕡) = $(sum(𝕡)) ≠ 1`")
    elseif length(𝕡) == length(r) * length(A)
        sum!(method.C₁, reshape(𝕡, :, length(A)))
        all(≈(1), method.C₁) || return error("`𝕡` is not normalized")
    else
        msg = "Require `length(𝕡) == length(r)` or `length(𝕡) == length(r) * length(A)`."
        msg*= "Get `(length(𝕡), length(r), length(A)) = $((length(𝕡), length(r), length(A)))`."
        return error(msg)
    end

    method(A, 𝕡)
    A .+= first(r)
    return A
end

struct InverseCDF{T}
    C::T        # qN × M
    C₁::T       #  1 × M
    C₂::T       # qN × 1
    qN::Int
    function InverseCDF(𝕡::AbstractArray, qN::Integer, M::Integer)
        _eltype = promote_type(eltype(𝕡), Float32)
        C = similar(𝕡, _eltype, qN, M)
        C₁= similar(𝕡, _eltype,  1, M)
        C₂= similar(𝕡, _eltype, qN, 1)
        return new{typeof(C)}(C, C₁, C₂, qN)
    end
end
function (f::InverseCDF)(I::AbstractArray, 𝕡)
    (; C, C₁, C₂, qN) = f
    if length(𝕡) == qN
        cumsum!(C₂, reshape(𝕡, qN, 1); dims = 1)
        C₂ ./= maximum(C₂)  # make sure CDF sum to one!
        rand!(C₁)           # one uniform random varaible per sample
        @. C = C₂ ≤ C₁
    else
        cumsum!(C, reshape(𝕡, qN, :); dims = 1)
        maximum!(C₁, C)
        C ./= C₁            # make sure CDF sum to one!
        rand!(C₁)           # one uniform random varaible per sample
        @. C = C ≤ C₁
    end
    sum!(C₁, C)
    return copyto!(I, C₁)      # index of new spin state for each sample
end


text:
# This file was generated, do not modify it. # hide
show(iterated_pipe, 2)

max_stars_repo_name: alan-turing-institute/MLJTutorials

max_issues_repo_name: alan-turing-institute/MLJTutorials

max_forks_repo_name: alan-turing-institute/MLJTutorials


text:
function extract_term_info(terms,ix)
    # 1 = basisname, 2 = coefname, 3 = colname
        return [c[ix] for c in split.(terms," : ")]
end

function get_terms(m)
    terms = coefnames(m.X.formulas)
    terms = vcat(terms...) # gets rid of an empty Any() XXX not sure where it comes from, only in MixedModel Timexpanded case
end

function condense_long(m)
    termsRaw = get_terms(m)
    terms = extract_term_info(termsRaw,2)
    colnames_basis_raw = get_colnames_basis(m.X.formulas)# this is unconverted basisfunction basis,
    colnames_basis = extract_term_info(termsRaw,3) # this is converted to strings!
    basisnames = extract_term_info(termsRaw,1)
    @debug terms
    @debug colnames_basis

    nchan = size(m.beta,1)

    terms_rep = permutedims(repeat(terms,1,nchan),[2,1])

    if length(colnames_basis)==1
        colnames_basis = [colnames_basis]
    end

    #println(colnames_basis_raw[1:100])
    #println(colnames_basis[1:100])
    #println(size(terms_rep))
    #println("$(length(colnames_basis_raw)),$(length(colnames_basis)),$nchan")
    #colnames_basis_rep = permutedims(repeat(colnames_basis_raw,Int(length(colnames_basis)/length(colnames_basis_raw)),nchan),[2,1])
    # XXX HOTFIX for above lines
    colnames_basis_rep = permutedims(repeat(colnames_basis,1,nchan),[2,1])
    try
        colnames_basis_rep = parse.(Float64,colnames_basis_rep)
    catch
        #do nothing
    end
    chan_rep = repeat(1:nchan,1,size(colnames_basis_rep,2))

    basisnames_rep = permutedims(repeat(basisnames,1,nchan),[2,1])
    return make_long_df(m,terms_rep,chan_rep,colnames_basis_rep,basisnames_rep) #DataFrame(term=linearize(terms_rep),estimate=linearize(m.beta),stderror=linearize(MixedModels.stderror(m)),channel=linearize(chan_rep),group="fixed",colnames_basis=linearize(colnames_rep))
end

function condense_long(m,times::AbstractArray)
    # Mass Univariate Case
    terms = coefnames(m.X.formulas)
    terms = vcat(terms...)
    colnames_basis = times#get_colnames_basis(m.X.formulas,times)

    @debug "terms: $(size(terms)),colnames_basis:$(size(colnames_basis)))"
    @debug "terms: $terms,colnames_basis:$colnames_basis"
    nchan = size(m.beta,1)
    ncols = length(colnames_basis)
    nterms = length(terms)

    # replicate
    terms_rep = permutedims(repeat(terms,outer=[1,nchan,ncols]),[2,3,1])
    colnames_basis_rep = permutedims(repeat(colnames_basis,1,nchan,nterms),[2 1 3])
    chan_rep = repeat(1:nchan,1,ncols,nterms)
    basisnames_rep = repeat(["mass-univariate"],nchan,ncols,nterms)
    #
    results = make_long_df(m,terms_rep,chan_rep,colnames_basis_rep,basisnames_rep)

    return results
end
#---
# Returns a long df given the already matched
function make_long_df(m,terms,chans,colnames,basisnames)
    @assert all(size(terms) .== size(chans)) "terms, chans and colnames need to have the same size at this point, $(size(terms)),$(size(chans)),$(size(colnames)), should be $(size(m.beta)))"
    @assert all(size(terms) .== size(colnames)) "terms, chans and colnames need to have the same size at this point"
    estimate,stderror,group = make_estimate(m)

    results = DataFrame(term=String.(linearize(terms)),
        channel = linearize(chans),
        basisname = linearize(basisnames),
        colname_basis=linearize(colnames),
        estimate=linearize(estimate),
        stderror=linearize(stderror),
        group = linearize(group),
        )
end
#---------

# extracts betas (and sigma's for mixed models) with string grouping indicator
# returns as a ch x beta, or ch x time x beta (for mass univariate)
function make_estimate(m::UnfoldLinearMixedModel)
    estimate = cat(m.beta,m.sigma,dims=ndims(m.beta))
    if ndims(m.beta) == 3
        group_f = repeat([nothing],size(m.beta,1),size(m.beta,2),size(m.beta,ndims(m.beta)))

        ranef_group = [x.group for x in MixedModels.tidyσs(m.modelinfo)]

            # reshape to pred x time x chan and then invert to chan x time x pred
        ranef_group = permutedims(reshape(ranef_group,:,size(m.beta,2),size(m.beta,1)),[3 2 1])

        #    beta = reshape(beta,:,nchan)'
        #    sigma = reshape(sigma,:,nchan)'

        #group_s = repeat(["ranef"],size(m.beta,1),size(m.beta,2),size(m.sigma,ndims(m.beta)))
        group_s = ranef_group
        stderror_fixef = permutedims(reshape(vcat([[b.se...] for b in m.modelinfo.bstr]...),reverse(size(m.beta))),[3,2,1])
        stderror_ranef = fill(nothing,size(m.sigma))
        stderror = cat(stderror_fixef,stderror_ranef,dims=3)
    else
        group_f = repeat([nothing],size(m.beta,1),size(m.beta,ndims(m.beta)))
        group_s = repeat(["ranef"],size(m.beta,1),size(m.sigma,ndims(m.beta)))
        stderror = fill(nothing,size(estimate))
    end
    group = cat(group_f,group_s,dims=ndims(m.beta))
    return Float64.(estimate),stderror,group
end
function make_estimate(m::UnfoldLinearModel)
    if hasfield(typeof(m.modelinfo),:stderror)
        stderror = Float64.(m.modelinfo.stderror)
    else
        stderror = fill(nothing,size(m.beta))
    end
    return Float64.(m.beta),stderror,fill(nothing,size(m.beta))
end

# Return the column names of the basis functions.
function get_colnames_basis(formula::FormulaTerm)
    return get_colnames_basis(formula.rhs)
end

function get_colnames_basis(rhs::Tuple)
    return rhs[1].basisfunction.colnames
end

function get_colnames_basis(rhs::AbstractTerm)
    return rhs.basisfunction.colnames
end

function get_basis_name(rhs::AbstractTerm)
    return rhs.basisfunction.name
end

function get_colnames_basis(formulas::AbstractArray{<:FormulaTerm})
    # In case of multiple basisfunction we can have an array of formulas.
    # in that case we have to add an unique identifier
    colnames_all = []
    for formula in formulas
        append!(colnames_all, get_colnames_basis(formula.rhs))
    end
#get_colnames_basis_name(formula.rhs) .*
    return colnames_all
end


text:
using SimplicialComplexes
using Base.Test

include("utils.jl")
include("simplicial_complex.jl")


text:
##### Beginning of file

module GitLabDstHost # Begin submodule RepoSnapshots.Hosts.GitLabDstHost

__precompile__(true)

import ..Types
import ..Utils

import Dates
import HTTP
import JSON
import TimeZones

import ..delayederror

function new_gitlab_session(
        ;
        gitlab_group::String,
        gitlab_bot_username::String,
        gitlab_bot_personal_access_token::String,
        )::Function

    _gitlab_group::String = strip(
        convert(String, gitlab_group)
        )
    _provided_gitlab_bot_username::String = strip(
        convert(String, gitlab_bot_username,)
        )
    _gitlab_bot_personal_access_token::String = strip(
        convert(String, gitlab_bot_personal_access_token)
        )

    function _get_gitlab_username()::String
        method::String = "GET"
        url::String = "https://gitlab.com/api/v4/user"
        headers::Dict{String, String} = Dict(
            "PRIVATE-TOKEN" => gitlab_bot_personal_access_token,
            )
        http_request = () -> HTTP.request(
            method,
            url,
            headers,
            )
        r::HTTP.Messages.Response = Utils.retry_function_until_success(
            http_request;
            max_attempts = 10,
            seconds_to_wait_between_attempts = 180,
        )
        r_body::String = String(r.body)
        parsed_r_body::Dict = JSON.parse(r_body)
        username::String = parsed_r_body["name"]
        username_stripped::String = strip(username)
        return username_stripped
    end

    function _get_all_my_namespaces()::Vector{Dict}
        method::String = "GET"
        url::String = "https://gitlab.com/api/v4/namespaces"
        headers::Dict{String, String} = Dict(
            "PRIVATE-TOKEN" => gitlab_bot_personal_access_token,
            )
        http_request = () -> HTTP.request(
            method,
            url,
            headers,
            )
        r = Utils.retry_function_until_success(
            http_request;
            max_attempts = 10,
            seconds_to_wait_between_attempts = 180,
        )
        r_body = String(r.body)
        parsed_body = JSON.parse(r_body)
        return parsed_body
    end

    function _get_namespace_id_for_my_group()::Int
        all_my_namespaces::Vector{Dict} = _get_all_my_namespaces()
        result::Int = 0
        for i = 1:length(all_my_namespaces)
            namespace = all_my_namespaces[i]
            if strip(namespace["name"]) == strip(_gitlab_group)
                result = namespace["id"]
            end
        end
        if result == 0
            delayederror("Could not find the id for my group")
        end
        return result
    end

    @info("Attempting to authenticate to GitLab...")
    _gitlab_username::String = _get_gitlab_username()
    if lowercase(strip(_gitlab_username)) !=
            lowercase(strip(_provided_gitlab_bot_username))
        delayederror(
            string(
                "Provided GitLab username ",
                "(\"$(_provided_gitlab_bot_username)\") ",
                "does not match ",
                "actual GitLab username ",
                "(\"$(_gitlab_username)\").",
                )
            )
    else
        @info(
            string(
                "Provided GitLab username matches ",
                "actual GitLab username.",
                ),
            _provided_gitlab_bot_username,
            _gitlab_username,
            )
    end
    @info("Successfully authenticated to GitLab :)")

    @info(
        string(
            "GitLab username: ",
            "$(_get_gitlab_username())",
            )
        )
    @info(
        string(
            "GitLab group (a.k.a. organization): ",
            "$(_gitlab_group)",
            )
        )

    function _create_gist(params::AbstractDict)::Nothing
        gist_description::String = strip(params[:gist_description])
        gist_content::String = strip(params[:gist_content])
        @info("Attempting to create snippet on GitLab...")
        method::String = "POST"
        url::String = "https://gitlab.com/api/v4/snippets"
        headers::Dict{String, String} = Dict(
            "PRIVATE-TOKEN" => gitlab_bot_personal_access_token,
            "content-type" => "application/json",
            )
        params::Dict{String, String} = Dict(
            "title" => "list.txt",
            "file_name" => "list.txt",
            "content" => gist_content,
            "description" => gist_description,
            "visibility" => "public",
            )
        body::String = JSON.json(params)
        try
            r::HTTP.Messages.Response = HTTP.request(
                method,
                url,
                headers,
                body,
                )
            @info("Successfully created snippet on GitLab.")
        catch exception
            @warn("ignoring exception: ", exception,)
        end
    end

    function _get_all_gists()::Vector{Dict}
        @info("Loading the list of all of my GitLab snippets")
        gist_dict_list::Vector{Dict} = Dict[]
        gist_id_list::Vector{Int} = Int[]
        need_to_continue::Bool = true
        current_page_number::Int = 1
        method::String = "GET"
        headers::Dict{String, String} = Dict(
            "PRIVATE-TOKEN" => gitlab_bot_personal_access_token,
            )
        url::String = ""
        while need_to_continue
            url = string(
                "https://gitlab.com/api/v4/snippets",
                "?per_page=100&page=$(current_page_number)&",
                )
            r = HTTP.request(
                method,
                url,
                headers,
                )
            r_body = String(r.body)
            parsed_body = JSON.parse(r_body)
            if length(parsed_body) == 0
                need_to_continue = false
            else
                need_to_continue = true
                current_page_number += 1
                for i = 1:length(parsed_body)
                    gist_dict = parsed_body[i]
                    gist_id = gist_dict["id"]
                    if gist_id in gist_id_list
                        @debug(
                            string("already have this gist"),
                            gist_id,
                        )
                    else
                        push!(gist_id_list, gist_id)
                        push!(gist_dict_list, gist_dict)
                    end
                end
            end
        end
        return gist_dict_list
    end

    function _retrieve_gist(params::AbstractDict)::String
        gist_description_to_match::String = strip(params[:gist_description])
        correct_gist_id::Int = 0
        correct_gist_raw_url::String = ""
        all_my_gists = _get_all_gists()
        for gist in all_my_gists
            if strip(gist["description"]) == gist_description_to_match
                correct_gist_id = gist["id"]
                correct_gist_raw_url = strip(gist["raw_url"])
            end
        end
        result::String = ""
        if correct_gist_id > 0
            @info("Downloading the correct GitLab snippet")
            method::String = "GET"
            headers::Dict{String, String} = Dict(
                "PRIVATE-TOKEN" => gitlab_bot_personal_access_token,
                )
            url::String = strip(correct_gist_raw_url)
            r = HTTP.request(
                method,
                url,
                headers,
                )
            result = strip(String(r.body))
        else
            result = ""
        end
        if length(result) == 0
            delayederror("Could not find the matching GitLab snippet")
        end
        return result
    end

    function _delete_gists(params::AbstractDict)::Nothing
        gist_description_to_match::String = strip(params[:gist_description])
        list_of_gist_ids_to_delete::Vector{Int} = Int[]
        all_my_gists::Vector{Dict} = _get_all_gists()
        for gist in all_my_gists
            if strip(gist["description"]) == gist_description_to_match
                push!(list_of_gist_ids_to_delete, gist["id"],)
            end
        end
        for gist_id_to_delete in list_of_gist_ids_to_delete
            method = "DELETE"
            url = string(
                "https://gitlab.com/api/v4/snippets/$(gist_id_to_delete)",
                )
            headers = Dict(
                "PRIVATE-TOKEN" => gitlab_bot_personal_access_token,
                )
            r = HTTP.request(
                method,
                url,
                headers,
                )
            @info(string("Deleted GitLab snippet id $(gist_id_to_delete)"))
        end
        return nothing
    end

    function _delete_gists_older_than_minutes(params::AbstractDict)::Nothing
        time::TimeZones.ZonedDateTime =
            params[:time]
        delete_gists_older_than_minutes::Int =
            params[:delete_gists_older_than_minutes]
        max_gist_age_milliseconds::Int =
            delete_gists_older_than_minutes*60*1000
        list_of_gist_ids_to_delete::Vector{Int} = Int[]
        all_my_gists::Vector{Dict} = _get_all_gists()
        for gist in all_my_gists
            gist_updated_at = Dates.DateTime(
                gist["updated_at"][1:end-1]
                )
            gist_updated_at_zoned = TimeZones.ZonedDateTime(
                gist_updated_at,
                TimeZones.TimeZone("UTC"),
                )
            gist_age = time - gist_updated_at_zoned
            if gist_age.value > max_gist_age_milliseconds
                push!(list_of_gist_ids_to_delete, gist["id"],)
            end
        end
        for gist_id_to_delete in list_of_gist_ids_to_delete
            method = "DELETE"
            url = string(
                "https://gitlab.com/api/v4/snippets/$(gist_id_to_delete)",
                )
            headers = Dict(
                "PRIVATE-TOKEN" => gitlab_bot_personal_access_token,
                )
            r = HTTP.request(
                method,
                url,
                headers,
                )
            @info(string("Deleted GitLab snippet id $(gist_id_to_delete)"))
        end
        return nothing
    end

    function _repo_name_with_org(
            ;
            repo::AbstractString,
            org::AbstractString,
            )::String
        repo_name_without_org::String = _repo_name_without_org(
            ;
            repo = repo,
            org = org,
            )
        org_stripped::String = strip(
            strip(strip(strip(strip(convert(String, org)), '/')), '/')
            )
        result::String = string(
            org_stripped,
            "/",
            repo_name_without_org,
            )
        return result
    end

    function _repo_name_without_org(
            ;
            repo::AbstractString,
            org::AbstractString,
            )::String
        repo_stripped::String = strip(
            strip(strip(strip(strip(convert(String, repo)), '/')), '/')
            )
        org_stripped::String = strip(
            strip(strip(strip(strip(convert(String, org)), '/')), '/')
            )
        if length(org_stripped) == 0
            result = repo_stripped
        else
            repo_stripped_lowercase::String = lowercase(repo_stripped)
            org_stripped_lowercase::String = lowercase(org_stripped)
            org_stripped_lowercase_withtrailingslash::String = string(
                org_stripped_lowercase,
                "/",
                )
            if startswith(repo_stripped_lowercase,
                    org_stripped_lowercase_withtrailingslash)
                index_start =
                    length(org_stripped_lowercase_withtrailingslash) + 1
                result = repo_stripped[index_start:end]
            else
                result = repo_stripped
            end
        end
        return result
    end

    function _get_destination_url(
            ;
            repo_name::String,
            credentials::Symbol,
            )::String
        repo_name_without_org::String = _repo_name_without_org(
            ;
            repo = repo_name,
            org = _gitlab_group,
            )
        result::String = ""
        if credentials == :with_auth
            result = string(
                "https://",
                _gitlab_username,
                ":",
                _gitlab_bot_personal_access_token,
                "@",
                "gitlab.com/",
                _gitlab_group,
                "/",
                repo_name_without_org,
                )
        elseif credentials == :with_redacted_auth
            result = string(
                "https://",
                _gitlab_username,
                ":",
                "*****",
                "@",
                "gitlab.com/",
                _gitlab_group,
                "/",
                repo_name_without_org,
                )
        elseif credentials == :without_auth
            result =string(
                "https://",
                "gitlab.com/",
                _gitlab_group,
                "/",
                repo_name_without_org,
                )
        else
            delayederror("$(credentials) is not a supported value for credentials")
        end
        return result
    end

    function _gitlab_repo_exists(
            ;
            repo_name::String,
            )::Bool
        repo_name_without_org = _repo_name_without_org(
            ;
            repo = repo_name,
            org = _gitlab_group,
            )
        result::Bool = try
            method = "GET"
            url = string(
                "https://gitlab.com/api/v4/",
                "projects/$(_gitlab_group)%2F$(repo_name)",
                )
            headers = Dict(
                "PRIVATE-TOKEN" => gitlab_bot_personal_access_token,
                )
            r = HTTP.request(
                method,
                url,
                headers,
                )
            true
        catch
            false
        end
        return result
    end

    function _create_repo(params::AbstractDict)::Nothing
        repo_name::String = strip(params[:repo_name])
        repo_name_with_org::String = _repo_name_with_org(
            ;
            repo = repo_name,
            org = _gitlab_group,
            )
        repo_name_without_org::String = _repo_name_without_org(
            ;
            repo = repo_name,
            org = _gitlab_group,
            )
        repo_destination_url_without_auth = _get_destination_url(
            ;
            repo_name = repo_name_without_org,
            credentials = :without_auth,
            )
        # if Utils._url_exists(repo_destination_url_without_auth)
        if false
            @info("According to HTTP GET request, the repo exists.")
        else
            if _gitlab_repo_exists(; repo_name = repo_name_without_org)
                @info("According to the GitLab API, the repo exists.")
            else
                @info(
                    string("Creating new repo on GitLab"),
                    repo_destination_url_without_auth,
                    )
                method = "POST"
                url = "https://gitlab.com/api/v4/projects"
                headers = Dict(
                    "PRIVATE-TOKEN" => gitlab_bot_personal_access_token,
                    "content-type" => "application/json",
                    )
                params = Dict(
                    "name" => repo_name_without_org,
                    "path" => repo_name_without_org,
                    "namespace_id" => _get_namespace_id_for_my_group(),
                    "issues_enabled" => false,
                    "merge_requests_enabled" => false,
                    "jobs_enabled" => false,
                    "wiki_enabled" => false,
                    "snippets_enabled" => false,
                    "resolve_outdated_diff_discussions" => false,
                    "container_registry_enabled" => false,
                    "shared_runners_enabled" => false,
                    "visibility" => "public",
                    "public_jobs" => false,
                    "only_allow_merge_if_pipeline_succeeds" =>
                        false,
                    "only_allow_merge_if_all_discussions_are_resolved" =>
                        false,
                    "lfs_enabled" => false,
                    "request_access_enabled" => false,
                    "printing_merge_request_link_enabled" => false,
                    "initialize_with_readme" => false,
                    )
                body = JSON.json(params)
                r = HTTP.request(
                    method,
                    url,
                    headers,
                    body,
                    )
            end
        end
        return nothing
    end

    function _generate_new_repo_description(
            params::AbstractDict,
            )::String
        source_url::String = params[:source_url]
        when::TimeZones.ZonedDateTime = params[:when]
        time_zone::TimeZones.TimeZone = params[:time_zone]
        by::String = strip(string("@", _gitlab_username))

        new_description::String = Utils.default_repo_description(
            ;
            from = source_url,
            when = when,
            time_zone = time_zone,
            by = by,
            )

        return new_description
    end

    function _update_repo_description(params::AbstractDict)::Nothing
        repo_name::String = strip(params[:repo_name])
        new_repo_description = strip(params[:new_repo_description])
        _create_repo(
            Dict(
                :repo_name => repo_name,
                ),
            )
        repo_name_without_org::String = _repo_name_without_org(
            ;
            repo = repo_name,
            org = _gitlab_group,
            )

        method_1 = "GET"
        url_1 = string(
            "https://gitlab.com/api/v4/",
            "projects/$(_gitlab_group)%2F$(repo_name)",
            )
        headers_1 = Dict(
            "PRIVATE-TOKEN" => gitlab_bot_personal_access_token,
            )
        http_request_1 = () -> HTTP.request(
            method_1,
            url_1,
            headers_1,
            )
        r_1 = Utils.retry_function_until_success(
            http_request_1;
            max_attempts = 10,
            seconds_to_wait_between_attempts = 180,
        )
        r_body_1 = String(r_1.body)
        parsed_body_1 = JSON.parse(r_body_1)
        repo_id = parsed_body_1["id"]

        method_2 = "PUT"
        url_2 = string(
            "https://gitlab.com/api/v4/",
            "projects/$(repo_id)",
            )
        headers_2 = Dict(
            "PRIVATE-TOKEN" => gitlab_bot_personal_access_token,
            "content-type" => "application/json",
            )
        params_2 = Dict(
            "description" => new_repo_description,
            )
        body_2 = JSON.json(params_2)
        @info("Attempting to update repo description on GitLab...")
        http_request_2 = () -> HTTP.request(
            method_2,
            url_2,
            headers_2,
            body_2,
            )
        r_2 = Utils.retry_function_until_success(
            http_request_2;
            max_attempts = 10,
            seconds_to_wait_between_attempts = 180,
            )
        @info("Successfully updated repo description on GitLab")
        return nothing
    end

    function _gitlab_provider(task::Symbol)::Function
        if task == :create_gist
            return _create_gist
        elseif task == :retrieve_gist
            return _retrieve_gist
        elseif task == :delete_gists
            return _delete_gists
        elseif task == :create_repo
            return _create_repo
        elseif task == :generate_new_repo_description
            return _generate_new_repo_description
        elseif task == :update_repo_description
            return _update_repo_description
        elseif task == :delete_gists_older_than_minutes
            return _delete_gists_older_than_minutes
        elseif task == :get_destination_url
            return _get_destination_url
        else
            delayederror("$(task) is not a valid task")
        end
    end

    return _gitlab_provider
end

end # End submodule RepoSnapshots.Hosts.GitLabDstHost

##### End of file


text:
function pseudocolor(pix::Uint8)
  if pix < 64
    pr = uint8(0)
    pg = uint8(0)
    pb = uint8(4*pix)
  elseif pix < 128
    pr = uint8(0)
    pg = uint8(4*(pix-64))
    pb = uint8(255)
  elseif pix < 192
    pr = uint8(0)
    pg = uint8(255)
    pb = uint8(4*(192 - pix))
  else
    pr = uint8(4*(pix - 192))
    pg = uint8(4*(256 - pix))
    pb = uint8(0)
  end
  return (pr, pg, pb)
end

img = open("data/juliaset.pgm");
magic = chomp(readline(img));   # => "P5"
if magic == "P5"
 out = open("jsetcolor.ppm", "w");
 println(out, "P6");
 params = chomp(readline(img));  # => "800 400 255"
 println(out, params);
 (wd, ht, pmax) = int(split(params));
 for i = 1:ht
    buf = readbytes(img, wd);
    for j = 1:wd
      (r,g,b) = pseudocolor(buf[j]);
      write(out,r); write(out,g); write(out,b);
    end
 end
 close(out);
else
 error("Not a NetPBM grayscale file")
end
close(img);

max_stars_repo_path: Module 3/Chapter05/netpbm.jl

max_issues_repo_path: Module 3/Chapter05/netpbm.jl

max_forks_repo_path: Module 3/Chapter05/netpbm.jl


text:
#
#
# Proximal Point Problem and Options
#
#
@doc raw"""
    ProximalProblem <: Problem
specify a problem for solvers based on the evaluation of proximal map(s).

# Fields
* `M` - a Riemannian manifold
* `cost` - a function ``F:\mathcal M→ℝ`` to
  minimize
* `proxes` - proximal maps ``\operatorname{prox}_{λ\varphi}:\mathcal M→\mathcal M``
  as functions (λ,x) -> y, i.e. the prox parameter λ also belongs to the signature of the proximal map.
* `number_of_proxes` - (length(proxes)) number of proximal Maps,
  e.g. if one of the maps is a combined one such that the proximal Maps
  functions return more than one entry per function

# See also
[`cyclic_proximal_point`](@ref), [`get_cost`](@ref), [`get_proximal_map`](@ref)
"""
mutable struct ProximalProblem{
    T,mT<:AbstractManifold,TCost,TProxes<:Union{Tuple,AbstractVector}
} <: Problem{T}
    M::mT
    cost::TCost
    proximal_maps!!::TProxes
    number_of_proxes::Vector{Int}
    function ProximalProblem(
        M::mT,
        cF,
        proxMaps::Union{Tuple,AbstractVector};
        evaluation::AbstractEvaluationType=AllocatingEvaluation(),
    ) where {mT<:AbstractManifold}
        return new{typeof(evaluation),mT,typeof(cF),typeof(proxMaps)}(
            M, cF, proxMaps, ones(length(proxMaps))
        )
    end
    function ProximalProblem(
        M::mT,
        cF,
        proxMaps::Union{Tuple,AbstractVector},
        nOP::Vector{Int};
        evaluation::AbstractEvaluationType=AllocatingEvaluation(),
    ) where {mT<:AbstractManifold}
        return if length(nOP) != length(proxMaps)
            throw(
                ErrorException(
                    "The number_of_proxes ($(nOP)) has to be the same length as the number of proxes ($(length(proxMaps)).",
                ),
            )
        else
            new{typeof(evaluation),mT,typeof(cF),typeof(proxMaps)}(M, cF, proxMaps, nOP)
        end
    end
end
function check_prox_number(n, i)
    (i > n) && throw(ErrorException("the $(i)th entry does not exists, only $n available."))
    return true
end
@doc raw"""
    get_proximal_map(p,λ,x,i)

evaluate the `i`th proximal map of `ProximalProblem p` at the point `x` of `p.M` with parameter ``λ>0``.
"""
function get_proximal_map(p::ProximalProblem{AllocatingEvaluation}, λ, x, i)
    check_prox_number(length(p.proximal_maps!!), i)
    return p.proximal_maps!![i](p.M, λ, x)
end
function get_proximal_map!(p::ProximalProblem{AllocatingEvaluation}, y, λ, x, i)
    check_prox_number(length(p.proximal_maps!!), i)
    return copyto!(p.M, y, p.proximal_maps!![i](p.M, λ, x))
end
function get_proximal_map(p::ProximalProblem{MutatingEvaluation}, λ, x, i)
    check_prox_number(length(p.proximal_maps!!), i)
    y = allocate_result(p.M, get_proximal_map, x)
    return p.proximal_maps!![i](p.M, y, λ, x)
end
function get_proximal_map!(p::ProximalProblem{MutatingEvaluation}, y, λ, x, i)
    check_prox_number(length(p.proximal_maps!!), i)
    return p.proximal_maps!![i](p.M, y, λ, x)
end
#
#
# Proximal based Options
#
#
@doc raw"""
    CyclicProximalPointOptions <: Options

stores options for the [`cyclic_proximal_point`](@ref) algorithm. These are the

# Fields
* `x` – the current iterate
* `stopping_criterion` – a [`StoppingCriterion`](@ref)
* `λ` – (@(iter) -> 1/iter) a function for the values of λ_k per iteration/cycle
* `oder_type` – (`:LinearOrder`) – whether
  to use a randomly permuted sequence (`:FixedRandomOrder`), a per
  cycle permuted sequence (`RandomOrder`) or the default linear one.

# Constructor
    CyclicProximalPointOptions(M, p)

Generate the options with the following keyword arguments

* `stopping_criterion` (`StopAfterIteration(2000)`) – a [`StoppingCriterion`](@ref).
* `λ` ( `(iter) -> 1.0 / iter`) a function to compute the ``λ_k, k ∈ \mathbb N``,
* `evaluation_order`(`:LinearOrder`) a Symbol indicating the order the proxes are applied.

# See also
[`cyclic_proximal_point`](@ref)
"""
mutable struct CyclicProximalPointOptions{TX,TStop<:StoppingCriterion,Tλ} <: Options
    x::TX
    stop::TStop
    λ::Tλ
    order_type::Symbol
    order::AbstractVector{Int}
end
@deprecate CyclicProximalPointOptions(
    x, s::StoppingCriterion, λ=(iter) -> 1.0 / iter, o::Symbol=:LinearOrder
) CyclicProximalPointOptions(
    DefaultManifold(2), x; stopping_criterion=s, λ=λ, evaluation_order=o
)

function CyclicProximalPointOptions(
    M::AbstractManifold,
    x::P;
    stopping_criterion::S=StopAfterIteration(2000),
    λ::F=(iter) -> 1.0 / iter,
    evaluation_order::Symbol=:LinearOrder,
) where {P,S,F}
    return CyclicProximalPointOptions{P,S,F}(x, stopping_criterion, λ, evaluation_order, [])
end
@doc raw"""
    DouglasRachfordOptions <: Options

Store all options required for the DouglasRachford algorithm,

# Fields
* `x` - the current iterate (result) For the parallel Douglas-Rachford, this is
  not a value from the `PowerManifold` manifold but the mean.
* `s` – the last result of the double reflection at the proxes relaxed by `α`.
* `λ` – function to provide the value for the proximal parameter during the calls
* `α` – relaxation of the step from old to new iterate, i.e.
  ``x^{(k+1)} = g(α(k); x^{(k)}, t^{(k)})``, where ``t^{(k)}`` is the result
  of the double reflection involved in the DR algorithm
* `R` – method employed in the iteration to perform the reflection of `x` at the prox `p`.
* `stop` – a [`StoppingCriterion`](@ref)
* `parallel` – indicate whether we are running a parallel Douglas-Rachford or not.

# Constructor

    DouglasRachfordOptions(M, p; kwargs...)

Generate the options for a Manifold `M` and an initial point `p`, where the following keyword arguments can be used

* `λ` – (`(iter)->1.0`) function to provide the value for the proximal parameter
  during the calls
* `α` – (`(iter)->0.9`) relaxation of the step from old to new iterate, i.e.
  ``x^{(k+1)} = g(α(k); x^{(k)}, t^{(k)})``, where ``t^{(k)}`` is the result
  of the double reflection involved in the DR algorithm
* `R` – ([`reflect`](@ref)) method employed in the iteration to perform the reflection of `x` at
  the prox `p`.
* `stopping_criterion` – ([`StopAfterIteration`](@ref)`(300)`) a [`StoppingCriterion`](@ref)
* `parallel` – (`false`) indicate whether we are running a parallel Douglas-Rachford
  or not.
"""
mutable struct DouglasRachfordOptions{TX,Tλ,Tα,TR,S} <: Options
    x::TX
    xtmp::TX
    s::TX
    stmp::TX
    λ::Tλ
    α::Tα
    R::TR
    stop::S
    parallel::Bool
    function DouglasRachfordOptions(
        ::AbstractManifold,
        x::P;
        λ::Fλ=(iter) -> 1.0,
        α::Fα=(iter) -> 0.9,
        R::FR=Manopt.reflect,
        stopping_criterion::S=StopAfterIteration(300),
        parallel=false,
    ) where {P,Fλ,Fα,FR,S<:StoppingCriterion}
        return new{P,Fλ,Fα,FR,S}(
            x, deepcopy(x), deepcopy(x), deepcopy(x), λ, α, R, stopping_criterion, parallel
        )
    end
    @deprecate DouglasRachfordOptions(
        x,
        λ=(iter) -> 1.0,
        α=(iter) -> 0.9,
        R=Manopt.reflect,
        stop::StoppingCriterion=StopAfterIteration(300),
        parallel=false,
    ) DouglasRachfordOptions(
        DefaultManifold(2), x; λ=λ, α=α, R=R, stopping_criterion=stop, parallel=parallel
    )
end
#
# Debug
#
# overwrite defaults, since we store the result in the mean field
#
# Debug the Cyclic Proximal point parameter
#
@doc raw"""
    DebugProximalParameter <: DebugAction

print the current iterates proximal point algorithm parameter given by
[`Options`](@ref)s `o.λ`.
"""
mutable struct DebugProximalParameter <: DebugAction
    io::IO
    format::String
    function DebugProximalParameter(;
        long::Bool=false,
        prefix=long ? "Proximal Map Parameter λ(i):" : "λ:",
        format="$prefix%s",
        io::IO=stdout,
    )
        return new(io, format)
    end
end
function (d::DebugProximalParameter)(::ProximalProblem, o::DouglasRachfordOptions, i::Int)
    (i > 0) && Printf.format(d.io, Printf.Format(d.format), o.λ(i))
    return nothing
end
function (d::DebugProximalParameter)(
    ::ProximalProblem, o::CyclicProximalPointOptions, i::Int
)
    (i > 0) && Printf.format(d.io, Printf.Format(d.format), o.λ(i))
    return nothing
end

#
# Record
@doc raw"""
    RecordProximalParameter <: RecordAction

recoed the current iterates proximal point algorithm parameter given by in
[`Options`](@ref)s `o.λ`.
"""
mutable struct RecordProximalParameter <: RecordAction
    recorded_values::Array{Float64,1}
    RecordProximalParameter() = new(Array{Float64,1}())
end
function (r::RecordProximalParameter)(
    ::ProximalProblem, o::CyclicProximalPointOptions, i::Int
)
    return record_or_reset!(r, o.λ(i), i)
end
function (r::RecordProximalParameter)(::ProximalProblem, o::DouglasRachfordOptions, i::Int)
    return record_or_reset!(r, o.λ(i), i)
end