text:
@testset "binaryor.jl" begin

    @testset "BinaryOr()" begin
        trailer = SeaPearl.Trailer()

        x = SeaPearl.BoolVar("x", trailer)
        y = SeaPearl.BoolVar("y", trailer)

        constraint = SeaPearl.BinaryOr(x, y, trailer)

        @test constraint in x.onDomainChange
        @test constraint in y.onDomainChange
        @test constraint.active.value
    end

    @testset "propagate!(::BinaryOr)" begin

        @testset "x false, y unassigned" begin

            trailer = SeaPearl.Trailer()
            x = SeaPearl.BoolVar("x", trailer)
            y = SeaPearl.BoolVar("y", trailer)

            constraint = SeaPearl.BinaryOr(x, y, trailer)
            toPropagate = Set{SeaPearl.Constraint}()
            prunedDomains = SeaPearl.CPModification()

            SeaPearl.assign!(x, false)

            @test SeaPearl.propagate!(constraint, toPropagate, prunedDomains)
            @test SeaPearl.assignedValue(y)
            @test prunedDomains == SeaPearl.CPModification("y" => [false])
            @test !constraint.active.value

        end

        @testset "x false, y false" begin

            trailer = SeaPearl.Trailer()
            x = SeaPearl.BoolVar("x", trailer)
            y = SeaPearl.BoolVar("y", trailer)

            constraint = SeaPearl.BinaryOr(x, y, trailer)
            toPropagate = Set{SeaPearl.Constraint}()
            prunedDomains = SeaPearl.CPModification()

            SeaPearl.assign!(x, false)
            SeaPearl.assign!(y, false)

            @test !SeaPearl.propagate!(constraint, toPropagate, prunedDomains)
            @test SeaPearl.length(y.domain) == 0
            @test prunedDomains == SeaPearl.CPModification("y" => [false])
            @test !constraint.active.value

        end

        @testset "x true, y unassigned" begin

            trailer = SeaPearl.Trailer()
            x = SeaPearl.BoolVar("x", trailer)
            y = SeaPearl.BoolVar("y", trailer)

            constraint = SeaPearl.BinaryOr(x, y, trailer)
            toPropagate = Set{SeaPearl.Constraint}()
            prunedDomains = SeaPearl.CPModification()

            SeaPearl.assign!(x, true)

            @test SeaPearl.propagate!(constraint, toPropagate, prunedDomains)
            @test prunedDomains == SeaPearl.CPModification()
            @test !constraint.active.value

        end

    end
end


text:
#
#
# REAL MONEY ######################################################
# REAL MONEY ######################################################
#
#

function place_first_poly_order!(config, open_orders, bot_path, client, usr)
    @info("NEW order found!")
    for (k,v) in open_orders["NEW"]
        ticker = client.get_ticker(symbol=k)["lastPrice"]
        current = parse(Float64, ticker)

        id_str = v["trigger"] * "--" * string(now())
        symbol = k
        quant = floor(config["order_volume"] / current, digits=config["symbols"][symbol]["precision"])
        order = try client.order_limit_buy(
            symbol=symbol,
            quantity=quant,
            price=ticker);
        catch
            nothing
        end
        if order !== nothing
            open_orders["BUY"]["EXTRA-0_" * id_str] = order
            delete!(open_orders["NEW"], k)
            open(joinpath(bot_path, "open_orders.json"), "w") do f
                JSON.print(f, open_orders, 4)
            end
            p = Dict()
            p["ticker"] = ticker
            try create_notification("new cycle", config, usr, params = p)
                catch;
            end
        end
    end
    return open_orders
end

