text:
# grouping method from Kaukoranta, Fränti, Nevlainen, "Reduced comparison for the exact GLA"

module KMeansPNNSmoothing

using Random
using Statistics
using StatsBase
using ExtractMacro

export kmeans,
       KMeansSeeder, KMMetaSeeder,
       KMUnif, KMMaxMin, KMScala, KMPlusPlus, KMPNN,
       KMPNNS, KMPNNSR, KMRefine

mutable struct Configuration
    m::Int
    k::Int
    n::Int
    c::Vector{Int}
    cost::Float64
    costs::Vector{Float64}
    centroids::Matrix{Float64}
    active::BitVector
    nonempty::BitVector
    csizes::Vector{Int}
    function Configuration(m::Int, k::Int, n::Int, c::Vector{Int}, costs::Vector{Float64}, centroids::Matrix{Float64})
        @assert length(c) == n
        @assert length(costs) == n
        @assert size(centroids) == (m, k)
        cost = sum(costs)
        active = trues(k)
        nonempty = trues(k)
        csizes = zeros(Int, k)
        if !all(c .== 0)
            for i = 1:n
                csizes[c[i]] += 1
            end
            nonempty .= csizes .> 0
        end
        return new(m, k, n, c, cost, costs, centroids, active, nonempty, csizes)
    end
    function Base.copy(config::Configuration)
        @extract config : m k n c cost costs centroids active nonempty csizes
        return new(m, k, n, copy(c), cost, copy(costs), copy(centroids), copy(active), copy(nonempty), copy(csizes))
    end
end

function Configuration(data::Matrix{Float64}, centroids::Matrix{Float64}, w::Union{Vector{<:Real},Nothing} = nothing)
    m, n = size(data)
    k = size(centroids, 2)
    @assert size(centroids, 1) == m

    c = zeros(Int, n)
    costs = fill(Inf, n)
    config = Configuration(m, k, n, c, costs, centroids)
    partition_from_centroids!(config, data, w)
    return config
end

function remove_empty!(config::Configuration)
    @extract config: m k n c costs centroids active nonempty csizes

    k_new = sum(nonempty)
    k_new == k && return config
    centroids = centroids[:, nonempty]
    new_inds = cumsum(nonempty)
    for i = 1:n
        @assert nonempty[c[i]]
        c[i] = new_inds[c[i]]
    end
    csizes = csizes[nonempty]
    nonempty = trues(k_new)
    active = trues(k_new)

    config.k = k_new
    config.centroids = centroids
    config.csizes = csizes
    config.nonempty = nonempty
    config.active = active

    return config
end

Base.@propagate_inbounds function _cost(d1, d2)
    v1 = 0.0
    @simd for l = 1:length(d1)
        v1 += (d1[l] - d2[l])^2
    end
    return v1
end

function partition_from_centroids!(config::Configuration, data::Matrix{Float64}, w::Union{Vector{<:Real},Nothing} = nothing)
    @extract config: m k n c costs centroids active nonempty csizes
    @assert size(data) == (m, n)

    active_inds = findall(active)
    all_inds = collect(1:k)

    fill!(nonempty, false)
    fill!(csizes, 0)

    num_fullsearch_th = zeros(Int, Threads.nthreads())

    Threads.@threads for i in 1:n
        @inbounds begin
            ci = c[i]
            wi = w ≡ nothing ? 1 : w[i]
            if ci > 0 && active[ci]
                old_v′ = costs[i]
                @views new_v′ = wi * _cost(data[:,i], centroids[:,ci])
                fullsearch = (new_v′ > old_v′)
            else
                fullsearch = (ci == 0)
            end
            num_fullsearch_th[Threads.threadid()] += fullsearch

            v, x, inds = fullsearch ? (Inf, 0, all_inds) : (costs[i], ci, active_inds)
            for j in inds
                @views v′ = wi * _cost(data[:,i], centroids[:,j])
                if v′ < v
                    v, x = v′, j
                end
            end
            costs[i], c[i] = v, x
        end
    end
    cost = sum(costs)
    for i in 1:n
        ci = c[i]
        csizes[ci] += 1
        nonempty[ci] = true
    end
    num_fullsearch = sum(num_fullsearch_th)

    config.cost = cost
    return config
end

let centroidsdict = Dict{NTuple{3,Int},Matrix{Float64}}(),
    centroidsthrdict = Dict{NTuple{3,Int},Vector{Matrix{Float64}}}(),
    zsdict = Dict{NTuple{2,Int},Vector{Float64}}(),
    zsthrdict = Dict{NTuple{2,Int},Vector{Vector{Float64}}}()

    global function centroids_from_partition!(config::Configuration, data::Matrix{Float64}, w::Union{AbstractVector{<:Real},Nothing})
        @extract config: m k n c costs centroids active nonempty csizes
        @assert size(data) == (m, n)

        new_centroids = get!(centroidsdict, (Threads.threadid(),m,k)) do
            zeros(Float64, m, k)
        end
        new_centroids_thr = get!(centroidsthrdict, (Threads.threadid(),m,k)) do
            [zeros(Float64, m, k) for id in 1:Threads.nthreads()]
        end
        zs = get!(zsdict, (Threads.threadid(),k)) do
            zeros(Float64, k)
        end
        zs_thr = get!(zsthrdict, (Threads.threadid(),k)) do
            [zeros(Float64, k) for id in 1:Threads.nthreads()]
        end

        foreach(nc_thr->fill!(nc_thr, 0.0), new_centroids_thr)
        foreach(z->fill!(z, 0.0), zs_thr)
        Threads.@threads for i = 1:n
            @inbounds begin
                j = c[i]
                wi = w ≡ nothing ? 1 : w[i]
                id = Threads.threadid()
                nc = new_centroids_thr[id]
                for l = 1:m
                    nc[l,j] += wi * data[l,i]
                end
                zs_thr[id][j] += wi
            end
        end
        fill!(new_centroids, 0.0)
        for nc_thr in new_centroids_thr
            new_centroids .+= nc_thr
        end
        zs = zeros(k)
        for zz in zs_thr
            zs .+= zz
        end
        fill!(active, false)
        @inbounds for j = 1:k
            z = zs[j]
            z > 0 || continue
            @assert nonempty[j]
            for l = 1:m
                new_centroids[l,j] /= z
                new_centroids[l,j] ≠ centroids[l,j] && (active[j] = true)
                centroids[l,j] = new_centroids[l,j]
            end
        end
        return config
    end

    global function clear_cache!()
        empty!(centroidsdict)
        empty!(centroidsthrdict)
        empty!(zsdict)
        empty!(zsthrdict)
    end
end

function check_empty!(config::Configuration, data::Matrix{Float64})
    @extract config: m k n c costs centroids active nonempty csizes
    num_nonempty = sum(nonempty)
    num_centroids = min(config.n, config.k)
    gap = num_centroids - num_nonempty
    gap == 0 && return false
    to_fill = findall(.~(nonempty))[1:gap]
    for j in to_fill
        local i::Int
        while true
            i = rand(1:n)
            csizes[c[i]] > 1 && break
        end
        ci = c[i]
        z = csizes[ci]
        datai = @view data[:,i]
        y = @view centroids[:,ci]
        centroids[:,ci] .= (z .* y - datai) ./ (z - 1)
        csizes[ci] -= 1
        config.cost -= costs[i]
        centroids[:,j] .= data[:,i]
        c[i] = j
        csizes[j] = 1
        nonempty[j] = true
        active[j] = true
        costs[i] = 0.0
    end
    return true
end

"""
A `KMeansSeeder` object is used to specify the seeding algorithm.
Currently the following basic objects are defined:

* `KMUnif`: sample centroids uniformly at random from the dataset, without replacement
* `KMPlusPlus`: kmeans++ by Arthur and Vassilvitskii (2006)
* `KMMaxMin`: furthest-point heuristic, by Katsavounidis et al. (1994)
* `KMScala`: kmeans‖ also called "scalable kmeans++", by Bahmani et al. (2012)
* `KMPNN`: pairwise nearest-neighbor hierarchical clustering, by Equitz (1989)

There are also meta-methods, whose parent type is `KMMetaSeeder`,

* `KMPNNS`: the PNN-smoothing meta-method
* `KMRefine`: the refine meta-method by Bradely and Fayyad (1998)

For each of these object, there is a corresponding implementation of
`init_centroids(::KMeansSeeder, data, k)`, which concretely performs the initialization.

The documentation of each method explains the arguments that can be passed to
control the initialization process.
"""
abstract type KMeansSeeder end

"""
    KMUnif()

The most basic `KMeansSeeder`: sample centroids uniformly at random from the
dataset, without replacement.
"""
struct KMUnif <: KMeansSeeder
end

"""
    KMPlusPlus()
    KMPlusPlus{NC}()

A `KMeansSeeder` that uses kmeans++. The parameter `NC` determines the number of
candidates for the "greedy" version of the algorithm. By default, it is `nothing`,
meaning that it will use the value `log(2+k)`. Otherwise it must be an integer.
The value `1` corresponds to the original (non-greedy) algorithm, which has a
specialized implementation.
"""
struct KMPlusPlus{NC} <: KMeansSeeder
end

KMPlusPlus() = KMPlusPlus{nothing}()

"""
    KMMaxMin()

A `KMeansSeeder` for the furthest-point heuristic, also called maxmin.
"""
struct KMMaxMin <: KMeansSeeder
end

"""
    KMScala(;rounds=5, ϕ=2.0)

A `KMeansSeeder` for "scalable kmeans++" or kmeans‖. The `rounds` option
determines the number of sampling rounds; the `ϕ` option determines the
oversampling factor, which is then computed as `ϕ * k`.
"""
struct KMScala <: KMeansSeeder
    rounds::Int
    ϕ::Float64
    KMScala(;rounds = 5, ϕ = 2.0) = new(rounds, ϕ)
end

"""
    KMPNN()

A `KMeansSeeder` for the pairwise nearest-neighbor hierarchical clustering
method. Note that this scales somewhere between the square and the cube of the number
of points in the dataset.
"""
struct KMPNN <: KMeansSeeder
end

"""
A `KMMetaSeeder{S0<:KMeansSeeder}` object is a sub-type of `KMeansSeeder` representing a meta-method,
using an object of type `S0` as an internal seeder.
"""
abstract type KMMetaSeeder{S0<:KMeansSeeder} <: KMeansSeeder end

struct _KMSelf <: KMeansSeeder
end

"""
    KMPNNS(init0=KMPlusPlus{1}(); ρ=0.5, rlevel=1)

A `KMMetaSeeder` to use the PNN-smoothing algorithm. The inner method `init0` can be any
`KMeansSeeder`. The argument `ρ` sets the number of sub-sets, using the formula ``⌈√(ρ N / k)⌉``
where ``N`` is the number of data points and ``k`` the number of clusters, but the result is
clamped between `1` and `N÷k`. The argument `rlevel` sets the recursion level.

See `KMPNNSR` for the fully-recursive version.
"""
struct KMPNNS{S<:KMeansSeeder} <: KMMetaSeeder{S}
    init0::S
    ρ::Float64
end
function KMPNNS(init0::S = KMPlusPlus{1}(); ρ = 0.5, rlevel::Int = 1) where {S <: KMeansSeeder}
    @assert rlevel ≥ 1
    kmseeder = init0
    for r = rlevel:-1:1
        kmseeder = KMPNNS{typeof(kmseeder)}(kmseeder, ρ)
    end
    return kmseeder
end

const KMPNNSR = KMPNNS{_KMSelf}

"""
    KMPNNSR(;ρ=0.5)

The fully-recursive version of the `KMPNNS` seeder. It keeps splitting the dataset until the
number of points is ``≤2k``, at which point it uses `KMPNN`. The `ρ` option is documented in `KMPNNS`.
"""
KMPNNSR(;ρ = 0.5) = KMPNNS{_KMSelf}(_KMSelf(), ρ)

"""
    KMRefine(init0=KMPlusPlus{1}(); J=10, rlevel=1)

A `KMMetaSeeder` to use the "refine" algorithm. The inner method `init0` can be any
`KMeansSeeder`. The argument `J` sets the number of sub-sets. The argument `rlevel` sets the
recursion level.
"""
struct KMRefine{S<:KMeansSeeder} <: KMMetaSeeder{S}
    init0::S
    J::Int
end
function KMRefine(init0::S = KMPlusPlus{1}(); J = 10, rlevel::Int = 1) where {S <: KMeansSeeder}
    @assert rlevel ≥ 1
    kmseeder = init0
    for r = rlevel:-1:1
        kmseeder = KMRefine{typeof(kmseeder)}(kmseeder, J)
    end
    return kmseeder
end

function init_centroids(::KMUnif, data::Matrix{Float64}, k::Int; kw...)
    m, n = size(data)
    # NOTE: the sampling uses Fisher-Yates when n < 24k, which is O(n+k);
    #       or self-avoid-sample (keep a set, resample if collisions happen)
    #       otherwise, which is O(k)
    centroid_inds = sample(1:n, k; replace = false)
    centroids = zeros(m, k)
    for j = 1:k
        # i = rand(1:n)
        i = centroid_inds[j]
        centroids[:,j] .= data[:,i]
    end
    return Configuration(data, centroids)
end

function compute_costs_one!(costs::Vector{Float64}, data::AbstractMatrix{<:Float64}, x::AbstractVector{<:Float64}, w::Nothing = nothing)
    m, n = size(data)
    @assert length(costs) == n
    @assert length(x) == m

    Threads.@threads for i = 1:n
        @inbounds @views costs[i] = _cost(data[:,i], x)
    end
    return costs
end

function compute_costs_one!(costs::Vector{Float64}, data::AbstractMatrix{<:Float64}, x::AbstractVector{<:Float64}, w::AbstractVector{<:Real})
    m, n = size(data)
    @assert length(costs) == n
    @assert length(x) == m
    @assert length(w) == n

    Threads.@threads for i = 1:n
        @inbounds @views costs[i] = w[i] * _cost(data[:,i], x)
    end
    return costs
end
compute_costs_one(data::AbstractMatrix{<:Float64}, args...) = compute_costs_one!(Array{Float64}(undef,size(data,2)), data, args...)

init_centroids(::KMPlusPlus{nothing}, data::Matrix{Float64}, k::Int; kw...) =
    init_centroids(KMPlusPlus{floor(Int, 2 + log(k))}(), data, k; kw...)

function init_centroids(::KMPlusPlus{NC}, data::Matrix{Float64}, k::Int; w = nothing) where NC
    m, n = size(data)
    @assert n ≥ k

    ncandidates::Int = NC

    centr = zeros(m, k)
    y = (w ≡ nothing ? rand(1:n) : sample(1:n, Weights(w)))
    datay = data[:,y]
    centr[:,1] = datay

    costs = compute_costs_one(data, datay, w)

    curr_cost = sum(costs)
    c = ones(Int, n)

    new_costs, new_c = similar(costs), similar(c)
    new_costs_best, new_c_best = similar(costs), similar(c)
    for j = 2:k
        pw = Weights(w ≡ nothing ? costs : costs .* w)
        nonz = count(pw .≠ 0)
        candidates = sample(1:n, pw, min(ncandidates,n,nonz), replace = false)
        cost_best = Inf
        y_best = 0
        for y in candidates
            datay = data[:,y]
            compute_costs_one!(new_costs, data, datay, w)
            cost = 0.0
            @inbounds for i = 1:n
                v = new_costs[i]
                v′ = costs[i]
                if v < v′
                    new_c[i] = j
                    cost += v
                else
                    new_costs[i] = v′
                    new_c[i] = c[i]
                    cost += v′
                end
            end
            if cost < cost_best
                cost_best = cost
                y_best = y
                new_costs_best, new_costs = new_costs, new_costs_best
                new_c_best, new_c = new_c, new_c_best
            end
        end
        @assert y_best ≠ 0 && cost_best < Inf
        datay = data[:,y_best]
        centr[:,j] .= datay
        costs, new_costs_best = new_costs_best, costs
        c, new_c_best = new_c_best, c
    end
    return Configuration(m, k, n, c, costs, centr)
end

function update_costs_one!(costs::Vector{Float64}, c::Vector{Int}, j::Int, data::AbstractMatrix{<:Float64}, x::AbstractVector{<:Float64}, w::Nothing = nothing)
    m, n = size(data)
    @assert length(costs) == n
    @assert length(c) == n
    @assert length(x) == m

    Threads.@threads for i = 1:n
        @inbounds begin
            old_v = costs[i]
            new_v = _cost(@view(data[:,i]), x)
            if new_v < old_v
                costs[i] = new_v
                c[i] = j
            end
        end
    end
    return costs
end

function update_costs_one!(costs::Vector{Float64}, c::Vector{Int}, j::Int, data::AbstractMatrix{<:Float64}, x::AbstractVector{<:Float64}, w::AbstractVector{<:Real})
    m, n = size(data)
    @assert length(costs) == n
    @assert length(c) == n
    @assert length(x) == m

    Threads.@threads for i = 1:n
        @inbounds begin
            old_v = costs[i]
            new_v = w[i] * _cost(@view(data[:,i]), x)
            if new_v < old_v
                costs[i] = new_v
                c[i] = j
            end
        end
    end
    return costs