function check_open_poly_sell!(config, open_orders, closed_orders, bot_path, client, usr)
    for (k,v) in open_orders["SELL"]
        if length(open_orders["SELL"]) > 0
            fees = client.get_trade_fee(symbol=v["symbol"])["tradeFee"][1]
        end
        if v["status"] != "place order"
            skip = false
            tmp = try client.get_order(
                symbol=v["symbol"],
                orderId=v["orderId"])
                catch
                @info("Skipped..")
                sleep(0.25)
                skip = true
            end
            if !skip
                id_str = split(k, "_")[end]
                #
                # IS SELL CORRECT??
                #
                if tmp["status"] != "FILLED"
                    qty_new = 0
                    value   = 0
                    for (k2,v2) in closed_orders[id_str]
                        if v2["side"] == "BUY"
                            qty_new += parse(Float64, v2["executedQty"])
                            value += parse(Float64, v2["cummulativeQuoteQty"]) * (1.0 + fees["taker"])
                        else
                            qty_new -= parse(Float64, v2["executedQty"])
                            value -= parse(Float64, v2["cummulativeQuoteQty"]) * (1.0 - fees["maker"])
                        end
                    end
                    new_quant = floor(qty_new, digits=v["precision"])
                    new_price = string(floor(value / new_quant * (1 + parse(Float64, config["take_profit"])/100), digits=config["symbols"][v["symbol"]]["sell_precision"]))
                    if new_quant > parse(Float64, v["origQty"]) * 1.02
                        @info("SELL PLACED WITH WRONG QTY!")
                        result = try client.cancel_order(
                            symbol=v["symbol"],
                            orderId=v["orderId"])
                        catch
                            nothing
                        end
                        new_id    = "SELL_" * id_str
                        order = try client.order_limit_sell(
                            symbol=v["symbol"],
                            quantity=new_quant,
                            price=new_price);
                            catch
                            nothing
                        end
                        if order !== nothing
                            order["time"] = string(now())
                            open_orders["SELL"][new_id] = order
                            open(joinpath(bot_path, "open_orders.json"), "w") do f
                                JSON.print(f, open_orders, 4)
                            end
                        else
                            open_orders["SELL"][new_id] = Dict()
                            open_orders["SELL"][new_id]["origQty"] = new_quant
                            open_orders["SELL"][new_id]["price"]   = new_price
                            open_orders["SELL"][new_id]["status"]  = "place order"
                            open_orders["SELL"][new_id]["time"]    = string(now())
                            open_orders["SELL"][new_id]["fail"]    = 0
                            open_orders["SELL"][new_id]["symbol"]  = v["symbol"]
                            open_orders["SELL"][new_id]["if_fail"] = new_quant - 1/10^config["symbols"][v["symbol"]]["precision"]

                            open(joinpath(bot_path, "open_orders.json"), "w") do f
                                JSON.print(f, open_orders, 4)
                            end
                        end
                    end
                end
                #
                #
                #

                if tmp["status"] == "FILLED"
                    !(id_str in keys(closed_orders)) ? closed_orders[id_str] = Dict() : ""
                    tmp["time"] = string(now())

                    delete!(open_orders["SELL"], k)
                    for (k2, v2) in open_orders["BUY"]
                        if split(k2, "_")[end] == id_str && v2["symbol"] == v["symbol"]
                            sleep(0.2)
                            tmp_buy = try client.get_order(
                                symbol=v["symbol"],
                                orderId=v2["orderId"])
                            catch
                                nothing
                            end

                            if tmp_buy !== nothing
                                if !(tmp_buy["status"] in ["FILLED", "CANCELED"])
                                    sleep(0.2)
                                    result = try client.cancel_order(
                                        symbol=v["symbol"],
                                        orderId=v2["orderId"])
                                    catch
                                        nothing
                                    end
                                    if result !== nothing
                                        delete!(open_orders["BUY"], k2)
                                    else
                                        open_orders["BUY"][k2]["status"] = "cancel"
                                    end
                                    open(joinpath(bot_path, "open_orders.json"), "w") do f
                                        JSON.print(f, open_orders, 4)
                                    end
                                else
                                    delete!(open_orders["BUY"], k2)
                                    open(joinpath(bot_path, "open_orders.json"), "w") do f
                                        JSON.print(f, open_orders, 4)
                                    end
                                end
                                if parse(Float64, tmp_buy["executedQty"]) > 0
                                    closed_orders[id_str][k2] = tmp_buy
                                    open(joinpath(bot_path, "closed_orders.json"), "w") do f
                                        JSON.print(f, closed_orders, 4)
                                    end
                                end
                            else
                                open_orders["BUY"][k2]["status"] = "cancel"
                                open(joinpath(bot_path, "open_orders.json"), "w") do f
                                    JSON.print(f, open_orders, 4)
                                end
                            end
                        end
                    end
                    open(joinpath(bot_path, "open_orders.json"), "w") do f
                        JSON.print(f, open_orders, 4)
                    end
                    qty = 0
                    net_qty = 0
                    val = 0
                    for (k2,v2) in closed_orders[id_str]
                        v2["side"] == "BUY" ? qty += parse(Float64, v2["executedQty"]) : ""
                        v2["side"] == "BUY" ? net_qty += parse(Float64, v2["executedQty"]) : net_qty -= parse(Float64, v2["executedQty"])
                        v2["side"] == "BUY" ? val -= parse(Float64, v2["cummulativeQuoteQty"]) * (1.0 + fees["taker"]) : val += parse(Float64, v2["cummulativeQuoteQty"]) * (1.0 - fees["maker"])
                    end

                    if val < 0 && val + parse(Float64, tmp["cummulativeQuoteQty"]) < 0
                        #
                        ## If there was an extra buy executed
                        closed_orders[id_str]["SELL-" * lpad(rand(1:1:100000), 6, "0")] = deepcopy(tmp);

                        open(joinpath(bot_path, "closed_orders.json"), "w") do f
                            JSON.print(f, closed_orders, 4)
                        end
                        #
                        ## Create new sell
                        if abs(val) > config["symbols"][v["symbol"]]["min_qty"]
                            #
                            ## qty is high enough to be sold
                            new_qty = floor(net_qty, digits=config["symbols"][v["symbol"]]["precision"])
                            new_price = string(floor(abs(val)/net_qty * (1 + parse(Float64, config["take_profit"])/100), digits=config["symbols"][v["symbol"]]["sell_precision"]))
                            sleep(0.2)
                            order = try client.order_limit_sell(
                                symbol=v["symbol"],
                                quantity=new_qty,
                                price=new_price);
                                catch
                                nothing
                            end
                            new_id = "SELL_" * id_str
                            if order !== nothing
                                order["time"] = string(now())
                                open_orders["SELL"][new_id] = order
                                open(joinpath(bot_path, "open_orders.json"), "w") do f
                                    JSON.print(f, open_orders, 4)
                                end
                                #
                                ## Place new buy
                                extra_iteration = 0
                                for (k2,v2) in closed_orders[id_str]
                                    if length(split(k2, "SELL")) < 2
                                        if parse(Int64, split(split(k2, "_")[1], "-")[end]) > extra_iteration
                                            extra_iteration = parse(Int64, split(split(k2, "_")[1], "-")[end])
                                        end
                                    end
                                end
                                extra_iteration += 1
                                if extra_iteration <= parse(Float64, config["extra_order_count"])
                                    new_price = string(round(parse(Float64, new_price) * (1 - config["extra_order_step"]), digits=config["symbols"][v["symbol"]]["sell_precision"]))
                                    quant = floor(config["order_volume"] * (config["extra_order_martingale"])^(extra_iteration + 1) / parse(Float64, new_price), digits=config["symbols"][v["symbol"]]["precision"])

                                    new_buy = Dict()
                                    new_buy["status"] = "place order"
                                    new_buy["origQty"] = quant
                                    new_buy["price"] = new_price
                                    new_buy["symbol"] = v["symbol"]
                                    new_buy_id = "EXTRA-" * string(extra_iteration) * "_" * id_str
                                    open_orders["BUY"][new_buy_id] = new_buy
                                    open(joinpath(bot_path, "open_orders.json"), "w") do f
                                        JSON.print(f, open_orders, 4)
                                    end
                                end
                            else
                                #
                                ## If new sell could not be placed
                                open_orders["SELL"][new_id] = Dict()
                                open_orders["SELL"][new_id]["origQty"] = new_quant
                                open_orders["SELL"][new_id]["price"]   = new_price
                                open_orders["SELL"][new_id]["status"]  = "place order"
                                open_orders["SELL"][new_id]["time"]    = string(now())
                                open_orders["SELL"][new_id]["symbol"]  = v["symbol"]
                                open_orders["SELL"][new_id]["fail"]    = 0
                                open_orders["SELL"][new_id]["if_fail"] = new_quant - 1/10^config["symbols"][v["symbol"]]["precision"]

                                open(joinpath(bot_path, "open_orders.json"), "w") do f
                                    JSON.print(f, open_orders, 4)
                                end
                            end
                        else
                            #
                            ## qty is NOT high enough to be sold
                            new_price = string(floor(abs(val)/net_qty * (1 + parse(Float64, config["take_profit"])/100), digits=config["symbols"][v["symbol"]]["sell_precision"]))

                            extra_iteration = 0
                            for (k2,v2) in closed_orders[id_str]
                                if length(split(k2, "SELL")) < 2
                                    if parse(Int64, split(split(k2, "_")[1], "-")[end]) > extra_iteration
                                        extra_iteration = parse(Int64, split(split(k2, "_")[1], "-")[end])
                                    end
                                end
                            end
                            extra_iteration += 1
                            if extra_iteration <= parse(Float64, config["extra_order_count"])
                                new_price = string(round(parse(Float64, new_price) * (1 - config["extra_order_step"]), digits=config["symbols"][v["symbol"]]["sell_precision"]))
                                quant = floor(config["order_volume"] * (config["extra_order_martingale"])^(extra_iteration + 1) / parse(Float64, new_price), digits=config["symbols"][v["symbol"]]["precision"])

                                new_buy = Dict()
                                new_buy["status"]  = "place order"
                                new_buy["origQty"] = quant
                                new_buy["price"]   = new_price
                                new_buy["symbol"]  = v["symbol"]
                                new_buy_id = "EXTRA-" * string(extra_iteration) * "_" * id_str
                                open_orders["BUY"][new_buy_id] = new_buy
                                open(joinpath(bot_path, "open_orders.json"), "w") do f
                                    JSON.print(f, open_orders, 4)
                                end
                            end
                        end
                    else
                        #
                        ####################################################################
                        #
                        #
                        ## If everything went well and the profit is positive
                        sell_id = !haskey(closed_orders[id_str], "SELL") ? "SELL" : "SELL-" * lpad(rand(1:1:100000), 6, "0")
                        closed_orders[id_str][sell_id] = deepcopy(tmp);

                        open(joinpath(bot_path, "closed_order-" * id_str * ".json"), "w") do f
                            JSON.print(f, closed_orders[id_str], 4)
                        end

                        try for (k5,v5) in closed_orders
                                if DateTime(split(k5, "--")[end]) < now() - Day(42)
                                    delete!(closed_orders, k5)
                                end
                            end
                        catch;
                        end

                        open(joinpath(bot_path, "closed_orders.json"), "w") do f
                            JSON.print(f, closed_orders, 4)
                        end
                    end
                    #
                    ####################################################################
                    #
                elseif tmp["status"] == "CANCELED"
                    delete!(open_orders["SELL"], k)
                    open(joinpath(bot_path, "open_orders.json"), "w") do f
                        JSON.print(f, open_orders, 4)
                    end
                    if parse(Float64, tmp["executedQty"]) > 0
                        closed_orders[id_str]["SELL-" * lpad(rand(1:1:100000), 6, "0")] = tmp
                        open(joinpath(bot_path, "closed_orders.json"), "w") do f
                            JSON.print(f, closed_orders, 4)
                        end
                    end
                elseif tmp["status"] != v["status"]
                    open_orders["SELL"][k] = tmp
                    open_orders["SELL"][k]["time"] = string(now())
                    open(joinpath(bot_path, "open_orders.json"), "w") do f
                        JSON.print(f, open_orders, 4)
                    end
                end
            end
        else
            if v["fail"] == 0
                qty = floor(v["origQty"], digits=config["symbols"][v["symbol"]]["precision"])
            elseif v["fail"] == 1

                id_str = split(k, "_")[end]
                #
                # IS SELL CORRECT??
                #
                qty_new = 0
                value   = 0
                for (k2,v2) in closed_orders[id_str]
                    if v2["side"] == "BUY"
                        qty_new += parse(Float64, v2["executedQty"])
                        value += parse(Float64, v2["cummulativeQuoteQty"]) * (1.0 + fees["taker"])
                    else
                        qty_new -= parse(Float64, v2["executedQty"])
                        value -= parse(Float64, v2["cummulativeQuoteQty"]) * (1.0 - fees["maker"])
                    end
                end
                new_quant = floor(qty_new, digits=config["symbols"][v["symbol"]]["precision"])
                new_price = string(floor(value / new_quant * (1 + parse(Float64, config["take_profit"])/100), digits=config["symbols"][v["symbol"]]["sell_precision"]))
                result = try client.cancel_order(
                    symbol=v["symbol"],
                    orderId=v["orderId"])
                catch
                    nothing
                end
                qty = new_quant
            else
                account = client.get_account()["balances"];
                new = new_quant
                for bal in account
                    if bal["asset"] == v["symbol"][1:3]
                        if parse(Float64, bal["free"]) <= new_quant
                            new = parse(Float64, bal["free"])
                        end
                    end
                end
                qty = floor(new, digits=config["symbols"][v["symbol"]]["precision"])
            end
            order = try client.order_limit_sell(
                symbol=v["symbol"],
                quantity=qty,
                price=v["price"]);
                catch
                nothing
            end
            if order !== nothing
                order["time"] = string(now())
                open_orders["SELL"][k] = order
                open(joinpath(bot_path, "open_orders.json"), "w") do f
                    JSON.print(f, open_orders, 4)
                end
            else
                v["fail"] = 1
                open(joinpath(bot_path, "open_orders.json"), "w") do f
                    JSON.print(f, open_orders, 4)
                end
            end
        end
    end
    sleep(0.25)
    return open_orders, closed_orders
end

function check_open_poly_buy!(config, open_orders, closed_orders, bot_path, client, usr)
    for (k,v) in open_orders["BUY"]
        if v["status"] == "place order"
            quant = round(v["origQty"], digits=config["symbols"][v["symbol"]]["precision"])
            new_price = v["price"]
            new_id = k
            order = try client.order_limit_buy(
                symbol= v["symbol"],
                quantity=quant,
                price=new_price);
                catch
                nothing
            end
            if order !== nothing
                order["time"] = string(now())
                open_orders["BUY"][new_id] = order
            else
                open_orders["BUY"][new_id] = Dict()
                open_orders["BUY"][new_id]["origQty"] = quant
                open_orders["BUY"][new_id]["price"]   = new_price
                open_orders["BUY"][new_id]["symbol"]  = v["symbol"]
                open_orders["BUY"][new_id]["status"]  = "place order"
                open_orders["BUY"][new_id]["time"]    = string(now())
            end
            open(joinpath(bot_path, "open_orders.json"), "w") do f
                JSON.print(f, open_orders, 4)
            end
        else
            skip = false
            tmp = try client.get_order(
                symbol=v["symbol"],
                orderId=v["orderId"])
                catch
                @info("Skipped..")
                sleep(0.25)
                skip = true
            end
            if !skip
                if tmp["status"] == "FILLED" || v["status"] == "continue" || parse(Float64, tmp["executedQty"]) == parse(Float64, tmp["origQty"])
                    if tmp["status"] != "FILLED" && tmp["status"] != "CANCELLED"
                        result = try client.cancel_order(
                            symbol=v["symbol"],
                            orderId=tmp["orderId"])
                        catch
                            nothing
                        end
                    end

                    fees = client.get_trade_fee(symbol=v["symbol"])["tradeFee"][1]
                    id_str = split(k, "_")[end]
                    extra_iteration = parse(Int64, split(split(k, "_")[1], "-")[2])
                    !(id_str in keys(closed_orders)) ? closed_orders[id_str] = Dict() : ""
                    tmp["time"] = string(now())
                    closed_orders[id_str][split(k, "_")[1]] = deepcopy(tmp);
                    open(joinpath(bot_path, "closed_orders.json"), "w") do f
                        JSON.print(f, closed_orders, 4)
                    end

                    delete!(open_orders["BUY"], k)
                    open(joinpath(bot_path, "open_orders.json"), "w") do f
                        JSON.print(f, open_orders, 4)
                    end

                    #
                    # EXTRA BUY
                    #
                    new_id = "EXTRA-" * string(extra_iteration + 1) * "_" * id_str
                    new_price = string(round(parse(Float64, v["price"]) * (1 - config["extra_order_step"]), digits=config["symbols"][v["symbol"]]["sell_precision"]))
                    quant = floor(config["order_volume"] * (config["extra_order_martingale"])^(extra_iteration + 1) / parse(Float64, new_price), digits=config["symbols"][v["symbol"]]["precision"])
                    if extra_iteration < parse(Float64, config["extra_order_count"])
                        order = try client.order_limit_buy(
                            symbol= v["symbol"],
                            quantity=quant,
                            price=new_price);
                            catch
                            nothing
                        end
                        if order !== nothing
                            order["time"] = string(now())
                            open_orders["BUY"][new_id] = order
                        else
                            open_orders["BUY"][new_id] = Dict()
                            open_orders["BUY"][new_id]["origQty"] = quant
                            open_orders["BUY"][new_id]["price"]   = new_price
                            open_orders["BUY"][new_id]["status"]  = "place order"
                            open_orders["BUY"][new_id]["symbol"]  = v["symbol"]
                            open_orders["BUY"][new_id]["time"]    = string(now())
                        end
                        open(joinpath(bot_path, "open_orders.json"), "w") do f
                            JSON.print(f, open_orders, 4)
                        end
                    end
                    #
                    # TAKE PROFIT
                    #
                    # Cancel current take profit orders
                    if length(open_orders["SELL"]) > 0
                        for (k2, v2) in open_orders["SELL"]
                            if split(k2, "_")[end] == id_str && v["symbol"] == v2["symbol"]
                                tmp_sell = try client.get_order(
                                    symbol=v["symbol"],
                                    orderId=v2["orderId"])
                                    catch
                                    try sleep(0.2)
                                        client.get_order(
                                            symbol=v["symbol"],
                                            orderId=v2["orderId"])
                                        catch nothing
                                    end
                                end
                                if !(tmp_sell["status"] in ["CANCELED", "FILLED"]) || tmp_sell == nothing
                                    sleep(0.1)
                                    tmp_sell = try client.cancel_order(
                                        symbol=v["symbol"],
                                        orderId=v2["orderId"])
                                    catch
                                        nothing
                                    end
                                end
                                if tmp_sell !== nothing
                                    if parse(Float64, v2["executedQty"]) > 0
                                        closed_orders[id_str]["SELL-" * lpad(rand(1:1:10000), 5, "0")] = result
                                        open(joinpath(bot_path, "closed_orders.json"), "w") do f
                                            JSON.print(f, closed_orders, 4)
                                        end
                                    end
                                    delete!(open_orders["SELL"], k2)
                                end
                            end
                        end
                    end

                    qty_new = 0
                    value   = 0
                    for (k2,v2) in closed_orders[id_str]
                        if v2["side"] == "BUY"
                            qty_new += parse(Float64, v2["executedQty"])
                            value += parse(Float64, v2["cummulativeQuoteQty"]) * (1.0 + fees["taker"])
                        else
                            qty_new -= parse(Float64, v2["executedQty"])
                            value -= parse(Float64, v2["cummulativeQuoteQty"]) * (1.0 - fees["maker"])
                        end
                    end
                    new_id    = "SELL_" * id_str
                    new_quant = floor(qty_new, digits=config["symbols"][v["symbol"]]["precision"])
                    new_price = string(floor(value / new_quant * (1 + fees["maker"] + parse(Float64, config["take_profit"])/100), digits=config["symbols"][v["symbol"]]["sell_precision"]))
                    if value > config["symbols"][v["symbol"]]["min_qty"]
                        order = try client.order_limit_sell(
                            symbol=v["symbol"],
                            quantity=new_quant,
                            price=new_price);
                            catch
                            nothing
                        end
                        if order !== nothing
                            order["time"] = string(now())
                            open_orders["SELL"][new_id] = order
                            open(joinpath(bot_path, "open_orders.json"), "w") do f
                                JSON.print(f, open_orders, 4)
                            end
                        else
                            open_orders["SELL"][new_id] = Dict()
                            open_orders["SELL"][new_id]["origQty"] = new_quant
                            open_orders["SELL"][new_id]["price"]   = new_price
                            open_orders["SELL"][new_id]["status"]  = "place order"
                            open_orders["SELL"][new_id]["time"]    = string(now())
                            open_orders["SELL"][new_id]["fail"]    = 0
                            open_orders["SELL"][new_id]["symbol"]  = v["symbol"]
                            open_orders["SELL"][new_id]["if_fail"] = new_quant - 1/10^config["symbols"][v["symbol"]]["precision"]

                            open(joinpath(bot_path, "open_orders.json"), "w") do f
                                JSON.print(f, open_orders, 4)
                            end
                        end
                    end
                    p = Dict()
                    p["extra_iteration"] = extra_iteration
                    p["executedQty"] = tmp["executedQty"]
                    try create_notification("buy exec", config, usr, params = p)
                        catch;
                    end
                    if !(extra_iteration < parse(Float64, config["extra_order_count"]))
                        try create_notification("no volume", config, usr)
                            catch;
                        end
                    end
                elseif v["status"]  == "cancel"
                    result = try client.cancel_order(
                        symbol=v["symbol"],
                        orderId=v["orderId"])
                    catch
                        nothing
                    end
                    if result !== nothing
                        delete!(open_orders["BUY"], k)
                        open(joinpath(bot_path, "open_orders.json"), "w") do f
                            JSON.print(f, open_orders, 4)
                        end
                    end
                elseif v["status"]  == "buy now"
                    @info(v["status"])
                    id_str = split(k, "_")[end]
                    extra_iteration = parse(Int64, split(split(k, "_")[1], "-")[2])
                    if extra_iteration < parse(Float64, config["extra_order_count"])
                        #
                        # Cancel current BUY order
                        if tmp["status"] != "CANCELED"
                            result = try client.cancel_order(
                                symbol=v["symbol"],
                                orderId=v["orderId"])
                            catch
                                nothing
                            end
                        else
                            result = tmp
                        end