end

function init_centroids(::KMPlusPlus{1}, data::Matrix{Float64}, k::Int; w = nothing)
    m, n = size(data)
    @assert n ≥ k

    centr = zeros(m, k)
    y = (w ≡ nothing ? rand(1:n) : sample(1:n, Weights(w)))
    datay = data[:,y]
    centr[:,1] = datay

    costs = compute_costs_one(data, datay, w)

    c = ones(Int, n)

    for j = 2:k
        pw = Weights(w ≡ nothing ? costs : costs .* w)
        y = sample(1:n, pw)
        datay = data[:,y]

        update_costs_one!(costs, c, j, data, datay, w)

        centr[:,j] .= datay
    end
    return Configuration(m, k, n, c, costs, centr)
end

function init_centroids(::KMMaxMin, data::Matrix{Float64}, k::Int; kw...)
    m, n = size(data)
    @assert n ≥ k

    centr = zeros(m, k)
    y = rand(1:n)
    datay = data[:,y]
    centr[:,1] = datay

    costs = compute_costs_one(data, datay)

    c = ones(Int, n)

    for j = 2:k
        y = argmax(costs)
        datay = data[:,y]

        update_costs_one!(costs, c, j, data, datay)

        centr[:,j] .= datay
    end
    return Configuration(m, k, n, c, costs, centr)
end

Base.@propagate_inbounds function _merge_cost(centroids::AbstractMatrix{<:Float64}, z::Int, z′::Int, j::Int, j′::Int)
    return @views (z * z′) / (z + z′) * _cost(centroids[:,j], centroids[:,j′])
end

function _get_nns(vs, j, k, centroids, csizes)
    k < 500 && return _get_nns(j, k, centroids, csizes)
    z = csizes[j]
    fill!(vs, (Inf, 0))
    Threads.@threads for j′ = 1:k
        j′ == j && continue
        @inbounds begin
            v1 = _merge_cost(centroids, z, csizes[j′], j, j′)
            id = Threads.threadid()
            if v1 < vs[id][1]
                vs[id] = v1, j′
            end
        end
    end
    v, x = Inf, 0
    for id in 1:Threads.nthreads()
        if vs[id][1] < v
            v, x = vs[id]
        end
    end
    return v, x
end

function _get_nns(j, k, centroids, csizes)
    z = csizes[j]
    v, x = Inf, 0
    @inbounds for j′ = 1:k
        j′ == j && continue
        z′ = csizes[j′]
        v1 = _merge_cost(centroids, z, csizes[j′], j, j′)
        if v1 < v
            v, x = v1, j′
        end
    end
    return v, x
end

function pairwise_nn!(config::Configuration, tgt_k::Int)
    @extract config : m k n centroids csizes
    if k < tgt_k
        @assert false # TODO: inflate the config? this shouldn't normally happen
    end
    k == tgt_k && return config

    # csizes′ = zeros(Int, k)
    # for i = 1:n
    #     csizes′[c[i]] += 1
    # end
    # @assert all(csizes′ .> 0)
    # @assert csizes == csizes′
    nns = zeros(Int, k)
    nns_costs = fill(Inf, k)
    vs = Threads.resize_nthreads!(Tuple{Float64,Int}[], (Inf, 0))
    @inbounds for j = 1:k
        nns_costs[j], nns[j] = _get_nns(vs, j, k, centroids, csizes)
    end

    @inbounds while k > tgt_k
        jm = findmin(@view(nns_costs[1:k]))[2]
        js = nns[jm]
        @assert nns_costs[js] == nns_costs[jm]
        @assert jm < js

        # update centroid
        zm, zs = csizes[jm], csizes[js]
        for l = 1:m
            centroids[l,jm] = (zm * centroids[l,jm] + zs * centroids[l,js]) / (zm + zs)
            centroids[l,js] = centroids[l,k]
        end

        # update csizes
        csizes[jm] += zs
        csizes[js] = csizes[k]
        csizes[k] = 0

        # update partition
        # not needed since we don't use c in this computation and
        # we call partition_from_centroids! right after this function
        # for i = 1:n
        #     ci = c[i]
        #     if ci == js
        #         c[i] = jm
        #     elseif ci == k
        #         c[i] = js
        #     end
        # end

        # update nns
        nns[js] = nns[k]
        nns[k] = 0
        nns_costs[js] = nns_costs[k]
        nns_costs[k] = Inf

        num_fullupdates = 0
        for j = 1:(k-1)
            # 1) merged cluster jm, or clusters which used to point to either jm or js
            #    perform a full update
            if j == jm || nns[j] == jm || nns[j] == js
                num_fullupdates += 1
                nns_costs[j], nns[j] = _get_nns(vs, j, k-1, centroids, csizes)
            # 2) clusters that did not point to jm or js
            #    only compare the old cost with the cost for the updated cluster
            else
                z = csizes[j]
                # note: what used to point at k now must point at js
                v, x = nns_costs[j], (nns[j] ≠ k ? nns[j] : js)
                j′ = jm
                z′ = csizes[j′]
                v′ = _merge_cost(centroids, z, z′, j, j′)
                if v′ < v
                    v, x = v′, j′
                end
                nns_costs[j], nns[j] = v, x
                @assert nns[j] ≠ j
            end
        end

        k -= 1
    end

    config.k = k
    config.centroids = centroids[:,1:k]
    config.csizes = csizes[1:k]
    @assert all(config.csizes .> 0)
    config.active = trues(k)
    config.nonempty = trues(k)
    fill!(config.c, 0) # reset in order for partition_from_centroids! to work
    return
end

function inner_init(S::KMPNNSR, data::Matrix{Float64}, k::Int)
    m, n = size(data)
    if n ≤ 2k
        return init_centroids(KMPNN(), data, k)
    else
        return init_centroids(S, data, k)
    end
end

inner_init(S::KMMetaSeeder{S0}, data::Matrix{Float64}, k::Int) where S0 = init_centroids(S.init0, data, k)

function init_centroids(S::KMPNNS{S0}, data::Matrix{Float64}, k::Int; kw...) where S0
    @extract S : ρ
    m, n = size(data)
    J = clamp(ceil(Int, √(ρ * n / k)), 1, n ÷ k)
    @assert J * k ≤ n
    # (J == 1 || J == n ÷ k) && @warn "edge case: J = $J"

    split = shuffle!(vcat((repeat([a], k) for a = 1:J)..., rand(1:J, (n - k*J))))
    @assert all(sum(split .== a) ≥ k for a = 1:J)
    configs = Vector{Configuration}(undef, J)
    Threads.@threads for a = 1:J
        rdata = data[:,split .== a]
        config = inner_init(S, rdata, k)
        lloyd!(config, rdata, 1_000, 1e-4, false)
        configs[a] = config
    end

    centroids_new = hcat((config.centroids for config in configs)...)
    c_new = zeros(Int, n)
    costs_new = zeros(n)
    inds = zeros(Int, J)
    for i = 1:n
        a = split[i]
        inds[a] += 1
        c_new[i] = configs[a].c[inds[a]] + k * (a-1)
        costs_new[i] = configs[a].costs[inds[a]]
    end
    mconfig = Configuration(m, k * J, n, c_new, costs_new, centroids_new)
    pairwise_nn!(mconfig, k)
    partition_from_centroids!(mconfig, data)
    return mconfig
end

function init_centroids(::KMPNN, data::Matrix{Float64}, k::Int; kw...)
    m, n = size(data)

    centroids = copy(data)
    c = collect(1:n)
    costs = zeros(n)
    config = Configuration(m, n, n, c, costs, centroids)
    pairwise_nn!(config, k)
    partition_from_centroids!(config, data)
    return config
end

function init_centroids(S::KMRefine{S0}, data::Matrix{Float64}, k::Int; kw...) where S0
    @extract S : J
    m, n = size(data)
    @assert J * k ≤ n
    split = shuffle!(vcat((repeat([a], k) for a = 1:J)..., rand(1:J, (n - k*J))))
    @assert all(sum(split .== a) ≥ k for a = 1:J)
    configs = Vector{Configuration}(undef, J)
    Threads.@threads for a = 1:J
        rdata = data[:,split .== a]
        config = inner_init(S, rdata, k)
        lloyd!(config, rdata, 1_000, 1e-4, false)
        configs[a] = config
    end
    pool = hcat((configs[a].centroids for a in 1:J)...)
    for a = 1:J
        config = Configuration(pool, configs[a].centroids)
        lloyd!(config, pool, 1_000, 1e-4, false)
        configs[a] = config
    end
    a_best = argmin([configs[a].cost for a in 1:J])
    return Configuration(data, configs[a_best].centroids)
end

function init_centroids(S::KMScala, data::Matrix{Float64}, k::Int; kw...)
    @extract S : rounds ϕ
    m, n = size(data)
    @assert n ≥ k

    centr = zeros(m, 1)
    y = rand(1:n)
    datay = data[:,y]
    centr[:,1] = datay

    costs = compute_costs_one(data, datay)

    cost = sum(costs)
    c = ones(Int, n)

    k′ = 1
    for r in 1:rounds
        w = (ϕ * k) / cost .* costs
        add_inds = findall(rand(n) .< w)
        add_k = length(add_inds)
        add_centr = data[:,add_inds]
        Threads.@threads for i in 1:n
            @inbounds begin
                v, x = costs[i], c[i]
                for j in 1:add_k
                    @views v′ = _cost(data[:,i], add_centr[:,j])
                    if v′ < v
                        v, x = v′, k′ + j
                    end
                end
                costs[i], c[i] = v, x
            end
        end
        cost = sum(costs)
        centr = hcat(centr, add_centr)
        k′ += add_k
    end
    @assert k′ ≥ k
    z = zeros(k′)
    for i = 1:n
        z[c[i]] += 1
    end
    # @assert all(z .> 0)
    cconfig = init_centroids(KMPlusPlus{1}(), centr, k; w=z)
    lloyd!(cconfig, centr, 1_000, 1e-4, false, z)
    return Configuration(data, cconfig.centroids)
end

function lloyd!(
        config::Configuration,
        data::Matrix{Float64},
        max_it::Int,
        tol::Float64,
        verbose::Bool,
        w::Union{Vector{<:Real},Nothing} = nothing
    )
    cost0 = config.cost
    converged = false
    it = 0
    for outer it = 1:max_it
        centroids_from_partition!(config, data, w)
        old_cost = config.cost
        found_empty = check_empty!(config, data)
        partition_from_centroids!(config, data, w)
        new_cost = config.cost
        if new_cost ≥ old_cost * (1 - tol) && !found_empty
            verbose && println("converged cost = $new_cost")
            converged = true
            break
        end
        verbose && println("lloyd it = $it cost = $new_cost")
    end
    return converged
end

struct Results
    exit_status::Symbol
    labels::Vector{Int}
    centroids::Matrix{Float64}
    cost::Float64
end

Results(exit_status, config::Configuration) = Results(exit_status, config.c, config.centroids, config.cost)

"""
  kmeans(data::Matrix{Float64}, k::Integer; keywords...)

Runs k-means using Lloyd's algorithm on the given data matrix (if the size is `d`×`n` then `d` is
the dimension and `n` the number of points, i.e. data is organized by column).

It returns an object of type `Results`, which contains the following fields:
* exit_status: a symbol that indicates the reason why the algorithm stopped. It can take two
  values, `:converged` or `:maxiters`.
* labels: a vector of labels (`n` integers from 1 to `k`)
* centroids: a `d`×`k` matrix of centroids
* cost: the final cost

The keyword arguments are:

* `max_it`: maximum number of iterations (default=1000). Normally the algorithm stops at fixed
  points.
* `seed`: random seed, either an integer or `nothing` (this is the default, it means no seeding
  is performed).
* `kmseeder`: seeder for the initial configuration (default=`KMPNNS()`). It can be a `KMeansSeeder`
  or a `Matrix{Float64}`. If it's a matrix, it represents the initial centroids (by column).
  See the documentation for `KMeansSeeder` for a list of available seeding algorithms and their
  options.
* `tol`: a `Float64`, relative tolerance for detecting convergence (default=1e-5).
* `verbose`: a `Bool`; if `true` (the default) it prints information on screen.
"""
function kmeans(
        data::Matrix{Float64}, k::Integer;
        max_it::Integer = 1000,
        seed::Union{Integer,Nothing} = nothing,
        kmseeder::Union{KMeansSeeder,Matrix{Float64}} = KMPNNS(),
        verbose::Bool = true,
        tol::Float64 = 1e-5,
    )

    if seed ≢ nothing
        Random.seed!(seed)
        if VERSION < v"1.7-"
            Threads.@threads for h = 1:Threads.nthreads()
                Random.seed!(seed + h)
            end
        end
    end
    m, n = size(data)

    if kmseeder isa KMeansSeeder
        config = init_centroids(kmseeder, data, k)
    else
        centroids = kmseeder
        size(centroids) == (m, k) || throw(ArgumentError("Incompatible kmseeder and data dimensions, data=$((m,k)) kmseeder=$(size(centroids))"))
        config = Configuration(data, centroids)
    end

    verbose && println("initial cost = $(config.cost)")
    converged = lloyd!(config, data, max_it, tol, verbose)

    exit_status = converged ? :converged : :maxiters

    clear_cache!()

    return Results(exit_status, config)
end

function gen_seeder(
        init::AbstractString = "pnns"
        ;
        init0::AbstractString = "",
        ρ::Float64 = 0.5,
        ncandidates::Union{Nothing,Int} = nothing,
        J::Int = 10,
        rlevel::Int = 1,
        rounds::Int = 5,
        ϕ::Float64 = 2.0,
    )
    all_basic_methods = ["++", "unif", "pnn", "maxmin", "scala"]
    all_rec_methods = ["refine", "pnns"]
    all_methods = [all_basic_methods; all_rec_methods]
    init ∈ all_methods || throw(ArgumentError("init should either be a matrix or one of: $all_methods"))
    if init ∈ all_rec_methods
        if init0 == ""
            init0="++"
            ncandidates ≡ nothing && (ncandidates = 1)
        end
        if init0 ∈ all_basic_methods
            init == "pnns" && rlevel < 1 && throw(ArgumentError("when init=$init and init0=$init0 rlevel must be ≥ 1"))
        elseif init0 == "self"
            init == "pnns" || throw(ArgumentError("init0=$init0 unsupported with init=$init"))
            rlevel = 0
        else
            throw(ArgumentError("when init=$init, init0 should be \"self\" or one of: $all_basic_methods"))
        end
    else
        init0 == "" || @warn("Ignoring init0=$init0 with init=$init")
    end

    if init ∈ all_basic_methods
        return init == "++"    ? KMPlusPlus{ncandidates}() :
               init == "unif"   ? KMUnif() :
               init == "pnn"    ? KMPNN() :
               init == "maxmin" ? KMMaxMin() :
               init == "scala"  ? KMScala(J, ϕ) :
               error("wat")
    elseif init == "pnns" && init0 == "self"
        @assert rlevel == 0
        return KMPNNSR(;ρ)
    else
        @assert rlevel ≥ 1
        kmseeder0 = init0 == "++"     ? KMPlusPlus{ncandidates}() :
                    init0 == "unif"   ? KMUnif() :
                    init0 == "pnn"    ? KMPNN() :
                    init0 == "maxmin" ? KMMaxMin() :
                    init0 == "scala"  ? KMScala(J, ϕ) :
                    error("wat")

        return init == "pnns"   ? KMPNNS(kmseeder0; ρ, rlevel) :
               init == "refine" ? KMRefine(kmseeder0; J, rlevel) :
               error("wut")
    end
end

# Centroid Index
# P. Fränti, M. Rezaei and Q. Zhao, Centroid index: cluster level similarity measure, Pattern Recognition, 2014
function CI(true_centroids::Matrix{Float64}, centroids::Matrix{Float64})
    m, tk = size(true_centroids)
    @assert size(centroids, 1) == m
    k = size(centroids, 2)

    matched = falses(tk)
    @inbounds for j = 1:k
        v = Inf
        p = 0
        for tj = 1:tk
            @views v1 = _cost(true_centroids[:,tj], centroids[:,j])
            if v1 < v
                v = v1
                p = tj
            end
        end
        matched[p] = true
    end

    return tk - count(matched)
end

# CI_sym(centroids1::Matrix{Float64}, centroids2::Matrix{Float64}) =
#     max(CI(centroids1, centroids2), CI(centroids2, centroids1))

function CI_sym(centroids1::Matrix{Float64}, centroids2::Matrix{Float64})
    m, k1 = size(centroids1)
    @assert size(centroids2, 1) == m
    k2 = size(centroids2, 2)

    a12 = zeros(Int, k1)
    a21 = zeros(Int, k2)
    v12 = fill(Inf, k1)
    v21 = fill(Inf, k2)
    @inbounds for j1 = 1:k1, j2 = 1:k2
        @views v = _cost(centroids1[:,j1], centroids2[:,j2])
        if v < v12[j1]
            v12[j1] = v
            a12[j1] = j2
        end
        if v < v21[j2]
            v21[j2] = v
            a21[j2] = j1
        end
    end

    return max(k1 - length(BitSet(a12)), k2 - length(BitSet(a21)))