#                         @info(result)
                        if result !== nothing
                            @info("order cancelled")
                            #
                            # Check if that order was partially filled -> if so: move cancelled order to closed_orders

                            if parse(Float64, result["executedQty"]) > 0
                                result["time"] = string(now())
                                !haskey(closed_orders, id_str) ? closed_orders[id_str] = Dict() : ""
                                closed_orders[id_str][split(k, "_")[1]] = result
                                extra_iteration += 1
                                open(joinpath(bot_path, "closed_orders.json"), "w") do f
                                    JSON.print(f, closed_orders, 4)
                                end
                            end
                            #
                            # Remove from open_orders
                            delete!(open_orders["BUY"], k)
                            #
                            # Get new order details
                            ticker = client.get_ticker(symbol=v["symbol"])["lastPrice"]
                            quant = floor(parse(Float64, v["origQty"]), digits=config["symbols"][v["symbol"]]["precision"])
                            new_id = "EXTRA-" * string(extra_iteration) * "_" * id_str
                            #
                            # Place new order
                            order = try client.order_limit_buy(
                                symbol=v["symbol"],
                                quantity=quant,
                                price=ticker);
                                catch
                                nothing
                            end
                            if order !== nothing
                                order["time"] = string(now())
                                open_orders["BUY"][new_id] = order
                            else
                                open_orders["BUY"][new_id] = Dict()
                                open_orders["BUY"][new_id]["origQty"] = quant
                                open_orders["BUY"][new_id]["price"]   = ticker
                                open_orders["BUY"][new_id]["status"]  = "place order"
                                open_orders["BUY"][new_id]["symbol"]  = v["symbol"]
                                open_orders["BUY"][new_id]["time"]    = string(now())
                            end
                            open(joinpath(bot_path, "open_orders.json"), "w") do f
                                JSON.print(f, open_orders, 4)
                            end
                        end
                    else
                        open_orders["BUY"][k] = tmp
                        open(joinpath(bot_path, "open_orders.json"), "w") do f
                            JSON.print(f, open_orders, 4)
                        end
                    end
                elseif tmp["status"] == "CANCELED" && parse(Float64, tmp["executedQty"]) == 0
                    delete!(open_orders["BUY"], k)
                    open(joinpath(bot_path, "open_orders.json"), "w") do f
                        JSON.print(f, open_orders, 4)
                    end
                elseif tmp["status"] == "PARTIALLY_FILLED" && v["status"] != tmp["status"]
                    v = deepcopy(tmp)
                    v["time"] = string(now())
                    v["partial_time"] = string(now())
                    open_orders["BUY"][k] = v
                    open(joinpath(bot_path, "open_orders.json"), "w") do f
                        JSON.print(f, open_orders, 4)
                    end
                elseif parse(Float64, tmp["executedQty"]) != 0 && parse(Float64, tmp["executedQty"]) != parse(Float64, tmp["origQty"]) && haskey(v, "partial_time")
                    if v["partial_time"] < string(now() - Minute(2))
                        result = try client.cancel_order(
                            symbol=v["symbol"],
                            orderId=tmp["orderId"])
                        catch
                            nothing
                        end
                        if result !== nothing
                            tmp["partial_time"] = v["partial_time"]
                            v = deepcopy(tmp)
                            v["status"] = "continue"
                            open_orders["BUY"][k] = v
                            open(joinpath(bot_path, "open_orders.json"), "w") do f
                                JSON.print(f, open_orders, 4)
                            end
                            p = Dict()
                            p["extra_iteration"] = extra_iteration
                            p["executedQty"] = tmp["executedQty"]
                            try create_notification("partial buy exec", config, usr, params = p)
                                catch;
                            end
                        end
                        if tmp["status"] == "CANCELED"
                            v["status"] = "continue"
                            open_orders["BUY"][k] = v
                            open(joinpath(bot_path, "open_orders.json"), "w") do f
                                JSON.print(f, open_orders, 4)
                            end
                        end
                    end
                elseif tmp["status"] != v["status"]
                    tmp["time"] = string(now())
                    open_orders["BUY"][k] = tmp
                    if haskey(v, "partial_time")
                        open_orders["BUY"][k]["partial_time"] = v["partial_time"]
                    end
                    open(joinpath(bot_path, "open_orders.json"), "w") do f
                        JSON.print(f, open_orders, 4)
                    end
                elseif tmp["status"] == "NEW" && split(k, "_")[1] == "EXTRA-0" && parse(Float64, tmp["executedQty"]) == 0.0
                    # Maybe the first BUY has to be updated
                    id_str = split(k, "_")[end]
                    t = DateTime(split(id_str, "--")[end])
                    if now() - t > Minute(1)
                        result = try client.cancel_order(
                            symbol=v["symbol"],
                            orderId=v["orderId"])
                        catch
                            nothing
                        end
                        if result !== nothing
                            delete!(open_orders["BUY"], k)
                            open(joinpath(bot_path, "open_orders.json"), "w") do f
                                JSON.print(f, open_orders, 4)
                            end
                            try create_notification("cycle closed", config, usr)
                                catch;
                            end
                        end
                    end
                end
            end
        end
    end
    return open_orders, closed_orders
end

ext: jl


text:
# Intra cluster communication
using Sockets

"""
    raft_server()
Takes raft protocol requests from another raft node.
"""
function raft_server(;address=IPv4(0), port=1346)
    @async begin
        server = 0 # Init server socket
        sockErr = true
        while (sockErr)
            try
                server = listen(address, port)
                sockErr = false
                @info ("Starting Raft protocol server at $(address):$(port)")
            catch e
                port += 1  # Try next port
            end
        end

        global ipAddress = address
        global ipPort   = port

        # NOTE: Starting each iteration in a new task will cause
        #       incorrect behavior
        while true
            sock = accept(server)
            if isopen(sock) != true
                throw(RavanaException("Error! Socket not open"))
            end
            try
                # Disassemble op and arguments
                (op, func, argv) = get_opt(sock, raft_proto_table)
                # Execute RAFT protocol command on this node
                ret = func(argv)
                b = byte_array(ret)
                write(sock, length(b), b)
            catch e
                @error ("Protocol Server Error: ", e)
                b = byte_array(e)
                write(sock, length(b), b)
            end
            close(sock)
        end
    end
end

"""
    raft_client()
Called by one raft node to communicate with another raft node.
"""
function raft_client(address, port, proto_op::Int32, argv)
    bytes = byte_array((proto_op, argv))
    size = UInt32(length(bytes))
    version = UInt16(RAFT_PROTO_VERSION)
    flags = UInt16(RAFT_PROTO_CLIENT)
    client = connect(address, port)

    write(client, size, version, flags, bytes)
    ret_size = read(client, Int)
    ret = array_to_type(read(client, ret_size))
    close(client)
    ret
end

# Raft protocol ops
const RAFT_REQUEST_VOTE          = Int32(100001)
const RAFT_APPEND_ENTRIES        = Int32(100002)

# Lookup table for Raft protocol Ops
const raft_proto_table = Dict(RAFT_APPEND_ENTRIES      =>  local_append_entries,
                              RAFT_REQUEST_VOTE        =>  local_request_vote)


text:
module GlobalDecls

    export DATADIR, RESULTSDIR, SKIPLIST, WEEKS_PER_YEAR, annualise
    export Region, london, se, rest_of_england
    export AggLevel, national, regional, local_authority
    export SIMULATION_YEAR

    const DATADIR="/home/graham_s/VirtualWorlds/projects/action_for_children/england/data/"
    const RESULTSDIR="/home/graham_s/VirtualWorlds/projects/action_for_children/england/results/"

    const SIMULATION_YEAR = 2019

    @enum AggLevel national regional local_authority
    @enum Region london se rest_of_england
    WEEKS_PER_YEAR = 365.25/7

    SKIPLIST = [
        # "E07000189" # south somerset missing in underlying data
    ]

    function annualise( m :: Real ) :: Real
        return m*WEEKS_PER_YEAR/1_000_000.0
    end

end


text:
function Same(x, k, stride)
    Ih, Iw = size(x)[1:2]
    padh = Int(div(ceil((stride[1]-1)Ih-stride[1]+k[1]), 2))
    padw = Int(div(ceil((stride[2]-1)Iw-stride[2]+k[2]), 2))
    return (padh, padh, padw, padw)
end

struct Im2Col
    x::AbstractArray
    Oh::Int
    Ow::Int
    b::Int
    c::Int
    function Im2Col(x, k, stride, padding; trans = "Cov")
        if padding == "same"
            padding = Same(x, k, stride)
        end
        i, j = 1, 1
        while j <= 2
            c = d = collect(size(x))
            c[j], d[j] = padding[i], padding[i + 1]
            x = cat(zeros(Tuple(c)), x, zeros(Tuple(d)), dims = j)
            i += 2
            j += 1
        end
        Xh, Xw, C, B = size(x)
        Fh, Fw = k
        Sh, Sw = stride[1], stride[2]
        Oh = Int(div((Xh - Fh), Sh) + 1) # ceil
        Ow = Int(div((Xw - Fw), Sw) + 1)
        y = zeros(Oh, Ow, Fh, Fw, C, B)
        for h in 1 : k[1]
            for w in 1 : k[2]
                y[:, :, h, w, :, :] = x[h : Sh : h+Oh*Sh-1, w : Sw : w+Ow*Sw-1, :, :]
            end
        end
        y = permutedims(y, [1, 5, 6, 2, 3, 4])
        if trans == "Cov"
            new(reshape(y, C*Fh*Fw, B*Oh*Ow), Oh, Ow, B, C)
        else
            new(reshape(y, B*C*Oh*Ow, Fh*Fw), Oh, Ow, B, C)
        end
    end
end

struct Col2Im
    x::Array
    function Col2Im(col, k, shape::IOShape, stride, padding)
        Fh, Fw = k
        Ih, Iw = shape.IShape
        Oh, Ow = shape.OShape
        B, C = Int(size(col)[2]/(Oh*Ow)), Int(size(col)[1]/(Fh*Fw))
        gis(f, o, stride, pad) = stride * (Oh - 1) + Fh - pad
        Ih, Iw = gis(Fh, Oh, stride[1], sum(padding[1:2])), gis(Fw, Ow, stride[2], sum(padding[3:4]))
        padding = ceil.(padding)
        padding = [sum(padding[1:2]), sum(padding[3:4])]
        col = permutedims(reshape(col, C, Fh, Fw, B, Oh, Ow), [4, 1, 2, 3, 5, 6])
        images = zeros(B, C, Ih+padding[1]+stride[1]-1, Iw+padding[2]+stride[2]-1)
        for h in 1 : Oh
            h_lim = h + stride[1] * Oh-1
            for w in 1 : Ow
                w_lim = w + stride[2] * Ow-1
                images[:, :, h:stride[1]:h_lim, w:stride[2]:w_lim] += col[:, :, h, w, :, :]
            end
        end
        padding .+= 1
        new(images[:, :, padding[1]:Ih+padding[1]-1, padding[2]:Iw+padding[2]-1])
    end
end


text:
# Utility functions for processing VLSV data.

"""
    getcell(meta, location) -> UInt

Return cell ID containing the given spatial `location`, excluding domain boundaries.
Only accept 3D location.
"""
function getcell(meta::MetaVLSV, loc)
   @unpack coordmin, coordmax, dcoord, ncells, cellid, maxamr = meta

   coordmin[1] < loc[1] < coordmax[1] || error("x coordinate out of bound!")
   coordmin[2] < loc[2] < coordmax[2] || error("y coordinate out of bound!")
   coordmin[3] < loc[3] < coordmax[3] || error("z coordinate out of bound!")

   dx, dy, dz = dcoord

   # Get cell indices
   indices = @inbounds SVector(
      round(UInt, (loc[1] - coordmin[1]) ÷ dx),
      round(UInt, (loc[2] - coordmin[2]) ÷ dy),
      round(UInt, (loc[3] - coordmin[3]) ÷ dz) )
   # Get cell id
   cid = @inbounds indices[1] + indices[2]*ncells[1] + indices[3]*ncells[1]*ncells[2] + 1

   ncells_lowerlevel = UInt(0)
   ncell = prod(ncells)

   @inbounds for ilevel = 0:maxamr
      cid in cellid && break

      ncells_lowerlevel += 2^(3*ilevel)*ncell

      indices = SVector(
         round(UInt, (loc[1] - coordmin[1]) ÷ dx * 2^(ilevel+1)),
         round(UInt, (loc[2] - coordmin[2]) ÷ dy * 2^(ilevel+1)),
         round(UInt, (loc[3] - coordmin[3]) ÷ dz * 2^(ilevel+1)) )

      cid = ncells_lowerlevel + indices[1] +
         2^(ilevel+1)*ncells[1]*indices[2] +
         4^(ilevel+1)*ncells[1]*ncells[2]*indices[3] + 1
   end

   cid
end

"""
    getlevel(meta, cid) -> Int

Return the AMR level of a given cell ID. Note that this function does not check if the VLSV
file of `meta` actually contains `cid`: it may be shadowed by refined children.
"""
function getlevel(meta::MetaVLSV, cid::Integer)
   ncell = prod(meta.ncells)
   ilevel = 0
   c = Int(cid) - ncell
   while c > 0
      ilevel += 1
      c -= 2^(3*ilevel)*ncell
   end
   ilevel
end

"""
    getparent(meta, cid) -> Int

Return the parent cell ID of given child `cid`.
"""
function getparent(meta::MetaVLSV, cid::Integer)
   @inbounds xcell, ycell = meta.ncells[1], meta.ncells[2]
   ncell = prod(meta.ncells)

   mylvl = getlevel(meta, cid)
   parentlvl = mylvl - 1

   if parentlvl < 0
      throw(ArgumentError("Cell ID $cid has no parent cell!"))
   else
      # get the first cellid on my level
      cid1st = get1stcell(mylvl, ncell) + 1
      # get row and column sequence on my level (starting with 0)
      xcell = xcell*2^mylvl
      ycell = ycell*2^mylvl

      myseq = cid - cid1st
      ix = myseq % xcell
      iz = myseq ÷ (xcell*ycell)
      iy = (myseq - iz*xcell*ycell) ÷ xcell
      # indexes on the parent level
      ixparent = ix ÷ 2
      iyparent = iy ÷ 2
      izparent = iz ÷ 2

      # get the first cellid on parent level
      cid1st -= ncell*8^parentlvl
      # get parent cellid (may not exist!!!)
      parentid = cid1st + izparent*xcell*ycell÷4 + iyparent*xcell÷2 + ixparent
   end
   parentid
end

"""
    getchildren(meta, cid) -> Vector{Int}

Return direct children of `cid`.
"""
function getchildren(meta::MetaVLSV, cid::Integer)
   xcell, ycell, zcell = meta.ncells
   ncell = prod(meta.ncells)

   mylvl = getlevel(meta, cid)

   # get the first cell ID on the my level
   cid1st = 1
   for i = 0:mylvl-1
      cid1st += ncell*8^i
   end
   # get my row and column sequence on my level (starting with 0)
   xcell = xcell*2^mylvl
   ycell = ycell*2^mylvl

   myseq = cid - cid1st
   ix = myseq % xcell
   iz = myseq ÷ (xcell*ycell)
   iy = (myseq - iz*xcell*ycell) ÷ xcell

   # get the children sequences on the finer level
   ix *= 2
   iy *= 2
   iz *= 2

   nchildren = 2^ndims(meta)
   cid = @MVector zeros(Int, nchildren)
   # get the first cell ID on the finer level
   cid1st += ncell*8^mylvl
   ix_, iy_ = [ix, ix+1], [iy, iy+1]
   iz_ = zcell != 1 ? [iz, iz+1] : [iz]
   for (n,i) in enumerate(Iterators.product(ix_, iy_, iz_))
      @inbounds cid[n] = cid1st + i[3]*xcell*ycell*4 + i[2]*xcell*2 + i[1]
   end
   SVector{nchildren}(cid)
end

"""
    getsiblings(meta, cid) -> Vector{Int}

Return sibling cells of a given `cid`, including itself.
"""
function getsiblings(meta::MetaVLSV, cid::Integer)
   xcell, ycell, zcell = meta.ncells
   ncell = prod(meta.ncells)

   mylvl = getlevel(meta, cid)

   mylvl == 0 && throw(ArgumentError("CellID $cid is not a child cell!"))

   xcell = xcell * 2^mylvl
   ycell = ycell * 2^mylvl

   # 1st cellid on my level
   cid1st = get1stcell(mylvl, ncell) + 1

   # xyz sequences on my level (starting with 0)
   myseq = cid - cid1st
   ix = myseq % xcell
   iz = myseq ÷ (xcell*ycell)
   iy = (myseq - iz*xcell*ycell) ÷ xcell

   ix1 = iseven(ix) ? ix + 1 : ix - 1
   iy1 = iseven(iy) ? iy + 1 : iy - 1
   iz1 = iseven(iz) ? iz + 1 : iz - 1
   # reorder
   ix, ix1 = minmax(ix, ix1)
   iy, iy1 = minmax(iy, iy1)
   iz, iz1 = minmax(iz, iz1)

   nsiblings = 2^ndims(meta)
   cid = @MVector zeros(Int, nsiblings)
   ix_, iy_ = [ix, ix1], [iy, iy1]
   iz_ = zcell != 1 ? [iz, iz1] : [iz]
   for (n,i) in enumerate(Iterators.product(ix_, iy_, iz_))
      @inbounds cid[n] = cid1st + i[3]*xcell*ycell + i[2]*xcell + i[1]
   end
   SVector{nsiblings}(cid)
end

"""
    isparent(meta, cid) -> Bool

Check if `cid` is a parent cell.
"""
function isparent(meta::MetaVLSV, cid::Integer)
   ncell_accum = get1stcell(meta.maxamr, prod(meta.ncells))

   cid ∉ meta.cellid && 0 < cid ≤ ncell_accum
end

"""
    getcellcoordinates(meta, cid) -> Vector{Float}

Return a given cell's coordinates.
"""
function getcellcoordinates(meta::MetaVLSV, cid::Integer)
   @unpack ncells, coordmin, coordmax = meta
   cid -= 1 # for easy divisions

   xcell, ycell, zcell = ncells
   reflevel = 0
   subtraction = prod(ncells) * (2^reflevel)^3
   # sizes on the finest level
   while cid ≥ subtraction
      cid -= subtraction
      reflevel += 1
      subtraction *= 8
      xcell *= 2
      ycell *= 2
      zcell *= 2
   end

   indices = SVector(
      cid % xcell,
      cid ÷ xcell % ycell,
      cid ÷ (xcell*ycell) )

   coords = @inbounds SVector(
      coordmin[1] + (indices[1] + 0.5) * (coordmax[1] - coordmin[1]) / xcell,
      coordmin[2] + (indices[2] + 0.5) * (coordmax[2] - coordmin[2]) / ycell,
      coordmin[3] + (indices[3] + 0.5) * (coordmax[3] - coordmin[3]) / zcell )

   coords
end

"""
    getvcellcoordinates(meta, vcellids, species="proton")

Return velocity cells' coordinates of `species` and `vcellids`.
"""
function getvcellcoordinates(meta::MetaVLSV, vcellids; species="proton")
   @unpack vblocks, vblock_size, dv, vmin = meta.meshes[species]

   bsize = prod(vblock_size)
   blockid = @. vcellids ÷ bsize
   # Get block coordinates
   blockInd = [SVector(
      bid % vblocks[1],
      bid ÷ vblocks[1] % vblocks[2],
      bid ÷ (vblocks[1] * vblocks[2]) )
      for bid in blockid]
   blockCoord = [SVector(
      bInd[1] * dv[1] * vblock_size[1] + vmin[1],
      bInd[2] * dv[2] * vblock_size[2] + vmin[2],
      bInd[3] * dv[3] * vblock_size[3] + vmin[3] )
      for bInd in blockInd]

   # Get cell indices
   vcellblockids = @. vcellids % bsize
   cellidxyz = [SVector(
      cid % vblock_size[1],
      cid ÷ vblock_size[1] % vblock_size[2],
      cid ÷ (vblock_size[1] * vblock_size[2]) )
      for cid in vcellblockids]

   # Get cell coordinates
   cellCoords = [zeros(SVector{3, Float32}) for _ in vcellblockids]
   @inbounds for i in eachindex(vcellblockids)
      cellCoords[i] = SVector(blockCoord[i][1] + (cellidxyz[i][1] + 0.5) * dv[1],
                              blockCoord[i][2] + (cellidxyz[i][2] + 0.5) * dv[2],
                              blockCoord[i][3] + (cellidxyz[i][3] + 0.5) * dv[3] )
   end
   cellCoords
end