end

# Variation of Information
# M. Meilă, Comparing clusterings—an information based distance, Journal of multivariate analysis, 2007

xlogx(x) = ifelse(iszero(x), zero(x), x * log(x))
entropy(p) = -sum(xlogx, p)

function VI(c1::Vector{Int}, c2::Vector{Int})
    n = length(c1)
    length(c2) == n || throw(ArgumentError("partitions bust have the same length, given: $(n) and $(length(c2))"))
    a, k1 = extrema(c1)
    a ≥ 1 || throw(ArgumentError("partitions elements must be ≥ 1, found $(a)"))
    a, k2 = extrema(c2)
    a ≥ 1 || throw(ArgumentError("partitions elements must be ≥ 1, found $(a)"))
    o = zeros(k1, k2)
    o1 = zeros(k1)
    o2 = zeros(k2)
    for i = 1:n
        j1, j2 = c1[i], c2[i]
        o[j1, j2] += 1
        o1[j1] += 1
        o2[j2] += 1
    end
    o ./= n
    o1 ./= n
    o2 ./= n
    vi = 2entropy(o) - entropy(o1) - entropy(o2)
    @assert vi > -1e-12
    return max(0.0, vi)
end

end # module

max_stars_repo_name: carlobaldassi/KMeansPNNSmoothing


text:
const depfile = joinpath(dirname(@__FILE__), "..", "deps", "deps.jl")
isfile(depfile) || error("IJulia not properly installed. Please run Pkg.build(\"IJulia\")")
include(depfile) # generated by Pkg.build("IJulia")
include(joinpath("..","deps","kspec.jl"))

# use our own random seed for msg_id so that we
# don't alter the user-visible random state (issue #336)
const IJulia_RNG = srand(MersenneTwister(0))
uuid4() = repr(Base.Random.uuid4(IJulia_RNG))

const orig_STDIN  = Ref{IO}()
const orig_STDOUT = Ref{IO}()
const orig_STDERR = Ref{IO}()
function __init__()
    srand(IJulia_RNG)
    orig_STDIN[]  = STDIN
    orig_STDOUT[] = STDOUT
    orig_STDERR[] = STDERR
end

const threadid = Vector{Int}(128) # sizeof(uv_thread_t) <= 8 on Linux, OSX, Win

# the following constants need to be initialized in init().
const ctx = Ref{Context}()
const publish = Ref{Socket}()
const raw_input = Ref{Socket}()
const requests = Ref{Socket}()
const control = Ref{Socket}()
const heartbeat = Ref{Socket}()
const profile = Dict{String,Any}()
const read_stdout = Ref{Base.PipeEndpoint}()
const read_stderr = Ref{Base.PipeEndpoint}()
const socket_locks = Dict{Socket,ReentrantLock}()

function qtconsole()
    if inited
        spawn(`$jupyter qtconsole --existing $connection_file`)
    else
        error("IJulia is not running. qtconsole must be called from an IJulia session.")
    end
end

function init(args)
    inited && error("IJulia is already running")
    if length(args) > 0
        merge!(profile, open(JSON.parse,args[1]))
        verbose && println("PROFILE = $profile")
        global connection_file = args[1]
    else
        # generate profile and save
        let port0 = 5678
            merge!(profile, Dict{String,Any}(
                "ip" => "127.0.0.1",
                "transport" => "tcp",
                "stdin_port" => port0,
                "control_port" => port0+1,
                "hb_port" => port0+2,
                "shell_port" => port0+3,
                "iopub_port" => port0+4,
                "key" => uuid4()
            ))
            fname = "profile-$(getpid()).json"
            global connection_file = "$(pwd())/$fname"
            println("connect ipython with --existing $connection_file")
            open(fname, "w") do f
                JSON.print(f, profile)
            end
        end
    end

    if !isempty(profile["key"])
        signature_scheme = get(profile, "signature_scheme", "hmac-sha256")
        isempty(signature_scheme) && (signature_scheme = "hmac-sha256")
        sigschm = split(signature_scheme, "-")
        if sigschm[1] != "hmac" || length(sigschm) != 2
            error("unrecognized signature_scheme $signature_scheme")
        end
        hmacstate[] = MbedTLS.MD(getfield(MbedTLS, Symbol("MD_", uppercase(sigschm[2]))),
                                 profile["key"])
    end

    ctx[] = Context()
    publish[] = Socket(ctx[], PUB)
    raw_input[] = Socket(ctx[], ROUTER)
    requests[] = Socket(ctx[], ROUTER)
    control[] = Socket(ctx[], ROUTER)
    heartbeat[] = Socket(ctx[], ROUTER)
    bind(publish[], "$(profile["transport"])://$(profile["ip"]):$(profile["iopub_port"])")
    bind(requests[], "$(profile["transport"])://$(profile["ip"]):$(profile["shell_port"])")
    bind(control[], "$(profile["transport"])://$(profile["ip"]):$(profile["control_port"])")
    bind(raw_input[], "$(profile["transport"])://$(profile["ip"]):$(profile["stdin_port"])")
    bind(heartbeat[], "$(profile["transport"])://$(profile["ip"]):$(profile["hb_port"])")

    # associate a lock with each socket so that multi-part messages
    # on a given socket don't get inter-mingled between tasks.
    for s in (publish[], raw_input[], requests[], control[], heartbeat[])
        socket_locks[s] = ReentrantLock()
    end

    start_heartbeat(heartbeat[])
    if capture_stdout
        read_stdout[], = redirect_stdout()
        redirect_stdout(IJuliaStdio(STDOUT,"stdout"))
    end
    if capture_stderr
        read_stderr[], = redirect_stderr()
        redirect_stderr(IJuliaStdio(STDERR,"stderr"))
    end
    redirect_stdin(IJuliaStdio(STDIN,"stdin"))

    send_status("starting")
    global inited = true
end

ext: jl


text:
# note that when using the binomial loss, the treatment effect must be between -1 and 1
# TO DO:
# - Get rid of learning rate mechanic- prove that it works in the continuous case but moves us off the constraint surface with binary
# - Initialize the step seach leaf values with those computed by the trees
# - Add in a regularizer to do the job of the learning rate

__precompile__()

module CausalEffectMorph

export ObsData,
       Squared, Binomial,
       constrained_boost, cross_validate

include_dependency("data_structures.jl")
include_dependency("losses.jl")
include_dependency("estimators.jl")

include("data_structures.jl")
include("losses.jl")
include("estimators.jl")

using .Estimators
using .Losses
using .ObsDataStructures

using DataStructures
using JuMP, Ipopt

const TF = [true, false]

function fit_const_pair(data::ObsData, loss::Squared, τ::Real)
    Ȳ = mean(data.Y.Y)
    return Dict(t => Leaf(Ȳ +(2*t-1) * τ * data.N_treated[t]/data.N) for t in TF)
end

function fit_const_pair(data::ObsData, loss::Binomial, τ::Real)
    m = Model(solver=IpoptSolver(print_level=0))
    @variable(m, 0 <= πt <= 1)
    @variable(m, 0 <= πf <= 1)
    @constraint(m, πt-πf == τ)
    @NLobjective(m, Max, data.N_treated[true]*log(1-πf) +
                         data.N_treated[false]*log(πt))
    status = solve(m)
    π = Dict(true=>getvalue(πt), false=>getvalue(πf))
    return Dict(t  => Leaf(log(π[t]/(1-π[t]))) for t in TF)
end

function predict_counterfactuals{T<:Estimators.Estimator}(estimator_pair::Dict{Bool,T}, data::ObsData)
    return Counterfactuals(predict(estimator_pair, data.X.X), data.W)
end

function obs_gradient{T<:Loss}(loss::T, Y::Outcome, F::Counterfactuals; idx=:)
    return Dict(t => neg_grad(loss, Γ(Y[idx],t=t), Γ(F[idx],o=true,t=t)) for t in TF)
end

function build_leaf_index{T<:Estimators.Estimator}(tree_pair::Dict{Bool,T}, X::Covariates)
    assignments = Dict(t=>assign_leaves(tree_pair[t], X.X) for t in TF)
    aa = vcat([ls for (t,ls) in assignments]...)
    leaves = Set(vcat([ls for (t,ls) in assignments]...))
    assignment_index_matrix = [(t,l,find(assignments[t].==l)) for l in leaves, t in TF]
    assignment_index = OrderedDict((t,l)=>ind for (t,l,ind) in vec(assignment_index_matrix) if length(ind)>0)
    return assignment_index
end

function filter_index(index, include_subjects)
    filtered_index = OrderedDict()
    for (k,v) in index
        v_in = v ∩ include_subjects
        length(v_in)>0 ? filtered_index[k] = v_in : nothing
    end
    return filtered_index
end

function step_search(loss::Squared, data::ObsData, F::Counterfactuals, idx::OrderedDict, λ, τ=nothing; tr=:)
    F1 = F.treated[true]
    F0 = F.treated[false]
    Y = data.Y.Y
    W = data.W

    idx1, idx0 = OrderedDict(), OrderedDict()
    for ((t,l),i) in idx
        t ? idx1[l] = i : idx0[l] = i
    end

    idx1obs = filter_index(idx1, findin(W,1) ∩ tr)
    idx0obs = filter_index(idx0, findin(W,0) ∩ tr)

    m = Model(solver=IpoptSolver(print_level=0))
    @variable(m, ν1[1:length(idx1)])
    @variable(m, ν0[1:length(idx0)])
    @constraint(m, sum(sum(ν1[i] for j in idx1[l]) for (i,l) in enumerate(keys(idx1))) -
                   sum(sum(ν0[i] for j in idx0[l]) for (i,l) in enumerate(keys(idx0))) == 0)
    @objective(m, Min,
                   sum(sum((Y[j] - F1[j] - ν1[i])^2 for j in idx1obs[l]) for (i,l) in enumerate(keys(idx1obs))) +
                   sum(sum((Y[j] - F0[j] - ν0[i])^2 for j in idx0obs[l]) for (i,l) in enumerate(keys(idx0obs))) +
                   sum(sum(λ*ν1[i]^2 for j in idx1[l]) for (i,l) in enumerate(keys(idx1))) +
                   sum(sum(λ*ν0[i]^2 for j in idx0[l]) for (i,l) in enumerate(keys(idx0))))
    status = solve(m)
    νopt1 = getvalue(ν1)
    νopt0 = getvalue(ν0) # this is fucked. even when I add the learning rate back it's still broken...
    return Dict(true => Dict(l=>νi for (l,νi) in zip(keys(idx1),νopt1)),
                false => Dict(l=>νi for (l,νi) in zip(keys(idx0),νopt0)))
end

function step_search(loss::Binomial, data::ObsData, F::Counterfactuals, idx::OrderedDict, λ, τ; tr=:)
    F1 = F.treated[true]
    F0 = F.treated[false]
    Y = data.Y.Y
    W = data.W

    idx1, idx0 = OrderedDict(), OrderedDict()
    for ((t,l),i) in idx
        t ? idx1[l] = i : idx0[l] = i
    end

    idx1obs = filter_index(idx1, findin(W,1) ∩ tr)
    idx0obs = filter_index(idx0, findin(W,0) ∩ tr)

    m = Model(solver=IpoptSolver(print_level=0))
    @variable(m, ν1[1:length(idx1)])
    @variable(m, ν0[1:length(idx0)])
    @NLconstraint(m, sum(sum((1+exp(-F1[j]-ν1[i]))^(-1) for j in idx1[l]) for (i,l) in enumerate(keys(idx1))) -
                     sum(sum((1+exp(-F0[j]-ν0[i]))^(-1) for j in idx0[l]) for (i,l) in enumerate(keys(idx0))) == data.N*τ)
    @NLobjective(m, Min,
                    sum(sum(log(1+exp(F1[j]+ν1[i])) - Y[j]*(F1[j]+ν1[i]) for j in idx1obs[l]) for (i,l) in enumerate(keys(idx1obs))) +
                    sum(sum(log(1+exp(F0[j]+ν0[i])) - Y[j]*(F0[j]+ν0[i]) for j in idx0obs[l]) for (i,l) in enumerate(keys(idx0obs))) +
                    sum(sum(λ*ν1[i]^2 for j in idx1[l]) for (i,l) in enumerate(keys(idx1))) +
                    sum(sum(λ*ν0[i]^2 for j in idx0[l]) for (i,l) in enumerate(keys(idx0))))
    status = solve(m)
    νopt1 = getvalue(ν1)
    νopt0 = getvalue(ν0)
    return Dict(true => Dict(l=>νi for (l,νi) in zip(keys(idx1),νopt1)),
                false => Dict(l=>νi for (l,νi) in zip(keys(idx0),νopt0)))
end

function predict_counterfactuals(tree_pair, X::Covariates, ν)
    return Counterfactuals(Dict(t=>[ν[t][li] for li in assign_leaves(tree_pair[t], X.X)] for t in TF), X.W)
end

function constrained_boost{T<:Real, T2<:Real, L<:Loss}(data::ObsData, loss::L, τ::T, n_trees::Int;
                           max_depth::Int=3, λ::T2=2, min_samples_leaf::Int=1,
                           tr=:)
    F = Vector{Counterfactuals}(0)

    const_pair = fit_const_pair(data[tr], loss, τ)
    predicted_counterfactuals = predict_counterfactuals(const_pair, data)
    push!(F, predicted_counterfactuals)
    residuals = obs_gradient(loss, data.Y, F[end], idx=tr)

    for i in 1:n_trees
        tree_pair = Dict(t=>fit_regression_tree(Γ(data.X[tr],t=t) , residuals[t],
                                                        min_samples_leaf=min_samples_leaf,
                                                        max_depth=max_depth) for t in TF);
        idx = build_leaf_index(tree_pair, data.X)
        ν = step_search(loss, data, F[end], idx, λ, τ, tr=tr)
        push!(F, F[end]+predict_counterfactuals(tree_pair, data.X, ν))
        residuals = obs_gradient(loss, data.Y, F[end], idx=tr)
    end
    return F
end

import MLBase.StratifiedKfold

function cross_validate{T<:Real, T2<:Real, L<:Squared}(data::ObsData, loss::L, τ::T, n_trees::Int;
                           max_depth::Int=3, λ::T2=2, min_samples_leaf::Int=1,
                           nfolds::Int=5)
    training_error, test_error = Vector{Vector{Float64}}(0), Vector{Vector{Float64}}(0)
    loss == Binomial() ? folds = StratifiedKfold(data.Y.Y + 2*data.W, nfolds) : folds = StratifiedKfold(data.W, nfolds)
    for tr in folds
        println("tr")
        println(tr)
        te = [i for i in 1:data.N if i ∉ tr]
        F = constrained_boost(data, loss, τ, n_trees,
                           max_depth=max_depth, λ=λ, min_samples_leaf=min_samples_leaf,
                           tr=tr)
        push!(training_error, [sum(evaluate(loss, data.Y.Y[tr], F[n].observed[true][tr]))/length(data.Y.Y[tr]) for n in 1:n_trees+1])
        push!(test_error, [sum(evaluate(loss, data.Y.Y[te], F[n].observed[true][te]))/length(data.Y.Y[te]) for n in 1:n_trees+1])
    end
    return training_error, test_error
end

end # module

max_stars_repo_name: naskoD/bachelorThesis

max_issues_repo_name: naskoD/bachelorThesis

max_forks_repo_name: naskoD/bachelorThesis


text:
const Guard = GenericGuard{ReentrantLock}
const ReadWriteGuard = GenericReadWriteGuard{ReadWriteLock}

ConcurrentUtils.guardwith(data, lock) = GenericGuard(lock, data)
ConcurrentUtils.guardwith(data, lock::AbstractReadWriteLock) =
    GenericReadWriteGuard(lock, data)

function ConcurrentUtils.GenericGuard{Lock}(data) where {Lock}
    lock = Lock()
    return GenericGuard(lock, data)
end

function ConcurrentUtils.GenericReadWriteGuard{Lock}(data) where {Lock}
    lock = Lock()
    return GenericReadWriteGuard(lock, data)
end

function ConcurrentUtils.guarding(f!::F, g::GenericGuard) where {F}
    data = g.data
    criticalsection() = f!(data)
    lock(criticalsection, g.lock)
end

ConcurrentUtils.guarding_read(f::F, g::GenericGuard) where {F} =
    ConcurrentUtils.guarding(f, g)

function ConcurrentUtils.guarding(f!::F, g::GenericReadWriteGuard) where {F}
    data = g.data
    criticalsection() = f!(data)
    lock(criticalsection, g.lock)
end

function ConcurrentUtils.guarding_read(f::F, g::GenericReadWriteGuard) where {F}
    data = g.data
    criticalsection() = f(data)
    lock_read(criticalsection, g.lock)
end

# Maybe this is a bad idea since it's hard to remember that `guarding(_, ::ReadGuard)`
# should not mutate the data just from its syntax.
#=
struct ReadGuard{Guard}
    guard::Guard