"Flatten vblocks-organized `VDFraw` into x-->y-->z ordered 3D `VDF`."
function flatten(vmesh::VMeshInfo, VDFraw)
   vblock_size, vblocks = vmesh.vblock_size, vmesh.vblocks
   blocksize = prod(vblock_size)
   sliceBz = vblocks[1]*vblocks[2]
   vsizex, vsizey = vblock_size[1] * vblocks[1], vblock_size[2] * vblocks[2]
   sliceCz = vblock_size[1]*vblock_size[2]

   VDF = zeros(Float32, vmesh.vblock_size[1]*vmesh.vblocks[1],
      vmesh.vblock_size[2]*vmesh.vblocks[2],
      vmesh.vblock_size[3]*vmesh.vblocks[3])

   @inbounds for i in eachindex(VDF)
      iB = (i - 1) ÷ blocksize
      iBx = iB % vblocks[1]
      iBy = iB % sliceBz ÷ vblocks[1]
      iBz = iB ÷ sliceBz
      iCellInBlock = (i - 1) % blocksize
      iCx = iCellInBlock % vblock_size[1]
      iCy = iCellInBlock % sliceCz ÷ vblock_size[1]
      iCz = iCellInBlock ÷ sliceCz
      iBCx = iBx*vblock_size[1] + iCx
      iBCy = iBy*vblock_size[2] + iCy
      iBCz = iBz*vblock_size[3] + iCz
      iF = iBCz*vsizex*vsizey + iBCy*vsizex + iBCx + 1
      VDF[iF] = VDFraw[i]
   end
   VDF
end

function isInsideDomain(meta::MetaVLSV, point)
   @unpack coordmin, coordmax = meta

   if coordmin[1] < point[1] ≤ coordmax[1] &&
      coordmin[2] < point[2] ≤ coordmax[2] &&
      coordmin[3] < point[3] ≤ coordmax[3]
      return true
   else
      return false
   end
end

"""
    getcellinline(meta, point1, point2) -> cellids, distances, coords

Returns cell IDs, distances and coordinates for every cell in a line between two given
points `point1` and `point2`. May be improved later with preallocation!
"""
function getcellinline(meta::MetaVLSV, point1, point2)
   @unpack coordmin, coordmax, ncells = meta

   if !isInsideDomain(meta, point1)
      throw(DomainError(point1, "point location outside simulation domain!"))
   elseif !isInsideDomain(meta, point2)
      throw(DomainError(point2, "point location outside simulation domain!"))
   end

   cell_lengths = @inbounds SVector(
      (coordmax[1] - coordmin[1]) / ncells[1],
      (coordmax[2] - coordmin[2]) / ncells[2],
      (coordmax[3] - coordmin[3]) / ncells[3] )

   distances = [0.0]
   cellids = [getcell(meta, point1)]
   coords = point1
   ϵ = eps(Float32)
   unit_vector = @. (point2 - point1) / $norm(point2 - point1 + ϵ)
   p = point1
   coef_min = MVector(0.0, 0.0, 0.0)
   coef_max = MVector(0.0, 0.0, 0.0)

   while true
      cid = getcell(meta, p)
      amrlvl = getlevel(meta, cid)

      # Get the max and min cell boundaries
      min_bounds = getcellcoordinates(meta, cid) - 0.5*cell_lengths*0.5^amrlvl
      max_bounds = min_bounds + cell_lengths

      # Check which face we hit first
      @. coef_min = (min_bounds - p) / unit_vector
      @. coef_max = (max_bounds - p) / unit_vector

      # Negative coefficients indicates the opposite direction
      @inbounds for i = 1:3
         if unit_vector[i] == 0.0
            coef_min[i] = Inf
            coef_max[i] = Inf
         end
         if coef_min[i] ≤ 0  coef_min[i] = Inf end
         if coef_max[i] ≤ 0  coef_max[i] = Inf end
      end

      # Find the minimum distance from a boundary times a factor
      d = min(minimum(coef_min), minimum(coef_max)) * 1.00001

      coordnew = @. p + d*unit_vector

      dot(point2 - coordnew, unit_vector) ≥ 0 || break

      cellidnew = getcell(meta, coordnew)

      push!(cellids, cellidnew)
      coords = hcat(coords, coordnew)
      push!(distances, norm(coordnew .- point1))

      p = coordnew
   end

   cellids, distances, coords
end

"""
    getslicecell(meta, sliceoffset, idim, minCoord, maxCoord) -> idlist, indexlist

Find the cell ids `idlist` which are needed to plot a 2d cut through of a 3d mesh, in a
direction `idim` at `sliceoffset`, and the `indexlist`, which is a mapping from original
order to the cut plane and can be used to select data onto the plane.
"""
function getslicecell(meta::MetaVLSV, sliceoffset, idim, minCoord, maxCoord)
   idim ∉ (1,2,3) && @error "Unknown slice direction $idim"
   @unpack ncells, maxamr, cellid, cellindex = meta

   nsize = ncells[idim]
   sliceratio = sliceoffset / (maxCoord - minCoord)
   0.0 ≤ sliceratio ≤ 1.0 || error("slice plane index out of bound!")

   # Find the ids
   nlen = 0
   ncell = prod(ncells)
   # number of cells up to each refinement level
   nStart = SVector{maxamr+2}(vcat(0, accumulate(+, (ncell*8^ilvl for ilvl = 0:maxamr))))

   indexlist = Int[]
   idlist = UInt[]

   cellidsorted = cellid[cellindex]

   @inbounds for ilvl = 0:maxamr
      nLow, nHigh = nStart[ilvl+1], nStart[ilvl+2]
      ids = cellidsorted[nLow .< cellidsorted .≤ nHigh]
      ix, iy, iz = getindexes(ilvl, ncells[1], ncells[2], nLow, ids)

      coords =
         if idim == 1
            ix
         elseif idim == 2
            iy
         else # 3
            iz
         end

      # Find the cut plane index for each refinement level (0-based)
      depth = floor(Int, sliceratio*nsize*2^ilvl)
      # Find the needed elements to create the cut and save the results
      elements = coords .== depth
      append!(indexlist, (nlen+1:nlen+length(ids))[elements])
      append!(idlist, ids[elements])

      nlen += length(ids)
   end

   idlist, indexlist
end

"""
    refineslice(meta, idlist, data, normal) -> Array

Generate scalar data on the finest refinement level given cellids `idlist` and variable
`data` on the slice perpendicular to `normal`.
"""
function refineslice(meta::MetaVLSV, idlist, data, normal)
   @unpack ncells, maxamr = meta

   if normal == :x
      dims = SVector{2}([ncells[2], ncells[3]] .* 2^maxamr)
   elseif normal == :y
      dims = SVector{2}([ncells[1], ncells[3]] .* 2^maxamr)
   elseif normal == :z
      dims = SVector{2}([ncells[1], ncells[2]] .* 2^maxamr)
   end

   dpoints = zeros(eltype(data), dims[1], dims[2])

   # Create the plot grid
   ncell = prod(ncells)
   nHigh, nLow = ncell, 0

   @inbounds for i = 0:maxamr
      ids = idlist[nLow .< idlist .≤ nHigh]
      d = data[nLow .< idlist .≤ nHigh]

      ix, iy, iz = getindexes(i, ncells[1], ncells[2], nLow, ids)

      # Get the correct coordinate values and the widths for the plot
      if normal == :x
         a, b = iy, iz
      elseif normal == :y
         a, b = ix, iz
      elseif normal == :z
         a, b = ix, iy
      end

      # Insert the data values into dpoints
      refineRatio = 2^(maxamr - i)
      iRange = 0:refineRatio-1
      X, Y = ndgrid(iRange, iRange)

      coords = [SVector(0, 0) for _ in a, _ in 1:2^(2*(maxamr-i))]
      @fastmath for ir = 1:2^(2*(maxamr-i)), ic in eachindex(a, b)
         coords[ic,ir] = SVector(muladd(a[ic], refineRatio, 1+X[ir]),
                                 muladd(b[ic], refineRatio, 1+Y[ir]) )
      end

      for ic in eachindex(d), ir = 1:2^(2*(maxamr-i))
         dpoints[ coords[ic,ir][1], coords[ic,ir][2] ] = d[ic]
      end

      nLow = nHigh
      nHigh += ncell*8^(i+1)
   end

   dpoints
end

"Compute every cell id's x, y and z indexes on the given refinement level (0-based)."
@inline function getindexes(ilevel, xcells, ycells, nCellUptoLowerLvl, ids)
   slicesize = xcells*ycells*4^ilevel

   iz = @. (ids - nCellUptoLowerLvl - 1) ÷ slicesize

   # number of ids up to the coordinate z in the refinement level ilevel
   idUpToZ = muladd.(iz, slicesize, nCellUptoLowerLvl)

   iy = @. (ids - idUpToZ - 1) ÷ (xcells*2^ilevel)
   ix = @. ids - idUpToZ - iy*xcells*2^ilevel - 1

   ix, iy, iz
end

@inline function getindexes(ilvl, xcells, ycells, nCellUptoLowerLvl, id::Int)
   slicesize = xcells*ycells*4^ilvl
   iz = (id - nCellUptoLowerLvl - 1) ÷ slicesize
   idUpToZ = muladd(iz, slicesize, nCellUptoLowerLvl)
   iy = (id - idUpToZ - 1) ÷ (xcells*2^ilvl)
   ix = id - idUpToZ - iy*xcells*2^ilvl - 1
   ix, iy, iz
end

"""
    getnearestcellwithvdf(meta, id) -> Int

Find the nearest spatial cell with VDF saved of a given cell `id` in the file `meta`.
"""
function getnearestcellwithvdf(meta::MetaVLSV, id)
   cells = getcellwithvdf(meta)
   isempty(cells) && throw(ArgumentError("No distribution saved in $(meta.name)"))
   coords = [zeros(SVector{3, Float32}) for _ in cells]
   @inbounds for i in eachindex(cells)
      coords[i] = getcellcoordinates(meta, cells[i])
   end
   coords_orig = getcellcoordinates(meta, id)
   d2 = [sum((c - coords_orig).^2) for c in coords]
   cells[argmin(d2)]
end

"""
    getcellwithvdf(meta) -> cellids

Get all the cell IDs with VDF saved.
"""
getcellwithvdf(meta::MetaVLSV) =
   readmesh(meta.fid, meta.footer, "SpatialGrid", "CELLSWITHBLOCKS")::Vector{UInt}

"Return the first cellid - 1 on `mylevel` given `ncells` on this level."
function get1stcell(mylevel, ncells)
   cid1st = 0
   for i = 0:mylevel-1
      cid1st += ncells*8^i
   end
   cid1st
end

fillmesh(meta::MetaVLSV, vars::AbstractString) = fillmesh(meta, [vars])

"""
    fillmesh(meta::MetaVLSV, vars; verbose=false) -> celldata, vtkGhostType

Fill the DCCRG mesh with quantity of `vars` on all refinement levels.
# Return arguments
- `celldata::Vector{Vector{Array}}`: data for each variable on each AMR level.
- `vtkGhostType::Array{UInt8}`: cell status (to be completed!).
"""
function fillmesh(meta::MetaVLSV, vars; verbose=false)
   @unpack maxamr, fid, footer, ncells, cellid, cellindex = meta

   nvarvg = findall(!startswith("fg_"), vars)
   nv = length(vars)
   T = Vector{DataType}(undef, nv)
   offset = @MVector zeros(Int, nv)
   arraysize = @MVector zeros(Int, nv)
   vsize  = @MVector zeros(Int, nv)
   @inbounds for i = 1:nv
      T[i], offset[i], arraysize[i], _, vsize[i] =
         getObjInfo(footer, vars[i], "VARIABLE", "name")
   end

   Tout = copy(T)
   for i in eachindex(T)
      if T[i] == Float64 Tout[i] = Float32 end
   end

   @inbounds celldata =
      [[zeros(Tout[iv], vsize[iv], ncells[1]*2^i, ncells[2]*2^i, ncells[3]*2^i)
      for i = 0:maxamr] for iv in 1:nv]

   @inbounds vtkGhostType =
      [zeros(UInt8, ncells[1]*2^i, ncells[2]*2^i, ncells[3]*2^i) for i = 0:maxamr]

   if maxamr == 0
      @inbounds for iv = 1:nv
         celldata[iv][1][:] = readvariable(meta, vars[iv])
      end
      return celldata, vtkGhostType
   end

   # Find the ids
   ncell = prod(ncells)
   nLow, nHigh = 0, ncell
   cellidsorted = cellid[cellindex]

   @inbounds for ilvl = 0:maxamr
      verbose && @info "scanning AMR level $ilvl..."
      ids = cellidsorted[nLow .< cellidsorted .≤ nHigh]

      # indicate the condition of non-existing cells
      idrefined = setdiff(nLow+1:nHigh, ids)

      for id in idrefined
         ix, iy, iz = getindexes(ilvl, ncells[1], ncells[2], nLow, id) .+ 1
         vtkGhostType[ilvl+1][ix,iy,iz] = 8
      end

      rOffsetsRaw = indexin(ids, cellid)

      if ilvl != maxamr
         for iv in nvarvg
            verbose && @info "reading variable $(vars[iv])..."
            a = mmap(fid, Vector{UInt8}, sizeof(T[iv])*vsize[iv]*arraysize[iv], offset[iv])
            dataRaw = reshape(reinterpret(T[iv], a), vsize[iv], arraysize[iv])
            data = @view dataRaw[:,rOffsetsRaw]

            fillcell!(iv, ilvl, ids, ncells, maxamr, nLow, celldata, data)
         end
      else # max amr level
         for (iv, var) = enumerate(vars)
            verbose && @info "reading variable $var..."
            if startswith(var, "fg_")
               celldata[iv][end][:] = readvariable(meta, var)
            else
               a = mmap(fid, Vector{UInt8}, sizeof(T[iv])*vsize[iv]*arraysize[iv],
                  offset[iv])
               dataRaw = reshape(reinterpret(T[iv], a), vsize[iv], arraysize[iv])
               data = @view dataRaw[:,rOffsetsRaw]

               fillcell!(iv, ids, ncells, maxamr, nLow, celldata, data)
            end
         end
      end
      nLow = nHigh
      nHigh += ncell*8^(ilvl+1)
   end

   celldata, vtkGhostType
end

function fillcell!(iv, ilvl, ids, ncells, maxamr, nLow, celldata, data)
   @inbounds for ilvlup = ilvl:maxamr
      r = 2^(ilvlup-ilvl) # ratio on refined level
      for c in eachindex(ids)
         ixr, iyr, izr = getindexes(ilvl, ncells[1], ncells[2], nLow, ids[c]) .* r
         for k = 1:r, j = 1:r, i = 1:r
            celldata[iv][ilvlup+1][:,ixr+i,iyr+j,izr+k] .= data[:,c]
         end
      end
   end
end

function fillcell!(iv, ids, ncells, maxamr, nLow, celldata, data)
   @inbounds for i in eachindex(ids)
      ix, iy, iz = getindexes(maxamr, ncells[1], ncells[2], nLow, ids[i]) .+ 1
      celldata[iv][end][:,ix,iy,iz] .= data[:,i]
   end
end

"""
    write_vtk(meta::MetaVLSV; kwargs...)
    write_vtk(file; kwargs...)

Convert VLSV file to VTK format.
# Keyword arguments
- `vars=[""]`: select which variables to convert.
- `ascii=false`: output stored in ASCII or compressed binary format.
- `maxamronly=false`: generate image files on the highest refinement level only.
- `verbose=false`: display logs during conversion.
"""
function write_vtk(meta::MetaVLSV; vars=[""], ascii=false, maxamronly=false, verbose=false)
   @unpack ncells, maxamr, dcoord, coordmin = meta

   append = ascii ? false : true

   filedata = Vector{String}(undef, maxamr+1)
   @inbounds for i in 1:maxamr+1
      filedata[i] = meta.name[1:end-5]*"_$i.vti"
   end

   if isempty(vars[1])
      vars = meta.variable
      index_cellID = findfirst(==("CellID"), vars)
      if !isnothing(index_cellID) deleteat!(vars, index_cellID) end
   end

   data, vtkGhostType = fillmesh(meta, vars; verbose)

   if maxamronly
      save_image(meta, meta.name[1:end-4]*"vti", vars, data, vtkGhostType[end], maxamr,
         append)
   else
      # Generate image file on each refinement level
      @inbounds for i in eachindex(vtkGhostType, filedata)
         fdata, ghost = filedata[i], vtkGhostType[i]
         save_image(meta, fdata, vars, data, ghost, i-1, append)
      end

      # Generate vthb file
      filemeta = meta.name[1:end-4]*"vthb"
      doc = XMLDocument()
      elm = ElementNode("VTKFile")
      setroot!(doc, elm)
      link!(elm, AttributeNode("type", "vtkOverlappingAMR"))
      link!(elm, AttributeNode("version", "1.1"))
      link!(elm, AttributeNode("byte_order", "LittleEndian")) # x86
      link!(elm, AttributeNode("header_type", "UInt64"))

      xamr = addelement!(elm, "vtkOverlappingAMR")

      origin = @sprintf "%f %f %f" coordmin[1] coordmin[2] coordmin[3]
      link!(xamr, AttributeNode("origin", origin))
      link!(xamr, AttributeNode("grid_description", "XYZ"))

      @inbounds for i = 0:maxamr
         xBlock = addelement!(xamr, "Block")
         link!(xBlock, AttributeNode("level", string(i)))
         spacing_str = @sprintf "%f %f %f" dcoord[1]/2^i dcoord[2]/2^i dcoord[3]/2^i
         link!(xBlock, AttributeNode("spacing", spacing_str))
         xDataSet = addelement!(xBlock, "DataSet")
         link!(xDataSet, AttributeNode("index", "0"))
         amr_box = [0, ncells[1]*2^i-1, 0, ncells[2]*2^i-1, 0, ncells[3]*2^i-1]
         box_str = @sprintf("%d %d %d %d %d %d", amr_box[1], amr_box[2], amr_box[3],
            amr_box[4], amr_box[5], amr_box[6])
         link!(xDataSet, AttributeNode("amr_box", box_str))
         link!(xDataSet, AttributeNode("file", filedata[i+1]))
      end

      write(filemeta, doc)
   end

   return
end

write_vtk(file; kwargs...) = write_vtk(load(file); kwargs...)