end

struct WriteGuard{Guard}
    guard::Guard
end

ConcurrentUtils.guarding(f::F, readguard::ReadGuard) where {F} =
    ConcurrentUtils.guarding_read(f, readguard.guard)

ConcurrentUtils.guarding(f::F, writeguard::WriteGuard) where {F} =
    ConcurrentUtils.guarding(f, writeguard.guard)

function ConcurrentUtils.read_write_guard(guard::AbstractReadWriteGuard)
    readguard = ReadGuard(guard)
    writeguard = WriteGuard(guard)
    return (readguard, writeguard)
end

ConcurrentUtils.read_write_guard(lock::AbstractReadWriteLock, data) =
    ConcurrentUtils.read_write_guard(GenericReadWriteGuard(lock, data))
=#


text:
include("nbody.jl")

t0 = 7257.93115525
h  = 0.015
tmax = 600.0
nbody(t0,h,tmax)

using PyPlot
data = readdlm("fcons.txt")
#for i=1:8
#  plot(data[:,(i-1)*4+2],data[:,(i-1)*4+3],".")
#end
plot(data[:,2],data[:,3],".")
nt = 40000

using PyCall

@pyimport matplotlib.animation as anim

#Construct Figure and Plot Data
fig = figure("TRAPPIST1A_motion",figsize=(10,10))
#ax = axes(xlim = (0,10),ylim=(0,10))
#fig = figure()
xmin = minimum(data[:,2]) ; xmax = maximum(data[:,2])
ymin = minimum(data[:,3]) ; ymax = maximum(data[:,3])
ax = axes(xlim = (xmin,xmax),ylim=(ymin,ymax))
global line1 = ax[:plot]([],[],"r-")[1]
global p1 = ax[:plot]([],[],"or")[1]

function init()
    global line1
    global p1
    line1[:set_data]([],[])
    p1[:set_data]([],[])
    return (line1,p1,Union{})
end

step = 50
function animate(i)
    k = i + 1
    global line1
    global p1
    line1[:set_data](data[max(1,step*(k-50)):(step*k),2],data[max(1,step*(k-50)):(step*k),3])
#    line1[:set_data](data[max(1,step*(k-10)):(step*k),2],data[max(1,step*(k-10)):(step*k),3])
    p1[:set_data]([data[step*k,2]],data[step*k,3])
    return (line1,Union{})
end

#Call the animator.
myanim = anim.FuncAnimation(fig, animate, init_func=init, frames=floor(Int64,nt/step), interval=50)

#This will require ffmpeg or equivalent.
#myanim[:save]("T1A_motion.mp4", bitrate=-1, extra_args=["-vcodec", "libx264", "-pix_fmt", "yuv420p"])
myanim[:save]("T1A_motion.mp4", bitrate=-1)
#myanim[:save]("T1A_motion.mp4")

max_forks_repo_name: langfzac/nbg-papers


text:
# Automatically generated using Clang.jl

const NISCOPE_MAJOR_VERSION = 19
const NISCOPE_MINOR_VERSION = 600
const MAX_FUNCTION_NAME_SIZE = 55
const MAX_ERROR_DESCRIPTION = IVI_MAX_MESSAGE_BUF_SIZE * 2 + MAX_FUNCTION_NAME_SIZE + 75

# Skipping MacroDefinition: handleErr ( fCall ) { int code = ( fCall ) ; if ( code != 0 ) { const char * fCallSrc = # fCall ; const char * funcName = fCallSrc + strspn ( fCallSrc , " (" ) ; size_t funcNameLen = strcspn ( funcName , " (" ) ; if ( funcNameLen > MAX_FUNCTION_NAME_SIZE - 1 ) funcNameLen = MAX_FUNCTION_NAME_SIZE - 1 ; if ( code < 0 ) { error = code ; strncpy ( errorSource , funcName , funcNameLen ) ; errorSource [ funcNameLen ] = '\0' ; goto Error ; } else if ( ( error == 0 ) && ( code > 0 ) ) { error = code ; strncpy ( errorSource , funcName , funcNameLen ) ; errorSource [ funcNameLen ] = '\0' ; } } }
# Skipping MacroDefinition: niScopeCheckParm ( fCall , paramPosition , errorElaboration ) if ( error = ( fCall ) , ( error = ( error < 0 ) ? ( error ) : VI_SUCCESS ) ) { Ivi_SetErrorInfo ( vi , VI_TRUE , error , Ivi_ParamPositionError ( paramPosition ) , errorElaboration ) ; goto Error ; } else
# Skipping MacroDefinition: niScopeCheckErrElab ( fCall , elab ) { int tempErrorForElab = ( fCall ) ; if ( tempErrorForElab < 0 ) { Ivi_SetErrorInfo ( vi , VI_TRUE , tempErrorForElab , 0 , elab ) ; error = tempErrorForElab ; goto Error ; } else if ( tempErrorForElab > 0 ) { Ivi_SetErrorInfo ( vi , VI_TRUE , tempErrorForElab , 0 , elab ) ; error = tempErrorForElab ; } }