"""
    save_image(meta::MetaVLSV, file, vars, data, vtkGhostType, level,
       ascii=false, append=true)

Save `data` of name `vars` at AMR `level` into VTK image file of name `file`.
# Arguments
- `file::String`: output file name.
- `vars::Vector{String}`: variable names to be saved.
- `data::Vector{Vector}`: data for all the variables on each refinement level.
- `vtkGhostType::Array{UInt8}`: array for visibility control.
- `level::Int`: refinement level (0-based).
- `ascii=false`: save output in ASCII or binary format.
- `append=true`: determines whether to append data at the end of file or do in-block writing.
"""
function save_image(meta::MetaVLSV, file, vars, data, vtkGhostType, level, ascii=false,
   append=true)
   @unpack coordmin, dcoord, ncells = meta
   origin = (coordmin[1], coordmin[2], coordmin[3])
   ratio = 2^level
   spacing = (dcoord[1] / ratio, dcoord[2] / ratio, dcoord[3] / ratio)

   vtk = vtk_grid(file, ncells[1]*ratio+1, ncells[2]*ratio+1, ncells[3]*ratio+1;
      origin, spacing, append, ascii)

   @inbounds for (iv, var) in enumerate(vars)
      vtk[var, VTKCellData()] = data[iv][level+1]
   end

   vtk["vtkGhostType", VTKCellData()] = vtkGhostType

   vtk_save(vtk)
end

"""
    issame(file1, file2, tol=1e-4; verbose=false) -> Bool

Check if two VLSV files are approximately identical.
"""
function issame(f1, f2, tol::AbstractFloat=1e-4; verbose=false)
   # 1st sanity check: minimal filesize difference
   if abs(filesize(f1) - filesize(f2)) / filesize(f2) > 1e-2
      verbose && println("The sizes of files are already quite different!")
      return false
   end

   meta1 = load(f1)
   meta2 = load(f2)
   varnames = meta1.variable
   strskip = r"CellID|rank|blocks"
   deleteat!(varnames, findall(endswith(strskip), varnames))

   isIdentical = true
   for vname in varnames
      v1 = readvariable(meta1, vname)
      v2 = readvariable(meta2, vname)

      s1, s2 = sum(v1), sum(v2)
      if abs(s1 - s2) > tol * abs(s1) && abs(s1 - s2) > tol * abs(s2)
         isIdentical = false
         verbose && println("$vname is quite different!")
         break
      end
   end
   verbose && isIdentical && println("$f1 and $f2 are identical under tolerance $tol.")
   close(meta1.fid)
   close(meta2.fid)
   return isIdentical
end


text:
# NOTE:
# unlike qiskit we want to distinguish and error when
# method does not exist in certain REST API endpoint.

"""
    read_token(qiskitrc::String[=expanduser("~/.qiskit/qiskitrc")])

Read IBMQ API token from `.qiskit/qiskitrc` file. Default location is
`~/.qiskit/qiskitrc`.
"""
function read_token(qiskitrc::String=expanduser("~/.qiskit/qiskitrc"))
    conf = ConfParse(qiskitrc)
    parse_conf!(conf)
    return retrieve(conf, "ibmq", "token")
end

"""
    struct AccountInfo

    AccountInfo([token=read_token()])

Create an `AccountInfo` object from given IBMQ API token. You can
create your own token at https://quantum-computing.ibm.com/ after
login. It will look at `~/.qiskit/qiskitrc` for the API token by
default. See [`read_token`](@ref) for more information.
"""
@option struct AccountInfo
    access_token::String
    auth::AuthAPI
    service::ServiceAPI
    project::ProjectAPI
    user_info::Schema.UserInfo

    AccountInfo() = AccountInfo(read_token())

    function AccountInfo(token::String)
        auth = AuthAPI()
        response = login(auth, token)
        access_token = response["id"]
        info = user_info(auth, access_token)
        service = ServiceAPI(info["urls"]["http"])
        user_hub = first(user_hubs(service, access_token))
        project = ProjectAPI(service.endpoint, user_hub["hub"], user_hub["group"], user_hub["project"])
        new(access_token, auth, service, project, Schema.UserInfo(info))
    end
end

user_urls(x::AccountInfo) = x.user_info.urls
hubs(x::AccountInfo) = hubs(x.service, x.access_token)
user_hubs(x::AccountInfo) = user_hubs(x.service, x.access_token)

"""
    devices(x::AccountInfo)

Query available devices using given `AccountInfo`.
"""
function devices(x::AccountInfo)
    raw = devices(x.project, x.access_token)
    return Configurations.from_dict_inner.(Schema.DeviceInfo, raw)
end

@option struct StorageInfo
    maxJobSizeInBytes::Int
    uploadUrl::String
end

@option mutable struct JobInfo
    kind::String
    backend::Dict{String, String}
    userId::String
    status::String
    id::String
    # optional
    timePerStep::Maybe{Dict{String, String}} = nothing
    name::Maybe{String} = nothing
    share_level::Maybe{Int}=nothing
    tags::Maybe{Vector{String}}=nothing
    qobj::Maybe{Schema.Qobj} = nothing
    allowObjectStorage::Maybe{Bool} = nothing
    objectStorageInfo::Maybe{StorageInfo} = nothing
    deleted::Maybe{Bool} = nothing
    runMode::Maybe{String} = nothing
    creationDate::Maybe{DateTime} = nothing
    endDate::Maybe{DateTime} = nothing
    hubInfo::Maybe{Dict{String, Any}} = nothing
end

function update_job_info(info::JobInfo, new::AbstractDict)
    d = to_dict(info; exclude_nothing=true)
    merge!(d, new)
    d["qobj"] = info.qobj
    return Configurations.from_dict_inner(JobInfo, d)
end

function Configurations.convert_to_option(::Type{JobInfo}, ::Type{DateTime}, s::String)
    DateTime(s, dateformat"yyyy-mm-ddTHH:MM:SS.sZ")
end

"""
    jobs(x::AccountInfo; kw...)

Query jobs submitted using given `AccountInfo`.
"""
function jobs(x::AccountInfo; kw...)
    raw = jobs(x.project, x.access_token; kw...)
    return Configurations.from_dict_inner.(JobInfo, raw)
end

@option struct RemoteJob
    dev::String
    name::Maybe{String}=nothing
    share_level::Maybe{Int}=nothing
    tags::Maybe{Vector{String}}=nothing
end

function RemoteJob(dev::Schema.DeviceInfo, name, share_level, tags)
    RemoteJob(dev.backend_name, name, share_level, tags)
end

function create_remote_job(x::AccountInfo, job::RemoteJob)
    raw = create_remote_job(x.project, job.dev, x.access_token;
        job_name=job.name, job_share_level=job.share_level, job_tags=job.tags)

    # forward remote job info
    raw["name"] = job.name
    raw["share_level"] = job.share_level
    raw["tags"] = job.tags
    return Configurations.from_dict_inner(JobInfo, raw)
end

"""
    submit(account::AccountInfo, remote_job::RemoteJob, qobj::Schema.Qobj)

Submit a `Qobj` to remote device as a remote_job.
"""
function submit(account::AccountInfo, remote_job::RemoteJob, qobj::Schema.Qobj)
    info = create_remote_job(account, remote_job)
    job_id = info.id
    upload_url = info.objectStorageInfo.uploadUrl
    job_api = JobAPI(account.project, job_id)

    try
        put_object_storage(job_api, upload_url, qobj, account.access_token)
        response = callback_upload(job_api, account.access_token)
        return update_job_info(info, response["job"])
    catch e
        if e isa HTTP.ExceptionRequest.StatusError
            try
                cancel(job_api, account.access_token)
            catch e
                if !(e isa HTTP.ExceptionRequest.StatusError)
                    rethrow(e)
                end
            end
        else
            rethrow(e)
        end
    end
    return
end

"""
    status(account::AccountInfo, job::JobInfo)

Query the current status of `job`.
"""
function status(account::AccountInfo, job::JobInfo)
    job_api = JobAPI(account.project, job.id)
    new = status(job_api, account.access_token)
    return update_job_info(job, new)
end

"""
    results(account::AccountInfo, job::JobInfo; use_object_storage::Bool = true)

Download the results of given `job`, return `nothing` if the job status is not `COMPLETED`.
"""
function results(account::AccountInfo, job::JobInfo; use_object_storage::Bool = true)
    response = status(account, job)
    response.status == "COMPLETED" || return

    job_api = JobAPI(account.project, job.id)

    if use_object_storage
        url = result_url(job_api, account.access_token)["url"]
        result_response = get_object_storage(job_api, url, account.access_token)

        try
            callback_download(job_api, account.access_token)
        catch e
            if e isa HTTP.ExceptionRequest.StatusError
                error("An error occurred while sending download completion acknowledgement.")
            else
                rethrow(e)
            end
        end
        return Configurations.from_dict_inner(Schema.Result, result_response)
    else
        response = retreive_job_info(job_api, account.access_token)
        if haskey(response, "qObjectResult")
            return response["qObjectResult"]
        else
            error("Unexpected return value received from the server: $response")
        end
    end
end


text:
import Pkg;

Pkg.add("MLJ")

## 通过 clone 的方式安装引入
Pkg.add("mocha")

Pkg.add("DataFrames")

Pkg.add("Plots")

Pkg.add("Flux")

Pkg.add("ECharts")

max_stars_repo_name: huangzhen/aquila

max_issues_repo_name: huangzhen/aquila

max_forks_repo_name: huangzhen/aquila


text:
# Autogenerated wrapper script for SymEngine_jll for x86_64-apple-darwin
export libsymengine

using GMP_jll
using MPFR_jll
using MPC_jll
JLLWrappers.@generate_wrapper_header("SymEngine")
JLLWrappers.@declare_library_product(libsymengine, "@rpath/libsymengine.0.8.dylib")
function __init__()
    JLLWrappers.@generate_init_header(GMP_jll, MPFR_jll, MPC_jll)
    JLLWrappers.@init_library_product(
        libsymengine,
        "lib/libsymengine.0.8.1.dylib",
        RTLD_LAZY | RTLD_DEEPBIND,
    )

    JLLWrappers.@generate_init_footer()
end  # __init__()