const NISCOPE_ATTR_CACHE = IVI_ATTR_CACHE
const NISCOPE_ATTR_RANGE_CHECK = IVI_ATTR_RANGE_CHECK
const NISCOPE_ATTR_QUERY_INSTRUMENT_STATUS = IVI_ATTR_QUERY_INSTRUMENT_STATUS
const NISCOPE_ATTR_RECORD_COERCIONS = IVI_ATTR_RECORD_COERCIONS
const NISCOPE_ATTR_SIMULATE = IVI_ATTR_SIMULATE
const NISCOPE_ATTR_INTERCHANGE_CHECK = IVI_ATTR_INTERCHANGE_CHECK
const NISCOPE_ATTR_CHANNEL_COUNT = IVI_ATTR_CHANNEL_COUNT
const NISCOPE_ATTR_SUPPORTED_INSTRUMENT_MODELS = IVI_ATTR_SUPPORTED_INSTRUMENT_MODELS
const NISCOPE_ATTR_GROUP_CAPABILITIES = IVI_ATTR_GROUP_CAPABILITIES
const NISCOPE_ATTR_SPECIFIC_DRIVER_DESCRIPTION = IVI_ATTR_SPECIFIC_DRIVER_DESCRIPTION
const NISCOPE_ATTR_SPECIFIC_DRIVER_PREFIX = IVI_ATTR_SPECIFIC_DRIVER_PREFIX
const NISCOPE_ATTR_SPECIFIC_DRIVER_VENDOR = IVI_ATTR_SPECIFIC_DRIVER_VENDOR
const NISCOPE_ATTR_SPECIFIC_DRIVER_CLASS_SPEC_MAJOR_VERSION = IVI_ATTR_SPECIFIC_DRIVER_CLASS_SPEC_MAJOR_VERSION
const NISCOPE_ATTR_SPECIFIC_DRIVER_CLASS_SPEC_MINOR_VERSION = IVI_ATTR_SPECIFIC_DRIVER_CLASS_SPEC_MINOR_VERSION
const NISCOPE_ATTR_SPECIFIC_DRIVER_REVISION = IVI_ATTR_SPECIFIC_DRIVER_REVISION
const NISCOPE_ATTR_INSTRUMENT_MANUFACTURER = IVI_ATTR_INSTRUMENT_MANUFACTURER
const NISCOPE_ATTR_INSTRUMENT_MODEL = IVI_ATTR_INSTRUMENT_MODEL
const NISCOPE_ATTR_INSTRUMENT_FIRMWARE_REVISION = IVI_ATTR_INSTRUMENT_FIRMWARE_REVISION
const NISCOPE_ATTR_IO_RESOURCE_DESCRIPTOR = IVI_ATTR_IO_RESOURCE_DESCRIPTOR
const NISCOPE_ATTR_LOGICAL_NAME = IVI_ATTR_LOGICAL_NAME
const NISCOPE_ATTR_DRIVER_SETUP = IVI_ATTR_DRIVER_SETUP
const NISCOPE_ATTR_ACQUISITION_TYPE = IVISCOPE_ATTR_ACQUISITION_TYPE
const NISCOPE_ATTR_SAMPLE_MODE = IVISCOPE_ATTR_SAMPLE_MODE
const NISCOPE_ATTR_CHANNEL_ENABLED = IVISCOPE_ATTR_CHANNEL_ENABLED
const NISCOPE_ATTR_PROBE_ATTENUATION = IVISCOPE_ATTR_PROBE_ATTENUATION
const NISCOPE_ATTR_VERTICAL_RANGE = IVISCOPE_ATTR_VERTICAL_RANGE
const NISCOPE_ATTR_VERTICAL_OFFSET = IVISCOPE_ATTR_VERTICAL_OFFSET
const NISCOPE_ATTR_VERTICAL_COUPLING = IVISCOPE_ATTR_VERTICAL_COUPLING
const NISCOPE_ATTR_MAX_INPUT_FREQUENCY = IVISCOPE_ATTR_MAX_INPUT_FREQUENCY
const NISCOPE_ATTR_INPUT_IMPEDANCE = IVISCOPE_ATTR_INPUT_IMPEDANCE
const NISCOPE_ATTR_HORZ_TIME_PER_RECORD = IVISCOPE_ATTR_HORZ_TIME_PER_RECORD
const NISCOPE_ATTR_ACQUISITION_START_TIME = IVISCOPE_ATTR_ACQUISITION_START_TIME
const NISCOPE_ATTR_HORZ_MIN_NUM_PTS = IVISCOPE_ATTR_HORZ_MIN_NUM_PTS
const NISCOPE_ATTR_HORZ_RECORD_LENGTH = IVISCOPE_ATTR_HORZ_RECORD_LENGTH
const NISCOPE_ATTR_HORZ_RECORD_REF_POSITION = IVI_CLASS_PUBLIC_ATTR_BASE + Int32(11)
const NISCOPE_ATTR_HORZ_SAMPLE_RATE = IVISCOPE_ATTR_HORZ_SAMPLE_RATE
const NISCOPE_ATTR_TRIGGER_TYPE = IVISCOPE_ATTR_TRIGGER_TYPE
const NISCOPE_ATTR_TRIGGER_SOURCE = IVISCOPE_ATTR_TRIGGER_SOURCE
const NISCOPE_ATTR_TRIGGER_LEVEL = IVISCOPE_ATTR_TRIGGER_LEVEL
const NISCOPE_ATTR_TRIGGER_DELAY_TIME = IVI_CLASS_PUBLIC_ATTR_BASE + Int32(15)
const NISCOPE_ATTR_TRIGGER_HOLDOFF = IVISCOPE_ATTR_TRIGGER_HOLDOFF
const NISCOPE_ATTR_TRIGGER_COUPLING = IVISCOPE_ATTR_TRIGGER_COUPLING
const NISCOPE_ATTR_TRIGGER_SLOPE = IVISCOPE_ATTR_TRIGGER_SLOPE
const NISCOPE_ATTR_TV_TRIGGER_EVENT = IVISCOPE_ATTR_TV_TRIGGER_EVENT
const NISCOPE_ATTR_TV_TRIGGER_LINE_NUMBER = IVISCOPE_ATTR_TV_TRIGGER_LINE_NUMBER
const NISCOPE_ATTR_TV_TRIGGER_SIGNAL_FORMAT = IVISCOPE_ATTR_TV_TRIGGER_SIGNAL_FORMAT
const NISCOPE_ATTR_TV_TRIGGER_POLARITY = IVISCOPE_ATTR_TV_TRIGGER_POLARITY
const NISCOPE_ATTR_GLITCH_CONDITION = IVISCOPE_ATTR_GLITCH_CONDITION
const NISCOPE_ATTR_GLITCH_WIDTH = IVISCOPE_ATTR_GLITCH_WIDTH
const NISCOPE_ATTR_GLITCH_POLARITY = IVISCOPE_ATTR_GLITCH_POLARITY
const NISCOPE_ATTR_WIDTH_CONDITION = IVISCOPE_ATTR_WIDTH_CONDITION
const NISCOPE_ATTR_WIDTH_LOW_THRESHOLD = IVISCOPE_ATTR_WIDTH_LOW_THRESHOLD
const NISCOPE_ATTR_WIDTH_HIGH_THRESHOLD = IVISCOPE_ATTR_WIDTH_HIGH_THRESHOLD
const NISCOPE_ATTR_WIDTH_POLARITY = IVISCOPE_ATTR_WIDTH_POLARITY
const NISCOPE_ATTR_RUNT_POLARITY = IVISCOPE_ATTR_RUNT_POLARITY
const NISCOPE_ATTR_RUNT_LOW_THRESHOLD = IVISCOPE_ATTR_RUNT_LOW_THRESHOLD
const NISCOPE_ATTR_RUNT_HIGH_THRESHOLD = IVISCOPE_ATTR_RUNT_HIGH_THRESHOLD
const NISCOPE_ATTR_MEAS_HIGH_REF = IVISCOPE_ATTR_MEAS_HIGH_REF
const NISCOPE_ATTR_MEAS_LOW_REF = IVISCOPE_ATTR_MEAS_LOW_REF
const NISCOPE_ATTR_MEAS_MID_REF = IVISCOPE_ATTR_MEAS_MID_REF
const NISCOPE_ATTR_HORZ_NUM_RECORDS = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(1)
const NISCOPE_ATTR_INPUT_CLOCK_SOURCE = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(2)
const NISCOPE_ATTR_OUTPUT_CLOCK_SOURCE = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(3)
const NISCOPE_ATTR_HORZ_ENFORCE_REALTIME = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(4)
const NISCOPE_ATTR_BINARY_SAMPLE_WIDTH = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(5)
const NISCOPE_ATTR_TRIGGER_HYSTERESIS = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(6)
const NISCOPE_ATTR_CLOCK_SYNC_PULSE_SOURCE = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(7)
const NISCOPE_ATTR_MASTER_ENABLE = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(8)
const NISCOPE_ATTR_MIN_SAMPLE_RATE = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(9)
const NISCOPE_ATTR_TRIGGER_WINDOW_MODE = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(12)
const NISCOPE_ATTR_TRIGGER_WINDOW_LOW_LEVEL = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(13)
const NISCOPE_ATTR_TRIGGER_WINDOW_HIGH_LEVEL = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(14)
const NISCOPE_ATTR_MEAS_REF_LEVEL_UNITS = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(16)
const NISCOPE_ATTR_MEAS_OTHER_CHANNEL = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(18)
const NISCOPE_ATTR_MEAS_HYSTERESIS_PERCENT = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(19)
const NISCOPE_ATTR_MEAS_LAST_ACQ_HISTOGRAM_SIZE = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(20)
const NISCOPE_ATTR_MEAS_VOLTAGE_HISTOGRAM_SIZE = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(21)
const NISCOPE_ATTR_MEAS_VOLTAGE_HISTOGRAM_LOW_VOLTS = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(22)
const NISCOPE_ATTR_MEAS_VOLTAGE_HISTOGRAM_HIGH_VOLTS = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(23)
const NISCOPE_ATTR_MEAS_TIME_HISTOGRAM_SIZE = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(24)
const NISCOPE_ATTR_MEAS_TIME_HISTOGRAM_LOW_VOLTS = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(25)
const NISCOPE_ATTR_MEAS_TIME_HISTOGRAM_HIGH_VOLTS = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(26)
const NISCOPE_ATTR_MEAS_TIME_HISTOGRAM_LOW_TIME = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(27)
const NISCOPE_ATTR_MEAS_TIME_HISTOGRAM_HIGH_TIME = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(28)
const NISCOPE_ATTR_MEAS_POLYNOMIAL_INTERPOLATION_ORDER = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(29)
const NISCOPE_ATTR_MEAS_INTERPOLATION_SAMPLING_FACTOR = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(30)
const NISCOPE_ATTR_MEAS_FILTER_CUTOFF_FREQ = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(31)
const NISCOPE_ATTR_MEAS_FILTER_CENTER_FREQ = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(32)
const NISCOPE_ATTR_MEAS_FILTER_RIPPLE = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(33)
const NISCOPE_ATTR_MEAS_FILTER_TRANSIENT_WAVEFORM_PERCENT = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(34)
const NISCOPE_ATTR_MEAS_FILTER_TYPE = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(35)
const NISCOPE_ATTR_MEAS_FILTER_ORDER = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(36)
const NISCOPE_ATTR_MEAS_FILTER_TAPS = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(37)
const NISCOPE_ATTR_MEAS_CHAN_LOW_REF_LEVEL = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(38)
const NISCOPE_ATTR_MEAS_CHAN_MID_REF_LEVEL = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(39)
const NISCOPE_ATTR_MEAS_CHAN_HIGH_REF_LEVEL = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(40)
const NISCOPE_ATTR_MEAS_FILTER_WIDTH = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(41)
const NISCOPE_ATTR_MEAS_FIR_FILTER_WINDOW = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(42)
const NISCOPE_ATTR_MEAS_ARRAY_GAIN = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(43)
const NISCOPE_ATTR_MEAS_ARRAY_OFFSET = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(44)
const NISCOPE_ATTR_MEAS_PERCENTAGE_METHOD = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(45)
const NISCOPE_ATTR_ACQ_ARM_SOURCE = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(53)
const NISCOPE_ATTR_IS_PROBE_COMP_ON = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(66)
const NISCOPE_ATTR_USE_SPEC_INITIAL_X = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(67)
const NISCOPE_ATTR_ALLOW_MORE_RECORDS_THAN_MEMORY = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(68)
const NISCOPE_ATTR_ONBOARD_MEMORY_SIZE = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(69)
const NISCOPE_ATTR_RIS_NUM_AVERAGES = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(70)
const NISCOPE_ATTR_RIS_METHOD = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(71)
const NISCOPE_ATTR_FETCH_INTERLEAVED_DATA = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(72)
const NISCOPE_ATTR_MAX_REAL_TIME_SAMPLING_RATE = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(73)
const NISCOPE_ATTR_MAX_RIS_RATE = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(74)
const NISCOPE_ATTR_TRIGGER_IMPEDANCE = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(75)
const NISCOPE_ATTR_DEVICE_NUMBER = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(76)
const NISCOPE_ATTR_FETCH_RELATIVE_TO = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(77)
const NISCOPE_ATTR_FETCH_OFFSET = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(78)
const NISCOPE_ATTR_FETCH_RECORD_NUMBER = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(79)
const NISCOPE_ATTR_FETCH_NUM_RECORDS = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(80)
const NISCOPE_ATTR_FETCH_MEAS_NUM_SAMPLES = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(81)
const NISCOPE_ATTR_POINTS_DONE = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(82)
const NISCOPE_ATTR_RECORDS_DONE = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(83)
const NISCOPE_ATTR_BACKLOG = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(84)
const NISCOPE_ATTR_POLL_INTERVAL = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(100)
const NISCOPE_ATTR_PLL_LOCK_STATUS = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(1303)
const NISCOPE_ATTR_DEVICE_TEMPERATURE = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(86)
const NISCOPE_ATTR_SAMP_CLK_TIMEBASE_SRC = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(87)
const NISCOPE_ATTR_SAMP_CLK_TIMEBASE_RATE = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(88)
const NISCOPE_ATTR_SAMP_CLK_TIMEBASE_DIV = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(89)
const NISCOPE_ATTR_REF_CLK_RATE = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(90)
const NISCOPE_ATTR_EXPORTED_SAMPLE_CLOCK_OUTPUT_TERMINAL = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(91)
const NISCOPE_ATTR_ENABLE_DC_RESTORE = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(93)
const NISCOPE_ATTR_ADV_TRIG_SRC = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(94)
const NISCOPE_ATTR_ARM_REF_TRIG_SRC = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(95)
const NISCOPE_ATTR_REF_TRIG_TDC_ENABLE = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(96)
const NISCOPE_ATTR_RESOLUTION = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(102)
const NISCOPE_ATTR_START_TO_REF_TRIGGER_HOLDOFF = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(103)
const NISCOPE_ATTR_SERIAL_NUMBER = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(104)
const NISCOPE_ATTR_OSCILLATOR_PHASE_DAC_VALUE = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(105)
const NISCOPE_ATTR_RIS_IN_AUTO_SETUP_ENABLE = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(106)
const NISCOPE_ATTR_CHANNEL_TERMINAL_CONFIGURATION = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(107)
const NISCOPE_ATTR_EXPORTED_ADVANCE_TRIGGER_OUTPUT_TERMINAL = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(109)
const NISCOPE_ATTR_READY_FOR_START_EVENT_OUTPUT_TERMINAL = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(110)
const NISCOPE_ATTR_READY_FOR_REF_EVENT_OUTPUT_TERMINAL = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(111)
const NISCOPE_ATTR_READY_FOR_ADVANCE_EVENT_OUTPUT_TERMINAL = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(112)
const NISCOPE_ATTR_FLEX_FIR_ANTIALIAS_FILTER_TYPE = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(271)
const NISCOPE_ATTR_DDC_ENABLED = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(300)
const NISCOPE_ATTR_DDC_FREQUENCY_TRANSLATION_ENABLED = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(302)
const NISCOPE_ATTR_DDC_CENTER_FREQUENCY = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(303)
const NISCOPE_ATTR_DDC_DATA_PROCESSING_MODE = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(304)
const NISCOPE_ATTR_DDC_FREQUENCY_TRANSLATION_PHASE_I = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(305)
const NISCOPE_ATTR_DDC_FREQUENCY_TRANSLATION_PHASE_Q = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(306)
const NISCOPE_ATTR_DIGITAL_GAIN = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(307)
const NISCOPE_ATTR_DIGITAL_OFFSET = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(308)
const NISCOPE_ATTR_OVERFLOW_ERROR_REPORTING = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(309)
const NISCOPE_ATTR_DDC_Q_SOURCE = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(310)
const NISCOPE_ATTR_FETCH_INTERLEAVED_IQ_DATA = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(311)
const NISCOPE_ATTR_EQUALIZATION_NUM_COEFFICIENTS = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(312)
const NISCOPE_ATTR_EQUALIZATION_FILTER_ENABLED = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(313)
const NISCOPE_ATTR_REF_TRIGGER_DETECTOR_LOCATION = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(314)
const NISCOPE_ATTR_REF_TRIGGER_MINIMUM_QUIET_TIME = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(315)
const NISCOPE_ATTR_ENABLE_TIME_INTERLEAVED_SAMPLING = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(128)
const NISCOPE_ATTR_DATA_TRANSFER_BLOCK_SIZE = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(316)
const NISCOPE_ATTR_TRIGGER_MODIFIER = IVISCOPE_ATTR_TRIGGER_MODIFIER
const NISCOPE_ATTR_TRIGGER_AUTO_TRIGGERED = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(278)
const NISCOPE_ATTR_EXPORTED_START_TRIGGER_OUTPUT_TERMINAL = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(97)
const NISCOPE_ATTR_EXPORTED_REF_TRIGGER_OUTPUT_TERMINAL = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(98)
const NISCOPE_ATTR_END_OF_RECORD_EVENT_OUTPUT_TERMINAL = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(99)
const NISCOPE_ATTR_END_OF_ACQUISITION_EVENT_OUTPUT_TERMINAL = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(101)
const NISCOPE_ATTR_5V_OUT_OUTPUT_TERMINAL = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(129)
const NISCOPE_ATTR_BANDPASS_FILTER_ENABLED = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(318)
const NISCOPE_ATTR_DITHER_ENABLED = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(319)
const NISCOPE_ATTR_FRACTIONAL_RESAMPLE_ENABLED = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(320)
const NISCOPE_ATTR_DATA_TRANSFER_MAXIMUM_BANDWIDTH = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(321)
const NISCOPE_ATTR_DATA_TRANSFER_PREFERRED_PACKET_SIZE = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(322)
const NISCOPE_ATTR_SIGNAL_COND_GAIN = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(279)
const NISCOPE_ATTR_SIGNAL_COND_OFFSET = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(280)
const NISCOPE_ATTR_P2P_ENABLED = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(338)
const NISCOPE_ATTR_P2P_CHANNELS_TO_STREAM = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(339)
const NISCOPE_ATTR_P2P_ENDPOINT_SIZE = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(342)
const NISCOPE_ATTR_P2P_SAMPLES_AVAIL_IN_ENDPOINT = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(328)
const NISCOPE_ATTR_P2P_MOST_SAMPLES_AVAIL_IN_ENDPOINT = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(341)
const NISCOPE_ATTR_P2P_SAMPLES_TRANSFERRED = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(340)
const NISCOPE_ATTR_P2P_ENDPOINT_OVERFLOW = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(344)
const NISCOPE_ATTR_P2P_FIFO_ENDPOINT_COUNT = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(345)
const NISCOPE_ATTR_P2P_ONBOARD_MEMORY_ENABLED = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(354)
const NISCOPE_ATTR_P2P_MANUAL_CONFIGURATION_ENABLED = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(343)
const NISCOPE_ATTR_P2P_DATA_TRANS_PERMISSION_ADDR = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(329)
const NISCOPE_ATTR_P2P_DATA_TRANS_PERMISSION_ADDR_TYPE = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(330)
const NISCOPE_ATTR_P2P_DESTINATION_WINDOW_ADDR = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(331)
const NISCOPE_ATTR_P2P_DESTINATION_WINDOW_ADDR_TYPE = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(332)
const NISCOPE_ATTR_P2P_DESTINATION_WINDOW_SIZE = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(333)
const NISCOPE_ATTR_P2P_NOTIFY_PUSH_MESSAGE_ON = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(334)
const NISCOPE_ATTR_P2P_NOTIFY_MESSAGE_PUSH_ADDR = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(335)
const NISCOPE_ATTR_P2P_NOTIFY_MESSAGE_PUSH_ADDR_TYPE = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(336)
const NISCOPE_ATTR_P2P_NOTIFY_MESSAGE_PUSH_VALUE = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(337)
const NISCOPE_ATTR_SAMP_CLK_TIMEBASE_MULT = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(367)
const NISCOPE_ATTR_P2P_STREAM_RELATIVE_TO = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(373)
const NISCOPE_ATTR_P2P_SAMPLES_TRANSFERRED_PER_RECORD = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(380)
const NISCOPE_ATTR_END_OF_RECORD_TO_ADVANCE_TRIGGER_HOLDOFF = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(366)
const NISCOPE_ATTR_ABSOLUTE_SAMPLE_CLOCK_OFFSET = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(374)
const NISCOPE_ATTR_FPGA_BITFILE_PATH = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(375)
const NISCOPE_ATTR_INTERLEAVING_OFFSET_CORRECTION_ENABLED = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(376)
const NISCOPE_ATTR_HIGH_PASS_FILTER_FREQUENCY = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(377)
const NISCOPE_ATTR_RUNT_TIME_CONDITION = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(132)
const NISCOPE_ATTR_RUNT_TIME_LOW_LIMIT = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(133)
const NISCOPE_ATTR_RUNT_TIME_HIGH_LIMIT = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(134)
const NISCOPE_ATTR_CABLE_SENSE_VOLTAGE = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(137)
const NISCOPE_ATTR_CABLE_SENSE_MODE = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(138)
const NISCOPE_ATTR_CABLE_SENSE_SIGNAL_ENABLE = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(139)
const NISCOPE_ATTR_ENABLED_CHANNELS = IVI_SPECIFIC_PUBLIC_ATTR_BASE + Int32(140)
const NISCOPE_VAL_RIS_EXACT_NUM_AVERAGES = 1
const NISCOPE_VAL_RIS_MIN_NUM_AVERAGES = 2
const NISCOPE_VAL_RIS_INCOMPLETE = 3
const NISCOPE_VAL_RIS_LIMITED_BIN_WIDTH = 5
const NISCOPE_VAL_SOFTWARE_TRIGGER_START = 0
const NISCOPE_VAL_SOFTWARE_TRIGGER_ARM_REFERENCE = 1
const NISCOPE_VAL_SOFTWARE_TRIGGER_REFERENCE = 2
const NISCOPE_VAL_SOFTWARE_TRIGGER_ADVANCE = 3
const NISCOPE_VAL_AC = IVISCOPE_VAL_AC
const NISCOPE_VAL_DC = IVISCOPE_VAL_DC
const NISCOPE_VAL_GND = IVISCOPE_VAL_GND
const NISCOPE_VAL_BANDWIDTH_FULL = -1.0
const NISCOPE_VAL_BANDWIDTH_DEVICE_DEFAULT = 0.0
const NISCOPE_VAL_20MHZ_BANDWIDTH = 2.0e7
const NISCOPE_VAL_100MHZ_BANDWIDTH = 1.0e8
const NISCOPE_VAL_20MHZ_MAX_INPUT_FREQUENCY = 2.0e7
const NISCOPE_VAL_100MHZ_MAX_INPUT_FREQUENCY = 1.0e8
const NISCOPE_VAL_50_OHMS = 50.0
const NISCOPE_VAL_75_OHMS = 75.0
const NISCOPE_VAL_1_MEG_OHM = 1.0e6
const NISCOPE_VAL_EDGE_TRIGGER = IVISCOPE_VAL_EDGE_TRIGGER
const NISCOPE_VAL_HYSTERESIS_TRIGGER = IVISCOPE_VAL_TRIGGER_TYPE_SPECIFIC_EXT_BASE + Int32(1)
const NISCOPE_VAL_DIGITAL_TRIGGER = IVISCOPE_VAL_TRIGGER_TYPE_SPECIFIC_EXT_BASE + Int32(2)
const NISCOPE_VAL_WINDOW_TRIGGER = IVISCOPE_VAL_TRIGGER_TYPE_SPECIFIC_EXT_BASE + Int32(3)
const NISCOPE_VAL_SOFTWARE_TRIGGER = IVISCOPE_VAL_TRIGGER_TYPE_SPECIFIC_EXT_BASE + Int32(4)
const NISCOPE_VAL_TV_TRIGGER = IVISCOPE_VAL_TV_TRIGGER
const NISCOPE_VAL_GLITCH_TRIGGER = IVISCOPE_VAL_GLITCH_TRIGGER
const NISCOPE_VAL_WIDTH_TRIGGER = IVISCOPE_VAL_WIDTH_TRIGGER
const NISCOPE_VAL_RUNT_TRIGGER = IVISCOPE_VAL_RUNT_TRIGGER
const NISCOPE_VAL_IMMEDIATE_TRIGGER = IVISCOPE_VAL_IMMEDIATE_TRIGGER
const NISCOPE_VAL_IMMEDIATE = "VAL_IMMEDIATE"
const NISCOPE_VAL_EXTERNAL = IVISCOPE_VAL_EXTERNAL
const NISCOPE_VAL_SW_TRIG_FUNC = "VAL_SW_TRIG_FUNC"
const NISCOPE_VAL_TTL0 = IVISCOPE_VAL_TTL0
const NISCOPE_VAL_TTL1 = IVISCOPE_VAL_TTL1
const NISCOPE_VAL_TTL2 = IVISCOPE_VAL_TTL2
const NISCOPE_VAL_TTL3 = IVISCOPE_VAL_TTL3
const NISCOPE_VAL_TTL4 = IVISCOPE_VAL_TTL4
const NISCOPE_VAL_TTL5 = IVISCOPE_VAL_TTL5
const NISCOPE_VAL_TTL6 = IVISCOPE_VAL_TTL6
const NISCOPE_VAL_TTL7 = IVISCOPE_VAL_TTL7
const NISCOPE_VAL_ECL0 = IVISCOPE_VAL_ECL0
const NISCOPE_VAL_ECL1 = IVISCOPE_VAL_ECL1
const NISCOPE_VAL_PXI_STAR = IVISCOPE_VAL_PXI_STAR
const NISCOPE_VAL_RTSI_0 = IVISCOPE_VAL_RTSI_0
const NISCOPE_VAL_RTSI_1 = IVISCOPE_VAL_RTSI_1
const NISCOPE_VAL_RTSI_2 = IVISCOPE_VAL_RTSI_2
const NISCOPE_VAL_RTSI_3 = IVISCOPE_VAL_RTSI_3
const NISCOPE_VAL_RTSI_4 = IVISCOPE_VAL_RTSI_4
const NISCOPE_VAL_RTSI_5 = IVISCOPE_VAL_RTSI_5
const NISCOPE_VAL_RTSI_6 = IVISCOPE_VAL_RTSI_6
const NISCOPE_VAL_RTSI_7 = "VAL_RTSI_7"
const NISCOPE_VAL_PFI_0 = "VAL_PFI_0"
const NISCOPE_VAL_PFI_1 = "VAL_PFI_1"
const NISCOPE_VAL_PFI_2 = "VAL_PFI_2"
const NISCOPE_VAL_PFI_3 = "VAL_PFI_3"
const NISCOPE_VAL_PFI_4 = "VAL_PFI_4"
const NISCOPE_VAL_PFI_5 = "VAL_PFI_5"
const NISCOPE_VAL_PFI_6 = "VAL_PFI_6"
const NISCOPE_VAL_PFI_7 = "VAL_PFI_7"
const NISCOPE_VAL_AUX_0_PFI_0 = "VAL_AUX_0_PFI_0"
const NISCOPE_VAL_AUX_0_PFI_1 = "VAL_AUX_0_PFI_1"
const NISCOPE_VAL_AUX_0_PFI_3 = "VAL_AUX_0_PFI_3"
const NISCOPE_VAL_AUX_0_PFI_4 = "VAL_AUX_0_PFI_4"
const NISCOPE_VAL_AUX_0_PFI_5 = "VAL_AUX_0_PFI_5"
const NISCOPE_VAL_AUX_0_PFI_6 = "VAL_AUX_0_PFI_6"
const NISCOPE_VAL_AUX_0_PFI_7 = "VAL_AUX_0_PFI_7"
const NISCOPE_VAL_AUX_0_PFI_2 = "VAL_AUX_0_PFI_2"
const NISCOPE_VAL_READ_POINTER = 388
const NISCOPE_VAL_PRETRIGGER = 477
const NISCOPE_VAL_NOW = 481
const NISCOPE_VAL_START = 482
const NISCOPE_VAL_TRIGGER = 483
const NISCOPE_VAL_NO_TRIGGER_MOD = IVISCOPE_VAL_NO_TRIGGER_MOD
const NISCOPE_VAL_AUTO = IVISCOPE_VAL_AUTO
const NISCOPE_VAL_AUTO_LEVEL = IVISCOPE_VAL_AUTO_LEVEL
const NISCOPE_VAL_HF_REJECT = IVISCOPE_VAL_HF_REJECT
const NISCOPE_VAL_LF_REJECT = IVISCOPE_VAL_LF_REJECT
const NISCOPE_VAL_AC_PLUS_HF_REJECT = IVISCOPE_VAL_TRIGGER_COUPLING_SPECIFIC_EXT_BASE + Int32(1)
const NISCOPE_VAL_NEGATIVE = IVISCOPE_VAL_NEGATIVE
const NISCOPE_VAL_POSITIVE = IVISCOPE_VAL_POSITIVE
const NISCOPE_VAL_SLOPE_EITHER = 3
const NISCOPE_VAL_NORMAL = IVISCOPE_VAL_NORMAL
const NISCOPE_VAL_FLEXRES = IVISCOPE_VAL_ACQUISITION_TYPE_SPECIFIC_EXT_BASE + Int32(1)
const NISCOPE_VAL_DDC = IVISCOPE_VAL_ACQUISITION_TYPE_SPECIFIC_EXT_BASE + Int32(2)
const NISCOPE_VAL_NO_INTERPOLATION = IVISCOPE_VAL_NO_INTERPOLATION
const NISCOPE_VAL_SINE_X = IVISCOPE_VAL_SINE_X
const NISCOPE_VAL_LINEAR = IVISCOPE_VAL_LINEAR
const NISCOPE_VAL_NTSC = IVISCOPE_VAL_NTSC
const NISCOPE_VAL_PAL = IVISCOPE_VAL_PAL
const NISCOPE_VAL_SECAM = IVISCOPE_VAL_SECAM
const NISCOPE_VAL_M_PAL = IVISCOPE_VAL_TV_SIGNAL_FORMAT_SPECIFIC_EXT_BASE + Int32(1)
const NISCOPE_VAL_480I_59_94_FIELDS_PER_SECOND = IVISCOPE_VAL_TV_SIGNAL_FORMAT_SPECIFIC_EXT_BASE + Int32(10)
const NISCOPE_VAL_480I_60_FIELDS_PER_SECOND = IVISCOPE_VAL_TV_SIGNAL_FORMAT_SPECIFIC_EXT_BASE + Int32(11)
const NISCOPE_VAL_480P_59_94_FRAMES_PER_SECOND = IVISCOPE_VAL_TV_SIGNAL_FORMAT_SPECIFIC_EXT_BASE + Int32(15)
const NISCOPE_VAL_480P_60_FRAMES_PER_SECOND = IVISCOPE_VAL_TV_SIGNAL_FORMAT_SPECIFIC_EXT_BASE + Int32(16)
const NISCOPE_VAL_576I_50_FIELDS_PER_SECOND = IVISCOPE_VAL_TV_SIGNAL_FORMAT_SPECIFIC_EXT_BASE + Int32(20)
const NISCOPE_VAL_576P_50_FRAMES_PER_SECOND = IVISCOPE_VAL_TV_SIGNAL_FORMAT_SPECIFIC_EXT_BASE + Int32(25)
const NISCOPE_VAL_720P_50_FRAMES_PER_SECOND = IVISCOPE_VAL_TV_SIGNAL_FORMAT_SPECIFIC_EXT_BASE + Int32(31)
const NISCOPE_VAL_720P_59_94_FRAMES_PER_SECOND = IVISCOPE_VAL_TV_SIGNAL_FORMAT_SPECIFIC_EXT_BASE + Int32(32)
const NISCOPE_VAL_720P_60_FRAMES_PER_SECOND = IVISCOPE_VAL_TV_SIGNAL_FORMAT_SPECIFIC_EXT_BASE + Int32(33)
const NISCOPE_VAL_1080I_50_FIELDS_PER_SECOND = IVISCOPE_VAL_TV_SIGNAL_FORMAT_SPECIFIC_EXT_BASE + Int32(40)
const NISCOPE_VAL_1080I_59_94_FIELDS_PER_SECOND = IVISCOPE_VAL_TV_SIGNAL_FORMAT_SPECIFIC_EXT_BASE + Int32(41)
const NISCOPE_VAL_1080I_60_FIELDS_PER_SECOND = IVISCOPE_VAL_TV_SIGNAL_FORMAT_SPECIFIC_EXT_BASE + Int32(42)
const NISCOPE_VAL_1080P_24_FRAMES_PER_SECOND = IVISCOPE_VAL_TV_SIGNAL_FORMAT_SPECIFIC_EXT_BASE + Int32(45)
const NISCOPE_VAL_TV_POSITIVE = IVISCOPE_VAL_TV_POSITIVE
const NISCOPE_VAL_TV_NEGATIVE = IVISCOPE_VAL_TV_NEGATIVE
const NISCOPE_VAL_TV_EVENT_FIELD1 = IVISCOPE_VAL_TV_EVENT_FIELD1
const NISCOPE_VAL_TV_EVENT_FIELD2 = IVISCOPE_VAL_TV_EVENT_FIELD2
const NISCOPE_VAL_TV_EVENT_ANY_FIELD = IVISCOPE_VAL_TV_EVENT_ANY_FIELD
const NISCOPE_VAL_TV_EVENT_ANY_LINE = IVISCOPE_VAL_TV_EVENT_ANY_LINE
const NISCOPE_VAL_TV_EVENT_LINE_NUMBER = IVISCOPE_VAL_TV_EVENT_LINE_NUMBER
const NISCOPE_VAL_GLITCH_GREATER_THAN = IVISCOPE_VAL_GLITCH_GREATER_THAN
const NISCOPE_VAL_GLITCH_LESS_THAN = IVISCOPE_VAL_GLITCH_LESS_THAN
const NISCOPE_VAL_GLITCH_POSITIVE = IVISCOPE_VAL_GLITCH_POSITIVE
const NISCOPE_VAL_GLITCH_NEGATIVE = IVISCOPE_VAL_GLITCH_NEGATIVE
const NISCOPE_VAL_GLITCH_EITHER = IVISCOPE_VAL_GLITCH_EITHER
const NISCOPE_VAL_WIDTH_WITHIN = IVISCOPE_VAL_WIDTH_WITHIN
const NISCOPE_VAL_WIDTH_OUTSIDE = IVISCOPE_VAL_WIDTH_OUTSIDE
const NISCOPE_VAL_WIDTH_POSITIVE = IVISCOPE_VAL_WIDTH_POSITIVE
const NISCOPE_VAL_WIDTH_NEGATIVE = IVISCOPE_VAL_WIDTH_NEGATIVE
const NISCOPE_VAL_WIDTH_EITHER = IVISCOPE_VAL_WIDTH_EITHER
const NISCOPE_VAL_RUNT_POSITIVE = IVISCOPE_VAL_RUNT_POSITIVE
const NISCOPE_VAL_RUNT_NEGATIVE = IVISCOPE_VAL_RUNT_NEGATIVE
const NISCOPE_VAL_RUNT_EITHER = IVISCOPE_VAL_RUNT_EITHER
const NISCOPE_VAL_RUNT_TIME_CONDITION_NONE = 0
const NISCOPE_VAL_RUNT_TIME_CONDITION_WITHIN = 1
const NISCOPE_VAL_RUNT_TIME_CONDITION_OUTSIDE = 2
const NISCOPE_VAL_CABLE_SENSE_MODE_DISABLED = 0
const NISCOPE_VAL_CABLE_SENSE_MODE_ON_DEMAND = 1
const NISCOPE_VAL_REAL_TIME = IVISCOPE_VAL_REAL_TIME
const NISCOPE_VAL_EQUIVALENT_TIME = IVISCOPE_VAL_EQUIVALENT_TIME
const NISCOPE_VAL_NO_SOURCE = "VAL_NO_SOURCE"
const NISCOPE_VAL_RTSI_CLOCK = "VAL_RTSI_CLOCK"
const NISCOPE_VAL_CLK_IN = "VAL_CLK_IN"
const NISCOPE_VAL_CLK_OUT = "VAL_CLK_OUT"
const NISCOPE_VAL_INTERNAL10MHZ_OSC = "VAL_INTERNAL10MHZ_OSC"
const NISCOPE_VAL_PXI_CLK = "VAL_PXI_CLK"
const NISCOPE_VAL_PXI_CLK10 = "VAL_PXI_CLK10"
const NISCOPE_VAL_PXI_CLK100 = "VAL_PXI_CLK100"
const NISCOPE_VAL_PXIE_DSTAR_A = "VAL_PXIE_DSTAR_A"
const NISCOPE_VAL_AUX_0_CLK_IN = "VAL_AUX_0_CLK_IN"
const NISCOPE_VAL_AUX_0_CLK_OUT = "VAL_AUX_0_CLK_OUT"
const NISCOPE_VAL_ONBOARD_CONFIGURABLE_RATE_CLK = "VAL_ONBOARD_CONFIGURABLE_RATE_CLK"
const NISCOPE_VAL_REF_TRIGGER = 1
const NISCOPE_VAL_START_TRIGGER = 2
const NISCOPE_VAL_END_OF_ACQUISITION_EVENT = 3
const NISCOPE_VAL_END_OF_RECORD_EVENT = 4
const NISCOPE_VAL_ADVANCE_TRIGGER = 5
const NISCOPE_VAL_READY_FOR_ADVANCE_EVENT = 6
const NISCOPE_VAL_READY_FOR_START_EVENT = 7
const NISCOPE_VAL_READY_FOR_REF_EVENT = 10
const NISCOPE_VAL_5V_OUT = 13
const NISCOPE_VAL_REF_CLOCK = 100
const NISCOPE_VAL_SAMPLE_CLOCK = 101
const NISCOPE_VAL_ENTERING_WINDOW = 0
const NISCOPE_VAL_LEAVING_WINDOW = 1
const NISCOPE_VAL_ENTERING_OR_LEAVING_WINDOW = 2
const NISCOPE_VAL_MAX_TIME_INFINITE = UInt32(0xffffffff)
const NISCOPE_VAL_MAX_TIME_IMMEDIATE = 0
const NISCOPE_VAL_MAX_TIME_NONE = 0
const NISCOPE_VAL_ACQ_COMPLETE = IVISCOPE_VAL_ACQ_COMPLETE
const NISCOPE_VAL_ACQ_IN_PROGRESS = IVISCOPE_VAL_ACQ_IN_PROGRESS
const NISCOPE_VAL_ACQ_STATUS_UNKNOWN = IVISCOPE_VAL_ACQ_STATUS_UNKNOWN
const NISCOPE_VAL_MEAS_LOWPASS = 0
const NISCOPE_VAL_MEAS_HIGHPASS = 1
const NISCOPE_VAL_MEAS_BANDPASS = 2
const NISCOPE_VAL_MEAS_BANDSTOP = 3
const NISCOPE_VAL_MEAS_LOW_HIGH = 0
const NISCOPE_VAL_MEAS_MIN_MAX = 1
const NISCOPE_VAL_MEAS_BASE_TOP = 2
const NISCOPE_VAL_MEAS_VOLTAGE = 0
const NISCOPE_VAL_MEAS_PERCENTAGE = 1
const NISCOPE_VAL_NO_MEASUREMENT = 4000
const NISCOPE_VAL_RISE_TIME = IVISCOPE_VAL_RISE_TIME
const NISCOPE_VAL_FALL_TIME = IVISCOPE_VAL_FALL_TIME
const NISCOPE_VAL_FREQUENCY = IVISCOPE_VAL_FREQUENCY
const NISCOPE_VAL_PERIOD = IVISCOPE_VAL_PERIOD
const NISCOPE_VAL_VOLTAGE_RMS = IVISCOPE_VAL_VOLTAGE_RMS
const NISCOPE_VAL_VOLTAGE_PEAK_TO_PEAK = IVISCOPE_VAL_VOLTAGE_PEAK_TO_PEAK
const NISCOPE_VAL_VOLTAGE_MAX = IVISCOPE_VAL_VOLTAGE_MAX
const NISCOPE_VAL_VOLTAGE_MIN = IVISCOPE_VAL_VOLTAGE_MIN
const NISCOPE_VAL_VOLTAGE_HIGH = IVISCOPE_VAL_VOLTAGE_HIGH
const NISCOPE_VAL_VOLTAGE_LOW = IVISCOPE_VAL_VOLTAGE_LOW
const NISCOPE_VAL_VOLTAGE_AVERAGE = IVISCOPE_VAL_VOLTAGE_AVERAGE
const NISCOPE_VAL_WIDTH_NEG = IVISCOPE_VAL_WIDTH_NEG
const NISCOPE_VAL_WIDTH_POS = IVISCOPE_VAL_WIDTH_POS
const NISCOPE_VAL_DUTY_CYCLE_NEG = IVISCOPE_VAL_DUTY_CYCLE_NEG
const NISCOPE_VAL_DUTY_CYCLE_POS = IVISCOPE_VAL_DUTY_CYCLE_POS
const NISCOPE_VAL_AMPLITUDE = IVISCOPE_VAL_AMPLITUDE
const NISCOPE_VAL_VOLTAGE_CYCLE_RMS = IVISCOPE_VAL_VOLTAGE_CYCLE_RMS
const NISCOPE_VAL_VOLTAGE_CYCLE_AVERAGE = IVISCOPE_VAL_VOLTAGE_CYCLE_AVERAGE
const NISCOPE_VAL_OVERSHOOT = IVISCOPE_VAL_OVERSHOOT
const NISCOPE_VAL_PRESHOOT = IVISCOPE_VAL_PRESHOOT
const NISCOPE_VAL_LOW_REF_VOLTS = IVISCOPE_VAL_MEASUREMENT_FUNCTION_SPECIFIC_EXT_BASE + Int32(0)
const NISCOPE_VAL_MID_REF_VOLTS = IVISCOPE_VAL_MEASUREMENT_FUNCTION_SPECIFIC_EXT_BASE + Int32(1)
const NISCOPE_VAL_HIGH_REF_VOLTS = IVISCOPE_VAL_MEASUREMENT_FUNCTION_SPECIFIC_EXT_BASE + Int32(2)
const NISCOPE_VAL_AREA = IVISCOPE_VAL_MEASUREMENT_FUNCTION_SPECIFIC_EXT_BASE + Int32(3)
const NISCOPE_VAL_CYCLE_AREA = IVISCOPE_VAL_MEASUREMENT_FUNCTION_SPECIFIC_EXT_BASE + Int32(4)
const NISCOPE_VAL_INTEGRAL = IVISCOPE_VAL_MEASUREMENT_FUNCTION_SPECIFIC_EXT_BASE + Int32(5)
const NISCOPE_VAL_VOLTAGE_BASE = IVISCOPE_VAL_MEASUREMENT_FUNCTION_SPECIFIC_EXT_BASE + Int32(6)
const NISCOPE_VAL_VOLTAGE_TOP = IVISCOPE_VAL_MEASUREMENT_FUNCTION_SPECIFIC_EXT_BASE + Int32(7)
const NISCOPE_VAL_FFT_FREQUENCY = IVISCOPE_VAL_MEASUREMENT_FUNCTION_SPECIFIC_EXT_BASE + Int32(8)
const NISCOPE_VAL_FFT_AMPLITUDE = IVISCOPE_VAL_MEASUREMENT_FUNCTION_SPECIFIC_EXT_BASE + Int32(9)
const NISCOPE_VAL_RISE_SLEW_RATE = IVISCOPE_VAL_MEASUREMENT_FUNCTION_SPECIFIC_EXT_BASE + Int32(10)
const NISCOPE_VAL_FALL_SLEW_RATE = IVISCOPE_VAL_MEASUREMENT_FUNCTION_SPECIFIC_EXT_BASE + Int32(11)
const NISCOPE_VAL_AC_ESTIMATE = IVISCOPE_VAL_MEASUREMENT_FUNCTION_SPECIFIC_EXT_BASE + Int32(12)
const NISCOPE_VAL_DC_ESTIMATE = IVISCOPE_VAL_MEASUREMENT_FUNCTION_SPECIFIC_EXT_BASE + Int32(13)
const NISCOPE_VAL_TIME_DELAY = IVISCOPE_VAL_MEASUREMENT_FUNCTION_SPECIFIC_EXT_BASE + Int32(14)
const NISCOPE_VAL_AVERAGE_PERIOD = IVISCOPE_VAL_MEASUREMENT_FUNCTION_SPECIFIC_EXT_BASE + Int32(15)
const NISCOPE_VAL_AVERAGE_FREQUENCY = IVISCOPE_VAL_MEASUREMENT_FUNCTION_SPECIFIC_EXT_BASE + Int32(16)
const NISCOPE_VAL_VOLTAGE_BASE_TO_TOP = IVISCOPE_VAL_MEASUREMENT_FUNCTION_SPECIFIC_EXT_BASE + Int32(17)
const NISCOPE_VAL_PHASE_DELAY = IVISCOPE_VAL_MEASUREMENT_FUNCTION_SPECIFIC_EXT_BASE + Int32(18)
const NISCOPE_VAL_VOLTAGE_HISTOGRAM_MEAN = IVISCOPE_VAL_MEASUREMENT_FUNCTION_SPECIFIC_EXT_BASE + Int32(1000)
const NISCOPE_VAL_VOLTAGE_HISTOGRAM_STDEV = IVISCOPE_VAL_MEASUREMENT_FUNCTION_SPECIFIC_EXT_BASE + Int32(1001)
const NISCOPE_VAL_VOLTAGE_HISTOGRAM_PEAK_TO_PEAK = IVISCOPE_VAL_MEASUREMENT_FUNCTION_SPECIFIC_EXT_BASE + Int32(1002)
const NISCOPE_VAL_VOLTAGE_HISTOGRAM_MEDIAN = IVISCOPE_VAL_MEASUREMENT_FUNCTION_SPECIFIC_EXT_BASE + Int32(1003)
const NISCOPE_VAL_VOLTAGE_HISTOGRAM_HITS = IVISCOPE_VAL_MEASUREMENT_FUNCTION_SPECIFIC_EXT_BASE + Int32(1004)
const NISCOPE_VAL_VOLTAGE_HISTOGRAM_MAX = IVISCOPE_VAL_MEASUREMENT_FUNCTION_SPECIFIC_EXT_BASE + Int32(1005)
const NISCOPE_VAL_VOLTAGE_HISTOGRAM_MIN = IVISCOPE_VAL_MEASUREMENT_FUNCTION_SPECIFIC_EXT_BASE + Int32(1006)
const NISCOPE_VAL_VOLTAGE_HISTOGRAM_MEAN_PLUS_STDEV = IVISCOPE_VAL_MEASUREMENT_FUNCTION_SPECIFIC_EXT_BASE + Int32(1007)
const NISCOPE_VAL_VOLTAGE_HISTOGRAM_MEAN_PLUS_2_STDEV = IVISCOPE_VAL_MEASUREMENT_FUNCTION_SPECIFIC_EXT_BASE + Int32(1008)
const NISCOPE_VAL_VOLTAGE_HISTOGRAM_MEAN_PLUS_3_STDEV = IVISCOPE_VAL_MEASUREMENT_FUNCTION_SPECIFIC_EXT_BASE + Int32(1009)
const NISCOPE_VAL_VOLTAGE_HISTOGRAM_MODE = IVISCOPE_VAL_MEASUREMENT_FUNCTION_SPECIFIC_EXT_BASE + Int32(1010)
const NISCOPE_VAL_VOLTAGE_HISTOGRAM_NEW_HITS = IVISCOPE_VAL_MEASUREMENT_FUNCTION_SPECIFIC_EXT_BASE + Int32(1011)
const NISCOPE_VAL_TIME_HISTOGRAM_MEAN = IVISCOPE_VAL_MEASUREMENT_FUNCTION_SPECIFIC_EXT_BASE + Int32(2000)
const NISCOPE_VAL_TIME_HISTOGRAM_STDEV = IVISCOPE_VAL_MEASUREMENT_FUNCTION_SPECIFIC_EXT_BASE + Int32(2001)
const NISCOPE_VAL_TIME_HISTOGRAM_PEAK_TO_PEAK = IVISCOPE_VAL_MEASUREMENT_FUNCTION_SPECIFIC_EXT_BASE + Int32(2002)
const NISCOPE_VAL_TIME_HISTOGRAM_MEDIAN = IVISCOPE_VAL_MEASUREMENT_FUNCTION_SPECIFIC_EXT_BASE + Int32(2003)
const NISCOPE_VAL_TIME_HISTOGRAM_HITS = IVISCOPE_VAL_MEASUREMENT_FUNCTION_SPECIFIC_EXT_BASE + Int32(2004)
const NISCOPE_VAL_TIME_HISTOGRAM_MAX = IVISCOPE_VAL_MEASUREMENT_FUNCTION_SPECIFIC_EXT_BASE + Int32(2005)
const NISCOPE_VAL_TIME_HISTOGRAM_MIN = IVISCOPE_VAL_MEASUREMENT_FUNCTION_SPECIFIC_EXT_BASE + Int32(2006)
const NISCOPE_VAL_TIME_HISTOGRAM_MEAN_PLUS_STDEV = IVISCOPE_VAL_MEASUREMENT_FUNCTION_SPECIFIC_EXT_BASE + Int32(2007)
const NISCOPE_VAL_TIME_HISTOGRAM_MEAN_PLUS_2_STDEV = IVISCOPE_VAL_MEASUREMENT_FUNCTION_SPECIFIC_EXT_BASE + Int32(2008)
const NISCOPE_VAL_TIME_HISTOGRAM_MEAN_PLUS_3_STDEV = IVISCOPE_VAL_MEASUREMENT_FUNCTION_SPECIFIC_EXT_BASE + Int32(2009)
const NISCOPE_VAL_TIME_HISTOGRAM_MODE = IVISCOPE_VAL_MEASUREMENT_FUNCTION_SPECIFIC_EXT_BASE + Int32(2010)
const NISCOPE_VAL_TIME_HISTOGRAM_NEW_HITS = IVISCOPE_VAL_MEASUREMENT_FUNCTION_SPECIFIC_EXT_BASE + Int32(2011)
const NISCOPE_VAL_LAST_ACQ_HISTOGRAM = 4001
const NISCOPE_VAL_FFT_PHASE_SPECTRUM = 4002
const NISCOPE_VAL_FFT_AMP_SPECTRUM_VOLTS_RMS = 4003
const NISCOPE_VAL_MULTI_ACQ_VOLTAGE_HISTOGRAM = 4004
const NISCOPE_VAL_MULTI_ACQ_TIME_HISTOGRAM = 4005
const NISCOPE_VAL_ARRAY_INTEGRAL = 4006
const NISCOPE_VAL_DERIVATIVE = 4007
const NISCOPE_VAL_INVERSE = 4008
const NISCOPE_VAL_HANNING_WINDOW = 4009
const NISCOPE_VAL_FLAT_TOP_WINDOW = 4010
const NISCOPE_VAL_POLYNOMIAL_INTERPOLATION = 4011
const NISCOPE_VAL_MULTIPLY_CHANNELS = 4012
const NISCOPE_VAL_ADD_CHANNELS = 4013
const NISCOPE_VAL_SUBTRACT_CHANNELS = 4014
const NISCOPE_VAL_DIVIDE_CHANNELS = 4015
const NISCOPE_VAL_MULTI_ACQ_AVERAGE = 4016
const NISCOPE_VAL_BUTTERWORTH_FILTER = 4017
const NISCOPE_VAL_CHEBYSHEV_FILTER = 4018
const NISCOPE_VAL_FFT_AMP_SPECTRUM_DB = 4019
const NISCOPE_VAL_HAMMING_WINDOW = 4020
const NISCOPE_VAL_WINDOWED_FIR_FILTER = 4021
const NISCOPE_VAL_BESSEL_FILTER = 4022
const NISCOPE_VAL_TRIANGLE_WINDOW = 4023
const NISCOPE_VAL_BLACKMAN_WINDOW = 4024
const NISCOPE_VAL_ARRAY_OFFSET = 4025
const NISCOPE_VAL_ARRAY_GAIN = 4026
const NISCOPE_VAL_REAL = 0
const NISCOPE_VAL_COMPLEX = 1
const NISCOPE_VAL_SINGLE_ENDED = 0
const NISCOPE_VAL_UNBALANCED_DIFFERENTIAL = 1
const NISCOPE_VAL_DIFFERENTIAL = 2
const NISCOPE_VAL_48_TAP_STANDARD = 0
const NISCOPE_VAL_48_TAP_HANNING = 1
const NISCOPE_VAL_16_TAP_HANNING = 2
const NISCOPE_VAL_8_TAP_HANNING = 3
const NISCOPE_VAL_ERROR_REPORTING_ERROR = 0
const NISCOPE_VAL_ERROR_REPORTING_WARNING = 1
const NISCOPE_VAL_ERROR_REPORTING_DISABLED = 2
const NISCOPE_VAL_ANALOG_DETECTION_CIRCUIT = 0
const NISCOPE_VAL_DDC_OUTPUT = 1
const NISCOPE_VAL_ADDR_PHYSICAL = 0
const NISCOPE_VAL_ADDR_VIRTUAL = 1
const NISCOPE_VAL_NOTIFY_NEVER = 0
const NISCOPE_VAL_NOTIFY_DONE = 1
const NISCOPE_VAL_STREAM_RELATIVE_TO_START_TRIGGER = 0
const NISCOPE_VAL_STREAM_RELATIVE_TO_REFERENCE_TRIGGER = 1
const NISCOPE_VAL_STREAM_RELATIVE_TO_SYNC_TRIGGER = 2
const NISCOPE_VAL_INPUT = 0
const NISCOPE_VAL_OUTPUT = 1
const NISCOPE_VAL_NONE = 0
const NISCOPE_VAL_TRUE = VI_TRUE
const NISCOPE_VAL_FALSE = VI_FALSE
const NISCOPE_VAL_SELF_CALIBRATION = 0
const NISCOPE_VAL_EXTERNAL_CALIBRATION = 1
const NISCOPE_VAL_RESTORE_FACTORY_CALIBRATION = 2
const NISCOPE_VAL_CAL_RESTORE_EXTERNAL_CALIBRATION = NISCOPE_VAL_RESTORE_FACTORY_CALIBRATION
const NISCOPE_VAL_ALL_MEASUREMENTS = 10000
const NISCOPE_WARN_INVALID_WFM_ELEMENT = IVISCOPE_WARN_INVALID_WFM_ELEMENT
const NISCOPE_WARN_HEATER_CIRCUIT_TEMPERATURE = IVI_SPECIFIC_WARN_BASE + Int32(1)
const NISCOPE_WARN_INVALID_DATA = IVI_SPECIFIC_WARN_BASE + Int32(2)
const NISCOPE_WARN_CHANNEL_OVERLOAD = IVI_SPECIFIC_WARN_BASE + Int32(3)
const NISCOPE_WARN_AUTOSETUP_NO_SIGNAL = IVI_SPECIFIC_WARN_BASE + Int32(4)
const NISCOPE_WARN_PLL_UNLOCKED = IVI_SPECIFIC_WARN_BASE + Int32(5)
const NISCOPE_WARN_PLL_UNLOCKED_AND_ADC_OVERLOAD = IVI_SPECIFIC_WARN_BASE + Int32(6)
const NISCOPE_WARN_TIMESTAMP_ROLLOVER = IVI_SPECIFIC_WARN_BASE + Int32(7)
const NISCOPE_WARN_ADC_OVERLOAD = IVI_SPECIFIC_WARN_BASE + Int32(8)
const NISCOPE_WARN_P2P_OVERFLOW = IVI_SPECIFIC_WARN_BASE + Int32(9)
const NISCOPE_ERROR_CHANNEL_NOT_ENABLED = IVISCOPE_ERROR_CHANNEL_NOT_ENABLED
const NISCOPE_ERROR_UNABLE_TO_PERFORM_MEASUREMENT = IVISCOPE_ERROR_UNABLE_TO_PERFORM_MEASUREMENT
const NISCOPE_ERROR_MAX_TIME_EXCEEDED = IVISCOPE_ERROR_MAX_TIME_EXCEEDED
const NISCOPE_ERROR_SOFTWARE_FAILURE = IVI_SPECIFIC_ERROR_BASE + Int32(1)
const NISCOPE_ERROR_HARDWARE_FAILURE = IVI_SPECIFIC_ERROR_BASE + Int32(2)
const NISCOPE_ERROR_INSUFFICIENT_MEMORY = IVI_SPECIFIC_ERROR_BASE + Int32(3)
const NISCOPE_ERROR_INVALID_DATA = IVI_SPECIFIC_ERROR_BASE + Int32(4)
const NISCOPE_ERROR_GAIN_CAL_FAILURE = IVI_SPECIFIC_ERROR_BASE + Int32(5)
const NISCOPE_ERROR_SINE_CAL_FAILURE = IVI_SPECIFIC_ERROR_BASE + Int32(6)
const NISCOPE_ERROR_LIN_CAL_FAILURE = IVI_SPECIFIC_ERROR_BASE + Int32(7)
const NISCOPE_ERROR_ADC_CAL_FAILURE = IVI_SPECIFIC_ERROR_BASE + Int32(8)
const NISCOPE_ERROR_ACQ_IN_PROGRESS = IVI_SPECIFIC_ERROR_BASE + Int32(9)
const NISCOPE_ERROR_DATA_NOT_AVAILABLE = IVI_SPECIFIC_ERROR_BASE + Int32(10)
const NISCOPE_ERROR_HEATER_CIRCUIT_CAL_FAILURE = IVI_SPECIFIC_ERROR_BASE + Int32(11)
const NISCOPE_ERROR_FLEXRES_CONFIG_CORRUPT = IVI_SPECIFIC_ERROR_BASE + Int32(12)
const NISCOPE_ERROR_GAIN_OFFSET_CAL_FAILURE = IVI_SPECIFIC_ERROR_BASE + Int32(13)
const NISCOPE_ERROR_CREATE_THREAD = IVI_SPECIFIC_ERROR_BASE + Int32(14)
const NISCOPE_ERROR_WRONG_PASSWORD = IVI_SPECIFIC_ERROR_BASE + Int32(15)
const NISCOPE_ERROR_INVALID_GAIN = IVI_SPECIFIC_ERROR_BASE + Int32(16)
const NISCOPE_ERROR_INVALID_CAL_SESSION = IVI_SPECIFIC_ERROR_BASE + Int32(19)
const NISCOPE_ERROR_BAD_MEASUREMENT = IVI_SPECIFIC_ERROR_BASE + Int32(20)
const NISCOPE_ERROR_BUFFER_NOT_ACQUIRED = IVI_SPECIFIC_ERROR_BASE + Int32(21)
const NISCOPE_ERROR_TRIGGER_HAS_NOT_OCCURRED = IVI_SPECIFIC_ERROR_BASE + Int32(22)
const NISCOPE_ERROR_ILLEGAL_RELATIVE_TO = IVI_SPECIFIC_ERROR_BASE + Int32(23)
const NISCOPE_ERROR_DATA_OVERWRITTEN = IVI_SPECIFIC_ERROR_BASE + Int32(24)
const NISCOPE_ERROR_INVALID_TIMESTAMP_EVENT_TAG = IVI_SPECIFIC_ERROR_BASE + Int32(26)
const NISCOPE_ERROR_TIMEOUT_TRANSFERRING_TIMESTAMPS = IVI_SPECIFIC_ERROR_BASE + Int32(27)
const NISCOPE_ERROR_TIMEOUT_CHECKING_STATUS = IVI_SPECIFIC_ERROR_BASE + Int32(28)
const NISCOPE_ERROR_TIMEOUT_TRANSFERRING_DATA = IVI_SPECIFIC_ERROR_BASE + Int32(29)
const NISCOPE_ERROR_PLL_FAILURE = IVI_SPECIFIC_ERROR_BASE + Int32(30)
const NISCOPE_ERROR_PAR_TO_SER_CONV_FAILURE = IVI_SPECIFIC_ERROR_BASE + Int32(31)
const NISCOPE_ERROR_DMA_CONFIG_ERROR = IVI_SPECIFIC_ERROR_BASE + Int32(32)
const NISCOPE_ERROR_ILLEGAL_USER_OFFSET = IVI_SPECIFIC_ERROR_BASE + Int32(33)
const NISCOPE_ERROR_NOT_A_SCOPE = IVI_SPECIFIC_ERROR_BASE + Int32(34)
const NISCOPE_ERROR_TIMEOUT_CLEARING_TIO = IVI_SPECIFIC_ERROR_BASE + Int32(35)
const NISCOPE_ERROR_RIS_DID_NOT_COMPLETE = IVI_SPECIFIC_ERROR_BASE + Int32(36)
const NISCOPE_ERROR_INVALID_RIS_METHOD = IVI_SPECIFIC_ERROR_BASE + Int32(37)
const NISCOPE_ERROR_INVALID_RIS_NUM_AVERAGES = IVI_SPECIFIC_ERROR_BASE + Int32(38)
const NISCOPE_ERROR_ILLEGAL_DATATYPE = IVI_SPECIFIC_ERROR_BASE + Int32(39)
const NISCOPE_ERROR_ATTRIBUTES_DIFFER_BY_CHANNEL = IVI_SPECIFIC_ERROR_BASE + Int32(40)
const NISCOPE_ERROR_TRIGGER_DELAY_INVALID_WITH_RIS = IVI_SPECIFIC_ERROR_BASE + Int32(41)
const NISCOPE_ERROR_INITIATE_NOT_CALLED = IVI_SPECIFIC_ERROR_BASE + Int32(42)
const NISCOPE_ERROR_INVALID_FUNCTION_USE = IVI_SPECIFIC_ERROR_BASE + Int32(43)
const NISCOPE_ERROR_HOLDOFF_DELAY_NONZERO = IVI_SPECIFIC_ERROR_BASE + Int32(44)
const NISCOPE_ERROR_CHANNEL_NAME_TOO_LONG = IVI_SPECIFIC_ERROR_BASE + Int32(45)
const NISCOPE_ERROR_DIGITIZER_ALREADY_IN_USE = IVI_SPECIFIC_ERROR_BASE + Int32(46)
const NISCOPE_ERROR_SIM_MODEL_NOT_SUPPORTED = IVI_SPECIFIC_ERROR_BASE + Int32(47)
const NISCOPE_ERROR_SPECIFICDLL_LOAD_FAILURE = IVI_SPECIFIC_ERROR_BASE + Int32(48)
const NISCOPE_ERROR_SPECIFICDLL_GET_ADDRESS_FAILURE = IVI_SPECIFIC_ERROR_BASE + Int32(49)
const NISCOPE_ERROR_TRIGGER_TYPE_INVALID = IVI_SPECIFIC_ERROR_BASE + Int32(50)
const NISCOPE_ERROR_INVALID_FETCH_PARAMETERS = IVI_SPECIFIC_ERROR_BASE + Int32(51)
const NISCOPE_ERROR_EXT_CAL_NOT_COMPLETE = IVI_SPECIFIC_ERROR_BASE + Int32(52)
const NISCOPE_ERROR_EXT_CAL_CONSTS_INVALID = IVI_SPECIFIC_ERROR_BASE + Int32(53)
const NISCOPE_ERROR_INVALID_NUM_WAVEFORMS = IVI_SPECIFIC_ERROR_BASE + Int32(54)
const NISCOPE_ERROR_DIGITIZER_NOT_SUPPORTED_WITH_ACCESSORY = IVI_SPECIFIC_ERROR_BASE + Int32(55)
const NISCOPE_ERROR_DIGITAL_TRIGGER_NOT_SUPPORTED_WITH_RIS = IVI_SPECIFIC_ERROR_BASE + Int32(56)
const NISCOPE_ERROR_SELF_CAL_NOT_COMPLETE = IVI_SPECIFIC_ERROR_BASE + Int32(57)
const NISCOPE_ERROR_PATTERN_NOISE_SAMPLE_WORDS = IVI_SPECIFIC_ERROR_BASE + Int32(58)
const NISCOPE_ERROR_PATTERN_NOISE_INPUT_EXPECTATION = IVI_SPECIFIC_ERROR_BASE + Int32(59)
const NISCOPE_ERROR_INVALID_SESSION = 0xbffa1190
const NISCOPE_ERROR_1MOHM_PATH_BANDWIDTH_LIMITED = IVI_SPECIFIC_ERROR_BASE + Int32(60)
const NISCOPE_ERROR_EXT_CAL_STEP_OUT_OF_ORDER = IVI_SPECIFIC_ERROR_BASE + Int32(61)
const NISCOPE_ERROR_MULTIPLES_2_ALLOWED_SAMPLE_CLK_TIMEBASE_DIV = IVI_SPECIFIC_ERROR_BASE + Int32(62)
const NISCOPE_ERROR_CAL_INPUT_VOLTAGE_DETECTED = IVI_SPECIFIC_ERROR_BASE + Int32(63)
const NISCOPE_ERROR_EXT_CAL_ATTENUATOR_FAILED = IVI_SPECIFIC_ERROR_BASE + Int32(64)
const NISCOPE_ERROR_TDC_CAL_TIME_OUT = IVI_SPECIFIC_ERROR_BASE + Int32(65)
const NISCOPE_ERROR_TDC_CAL_FAILURE = IVI_SPECIFIC_ERROR_BASE + Int32(66)
const NISCOPE_ERROR_TRIGGER_CAL_FAILURE = IVI_SPECIFIC_ERROR_BASE + Int32(67)
const NISCOPE_ERROR_OFFSET_DAC_CAL_FAILURE = IVI_SPECIFIC_ERROR_BASE + Int32(68)
const NISCOPE_ERROR_INVALID_FETCH_POSITION = IVI_SPECIFIC_ERROR_BASE + Int32(69)
const NISCOPE_ERROR_RIS_MODE_NOT_SUPPORTED_WITH_P2P = IVI_SPECIFIC_ERROR_BASE + Int32(70)

struct niScope_wfmInfo
	relativeInitialX::Float64
	absoluteInitialX::Float64
	xIncrement::Float64
	actualSamples::Int32
	offset::Float64
	gain::Float64
    reserved1::Cint
    reserved2::Cint
end

struct niScope_coefficientInfo
    offset::Cint
    gain::Cint
    reserved1::Cint
    reserved2::Cint
end

struct NIComplexNumber_struct
    real::Cint
    imaginary::Cint
end

const NIComplexNumber = NIComplexNumber_struct

struct NIComplexI16_struct
    real::Cint
    imaginary::Cint
end

const NIComplexI16 = NIComplexI16_struct
const NISCOPE_VAL_CAL_ACTION_STORE = 0
const NISCOPE_VAL_CAL_ACTION_ABORT = 1
const NISCOPE_VAL_CAL_ACTION_RESET = 101
const NISCOPE_VAL_CAL_ABORT_ON_ERR = 0
const NISCOPE_VAL_CAL_ABORT_ON_MAJOR_ERR = 1
const NISCOPE_VAL_CAL_SKIP_BAD_SECTIONS = 2
const NISCOPE_VAL_CAL_RESET_BAD_SECTIONS = 3
const NISCOPE_VAL_CAL_SELF = 1
const NISCOPE_VAL_CAL_EXTERNAL = 0
const NISCOPE_VAL_CAL_MANUFACTURE = 2

# Skipping MacroDefinition: NISCOPE_VAL_CAL_UNROUTE_SIGNAL ( ViUInt32 ) 0xfffffff
# Skipping MacroDefinition: NISCOPE_VAL_CAL_POSITIVE ( ViUInt32 ) 0xffff

const NISCOPE_VAL_CAL_NEGATIVE = 0
const NISCOPE_VAL_CAL_SOURCE_GROUND = 0
const NISCOPE_VAL_CAL_SOURCE_POSITIVEFS = 1
const NISCOPE_VAL_CAL_SOURCE_NEGATIVEFS = 2
const NISCOPE_VAL_CAL_10V_CH0 = 0


text:
export quadratic_estimate

"""

    quadratic_estimate(ds::DataSet, which; wiener_filtered=true)
    quadratic_estimate((ds₁::DataSet, ds₂::DataSet), which; wiener_filtered=true)

Compute the quadratic estimate of `ϕ` given data.

The `ds` or `(ds₁,ds₂)` tuple contain the DataSet object(s) which house the
data and covariances used in the estimate. Note that only the Fourier-diagonal
approximations for the beam, mask, and noise, i.e. `B̂`, `M̂`, and
`Cn̂`, are accounted for. To account full operators (if they are not actually
Fourier-diagonal), you should compute the impact using Monte Carlo.

If a tuple is passed in, the result will come from correlating the data from
`ds₁` with that from `ds₂`.

An optional keyword argument `AL` can be passed in case the QE normalization
was already computed, in which case it won't be recomputed during the
calculation.

Returns a named tuple of `(;ϕqe, AL, Nϕ)` where `ϕqe` is the (possibly Wiener
filtered, depending on `wiener_filtered` option) quadratic estimate, `AL` is the
normalization (which is already applied to ϕqe, it does not need to be applied
again), and `Nϕ` is the analytic N⁰ noise bias (Nϕ==AL if using unlensed
weights, currently only Nϕ==AL is always returned, no matter the weights)
"""
function quadratic_estimate(
    (ds₁,ds₂) :: NTuple{2,DataSet},
    which = nothing;
    wiener_filtered = true,
    AL = nothing,
    weights = :unlensed
)
    @assert weights in (:lensed, :unlensed) "weights should be :lensed or :unlensed"
    if isnothing(which)
        which = ds₁.d isa FlatS0 ? :TT : :EB
    end
    @assert (which in [:TT, :EE, :EB]) "which='$which' not implemented"
    @assert (ds₁.Cf===ds₂.Cf && ds₁.Cf̃===ds₂.Cf̃ && ds₁.Cn̂===ds₂.Cn̂ && ds₁.Cϕ===ds₂.Cϕ && ds₁.B̂===ds₂.B̂) "operators in `ds₁` and `ds₂` should be the same"
    @unpack Cf, Cf̃, Cn̂, Cϕ, B̂, M̂ = ds₁()
    pol = which == :TT ? :I : :P
    quadratic_estimate(Val(which), (ds₁.d[pol], ds₂.d[pol]), Cf[pol], Cf̃[pol], Cn̂[pol], Cϕ, (M̂*B̂)[pol], wiener_filtered, weights, AL)
end

quadratic_estimate(ds::DataSet, args...; kwargs...) = quadratic_estimate((ds,ds), args...; kwargs...)

@doc doc"""

    QE_leg(C::Diagonal, inds...)

The quadratic estimate and normalization expressions all consist of
terms involving products of two "legs", each leg which look like:

    C * l[i] * l̂[j] * l̂[k] * ...

where C is some field or diagonal covariance, l[i] is the Fourier
wave-vector in direction i (for i=1:2), and l̂[i] = l[i]/‖l‖. For
example, there's a leg in the EB estimator that looks like:

    (CE * (CẼ+Cn) \ d[:E])) * l[i] * l̂[j] * l̂[k]

The function `QE_leg` computes quatities like these, e.g. the above
would be given by:

    QE_leg((CE * (CẼ+Cn) \ d[:E])), [i], j, k)

(where note that specifying whether its the Fourier wave-vector l
instead of the unit-vector l̂ is done by putting that index in
brackets).

Additionally, all of these terms are symmetric in their indices, i.e.
in `(i,j,k)` in this case. The `QE_leg` function is smart about this,
and is memoized so that each unique set of indices is only computed
once. This leads to a pretty drastic speedup for terms with many
indices like those that arize in the EE and EB normalizations, and
lets us write code which is both clear and fast without having to
think too hard about these symmetries.

"""
QE_leg(C::Diagonal, inds...) = QE_leg(C.diag, inds...)
function QE_leg(C::Field, inds...)
    n = count((x->x isa Int),inds)
    p₁,p₂ = (count(==(i), first.(inds)) for i=1:2)
    QE_leg(C, (n, p₁, p₂))
end
@memoize function QE_leg(C::Field, (n, p₁, p₂)::Tuple)
    Map(@. nan2zero($Ð(C) * ∇[1].diag^p₁ * ∇[2].diag^p₂ / sqrt(∇².diag)^n))
end
ϵ(x...) = levicivita([x...])
inds(D) = collect(product(repeated(1:2,D)...))[:]

function quadratic_estimate(::Val{:TT}, (d₁,d₂)::NTuple{2,FlatS0}, Cf, Cf̃, Cn, Cϕ, TF, wiener_filtered, weights, AL=nothing)

    ΣTtot = TF^2 * Cf̃ + Cn
    CT = (weights==:unlensed) ? Cf : Cf̃

    # unnormalized estimate
    ϕqe_unnormalized = @subst -sum(∇[i] * Fourier(QE_leg($(ΣTtot\(TF*d₁))) * QE_leg($(CT*(ΣTtot\(TF*d₂))), [i])) for i=1:2)

    # normalization
    if AL == nothing
        AL = @subst begin
            A(i,j) = (
                QE_leg($(TF^2 * CT^2 / ΣTtot), [i], [j]) * QE_leg($(TF^2      / ΣTtot)     )
              + QE_leg($(TF^2 * CT   / ΣTtot), [i]     ) * QE_leg($(TF^2 * CT / ΣTtot), [j])
            )
            pinv(Diagonal(sum(∇[i].diag .* ∇[j].diag .* Fourier(A(i,j)) for (i,j) in inds(2))))
        end
    end
    Nϕ = AL # true only for unlensed weights

    Memoization.empty_cache!(QE_leg)

    ϕqe = (wiener_filtered ? (Cϕ*pinv(Cϕ+Nϕ)) : 1) * (AL*ϕqe_unnormalized)
    (;ϕqe, AL, Nϕ)

end

function quadratic_estimate(::Val{:EE}, (d₁,d₂)::NTuple{2,FlatS2}, Cf, Cf̃, Cn, Cϕ, TF, wiener_filtered, weights, AL=nothing)

    TF² = TF[:E]^2
    ΣEtot = TF² * Cf̃[:E] + Cn[:E]
    CE = ((weights==:unlensed) ? Cf : Cf̃)[:E]

    # unnormalized estimate
    ϕqe_unnormalized = @subst begin
        I(i) = -(
            2sum(QE_leg($(CE * (ΣEtot \ (TF*d₁)[:E])), [i], j, k) * QE_leg($((ΣEtot \ (TF*d₂)[:E])), j, k) for (j,k) in inds(2))
               - QE_leg($(CE * (ΣEtot \ (TF*d₁)[:E])), [i]      ) * QE_leg($((ΣEtot \ (TF*d₂)[:E]))      )
        )
        sum(∇[i] * Fourier(I(i)) for i=1:2)
    end

    # normalization
    if AL == nothing
        AL = @subst begin
            A1(i,j) = -4 * sum( ϵ(m,p,3) * ϵ(n,q,3) * (
                  QE_leg($(TF² * CE^2 / ΣEtot), [i], [j], k, l, m, n) .* QE_leg($(TF²      / ΣEtot),      k, l, p, q)
                + QE_leg($(TF² * CE   / ΣEtot), [i],      k, l, m, n) .* QE_leg($(TF² * CE / ΣEtot), [j], k, l, p, q))
                for (k,l,m,n,p,q) in inds(6)
            )
            A2(i,j) = (
                  QE_leg($(TF² * CE^2 / ΣEtot), [i], [j]) .* QE_leg($(TF²      / ΣEtot)     )
                + QE_leg($(TF² * CE   / ΣEtot), [i]     ) .* QE_leg($(TF² * CE / ΣEtot), [j])
            )
            pinv(Diagonal(sum(∇[i].diag .* ∇[j].diag .* Fourier(A1(i,j) + A2(i,j)) for (i,j) in inds(2))))
        end
    end
    Nϕ = AL # true only for unlensed weights

    Memoization.empty_cache!(QE_leg)

    ϕqe = (wiener_filtered ? (Cϕ*pinv(Cϕ+Nϕ)) : 1) * (AL*ϕqe_unnormalized)
    (;ϕqe, AL, Nϕ)

end

function quadratic_estimate(::Val{:EB}, (d₁,d₂)::NTuple{2,FlatS2}, Cf, Cf̃, Cn, Cϕ, TF, wiener_filtered, weights, AL=nothing; zeroB=false)

    CE, CB = getindex.(Ref((weights==:unlensed) ? Cf : Cf̃),(:E,:B))
    TF²E, TF²B = TF[:E]^2, TF[:B]^2
    ΣEtot = TF²E * Cf̃[:E] + Cn[:E]
    ΣBtot = TF²B * Cf̃[:B] + Cn[:B]

    # unnormalized estimate
    ϕqe_unnormalized = @subst begin
        I(i) = 2 * sum(  ϵ(k,l,3) * (
                           QE_leg($(CE * (ΣEtot \ (TF*d₁)[:E])), [i], j, k) * QE_leg($(     (ΣBtot \ (TF*d₂)[:B])),      j, l)
            - (zeroB ? 0 : QE_leg($(      ΣEtot \ (TF*d₁)[:E]),       j, k) * QE_leg($(CB * (ΣBtot \ (TF*d₂)[:B])), [i], j, l)))
            for (j,k,l) in inds(3)
        )
        sum(∇[i] * Fourier(I(i)) for i=1:2)
    end

    # normalization
    if AL == nothing
        AL = @subst begin
            A(i,j) = 4 * sum( ϵ(m,p,3) * ϵ(n,q,3) * (
                                 QE_leg($(TF²E * CE^2 / ΣEtot), [i], [j], k, l, m, n) * QE_leg($(TF²B        / ΣBtot),           k, l, p, q)
                + (zeroB ? 0 : -2QE_leg($(TF²E * CE   / ΣEtot), [i],      k, l, m, n) * QE_leg($(TF²B * CB   / ΣBtot),      [j], k, l, p, q))
                + (zeroB ? 0 :   QE_leg($(TF²E        / ΣEtot),           k, l, m, n) * QE_leg($(TF²B * CB^2 / ΣBtot), [i], [j], k, l, p, q)))
                for (k,l,m,n,p,q) in inds(6)
            )
            pinv(Diagonal(sum(∇[i].diag .* ∇[j].diag .* Fourier(A(i,j)) for (i,j) in inds(2))))
        end
    end
    Nϕ = AL # true only for unlensed weights

    Memoization.empty_cache!(QE_leg)

    ϕqe = (wiener_filtered ? (Cϕ*pinv(Cϕ+Nϕ)) : 1) * (AL * ϕqe_unnormalized)
    (;ϕqe, AL, Nϕ)

end