text:
using Mocking

apply_security_groups_to_load_balancer_1_patch = @patch function apply_security_groups_to_load_balancer(a...; b...)
    return Dict{String, Any}("SecurityGroups" => Any["sg-fc448899"])
end

attach_load_balancer_to_subnets_1_patch = @patch function attach_load_balancer_to_subnets(a...; b...)
    return Dict{String, Any}("Subnets" => Any["subnet-15aaab61", "subnet-0ecac448"])
end

configure_health_check_1_patch = @patch function configure_health_check(a...; b...)
    return Dict{String, Any}("HealthCheck" => Dict{String, Any}("Interval" => 30, "UnhealthyThreshold" => 2, "Timeout" => 3, "HealthyThreshold" => 2, "Target" => "HTTP:80/png"))
end

create_load_balancer_1_patch = @patch function create_load_balancer(a...; b...)
    return Dict{String, Any}("DNSName" => "my-load-balancer-1234567890.us-west-2.elb.amazonaws.com")
end

create_load_balancer_2_patch = @patch function create_load_balancer(a...; b...)
    return Dict{String, Any}("DNSName" => "my-load-balancer-123456789.us-west-2.elb.amazonaws.com")
end

create_load_balancer_3_patch = @patch function create_load_balancer(a...; b...)
    return Dict{String, Any}("DNSName" => "my-load-balancer-1234567890.us-west-2.elb.amazonaws.com")
end

create_load_balancer_4_patch = @patch function create_load_balancer(a...; b...)
    return Dict{String, Any}("DNSName" => "my-load-balancer-123456789.us-west-2.elb.amazonaws.com")
end

create_load_balancer_5_patch = @patch function create_load_balancer(a...; b...)
    return Dict{String, Any}("DNSName" => "internal-my-load-balancer-123456789.us-west-2.elb.amazonaws.com")
end

deregister_instances_from_load_balancer_1_patch = @patch function deregister_instances_from_load_balancer(a...; b...)
    return Dict{String, Any}("Instances" => Any[Dict{String, Any}("InstanceId" => "i-207d9717"), Dict{String, Any}("InstanceId" => "i-afefb49b")])
end

describe_instance_health_1_patch = @patch function describe_instance_health(a...; b...)
    return Dict{String, Any}("InstanceStates" => Any[Dict{String, Any}("Description" => "N/A", "InstanceId" => "i-207d9717", "State" => "InService", "ReasonCode" => "N/A"), Dict{String, Any}("Description" => "N/A", "InstanceId" => "i-afefb49b", "State" => "InService", "ReasonCode" => "N/A")])
end

describe_load_balancer_attributes_1_patch = @patch function describe_load_balancer_attributes(a...; b...)
    return Dict{String, Any}("LoadBalancerAttributes" => Dict{String, Any}("ConnectionSettings" => Dict{String, Any}("IdleTimeout" => 60), "CrossZoneLoadBalancing" => Dict{String, Any}("Enabled" => false), "ConnectionDraining" => Dict{String, Any}("Timeout" => 300, "Enabled" => false), "AccessLog" => Dict{String, Any}("Enabled" => false)))
end

describe_load_balancer_policies_1_patch = @patch function describe_load_balancer_policies(a...; b...)
    return Dict{String, Any}("PolicyDescriptions" => Any[Dict{String, Any}("PolicyName" => "my-authentication-policy", "PolicyAttributeDescriptions" => Any[Dict{String, Any}("AttributeName" => "PublicKeyPolicyName", "AttributeValue" => "my-PublicKey-policy")], "PolicyTypeName" => "BackendServerAuthenticationPolicyType")])
end

describe_load_balancer_policy_types_1_patch = @patch function describe_load_balancer_policy_types(a...; b...)
    return Dict{String, Any}("PolicyTypeDescriptions" => Any[Dict{String, Any}("Description" => "Policy that controls whether to include the IP address and port of the originating request for TCP messages. This policy operates on TCP listeners only.", "PolicyTypeName" => "ProxyProtocolPolicyType", "PolicyAttributeTypeDescriptions" => Any[Dict{String, Any}("Cardinality" => "ONE", "AttributeName" => "ProxyProtocol", "AttributeType" => "Boolean")])])
end

describe_load_balancers_1_patch = @patch function describe_load_balancers(a...; b...)
    return Dict{String, Any}("LoadBalancerDescriptions" => Any[Dict{String, Any}("CanonicalHostedZoneNameID" => "Z3DZXE0EXAMPLE", "Subnets" => Any["subnet-15aaab61"], "AvailabilityZones" => Any["us-west-2a"], "CreatedTime" => "2015-03-19T03:24:02.650Z", "SecurityGroups" => Any["sg-a61988c3"], "CanonicalHostedZoneName" => "my-load-balancer-1234567890.us-west-2.elb.amazonaws.com", "HealthCheck" => Dict{String, Any}("Interval" => 30, "UnhealthyThreshold" => 2, "Timeout" => 3, "HealthyThreshold" => 2, "Target" => "HTTP:80/png"), "Scheme" => "internet-facing", "ListenerDescriptions" => Any[Dict{String, Any}("PolicyNames" => Any[], "Listener" => Dict{String, Any}("LoadBalancerPort" => 80, "InstancePort" => 80, "Protocol" => "HTTP", "InstanceProtocol" => "HTTP")), Dict{String, Any}("PolicyNames" => Any["ELBSecurityPolicy-2015-03"], "Listener" => Dict{String, Any}("LoadBalancerPort" => 443, "InstancePort" => 443, "SSLCertificateId" => "arn:aws:iam::123456789012:server-certificate/my-server-cert", "Protocol" => "HTTPS", "InstanceProtocol" => "HTTPS"))], "SourceSecurityGroup" => Dict{String, Any}("GroupName" => "my-elb-sg", "OwnerAlias" => "123456789012"), "VPCId" => "vpc-a01106c2", "Policies" => Dict{String, Any}("OtherPolicies" => Any["my-PublicKey-policy", "my-authentication-policy", "my-SSLNegotiation-policy", "my-ProxyProtocol-policy", "ELBSecurityPolicy-2015-03"], "AppCookieStickinessPolicies" => Any[], "LBCookieStickinessPolicies" => Any[Dict{String, Any}("PolicyName" => "my-duration-cookie-policy", "CookieExpirationPeriod" => 60)]), "BackendServerDescriptions" => Any[Dict{String, Any}("PolicyNames" => Any["my-ProxyProtocol-policy"], "InstancePort" => 80)], "DNSName" => "my-load-balancer-1234567890.us-west-2.elb.amazonaws.com", "Instances" => Any[Dict{String, Any}("InstanceId" => "i-207d9717"), Dict{String, Any}("InstanceId" => "i-afefb49b")], "LoadBalancerName" => "my-load-balancer")])
end

describe_tags_1_patch = @patch function describe_tags(a...; b...)
    return Dict{String, Any}("TagDescriptions" => Any[Dict{String, Any}("LoadBalancerName" => "my-load-balancer", "Tags" => Any[Dict{String, Any}("Value" => "lima", "Key" => "project"), Dict{String, Any}("Value" => "digital-media", "Key" => "department")])])
end

detach_load_balancer_from_subnets_1_patch = @patch function detach_load_balancer_from_subnets(a...; b...)
    return Dict{String, Any}("Subnets" => Any["subnet-15aaab61"])
end

disable_availability_zones_for_load_balancer_1_patch = @patch function disable_availability_zones_for_load_balancer(a...; b...)
    return Dict{String, Any}("AvailabilityZones" => Any["us-west-2b"])
end

enable_availability_zones_for_load_balancer_1_patch = @patch function enable_availability_zones_for_load_balancer(a...; b...)
    return Dict{String, Any}("AvailabilityZones" => Any["us-west-2a", "us-west-2b"])
end

modify_load_balancer_attributes_1_patch = @patch function modify_load_balancer_attributes(a...; b...)
    return Dict{String, Any}("LoadBalancerAttributes" => Dict{String, Any}("CrossZoneLoadBalancing" => Dict{String, Any}("Enabled" => true)), "LoadBalancerName" => "my-load-balancer")
end

modify_load_balancer_attributes_2_patch = @patch function modify_load_balancer_attributes(a...; b...)
    return Dict{String, Any}("LoadBalancerAttributes" => Dict{String, Any}("ConnectionDraining" => Dict{String, Any}("Timeout" => 300, "Enabled" => true)), "LoadBalancerName" => "my-load-balancer")
end

register_instances_with_load_balancer_1_patch = @patch function register_instances_with_load_balancer(a...; b...)
    return Dict{String, Any}("Instances" => Any[Dict{String, Any}("InstanceId" => "i-d6f6fae3"), Dict{String, Any}("InstanceId" => "i-207d9717"), Dict{String, Any}("InstanceId" => "i-afefb49b")])
end


text:
# Use baremodule to shave off a few KB from the serialized `.ji` file
baremodule OpenSpiel_jll
using Base
using Base: UUID
import JLLWrappers

JLLWrappers.@generate_main_file_header("OpenSpiel")
JLLWrappers.@generate_main_file("OpenSpiel", UUID("bd10a763-4654-5023-a028-c4918c6cd33e"))
end  # module OpenSpiel_jll

ext: jl


text:
using CompScienceMeshes
using BEAST

meshfile = Pkg.dir("BEAST","test","sphere.in")
Γ = readmesh(meshfile)
X = raviartthomas(Γ)

els, ad = BEAST.assemblydata(X)

els2 = elements(geometry(X))
ad2 = assemblydata(X)

@assert ad2.data == ad.data
@assert length(els) == numcells(Γ)

resize!(X.fns, 1)

els, ad = BEAST.assemblydata(X)
@assert length(els) == 2
@assert size(ad.data) == (1,3,2)


text:
module Batched

import LinearAlgebra: BLAS

"""
    tr(::AbstractArray{T, 3}) where T

Batched matrix trace.
"""
tr(A::AbstractArray{T, 3}) where T = tr!(A, similar(A, (size(A, 3), )))

function tr!(A::AbstractArray{T, 3}, B::AbstractVector{T}) where T
    @assert size(A, 1) == size(A, 2) "Expect a square matrix" # checksquare
    @boundscheck size(A, 3) == size(B, 1) || error("Batch size mismatch")

    nbatch = size(A, 3)
    n = size(A, 1)
    @inbounds for k in 1:nbatch
        for i in 1:n
            B[k] += A[i, i, k]
        end
    end
    B
end

function gemm! end
function gemm end

for (gemm, elty) in
        ((:dgemm_,:Float64),
         (:sgemm_,:Float32),
         (:zgemm_,:ComplexF64),
         (:cgemm_,:ComplexF32))
    @eval begin
        function gemm!(transA::AbstractChar, transB::AbstractChar, alpha::($elty), A::AbstractArray{$elty, 3}, B::AbstractArray{$elty, 3}, beta::($elty), C::AbstractArray{$elty, 3})
            @assert !BLAS.has_offset_axes(A, B, C)
            @assert size(A, 3) == size(B, 3) == size(C, 3) "batch size mismatch"
            m = size(A, transA == 'N' ? 1 : 2)
            ka = size(A, transA == 'N' ? 2 : 1)
            kb = size(B, transB == 'N' ? 1 : 2)
            n = size(B, transB == 'N' ? 2 : 1)
            if ka != kb || m != size(C,1) || n != size(C,2)
                throw(DimensionMismatch("A has size ($m,$ka), B has size ($kb,$n), C has size $(size(C))"))
            end
            BLAS.chkstride1(A)
            BLAS.chkstride1(B)
            BLAS.chkstride1(C)

            ptrA = Base.unsafe_convert(Ptr{$elty}, A)
            ptrB = Base.unsafe_convert(Ptr{$elty}, B)
            ptrC = Base.unsafe_convert(Ptr{$elty}, C)

            for k in 1:size(A, 3)
                ccall((BLAS.@blasfunc($gemm), BLAS.libblas), Cvoid,
                    (Ref{UInt8}, Ref{UInt8}, Ref{BLAS.BlasInt}, Ref{BLAS.BlasInt},
                     Ref{BLAS.BlasInt}, Ref{$elty}, Ptr{$elty}, Ref{BLAS.BlasInt},
                     Ptr{$elty}, Ref{BLAS.BlasInt}, Ref{$elty}, Ptr{$elty},
                     Ref{BLAS.BlasInt}),
                     transA, transB, m, n,
                     ka, alpha, ptrA, max(1,stride(A,2)),
                     ptrB, max(1,stride(B,2)), beta, ptrC,
                     max(1,stride(C,2)))

                ptrA += size(A, 1) * size(A, 2) * sizeof($elty)
                ptrB += size(B, 1) * size(B, 2) * sizeof($elty)
                ptrC += size(C, 1) * size(C, 2) * sizeof($elty)
            end
            C
        end
        function gemm(transA::AbstractChar, transB::AbstractChar, alpha::($elty), A::AbstractArray{$elty, 3}, B::AbstractArray{$elty, 3})
            gemm!(transA, transB, alpha, A, B, zero($elty), similar(B, $elty, (size(A, transA == 'N' ? 1 : 2), size(B, transB == 'N' ? 2 : 1), size(B, 3))))
        end
        function gemm(transA::AbstractChar, transB::AbstractChar, A::AbstractArray{$elty, 3}, B::AbstractArray{$elty, 3})
            gemm(transA, transB, one($elty), A, B)
        end
    end
end

####################

const _BATCHED_MATRIX_LIST = [
        (:BatchedMatrix, 'N'),
        (:(BatchedTranspose{T, N, <:BatchedMatrix} where N), 'T'),
        (:(BatchedAdjoint{T, N, <:BatchedMatrix} where N), 'C')
]

for (TA, transA) in _BATCHED_MATRIX_LIST, (TB, transB) in _BATCHED_MATRIX_LIST
    @eval function batched_mul!(C::BatchedMatrix{T}, A::$TA, B::$TB) where T
        @boundscheck check_batch_dim_size(A, B, C)
        batchA, batchB, batchC = merge_batch_dim(A), merge_batch_dim(B), merge_batch_dim(C)
        batched_gemm!($transA, $transB, one(T), batchA, batchB, one(T), batchC)
        C
    end
end

function batched_mul!(C::AbstractArray{T, 3}, A::AbstractArray{T, 3}, B::AbstractArray{T, 3}) where T
end

"""
    Transpose{B, T, AT <: AbstractArray{T, 3}} <: AbstractArray{T, 3}

Batched transpose. Transpose a batch of matrix.
"""
struct Transpose{B, T, AT <: AbstractArray{T, 3}} <: AbstractArray{T, 3}
    parent::AT

    Transpose(A::AT) where {T, AT <: AbstractArray{T, 3}} = new{size(A, 3), T, AT}(A)
end

Base.size(m::Transpose) = (size(m.parent, 2), size(m.parent, 1), size(m.parent, 3))
Base.axes(m::Transpose) = (axes(m.parent, 2), axes(m.parent, 1), axes(m.parent, 3))
Base.@propagate_inbounds Base.getindex(m::Transpose, i::Int, j::Int, k::Int) = getindex(m.parent, j, i, k)
Base.@propagate_inbounds Base.setindex!(m::Transpose, v, i::Int, j::Int, k::Int) = setindex!(m.parent, v, j, i, k)
Base.IndexStyle(::Type{<:Transpose}) = IndexCartesian()
batched_transpose(m::AbstractArray{T, 3}) where T = Transpose(m)

gemm!(A::AbstractArray{T, 3}, B::Batched.Transpose, C::AbstractArray{T, 3}) where T =
    gemm!('N', 'T', one(T), A, B.parent, one(T), C)
gemm!(A::Batched.Transpose, B::AbstractArray{T, 3}, C::AbstractArray{T, 3}) where T =
    gemm!('T', 'N', one(T), A.parent, B, one(T), C)

struct BatchedScale{B, K, T, VT <: AbstractVector{T}} <: AbstractArray{T, 3}
    scalar::VT
    BatchedScale{B, K}(scalars::VT) where {B, K, T, VT <: AbstractVector{T}} = new{B, K, T, VT}(scalars)
end

Base.size(x::BatchedScale{B, K, T}) where {B, K, T} = (K, K, B)
Base.@propagate_inbounds Base.getindex(m::BatchedScale{B, K, T}, i::Int, j::Int, k::Int) where {B, K, T} =
    i == j ? getindex(m.scalars, k) : zero(T)
Base.IndexStyle(::Type{<:BatchedScale}) = IndexCartesian()
Base.transpose(A::BatchedScale) = A

end


text:
mutable struct KeepLaneAcc
    a::Float64
    lane::Lane
end

function AutomotiveDrivingModels.propagate(veh::Vehicle, action::KeepLaneAcc, roadway::Roadway, Δt::Float64)
    s_ = veh.state.posF.s + veh.state.v*cos(veh.state.posF.ϕ)*Δt + 0.5*action.a*Δt^2
    s_ = clamp(s_, veh.state.posF.s, get_end(action.lane) )
    # println("From ground truth $(veh.state.posF.s) -> $s_ at $(veh.state.v)")
    v_ = veh.state.v + action.a*Δt # no backup
    v_ = clamp(v_, 0., action.lane.speed_limit.hi)

#     println(action.a, s_, action.lane)
    posF = Frenet(action.lane, s_)
    return VehicleState(posF, roadway, v_)
end

"""
Commonly referred to as IDM
"""
@with_kw mutable struct IntelligentDriverModel2D <: LaneFollowingDriver
    lane::Lane = env.roadway.segments[6].lanes[1]
    a::Float64 = NaN # predicted acceleration
    σ::Float64 = NaN # optional stdev on top of the model, set to zero or NaN for deterministic behavior

    k_spd::Float64 = 1.0 # proportional constant for speed tracking when in freeflow [s⁻¹]

    δ::Float64 = 4.0 # acceleration exponent [-]
    T::Float64  = 1.5 # desired time headway [s]
    v_des::Float64 = 9.0 # desired speed [m/s]
    s_min::Float64 = 5.0 # minimum acceptable gap [m]
    a_max::Float64 = 2.0 # maximum acceleration ability [m/s²]
    d_cmf::Float64 = 2.0 # comfortable deceleration [m/s²] (positive)
    d_max::Float64 = 9.0 # maximum decelleration [m/s²] (positive)
end

AutomotiveDrivingModels.get_name(::IntelligentDriverModel) = "IDM"
function AutomotiveDrivingModels.set_desired_speed!(model::IntelligentDriverModel2D, v_des::Float64)
    model.v_des = v_des
    model
end
function AutomotiveDrivingModels.track_longitudinal!(model::IntelligentDriverModel2D, v_ego::Float64, v_oth::Float64, headway::Float64)

    if !isnan(v_oth) && headway > 0.
        # @assert !isnan(headway) && headway > 0

        Δv = v_oth - v_ego
        s_des = model.s_min + v_ego*model.T - v_ego*Δv / (2*sqrt(model.a_max*model.d_cmf))
        v_ratio = model.v_des > 0.0 ? (v_ego/model.v_des) : 1.0
        model.a = model.a_max * (1.0 - v_ratio^model.δ - (s_des/headway)^2)
    elseif headway < 0.
        model.a = -model.d_max
    else
        # no lead vehicle, just drive to match desired speed
        Δv = model.v_des - v_ego
        model.a = Δv*model.k_spd # predicted accel to match target speed
    end

    @assert !isnan(model.a)

    model.a = clamp(model.a, -model.d_max, model.a_max)

    model
end
function Base.rand(model::IntelligentDriverModel2D)
    if isnan(model.σ) || model.σ ≤ 0.0
        KeepLaneAcc(model.a, model.lane)
    else
        KeepLaneAcc(rand(Normal(model.a, model.σ)), model.lane)
    end
end
function Distributions.pdf(model::IntelligentDriverModel2D, a::KeepLaneAcc)
    if isnan(model.σ) || model.σ ≤ 0.0
        Inf
    else
        pdf(Normal(model.a, model.σ), a.a)
    end
end
function Distributions.logpdf(model::IntelligentDriverModel, a::KeepLaneAcc)
    if isnan(model.σ) || model.σ ≤ 0.0
        Inf
    else
        logpdf(Normal(model.a, model.σ), a.a)
    end
end

function AutomotiveDrivingModels.observe!(model::IntelligentDriverModel2D, scene::Scene, roadway::Roadway, egoid::Int)

    vehicle_index = findfirst(egoid, scene)

    fore = get_neighbor_fore_along_lane(scene, vehicle_index, roadway,
                                        VehicleTargetPointFront(), VehicleTargetPointRear(), VehicleTargetPointFront())
    v_ego = scene[vehicle_index].state.v

    if fore.ind != nothing
        headway, v_oth = fore.Δs, scene[fore.ind].state.v
    else
        headway, v_oth = NaN, NaN
    end

    track_longitudinal!(model, v_ego, v_oth, headway)
#     println(model.a)
    return model
end

max_stars_repo_name: MaximeBouton/AutomotivePOMDPs

max_issues_repo_name: MaximeBouton/AutomotivePOMDPs

max_forks_repo_name: MaximeBouton/AutomotivePOMDPs


text:
using TailRiskScenGen: MonotonicRiskRegion, SurvivorApproximator, prob_nonrisk
using Distributions
using DataStructures
using LinearAlgebra: I
using StatsFuns

d = 2
N = 10000
α = 0.99
β = 0.95
dist = MvNormal(Array{Float64}(I, d, d))
sample = rand(dist, N)
surv = SurvivorApproximator(sample, α)
Ω = MonotonicRiskRegion(surv, 0.95)

x = rand(dist)
x ∈ Ω

tic()
count = 0
for i in 1:1000
    x = rand(dist)
    if !(x ∈ Ω) count+=1 end
end
approx_prob = count/1000
toc()

α = 0.99
β_list = [0.95, 0.99]
num_points_nonrisk = 1000
num_points_surv = 10000
dist = MvNormal(eye(d))
prob_nonrisk(dist, β_list, num_points_surv, num_points_nonrisk, α)


text:
using EffectiveWaves, Test

@testset "match wave low volfrac" begin

## Low volume fraction
    medium = Acoustic(2; ρ=1.0, c=1.0)
    ms = MultipleScattering

    exclusion_distance = 1.005

    specie = Specie(Particle(
        Acoustic(2; ρ=0.5, c=0.5), ms.Circle(0.4));
        volume_fraction=0.001,
        exclusion_distance=exclusion_distance
    )

    θin = 0.2
    tol = 1e-8
    basis_order = 2

    normal = [-1.0,0.0] # an outward normal to the surface
    material = Material(Halfspace(normal),[specie])
    source = PlaneSource(medium, [cos(θin),sin(θin)])

    ωs = [0.2,1.2]

    wave_effs_arr = map(ωs) do ω
        WaveModes(ω, source, material;
            basis_order=basis_order, tol = tol,
            extinction_rescale = false,
            # , mesh_points = 10, mesh_size = 2.0 #, max_Rek = 20.0, max_Imk = 20.0
            num_wavenumbers = 1)
    end

    k_eff_φs = wavenumber_low_volumefraction(ωs, medium, [specie]; basis_order=basis_order)

    wave_eff_φs = [
        WaveMode(ωs[i], k_eff_φs[i], source, material; tol = tol,
            basis_order=basis_order,
            extinction_rescale = true)
    for i in eachindex(ωs)]

    @test maximum(abs.([w[1].wavenumber for w in wave_effs_arr] - k_eff_φs)) < 1e-7

    # Check that the eigenmodes point the same direction
    ds = [
        abs(dot(wave_eff_φs[i].eigenvectors[:], wave_effs_arr[i][1].eigenvectors[:]))
    for i in eachindex(ωs)]
    ds2 = [
        norm(wave_eff_φs[i].eigenvectors[:])*norm(wave_effs_arr[i][1].eigenvectors[:])
    for i in eachindex(ωs)]
    @test maximum(ds./ds2 .- 1.0) < tol

    match_ws = [
        MatchPlaneWaveMode(ωs[i], source, material;
            max_size=150,
            tol = tol, wave_effs = wave_effs_arr[i][1:1])
    for i in eachindex(ωs)]

    @test maximum(match_error(m,material.shape) for m in match_ws) < 1e-6

    avg_wave_φs = [
        DiscretePlaneWaveMode(match_ws[i].discrete_wave.x, wave_eff_φs[i], material.shape)
    for i in eachindex(ωs)]

    Rs_near = [reflection_coefficient(ωs[i], match_ws[i].discrete_wave, source, material) for i in eachindex(ωs)]
    Rs_near_φ = [reflection_coefficient(ωs[i], avg_wave_φs[i], source, material) for i in eachindex(ωs)]
    @test maximum(abs.(Rs_near_φ - Rs_near)) < 5e-6

    Rs = [reflection_coefficient(ωs[i], match_ws[i], source, material) for i in eachindex(ωs)]
    Rs_φ = [reflection_coefficient(ωs[i], wave_eff_φs[i], source, material) for i in eachindex(ωs)]
    @test maximum(abs.(Rs_near_φ - Rs_near)) < 5e-6

    @test maximum(
        norm(match_ws[i].discrete_wave.amplitudes[:] .- avg_wave_φs[i].amplitudes[:])/norm(avg_wave_φs[i].amplitudes[:])
    for i in eachindex(ωs)) < 1e-3
end

max_stars_repo_name: arturgower/EffectiveWaves


text:
module BacktestEngine

function test()
    return 3
end

#export load_ticks

#include("types.jl")

# function load_ticks(path::String)::Vector{Tick}
#     csv = CSV.read(path, normalizenames=true)
#     asks = csv[:Ask]
#     bids = csv[:Bid]
#     vol = csv[:Volume]
#     last = csv[:Last]
#     d = csv[:Date]
#     t = csv[:Time]

#     ticks = Vector{Tick}(undef, length(asks))
#     for i in 1:length(t)
#         dt = d[i]+t[i]
#         t = Tick(dt, asks[i], bids[i], vol[i])
#         ticks[i] = t
#     end
#     return ticks
# end

end
end


text:
using DashBootstrapComponents, DashHtmlComponents

nav1 = dbc_nav(
    [
        dbc_navitem(dbc_navlink("A link", href = "#")),
        dbc_navitem(dbc_navlink("Another link with a longer label", href = "#")),
    ],
    fill = true,
);

nav2 = dbc_nav(
    [
        dbc_navitem(dbc_navlink("A link", href = "#")),
        dbc_navitem(dbc_navlink("Another link with a longer label", href = "#")),
    ],
    justified = true,
);

navs = html_div([nav1, html_hr(), nav2]);

max_stars_repo_name: glsdown/dash-bootstrap-components

max_issues_repo_name: glsdown/dash-bootstrap-components

max_forks_repo_name: glsdown/dash-bootstrap-components


text:
export px, inch, mm, cm, defaultparams

# useful units

const px = 1
const inch = 72
const mm = inch/25.4
const cm = 10mm

# graphical default parameters

"Graphical default parameters for GViz"
defaultparams = Dict(:margin_top => 2mm,              # margin between data view and frame
                     :margin_bottom => 2mm,
                     :margin_left => 20mm,
                     :margin_right => 10mm,

                     :title_font => "SansSerif bold 8",
                     :title_color => "grey",

                     :axis_font => "SansSerif bold 6",
                     :axis_color => "grey",
                     :axis_line_width => 1,
                     :axis_tick_length => 5,

                     :base_line_y => 0.0,
                     :base_line_color => "lightgrey",
                     :base_line_width => 1,

                     # DataTrack layers
                     :shape => "circle",
                     :size => 2,
                     :border => 0.1,
                     :color => "black",
                     :fill => "red",
                     :alpha => 0.5,

                     # GenomicFeatureTrack
                     :feature_height => 6mm,
                     :feature_fill => "lightblue",
                     :feature_label_font => "Sansserif 6",
                     :feature_label_color => "grey",
                     :feature_label_spacing => 1mm,

                     :width => 20cm,
                     :height => 3cm)


text:
using MultiscaleGraphSignalTransforms, LightGraphs, Plots, LaTeXStrings

## Build Graph
N = 128; G = path_graph(N)
X = zeros(N,2); X[:, 1] = 1:N
L = Matrix(laplacian_matrix(G))
𝛌, 𝚽 = eigen(L); standardize_eigenvectors!(𝚽)
W = 1.0 * adjacency_matrix(G)

G_Sig = GraphSig(W, xy = X)
GP = partition_tree_fiedler(G_Sig; swapRegion = false)

function anti_diag(A)
    N = size(A, 1)
    return [A[i, N+1-i] for i = 1:N]
end

## construct 1D smooth orthogonal projector
ϵa = 16
pair_inds = vcat(vec((64 + ϵa):-1:65)', vec((64 - ϵa + 1):64)')
Uf = Matrix{Float64}(I, N, N)
β = 64.5

for i in 1:size(pair_inds, 2)
    pv, nv = pair_inds[:, i]
    t = abs(pv - β) / ϵa
    Uf[pv, pv] = rising_cutoff(t)
    Uf[pv, nv] = rising_cutoff(-t)
    Uf[nv, pv] = -rising_cutoff(-t)
    Uf[nv, nv] = rising_cutoff(t)
end

P0 = Uf' * diagm(χ(1:64, N)) * Uf
P1 = Uf' * diagm(χ(65:N, N)) * Uf

##
distDCT = zeros(N,N)
for i in 1:N-1, j = i+1:N
    distDCT[i,j] = abs(i-j)
end
distDCT = distDCT + distDCT'

function path_spectrogram(f, D, 𝚽; c = 0.01)
    N = length(f)
    dmatrix = zeros(N, N)
    for l = 1:N
        P = 𝚽 * diagm(nat_spec_filter(l, D; σ = c * maximum(D))) * 𝚽'
        for x = 1:N
            ψ = P * spike(x, N)
            ψ ./= norm(ψ, 2)
            dmatrix[l, x] = ψ' * f
        end
    end
    heatmap(abs.(dmatrix), c = :thermal, ratio = 1, xlim = [0.5, N+1],
            ylim = [0.5, N], xlab = latexstring("x"), ylab = latexstring("l"),
            guidefontsize = 18)
    xticks!([16:16:128;], [string(k) for k in 16:16:128])
    yticks!([1;16:16:128], vcat("DC", [string(k) for k in 15:16:127]))
    plt = plot!(size = (500, 400))
    return plt
end


text:
# This file is part of InvertedFiles.jl

function push_posting_list!(Q, idx::WeightedInvertedFile, tokenID, val)
	@inbounds p = PostingList(idx.lists[tokenID], idx.weights[tokenID], convert(Float64, val))
	push!(Q, p)
end

"""
search(callback::Function, idx::WeightedInvertedFile, Q, P; t=1)

Find candidates for solving query `Q` using `idx`. It calls `callback` on each candidate `(objID, dist)`

# Arguments:
- `callback`: callback function on each candidate
- `idx`: inverted index
- `Q`: the set of involved posting lists, see [`prepare_posting_lists_for_querying`](@ref)
- `P`: a vector of starting positions in Q (initial state as ones)
"""
function search(callback::Function, idx::WeightedInvertedFile, Q, P_, t)
	umerge(Q, P_; t) do L, P, m
		@inbounds w = 1.0 - L[1].val * L[1].W[P[1]]
		@inbounds objID = L[1].I[P[1]]
		@inbounds @simd for i in 2:m
			w -= L[i].val * L[i].W[P[i]]
		end

		callback(objID, w)
	end
end


text:
module Jalerkin

using LinearAlgebra
using StaticArrays

include("Definitions.jl")
include("Macros.jl")
include("Mesh.jl")
include("Quadrature.jl")
include("FiniteElement.jl")

export fp_precision
export Mesh, GenericMesh1D, Mesh1D, Point, Cell
export get_cell_vector, get_coord_vector
export Quadrature, TrapezoidQuadrature, GaussianQuadrature
export get_nodes, get_weights, quad
export FiniteElement, Lagrange, get_order, get_mesh, get_quad_rule

end # module

max_stars_repo_name: rrgalvan/Jalerkin

max_issues_repo_name: rrgalvan/Jalerkin

max_forks_repo_name: rrgalvan/Jalerkin


text:
####################################################################################################
"""
This function checks whether the solution with eigenvalues `eigvalues` is stable and also compute the number of unstable eigenvalues with nonzero imaginary part
"""
function isStable(contparams::ContinuationPar, eigvalues)::Tuple{Bool, Int64, Int64}
	# the return type definition above is to remove type instability in continuation
	# numerical precision for deciding if an eigenvalue is above a threshold
	precision = contparams.precisionStability

	# update number of unstable eigenvalues
	n_unstable = mapreduce(x -> real(x) > precision, +, eigvalues)

	# update number of unstable eigenvalues with nonzero imaginary part
	n_imag = mapreduce(x -> (abs(imag(x)) > precision) * (real(x) > precision), +, eigvalues)
	return n_unstable == 0, n_unstable, n_imag
end
isStable(contparams::ContinuationPar, ::Nothing) = (true, 0, 0)

# we detect a bifurcation by a change in the number of unstable eigenvalues
function detectBifucation(state::ContState)
	n1, n2 = state.n_unstable
	# deals with missing value encoded by n_unstable = -1
	if n1 == -1 || n2 == -1; return false; end
	# detect a bifurcation if the numbers do not match
	return n1 !== n2
end
####################################################################################################

# Test function for Fold bifurcation
@inline detectFold(p1, p2, p3) = (p3 - p2) * (p2 - p1) < 0

function locateFold!(contparams::ContinuationPar, contres::ContResult, z, tau, normC, printsolution, verbosity)
	branch = contres.branch
	# Fold point detection based on continuation parameter monotony
	if contparams.detectFold && length(branch) > 2 && detectFold(branch[end-2:end].param...)
		(verbosity > 0) && printstyled(color=:red, "--> Fold bifurcation point in ", getinterval(branch[end-1].param, branch[end].param), "\n")
		npar = length( branch[1]) - 9
		push!(contres.specialpoint, SpecialPoint(
			type = :fold,
			idx = length(branch)-1,
			param = branch[end-1].param,
			norm = normC(z.u),
			printsol = NamedTuple{keys(branch[end-1])[1:npar]}(values(branch[end-1])[1:npar]),
			x = _copy(z.u), tau = copy(tau),
			ind_ev = 0,
			# it means the fold occurs between step-2 and step:
			step = length(branch)-1,
			status = :guess,
			δ = (0, 0),
			precision = -1.,
			interval = (branch[end-1].param, branch[end-1].param)))
		return true
	else
		return false
	end
end

locateFold!(contres::ContResult, iter::ContIterable, state::ContState) = locateFold!(iter.contParams, contres, getSolution(state), state.tau, iter.normC, iter.recordFromSolution, iter.verbosity)
####################################################################################################
"""
Function for coarse detection of bifurcation points.
"""
function getBifurcationType(contparams::ContinuationPar, state, normC, printsolution, verbosity, status::Symbol, interval::Tuple{T, T}) where T
	# this boolean ensures that edge cases are handled
	detected = false

	# get current number of unstable eigenvalues and
	# unstable eigenvalues with nonzero imaginary part
	n_unstable, n_unstable_prev = state.n_unstable
	n_imag, n_imag_prev = state.n_imag

	# computation of the index of the bifurcating eigenvalue
	ind_ev = n_unstable < n_unstable_prev ? n_unstable_prev : n_unstable
	# bifurcation type
	tp = :none

	δn_unstable = abs(n_unstable - n_unstable_prev)
	δn_imag		= abs(n_imag - n_imag_prev)

	# codim 1 bifurcation point detection based on eigenvalues distribution
	if δn_unstable == 1
	# In this case, only a single eigenvalue crossed the imaginary axis
	# Either it is a Branch Point
		if δn_imag == 0
			tp = :bp
		elseif δn_imag == 1
	# Either it is a Hopf bifurcation for a Complex valued system
			tp = contparams.newtonOptions.eigsolver isa AbstractFloquetSolver ? :pd : :hopf
		else # I dont know what bifurcation this is
			tp = :nd
		end
		detected = true
	elseif δn_unstable == 2
		if δn_imag == 2
			tp = contparams.newtonOptions.eigsolver isa AbstractFloquetSolver ? :ns : :hopf
		else
			tp = :nd
		end
		detected = true
	elseif δn_unstable > 2
		tp = :nd
		detected = true
	end

	if δn_unstable < δn_imag
		@warn "Error in eigenvalues computation. It seems that an eigenvalue is missing, probably conj(λ) for some already computed eigenvalue λ. This makes the identification (but not the detection) of bifurcation points erroneous. You should increase the number of requested eigenvalues `nev`."
		tp = :nd
		detected = true
	end

	# rule out initial condition where we populate n_unstable = (-1,-1) and n_imag = (-1,-1)
	if prod(state.n_unstable) < 0 || prod(state.n_imag) < 0
		tp = :nd
		detected = true
	end

	if detected
		# record information about the bifurcation point
		# because of the way the results are recorded, with state corresponding to the (continuation) step = 0 saved in br.branch[1], it means that br.eig[k] corresponds to state.step = k-1. Thus, the eigen-elements (and other information)  corresponding to the current bifurcation point are saved in br.eig[step+1]
		specialpoint = SpecialPoint(state, tp, status, printsolution, normC, interval;
			δ = (n_unstable - n_unstable_prev, n_imag - n_imag_prev),
			idx = state.step + 1,
			ind_ev = ind_ev)
		(verbosity>0) && printstyled(color=:red, "--> ", tp, " Bifurcation point at p ≈ ", getp(state), ", δn_unstable = ", δn_unstable,",  δn_imag = ", δn_imag, "\n")
	else
		throw("We could not detect/identify the bifurcation point. (δn_unstable, δn_imag) = ($δn_unstable, $δn_imag)")
	end
	return detected, specialpoint
end

"""
Function to locate precisely bifurcation points using a bisection algorithm. We make sure that at the end of the algorithm, the state is just after the bifurcation point (in the s coordinate).
"""
function locateBifurcation!(iter::ContIterable, _state::ContState, verbose::Bool = true)
	@assert detectBifucation(_state) "No bifurcation detected for the state"
	verbose && println("----> Entering [Locate-Bifurcation], state.n_unstable = ", _state.n_unstable)

	# type of scalars in iter
	_T = eltype(iter)

	# number of unstable eigenvalues after, before the bifurcation point
	n2, n1 = _state.n_unstable
	if n1 == -1 || n2 == -1 return :none, (_T(0), _T(0)) end

	# get continuation parameters
	contParams = iter.contParams

	if abs(_state.ds) < contParams.dsmin; return :none, (_T(0), _T(0)); end
	verbose && println("----> [Bisection] initial ds = ", _state.ds)

	# we create a new state for stepping through the continuation routine
	after = copy(_state)	# after the bifurcation point
	state = copy(_state)	# current state of the bisection
	before = copy(_state)	# before the bifurcation point

	# we reverse some indicators for `before`. It is OK, it will never be used other than for getp(before)
	before.n_unstable = (before.n_unstable[2], before.n_unstable[1])
	before.n_imag = (before.n_imag[2], before.n_imag[1])
	before.z_pred.p, before.z_old.p = before.z_old.p, before.z_pred.p

	# the bifurcation point is before the current state so we want to first iterate backward with
	# half step size. We turn off stepsizecontrol because it would not make a bisection otherwise
	state.ds *= -1
	state.step = 0
	state.stepsizecontrol = false

	# this variable is used for the iterator
	next = (state, state)

	# record sequence of unstable eigenvalue number and parameters
	nunstbls = [n2]
	nimags   = [state.n_imag[1]]

	# interval which contains the bifurcation point
	interval = getinterval(getp(state), getpreviousp(state))

	# index of active index in the bisection interval, allows to track interval
	indinterval = interval[1] == getp(state) ? 1 : 2

	verbose && println("----> [Bisection] state.ds = ", state.ds)

	# we put this to be able to reference it at the end of this function
	# we don't know its type yet
	eiginfo = nothing

	# we compute the number of changes in n_unstable
	n_inversion = 0
	status = :guess

	biflocated = false

	# for a polynomial tangent predictor, we disable the update of the predictor parameters
	# TODO Find better way to do this
	if iter.tangentAlgo isa PolynomialPred
		iter.tangentAlgo.update = false
	end

	# emulate a do-while
	while true
		if ~state.isconverged
			@error "Newton failed to fully locate bifurcation point using bisection parameters!"
			break
		 end

		# if PALC stops, break the bisection
		if isnothing(next)
			break
		end

		# perform one continuation step
		(_, state) = next

		# the eigenelements have been computed/stored in state during the call iterate(iter, state)
		push!(nunstbls, state.n_unstable[1])
		push!(nimags, state.n_imag[1])

		if nunstbls[end] == nunstbls[end-1]
			# bifurcation point still after current state, keep going
			state.ds /= 2
		else
			# we passed the bifurcation point, reverse continuation
			state.ds /= -2
			n_inversion += 1
			indinterval = (indinterval == 2) ? 1 : 2
		end

		if iseven(n_inversion)
			copyto!(after, state)
		else
			copyto!(before, state)
		end

		state.step > 0 && (interval = @set interval[indinterval] = getp(state))

		# we call the finalizer
		# iter.finaliseSolution(state.z_old, state.tau, state.step, nothing; bisection = true)

		if verbose
			ct0 = rightmost(state.eigvals)
			printstyled(color=:blue,
				"----> $(state.step) - [Bisection] (n1, n_current, n2) = ", (n1, nunstbls[end], n2),
				", ds = ", state.ds, " p = ", getp(state), ", #reverse = ", n_inversion,
				"\n----> bifurcation ∈ ", getinterval(interval...),
				", precision = ", @sprintf("%.3E", interval[2] - interval[1]),
				"\n----> ", length(ct0)," Eigenvalues closest to ℜ=0:\n")
			verbose && Base.display(ct0[1:min(5, length(ct0))])
		end

		biflocated = abs(real.(rightmost(state.eigvals))[1]) < contParams.tolBisectionEigenvalue

		if (isnothing(next) == false &&
				abs(state.ds) >= contParams.dsminBisection &&
				state.step < contParams.maxBisectionSteps &&
				n_inversion < contParams.nInversion &&
				biflocated == false) == false
			break
		end

		next = iterate(iter, state; _verbosity = 0)
	end

	verbose && printstyled(color=:red, "----> Found at p = ", getp(state), ", δn = ", abs(2nunstbls[end]-n1-n2),", δim = ",abs(2nimags[end]-sum(state.n_imag))," from p = ",getp(_state),"\n")

	if iter.tangentAlgo isa PolynomialPred
		iter.tangentAlgo.update = true
	end

	######## update current state ########
	# So far we have (possibly) performed an even number of bifurcation crossings
	# we started at the right of the bifurcation point. The current state is thus at the
	# right of the bifurcation point if iseven(n_inversion) == true. Otherwise, the bifurcation
	# point is still deemed undetected
	if iseven(n_inversion)
		status = n_inversion >= contParams.nInversion ? :converged : :guess
		copyto!(_state.z_pred, state.z_pred)
		copyto!(_state.z_old,  state.z_old)
		copyto!(_state.tau, state.tau)

		_state.eigvals = state.eigvals
		if saveEigenvectors(contParams)
			_state.eigvecs = state.eigvecs
		end

		# to prevent bifurcation detection, update the following numbers carefully
		# since the current state is after the bifurcation point, we just save
		# the current state of n_unstable and n_imag
		_state.n_unstable = (state.n_unstable[1], before.n_unstable[1])
		_state.n_imag = (state.n_imag[1], before.n_imag[1])

		# previous_p = n_inversion == 0 ? before.z_pred.p : getp(before)
		interval = (getp(state), getp(before))
	else
		status = :guessL
		copyto!(_state.z_pred, after.z_pred)
		copyto!(_state.z_old,  after.z_old)
		copyto!(_state.tau, after.tau)

		_state.eigvals = after.eigvals
		if contParams.saveEigenvectors
			_state.eigvecs = after.eigvecs
		end

		# to prevent bifurcation detection, update the following numbers carefully
		# since the current state is before the bifurcation point, we just save
		# the current state of n_unstable and n_imag
		_state.n_unstable = (after.n_unstable[1], state.n_unstable[1])
		_state.n_imag = (after.n_imag[1], state.n_imag[1])
		interval = (getp(state), getp(after))
	end
	verbose && println("----> Leaving [Loc-Bif]")
	return status, getinterval(interval...)
end


text:
using StatsBase

for path in ["94.jl.85.out", "94.jl.90.out"]
    X = readcsv(path)
    N = size(X,1)

    C = corspearman(map(Float64,X[:,3]), map(Float64, X[:,4]))
    println("Spearman correlation for $path: $C")

    v1 = map(Float64,X[:,3])
    v2 = map(Float64,X[:,4])
    r1 = similar(v1)
    r2 = similar(v2)

    r1[sortperm(v1)] = 1:N
    r2[sortperm(v2)] = 1:N

    C = 1 - 6*sum((r1 - r2).*(r1 - r2)) / (N^3-N)
    println("(manual computation) Spearman correlation for $path: $C")
end


text:
@testset "Chebyshev polynomials" begin
    c = Chebyshev(5)
    @test points(c)[3] === 0.0
    test_univariate(c, 5, -1..1; xs = linspace(-1,1,10), f = x->x^2+9*x-7)
end

@testset "Domain transformations" begin
    test_univariate(DomainTrans(ℝ⁺, Chebyshev(10)), 10, ℝ⁺; xs = linspace(0,5,10), f = x -> 1/(1+x)^2)
    test_univariate(DomainTrans(ℝ, REALCIRCLE, Chebyshev(10)), 10, ℝ;
                    xs = linspace(0,5,10), f = x -> 1/(1+x^2))
end

@testset "Value transformations" begin
    fam = ValueTrans(x->Shift(-log(x+one(x))), Chebyshev(10))
    test_univariate(fam, 10, -1..1;
                    xs = linspace(-1,1,10),
                    test_basis = false,
                    f = x -> 1/(2+x)^2+log(x+1),
                    test_partial = false)
    @test_throws ArgumentError basis(fam, 0.0)
end

@testset "Parametric function semantics" begin
    fam = Chebyshev(10)
    θ = rand_θ(10)
    f = ParametricFunction(fam, θ)
    @test degf(f) == 10
    @test parameters(f) == θ
    @test points(f) == points(fam)
    @test domain(f) == domain(fam)
end


text:
import AutomotiveDrivingModels.observe!

mutable struct IDMDriver <: LaneFollowingDriver
    a::Float64 # predicted acceleration
    σ::Float64 # optional stdev on top of the model, set to zero or NaN for deterministic behavior

    k_spd::Float64 # proportional constant for speed tracking when in freeflow [s⁻¹]

    δ::Float64 # acceleration exponent [-]
    T::Float64 # desired time headway [s]
    v_des::Float64 # desired speed [m/s]
    s_min::Float64 # minimum acceptable gap [m]
    a_max::Float64 # maximum acceleration ability [m/s²]
    d_cmf::Float64 # comfortable deceleration [m/s²] (positive)
    d_max::Float64 # maximum decelleration [m/s²] (positive)
    max_horizon::Float64

    function IDMDriver(;
        σ::Float64     =   NaN,
        k_spd::Float64 =   1.0,
        δ::Float64     =   4.0,
        T::Float64     =   1.5,
        v_des::Float64 =  29.0, # typically overwritten
        s_min::Float64 =   5.0,
        a_max::Float64 =   3.0,
        d_cmf::Float64 =   2.0,
        d_max::Float64 =   9.0,
        max_horizon::Float64 = 35.0,
        )

        retval = new()
        retval.a     = NaN
        retval.σ     = σ
        retval.k_spd = k_spd
        retval.δ     = δ
        retval.T     = T
        retval.v_des = v_des
        retval.s_min = s_min
        retval.a_max = a_max
        retval.d_cmf = d_cmf
        retval.d_max = d_max
        retval.max_horizon = max_horizon
        retval
    end
end
get_name(::IDMDriver) = "IDM"
function set_desired_speed!(model::IDMDriver, v_des::Float64)
    model.v_des = v_des
    model
end

function AutomotiveDrivingModels.observe!(model::IDMDriver, scene::Frame{Entity{VehicleState, BicycleModel, Int}}, roadway::Roadway, egoid::Int)
    vehicle_index = findfirst(scene, egoid)

    fore = get_neighbor_fore_along_lane(scene, vehicle_index, roadway, VehicleTargetPointFront(), VehicleTargetPointRear(), VehicleTargetPointFront(),max_distance_fore=model.max_horizon)

    v_ego = scene[vehicle_index].state.v
    v_oth = NaN
    headway = NaN

    if fore.ind != 0
        v_oth = scene[fore.ind].state.v
        headway = fore.Δs
    end

    track_longitudinal!(model, v_ego, v_oth, headway)

    return model
end

function AutomotiveDrivingModels.observe!(model::IDMDriver, scene::Scene, roadway::Roadway, egoid::Int)
    vehicle_index = findfirst(scene, egoid)

    fore = get_neighbor_fore_along_lane(scene, vehicle_index, roadway, VehicleTargetPointFront(), VehicleTargetPointRear(), VehicleTargetPointFront(),max_distance_fore=model.max_horizon)

    v_ego = scene[vehicle_index].state.v
    v_oth = NaN
    headway = NaN

    if fore.ind != 0
        v_oth = scene[fore.ind].state.v
        headway = fore.Δs
    end

    track_longitudinal!(model, v_ego, v_oth, headway)

    return model
end

function track_longitudinal!(model::IDMDriver, v_ego::Float64, v_oth::Float64, headway::Float64)

    if !isnan(v_oth)
        #@assert !isnan(headway) && headway > 0
        if headway > 0.0
            Δv = v_oth - v_ego
            s_des = model.s_min + v_ego*model.T - v_ego*Δv / (2*sqrt(model.a_max*model.d_cmf))
            v_ratio = model.v_des > 0.0 ? (v_ego/model.v_des) : 1.0
            model.a = model.a_max * (1.0 - v_ratio^model.δ - (s_des/headway)^2)
        # elseif headway > -3.0
        #    model.a = -model.d_max
        else
            Δv = model.v_des - v_ego
            model.a = Δv*model.k_spd
        end
    else
        # no lead vehicle, just drive to match desired speed
        Δv = model.v_des - v_ego
        model.a = Δv*model.k_spd # predicted accel to match target speed
    end

	if isnan(model.a)
		println("headway: ",headway, " v_oth: ",v_oth, " v_ego: ",v_ego)
	end
    @assert !isnan(model.a)

    model.a = clamp(model.a, -model.d_max, model.a_max)

    model
end

function Base.rand(model::IDMDriver)
    if isnan(model.σ) || model.σ ≤ 0.0
        LaneFollowingAccel(model.a)
    else
        LaneFollowingAccel(rand(Normal(model.a, model.σ)))
    end
end
function Distributions.pdf(model::IDMDriver, a::LaneFollowingAccel)
    if isnan(model.σ) || model.σ ≤ 0.0
        Inf
    else
        pdf(Normal(model.a, model.σ), a.a)
    end
end
function Distributions.logpdf(model::IDMDriver, a::LaneFollowingAccel)
    if isnan(model.σ) || model.σ ≤ 0.0
        Inf
    else
        logpdf(Normal(model.a, model.σ), a.a)
    end
end


text:
# TODO: implement methods for n-dim arrays

"""
    FlipX <: Augmentor.AffineOperation

Description
--------------

Reverses the x-order of each pixel row. Another way of describing
it would be that it mirrors the image on the y-axis, or that it
mirrors the image horizontally.

If created using the parameter `p`, the operation will be lifted
into `Either(p=>FlipX(), 1-p=>NoOp())`, where `p` denotes the
probability of applying `FlipX` and `1-p` the probability for
applying [`NoOp`](@ref). See the documentation of
[`Either`](@ref) for more information.

Usage
--------------

    FlipX()

    FlipX(p)

Arguments
--------------

- **`p::Number`** : Optional. Probability of applying the
    operation. Must be in the interval [0,1].

See also
--------------

[`FlipY`](@ref), [`Either`](@ref), [`augment`](@ref)

Examples
--------------

```jldoctest
julia> using Augmentor

julia> img = [200 150; 50 1]
2×2 Array{Int64,2}:
 200  150
  50    1

julia> img_new = augment(img, FlipX())
2×2 Array{Int64,2}:
 150  200
   1   50
```
"""
struct FlipX <: AffineOperation end
FlipX(p::Number) = Either(FlipX(), p)

@inline supports_stepview(::Type{FlipX}) = true

toaffinemap(::FlipX, img::AbstractMatrix) = recenter(@SMatrix([1. 0; 0 -1.]), center(img))
applyeager(::FlipX, img::Array, param) = plain_array(reverse(img; dims=2))
applyeager(op::FlipX, img::AbstractArray, param) = plain_array(applystepview(op, img, param))
applylazy_fallback(op::FlipX, img::AbstractMatrix, param) = applystepview(op, img, param)

function applystepview(::FlipX, img::AbstractMatrix, param)
    idx = map(i->1:1:length(i), axes(img))
    indirect_view(img, (idx[1], reverse(idx[2])))
end

function showconstruction(io::IO, op::FlipX)
    print(io, typeof(op).name.name, "()")
end

function Base.show(io::IO, op::FlipX)
    if get(io, :compact, false)
        print(io, "Flip the X axis")
    else
        print(io, "Augmentor.")
        showconstruction(io, op)
    end
end

# --------------------------------------------------------------------

"""
    FlipY <: Augmentor.AffineOperation

Description
--------------

Reverses the y-order of each pixel column. Another way of
describing it would be that it mirrors the image on the x-axis,
or that it mirrors the image vertically.

If created using the parameter `p`, the operation will be lifted
into `Either(p=>FlipY(), 1-p=>NoOp())`, where `p` denotes the
probability of applying `FlipY` and `1-p` the probability for
applying [`NoOp`](@ref). See the documentation of
[`Either`](@ref) for more information.

Usage
--------------

    FlipY()

    FlipY(p)

Arguments
--------------

- **`p::Number`** : Optional. Probability of applying the
    operation. Must be in the interval [0,1].

See also
--------------

[`FlipX`](@ref), [`Either`](@ref), [`augment`](@ref)

Examples
--------------

```jldoctest
julia> using Augmentor

julia> img = [200 150; 50 1]
2×2 Array{Int64,2}:
 200  150
  50    1

julia> img_new = augment(img, FlipY())
2×2 Array{Int64,2}:
  50    1
 200  150
```
"""
struct FlipY <: AffineOperation end
FlipY(p::Number) = Either(FlipY(), p)

@inline supports_stepview(::Type{FlipY}) = true

toaffinemap(::FlipY, img::AbstractMatrix) = recenter(@SMatrix([-1. 0; 0 1.]), center(img))
applyeager(::FlipY, img::Array, param) = plain_array(reverse(img; dims=1))
applyeager(op::FlipY, img::AbstractArray, param) = plain_array(applystepview(op, img, param))
applylazy_fallback(op::FlipY, img::AbstractMatrix, param) = applystepview(op, img, param)

function applystepview(::FlipY, img::AbstractMatrix, param)
    idx = map(i->1:1:length(i), axes(img))
    indirect_view(img, (reverse(idx[1]), idx[2]))
end

function showconstruction(io::IO, op::FlipY)
    print(io, typeof(op).name.name, "()")
end

function Base.show(io::IO, op::FlipY)
    if get(io, :compact, false)
        print(io, "Flip the Y axis")
    else
        print(io, "Augmentor.")
        showconstruction(io, op)
    end
end


text:
abstract type SubspaceQueryStrategy <: QueryStrategy end

function get_query_objects(qs::SubspaceQueryStrategy,
                        query_data::Array{T, 2} where T <: Real,
                        pools::Vector{Symbol},
                        global_indices::Vector{Int},
                        history::Vector{Vector{Int}})::Vector{Int}
    pool_map = MLLabelUtils.labelmap(pools)
    haskey(pool_map, :U) || throw(ArgumentError("No more points that are unlabeled."))
    scores = qs_score(qs, query_data, pool_map)
    @assert length(scores) == size(query_data, 2)
    all_history_values = (length(history) > 0) && (collect(Iterators.flatten(history)))
    candidates = [i for i in pool_map[:U] if global_indices[i] ∉ all_history_values]
    debug(getlogger(@__MODULE__), "[QS] Selecting from $(length(candidates)) candidates.")
    local_query_index = candidates[argmax(scores[candidates])]
    return [global_indices[local_query_index]]
end


text:
const _pkg_root = dirname(dirname(@__FILE__))
const _pkg_deps = joinpath(_pkg_root,"deps")
const _pkg_assets = joinpath(_pkg_root,"assets")

!isdir(_pkg_assets) && mkdir(_pkg_assets)

download("http://knockoutjs.com/downloads/knockout-3.4.2.js", joinpath(_pkg_assets, "knockout.js"))
download("http://mbest.github.io/knockout.punches/knockout.punches.min.js", joinpath(_pkg_assets, "knockout_punches.js"))


text:
""" PHYSICAL Solver: \n
# ==================================================== \n
Type : Module \n
Inner fucntions : \n
    {SimulationDataStruct} = Solver.PreProcessing({AnalysisDataStruct}) \n
    nothing = Solver.Update({SimulationDataStruct},{AnalysisDataStruct}) \n
# ==================================================== \n """
module Solver
export
    PreProcessing,
    Update

include("InitializingFunctions.jl")
include("PhysicalFunctions.jl")

    """ PREPROCESSING in SOVER """
    function PreProcessing(inAnalysisDataStruct::AnalysisDataStruct)
        inSimulationDataStruct = SimulationDataStruct(GenParticles(inAnalysisDataStruct),
                                                      GenGrids(inAnalysisDataStruct),
                                                      GenPhases(inAnalysisDataStruct),
                                                      GenBoundaries(inAnalysisDataStruct),
                                                      [inAnalysisDataStruct.nMaxParticles]        # initializing number of Alive Particles
                                                     )
        return inSimulationDataStruct
    end #PreProcessing

    """ Physical Calculation for a Time Step """
    function Update(inSimDataStruct::SimulationDataStruct, inAnsDataStruct::AnalysisDataStruct)
        Δt = inAnsDataStruct.timeStep.dt
        gravity = inAnsDataStruct.gravity

        CalculateGravityForce(inSimDataStruct,gravity,Δt)
        for iteration in 1:3
            CalculateGridId(inSimDataStruct, inAnsDataStruct)
            sort!(inSimDataStruct.particles)
            CalculateLambda(inSimDataStruct, inAnsDataStruct)
            CalculatePositionCorrection(inSimDataStruct, inAnsDataStruct)
        end
        CalculateUpdate(inSimDataStruct,Δt)
    end
end

max_stars_repo_name: Hipgineer/JuliaPBF

max_issues_repo_name: Hipgineer/JuliaPBF

max_forks_repo_name: Hipgineer/JuliaPBF


text:
"""
    F = JopErf(spc)

where `F` is the error function operator with domain and range given by `spc::JetSpace`.
we use 'erf'(z) = 2/√π ∫_{0}^{z} \exp{-t^2}dt,
the derivative of which is 2/√π  \exp{-z^2}.
We expect the domain and range to be real.
"""
JopErf(spc::JetSpace) = JopNl(dom = spc, rng = spc, f! = JopErf_f!, df! = JopErf_df!)
export JopErf

# TODO: assert denominator in linearization is > 0

JopErf_f!(d::AbstractArray{T}, m::AbstractArray{T}) where {T<:Real} = d .= erf.(m)
JopErf_df!(δd::AbstractArray{T}, δm::AbstractArray{T}; mₒ) where {T<:Real} = δd .= (2/sqrt( π )) .* exp.(-mₒ.^2) .* δm


text:
#= --------------------------------------------------------- =#
mutable struct TBModel{T<:Real}
    pos::Matrix{T}  		# position of elements in unit cell
    periodicity::BitVector  # periodicity along latx,laty,latz (boolean = yes/no)
    neighbors::Vector{Vector{Int}} # neighboring indices
	hopfun::Function        # hopping function (input is two positions; output is basisdim×basisdim array)
    basisdim::Int           # dimension of basis on each site (must be same everywhere, for now)
end
TBModel(hopfun) = TBModel( Matrix{Float64}(undef,0,2),  # pos
                     falses(2),                   # periodicity
					 Vector{Vector{Int}}(),       # neighbors
					 hopfun,                      # hopfun
                     1)                           # basisdim

#= --------------------------------------------------------- =#
function interDistance(ri::AbstractVector{<:Real}, rj::AbstractVector{<:Real},
					   periodicity::AbstractVector{Bool}=falses(length(first(ri))))

	# TODO: get rid of all the array allocations here...!
	# not periodic in any dimension; regular flat metric
	if all(!, periodicity)
		return sqrt( sum( abs2.(ri - rj) ) )

	# periodic in some directions; use distance on a flat n-torus,
	# with n = sum(periodicity)
	else
		# TODO: don't hardcode this to a [0,1]² box
		s = zero(eltype(ri))
		rij = abs.(ri - rj)
		for xyz in eachindex(ri)
			if periodicity[xyz] # dimension is periodic
				s += min(rij[xyz], 1-rij[xyz])^2
			else			    # dimension is not periodic
				s += rij[xyz]^2
			end
		end
		return sqrt(s)
	end

end

#= --------------------------------------------------------- =#
function interAngle(ri::AbstractVector{<:Real}, rj::AbstractVector{<:Real},
                    periodicity::AbstractVector{Bool}=falses(length(first(ri))))
    #= calculates the angle between two 2D points ri and rj, on a
       possibly periodic (flat) manifold =#
    rij = ri - rj
    for xy = 1:2 # fold to shortest distance over periodic directions
		if periodicity[xy]
			# TODO: Don't hardcode this to a [0,1]² box...
            if abs(rij[xy]) > abs(rij[xy]-1)
                rij[xy] -= 1
            elseif abs(rij[xy]) > abs(rij[xy]+1)
                rij[xy] += 1
            end
        end
    end
    return atan(rij[2],rij[1])
end

#= --------------------------------------------------------- =#
function setHopping!(tb::TBModel)

	n = size(tb.pos,1)
    basisiter = Base.OneTo(tb.basisdim)
    H = spzeros(ComplexF64, n*tb.basisdim, n*tb.basisdim)

	#= fill upper triangle with hopping amplitude by evaluating
    the function hopfun as a function of (i,j)-positions  =#
	for i = Base.OneTo(n)
		for j = (i+1):n
			t = tb.hopfun(tb.pos[i,:], tb.pos[j,:], tb.periodicity)
            for k = basisiter
                for l = basisiter
                    if !iszero(t[k,l])
                        H[i+(k-1)*n,j+(l-1)*n] = t[k,l]  # upper triangular part
                    end
                end
			end
		end
	end

    #= fill out the lower triangle, using Hermicity of H =#
    H = H + H' # when done this way, it has be done _before_ diagonal part is entered

	# ---- diagonal of H should be computed here ----
    for i = 1:n
        # stupid implementation; but easier to extend to index-dependence
        t = tb.hopfun(tb.pos[i,:], tb.pos[i,:], tb.periodicity)
        for k = basisiter
            for l = basisiter
                H[i+(k-1)*n,i+(l-1)*n] = t[k,l]
            end
        end
    end

	# set neighbors (useful for visualization and analysis)
	tb.neighbors = Vector{Vector{Int}}(undef, n)
	for i = 1:n
		# TODO: This is a horrible way to do this with a sparse array, lol... Should use a
		#		combination of column iteration and nzrange instead...
		tb.neighbors[i] = findall(!iszero, H[i,1:n])
	end

	return H
end

#= --------------------------------------------------------- =#
function buildHoneycombRestriction(restrictfun::Function, maxscale::Real)
	maxscale≤0 && throw(DomainError(maxscale, "maxscale must be a positive value"))

	intralat = [ [sqrt(3)*0.5, 0.5], [0.0, 1.0] ]
	shiftmag = 0.5/sqrt(3)
	steplist = -round(1.25*maxscale):round(1.25*maxscale)

	posa = Matrix{Float64}(undef, 0,2) # initialize arrays
	posb = similar(posa)

	# grow A and B sublattice arrays if points inside restriction
	for n = steplist
		for m = steplist
            newposa = sum(intralat.*[n,m]) + [shiftmag,0]
            newposb = sum(intralat.*[n,m]) - [shiftmag,0]

			if restrictfun(newposa)
				posa = vcat(posa, newposa')
			end
			if restrictfun(newposb)
				posb = vcat(posb, newposb')
			end

		end
	end

	# remove dangling bonds
	dangleremoved = 1
	while dangleremoved != 0
		dangleremoved = 0

		# fix A lattice
		delrows = Int[]
		for i in 1:size(posa,1)
			nneighbors = sum(map((x,y) -> interDistance(posa[i,:], [x,y]), posb[:,1], posb[:,2]) .< 0.58)
			if nneighbors < 2 # dangling bond
			    push!(delrows,i)
			end
		end
		posa = posa[setdiff(1:end,delrows),:];
		dangleremoved += length(delrows)

		# fix B lattice
		delrows = Int[]
		for i in 1:size(posb,1)
			nneighbors = sum(map((x,y) -> interDistance([x,y], posb[i,:]), posa[:,1], posa[:,2]) .< 0.58)
			if nneighbors < 2 # dangling bond
			    push!(delrows,i)
			end
		end
		posb = posb[setdiff(1:end,delrows),:];
		dangleremoved += length(delrows)
	end

	return vcat(posa, posb)
end

#= --------------------------------------------------------- =#
function solve(tb::TBModel; eigvecflag::Bool=false)
	H = setHopping!(tb)
    if !eigvecflag
    	ε = eigvals(Hermitian(Matrix(H)))
        return ε
    else
        F = eigen(Hermitian(Matrix(H)))
        return F.values, F.vectors
    end

end

#= --------------------------------------------------------- =#
function hopHoneycomb(ri::AbstractVector{<:Real}, rj::AbstractVector{<:Real},
                      periodicity::AbstractVector{Bool}=falses(length(first(ri))))
    #= Nearest neighbor hopping on a honeycomb (graphene-like) lattice
       with unity hopping amplitude =#
	a = interDistance(ri, rj, periodicity)
	t = a<.58 ? -1.0 : 0.0

end

#= --------------------------------------------------------- =#
function hopAgarwala(ri::AbstractVector{<:Real}, rj::AbstractVector{<:Real},
                     periodicity::AbstractVector{Bool}=trues(length(first(ri))),
                     R=1.0/6, M=-0.5, t₂=0.25, λ=0.5, a=1.0/24)
    #= Implementation of the hopping type described in Eq. (5) of Agarwala &
       Shenoy, PRL 118, 236402 (2017) =#

	r = interDistance(ri, rj, periodicity)

    if r > R
		t = zeros(ComplexF64, 2, 2)
    elseif r > zero(eltype(r))
        ϕ = interAngle(ri, rj, periodicity) # works properly also in periodic cases

        # the minus sign in front of exp _must_ be there in front of _both_
        # offdiag terms in order to get the right result; i.e. t is not Hermitian
		# on its own (but H is still Hermitian, by construction)
		imcisϕ = -cis(-ϕ)*im
		sin²ϕ  = sin(ϕ)^2
        offdiagᴬᴮ = ( imcisϕ + λ*(sin²ϕ*(1+im)-1) ) / 2
        offdiagᴮᴬ = ( imcisϕ + λ*(sin²ϕ*(1-im)-1) ) / 2
        diagᴬᴬ = 0.5 * (-1 + t₂)
        diagᴮᴮ = 0.5 * (+1 + t₂)

        t = exp(1 - r/a) .* [ diagᴬᴬ offdiagᴬᴮ; offdiagᴮᴬ diagᴮᴮ ]
    else # on-site/self term (r=0)
        t = [(2+M) (1-im)*λ; (1+im)*λ -(2+M)] # this term must be Hermitian though
    end

	return t
end

#= --------------------------------------------------------- =#
circularRestriction(r,a) = sqrt(sum(r.^2)) < a
circularRestriction(r) = circularRestriction(r,15)

max_issues_repo_name: thchr/super-tb

max_forks_repo_name: thchr/super-tb


text:
column_loader(T::Type{Bool}, io, metadata) = begin
    # Bool are saved as UInt8
    buffer = Vector{UInt8}(undef, metadata.len)
    readbytes!(io, buffer, metadata.len)
    Bool.(Blosc.decompress(UInt8, buffer))
end

column_loader!(buffer, T::Type{Bool}, io, metadata) = begin
    # Bool are saved as UInt8
    read!(io, buffer)
    res = Blosc.decompress(UInt8, buffer)
    Bool.(res)
end

compress_then_write(b::Vector{Bool}, io) = begin
    b8 = UInt8.(b)
    bbc = Blosc.compress(b8)
    write(io, bbc)
    return (len = length(bbc), type = Bool)
end

max_stars_repo_name: xiaodaigh/JDF

max_issues_repo_name: xiaodaigh/JDF

max_forks_repo_name: xiaodaigh/JDF


text:
"""
    AbstractPass
Supertype for all passes.
"""
abstract type AbstractPass end

"""
    CollectTopLevelNode <: AbstractPass
In this pass, all tags and identifiers in all translation units are collected for
CTU analysis.

See also [`collect_top_level_nodes!`](@ref).
"""
mutable struct CollectTopLevelNode <: AbstractPass
    trans_units::Vector{TranslationUnit}
    dependent_headers::Vector{String}
    system_dirs::Vector{String}
    show_info::Bool
end
CollectTopLevelNode(tus, dhs, sys; info=false) = CollectTopLevelNode(tus, dhs, sys, info)

function (x::CollectTopLevelNode)(dag::ExprDAG, options::Dict)
    general_options = get(options, "general", Dict())
    log_options = get(general_options, "log", Dict())
    is_local_only = get(general_options, "is_local_header_only", true)
    show_info = get(log_options, "CollectTopLevelNode_log", x.show_info)

    empty!(dag.nodes)
    empty!(dag.sys)
    for tu in x.trans_units
        tu_cursor = getTranslationUnitCursor(tu)
        header_name = spelling(tu_cursor) |> normpath
        @info "Processing header: $header_name"
        for cursor in children(tu_cursor)
            file_name = get_filename(cursor) |> normpath
            if is_local_only && header_name != file_name && file_name ∉ x.dependent_headers
                if any(sysdir->startswith(file_name, sysdir), x.system_dirs)
                    collect_top_level_nodes!(dag.sys, cursor, general_options)
                end
                continue
            end
            collect_top_level_nodes!(dag.nodes, cursor, general_options)
        end
    end

    return dag
end

"""
    CollectDependentSystemNode <: AbstractPass
In this pass, those dependent tags/identifiers are to the `dag.nodes`.

See also [`collect_system_nodes!`](@ref).
"""
mutable struct CollectDependentSystemNode <: AbstractPass
    dependents::Dict{ExprNode,Int}
    show_info::Bool
end
CollectDependentSystemNode(; info=true) = CollectDependentSystemNode(Dict{ExprNode,Int}(), info)

# FIXME: refactor and improve the support for system nodes
function (x::CollectDependentSystemNode)(dag::ExprDAG, options::Dict)
    general_options = get(options, "general", Dict())
    log_options = get(general_options, "log", Dict())
    show_info = get(log_options, "CollectDependentSystemNode_log", x.show_info)

    empty!(x.dependents)
    for node in dag.nodes
        collect_dependent_system_nodes!(dag, node, x.dependents)
    end
    isempty(x.dependents) && return dag

    new_deps = copy(x.dependents)
    old_deps = copy(x.dependents)
    while true
        for (node, i) in new_deps
            collect_dependent_system_nodes!(dag, node, x.dependents)
        end

        new_deps = setdiff(x.dependents, old_deps)

        isempty(new_deps) && break

        old_deps = copy(x.dependents)
    end

    show_info && @warn "[CollectDependentSystemNode]: found symbols in the system headers: $([n.id for (n,v) in x.dependents])"
    prepend!(dag.nodes, collect(v[1] for v in sort(collect(x.dependents), by=x->x[2])))

    return dag
end

"""
    IndexDefinition <: AbstractPass
In this pass, the indices of struct/union/enum tags and those of function/typedef/macro
identifiers are cached in the DAG for future use. Note that, the "Definition" in the type
name means a little bit more.

The `adj` list of each node is also cleared in this pass.
"""
mutable struct IndexDefinition <: AbstractPass
    show_info::Bool
end
IndexDefinition(; info=false) = IndexDefinition(info)

function (x::IndexDefinition)(dag::ExprDAG, options::Dict)
    general_options = get(options, "general", Dict())
    log_options = get(general_options, "log", Dict())
    show_info = get(log_options, "IndexDefinition_log", x.show_info)

    empty!(dag.tags)
    empty!(dag.ids)
    for (i, node) in enumerate(dag.nodes)
        !isempty(node.adj) && empty!(node.adj)

        if is_tag_def(node)
            if haskey(dag.tags, node.id)
                n = dag.nodes[dag.tags[node.id]]
                @assert is_same(n.cursor, node.cursor) "duplicated definitions should be exactly the same!"
                show_info && @info "[IndexDefinition]: marked an indexed tag $(node.id) at nodes[$i]"
                ty = dup_type(node.type)
                dag.nodes[i] = ExprNode(node.id, ty, node.cursor, node.exprs, node.adj)
            else
                show_info && @info "[IndexDefinition]: indexing tag $(node.id) at nodes[$i]"
                dag.tags[node.id] = i
            end
        end

        if is_identifier(node)
            if haskey(dag.ids, node.id)
                show_info &&
                    @info "[IndexDefinition]: found duplicated identifier $(node.id) at nodes[$i]"
                ty = dup_type(node.type)
                dag.nodes[i] = ExprNode(node.id, ty, node.cursor, node.exprs, node.adj)
            else
                show_info && @info "[IndexDefinition]: indexing identifier $(node.id) at nodes[$i]"
                dag.ids[node.id] = i
            end
        end
    end

    return dag
end

"""
    CollectNestedRecord <: AbstractPass
In this pass, nested record nodes are collected and pushed into the DAG.
"""
mutable struct CollectNestedRecord <: AbstractPass
    show_info::Bool
end
CollectNestedRecord(; info=false) = CollectNestedRecord(info)

function (x::CollectNestedRecord)(dag::ExprDAG, options::Dict)
    general_options = get(options, "general", Dict())
    log_options = get(general_options, "log", Dict())
    show_info = get(log_options, "CollectNestedRecord_log", x.show_info)
    use_deterministic_sym = get(general_options, "use_deterministic_symbol", false)

    new_tags = Dict{Symbol,Int}()
    for (id, i) in dag.tags
        node = dag.nodes[i]
        !is_record(node) && continue
        is_dup_tagtype(node) && continue
        collect_nested_record!(dag, node, new_tags, use_deterministic_sym)
    end

    merge!(dag.tags, new_tags)

    return dag
end

"""
    FindOpaques <: AbstractPass
Those opaque structs/unions/enums are marked in this pass.
"""
mutable struct FindOpaques <: AbstractPass
    show_info::Bool
end
FindOpaques(; info=false) = FindOpaques(info)

function (x::FindOpaques)(dag::ExprDAG, options::Dict)
    general_options = get(options, "general", Dict())
    log_options = get(general_options, "log", Dict())
    show_info = get(log_options, "FindOpaques_log", x.show_info)

    for (i, node) in enumerate(dag.nodes)
        is_forward_decl(node) || continue
        if !haskey(dag.tags, node.id)
            ty = opaque_type(node.type)
            dag.nodes[i] = ExprNode(node.id, ty, node.cursor, node.exprs, node.adj)
            dag.tags[node.id] = i
            show_info &&
                @info "[FindOpaques]: marked an opaque tag-type $(node.id)"
        end
    end

    return dag
end

"""
    LinkTypedefToAnonymousTagType <: AbstractPass
In this pass, the id info of anonymous tag-type nodes is added to those typedef nodes that
directly/indirectly have a reference to these nodes. The id info is injected in the upstream
passes.
"""
mutable struct LinkTypedefToAnonymousTagType <: AbstractPass
    cache::Dict{Int,Vector{Int}}
    is_system::Bool
    show_info::Bool
end
LinkTypedefToAnonymousTagType(;is_system=false, info=false) = LinkTypedefToAnonymousTagType(Dict(), is_system, info)

function (x::LinkTypedefToAnonymousTagType)(dag::ExprDAG, options::Dict)
    general_options = get(options, "general", Dict())
    log_options = get(general_options, "log", Dict())
    show_info = get(log_options, "LinkTypedefToAnonymousTagType_log", x.show_info)

    empty!(x.cache)
    nodes = x.is_system ? dag.sys : dag.nodes
    # loop through all the nodes to cache the indices of all the typedefs that refer to
    # an anonymous tag-type
    for i in 1:(length(nodes) - 1)
        cur = nodes[i]
        is_anonymous(cur) || continue
        x.cache[i] = Int[]
        # since an anonymous tag-type may have mutiple typedefs, we need another loop
        for j in (i + 1):length(nodes)
            n = nodes[j]
            is_typedef(n) || break  # keep searching until we hit a non-typedef node
            refback = children(n.cursor)
            if !isempty(refback) && first(refback) == cur.cursor
                push!(x.cache[i], j)
            end
        end
    end
    # loop through all anonymous tag-types and apply node editing
    for (k, v) in x.cache
        isempty(v) && continue  # skip non-typedef anonymous tag-types e.g. enum
        anonymous = nodes[k]
        for i in v
            node = nodes[i]
            ty = TypedefToAnonymous(anonymous.id)
            nodes[i] = ExprNode(node.id, ty, node.cursor, node.exprs, node.adj)
            show_info &&
                @info "[LinkTypedefToAnonymousTagType_log]: store $(anonymous.cursor)'s id to typedef node $(node.id)"
        end
    end
    return dag
end

"""
    ResolveDependency <: AbstractPass
In this pass, the `adj` list of each node in the DAG is populated by its parent definition
nodes. Make sure you run the [`IndexDefinition`](@ref) pass before this pass.

Note that, in the case of circular forward decls, circular dependencies may be introduced
in the DAG, this should be handled in the downstream passes.

See also [`resolve_dependency!`](@ref)
"""
mutable struct ResolveDependency <: AbstractPass
    show_info::Bool
end
ResolveDependency(; info=false) = ResolveDependency(info)

function (x::ResolveDependency)(dag::ExprDAG, options::Dict)
    general_options = get(options, "general", Dict())
    log_options = get(general_options, "log", Dict())
    show_info = get(log_options, "ResolveDependency_log", x.show_info)

    general_options["nested_tags"] = collect_nested_tags(dag)
    for node in dag.nodes
        resolve_dependency!(dag, node, general_options)
        unique!(node.adj)  # FIXME: check this
        if show_info
            deps = Dict(n => dag.nodes[n] for n in node.adj)
            @info "[ResolveDependency]: resolved dependency for $(node.cursor)" deps
        end
    end
    delete!(general_options, "nested_tags")

    return dag
end

@enum DFSMarkStatus begin
    UNMARKED = 0x00
    TEMPORARY = 0x01
    PERMANENT = 0x02
end

"""
    RemoveCircularReference <: AbstractPass
In this pass, circular dependencies that are introduced by mutually referenced structs are
removed, so we can do a topological sort on the DAG.

Make sure you run the [`ResolveDependency`](@ref) pass before this pass.
"""
mutable struct RemoveCircularReference <: AbstractPass
    show_info::Bool
end
RemoveCircularReference(; info=false) = RemoveCircularReference(info)

function detect_cycle!(nodes, marks, cycle, i)
    node = nodes[i]
    mark = marks[i]
    # skip if this node has already been visited
    mark == PERMANENT && return cycle

    # hit a backward edge, record the index
    if mark == TEMPORARY
        push!(cycle, i)
        return cycle
    end

    marks[i] = TEMPORARY
    for n in node.adj
        detect_cycle!(nodes, marks, cycle, n)
        if !isempty(cycle)
            push!(cycle, i)
            return cycle
        end
    end
    marks[i] = PERMANENT
    return cycle
end

function is_non_pointer_ref(child, parent)
    is_non_pointer_dep = false
    for fc in fields(getCursorType(child.cursor))
        fty = getCursorType(fc)
        is_jl_pointer(tojulia(fty)) && continue
        c = getTypeDeclaration(fty)
        if c == parent.cursor
            is_non_pointer_dep = true
        end
    end
    return is_non_pointer_dep
end

function (x::RemoveCircularReference)(dag::ExprDAG, options::Dict)
    general_options = get(options, "general", Dict())
    log_options = get(general_options, "log", Dict())
    show_info = get(log_options, "RemoveCircularReference_log", x.show_info)

    marks = fill(UNMARKED, size(dag.nodes))
    while any(x -> x != PERMANENT, marks)
        fill!(marks, UNMARKED)
        for (i, node) in enumerate(dag.nodes)
            marks[i] == UNMARKED || continue
            cycle = Int[]
            detect_cycle!(dag.nodes, marks, cycle, i)
            isempty(cycle) && continue

            # firstly remove cycle reference caused by mutually referenced structs
            typedef_only = true
            for i in 1:(length(cycle) - 1)
                np, nc = cycle[i], cycle[i + 1]
                parent, child = dag.nodes[np], dag.nodes[nc]
                if child.type isa AbstractStructNodeType
                    # only pointer references can be safely removed
                    if !is_non_pointer_ref(child, parent)
                        typedef_only = false
                        idx = findfirst(x -> x == np, child.adj)
                        deleteat!(child.adj, idx)
                        id = child.id
                        ty = StructMutualRef()
                        dag.nodes[nc] = ExprNode(id, ty, child.cursor, child.exprs, child.adj)
                        show_info &&
                            @info "[RemoveCircularReference]: removed $(child.id)'s dependency $(parent.id)"
                    end
                end
                # exit earlier
                (i + 1) == first(cycle) && break
            end

            # there are cases where the circular reference can only be de-referenced at a
            # typedef, so we do the for-loop another round for that.
            if typedef_only
                for i in 1:(length(cycle) - 1)
                    np, nc = cycle[i], cycle[i + 1]
                    parent, child = dag.nodes[np], dag.nodes[nc]
                    is_typedef_elaborated(child) || continue
                    jlty = tojulia(getTypedefDeclUnderlyingType(child.cursor))
                    # make sure the underlying type is a pointer
                    is_jl_pointer(jlty) || continue
                    empty!(child.adj)
                    id = child.id
                    ty = TypedefMutualRef()
                    dag.nodes[nc] = ExprNode(id, ty, child.cursor, child.exprs, child.adj)
                    show_info &&
                        @info "[RemoveCircularReference]: removed $(child.id)'s dependency $(parent.id)"
                    # exit earlier
                    (i + 1) == first(cycle) && break
                end
            end

            # whenever a cycle is found, we reset all of the marks and restart again
            # FIXME: optimize this
            break
        end
    end
    return dag
end

"""
    TopologicalSort <: AbstractPass
Make sure you run the [`RemoveCircularReference`](@ref) pass before this pass.
"""
mutable struct TopologicalSort <: AbstractPass
    show_info::Bool
end
TopologicalSort(; info=false) = TopologicalSort(info)

function dfs_visit!(list, nodes, marks, i)
    node = nodes[i]
    mark = marks[i]
    mark == PERMANENT && return nothing
    @assert mark != TEMPORARY

    marks[i] = TEMPORARY
    for n in node.adj
        dfs_visit!(list, nodes, marks, n)
    end
    marks[i] = PERMANENT
    push!(list, node)
    return nothing
end

function (x::TopologicalSort)(dag::ExprDAG, options::Dict)
    general_options = get(options, "general", Dict())
    log_options = get(general_options, "log", Dict())
    show_info = get(log_options, "TopologicalSort_log", x.show_info)

    show_info && @info "[TopologicalSort]: sorting the DAG ..."
    marks = fill(UNMARKED, size(dag.nodes))
    list = []
    for (i, node) in enumerate(dag.nodes)
        marks[i] == UNMARKED || continue
        dfs_visit!(list, dag.nodes, marks, i)
    end
    dag.nodes .= list

    # clean up indices because they are no longer valid
    empty!(dag.tags)
    empty!(dag.ids)

    return dag
end

"""
    CatchDuplicatedAnonymousTags <: AbstractPass
Most duplicated tags are marked as StructDuplicated/UnionDuplicated/EnumDuplicated except
those anonymous tag-types. That's why this pass is necessary.
"""
mutable struct CatchDuplicatedAnonymousTags <: AbstractPass
    show_info::Bool
end
CatchDuplicatedAnonymousTags(; info=false) = CatchDuplicatedAnonymousTags(info)

function (x::CatchDuplicatedAnonymousTags)(dag::ExprDAG, options::Dict)
    general_options = get(options, "general", Dict())
    log_options = get(general_options, "log", Dict())
    show_info = get(log_options, "CatchDuplicatedAnonymousTags_log", x.show_info)

    for (i, node) in enumerate(dag.nodes)
        !haskey(dag.tags, node.id) && continue
        is_dup_tagtype(node) && continue
        # `is_anonymous` cannot be used here because the node type may have been changed.
        sid = string(node.id)
        !(startswith(sid, "##Ctag") || startswith(sid, "__JL_Ctag")) && continue
        for (id2, idx2) in dag.tags
            node2 = dag.nodes[idx2]
            node == node2 && continue
            is_dup_tagtype(node2) && continue
            !(startswith(sid, "##Ctag") || startswith(sid, "__JL_Ctag")) && continue
            !is_same(node.cursor, node2.cursor) && continue
            show_info &&
                @info "[CatchDuplicatedAnonymousTags]: found duplicated anonymous tag-type $(node2.id) at dag.nodes[$idx2]."
            ty = dup_type(node2.type)
            dag.nodes[idx2] = ExprNode(node2.id, ty, node2.cursor, node2.exprs, node2.adj)
        end
    end
end

"""
    DeAnonymize <: AbstractPass
In this pass, naive anonymous tag-types are de-anonymized and the correspoding typedefs
are marked [`Skip`](@ref).

```c
typedef struct {
    int x;
} my_struct;
```

In the C code above, what the C programmer really want to do is to bring the name
"my_struct" from the tag scope into the identifier scope and force users to use `my_struct`
instead of `struct my_struct`. In Julia world, we can just do:

```julia
struct my_struct
    x::Cint
end
```

Make sure you run the [`ResolveDependency`](@ref) pass before this pass.
"""
mutable struct DeAnonymize <: AbstractPass
    show_info::Bool
end
DeAnonymize(; info=false) = DeAnonymize(info)

function (x::DeAnonymize)(dag::ExprDAG, options::Dict)
    general_options = get(options, "general", Dict())
    log_options = get(general_options, "log", Dict())
    show_info = get(log_options, "DeAnonymize_log", x.show_info)

    for (i, node) in enumerate(dag.nodes)
        is_typedef_to_anonymous(node) || continue
        # a typedef to anonymous node should have only one dependent node and
        # this node must be an anonymous tag-type node.
        @assert length(node.adj) == 1
        dep_idx = node.adj[1]
        dep_node = dag.nodes[dep_idx]

        # skip earlier if the dependent node is a duplicated anonymous tag-type
        is_dup_tagtype(dep_node) && continue

        # loop through the `adj`-list of all nodes in the DAG to find whether this
        # typedef node is the only node that refers to the anonymous tag-type node.
        apply_edit = true
        for n in dag.nodes
            n == node && continue
            if any(i -> dag.nodes[i] == dep_node, n.adj)
                apply_edit = false
            end
        end

        apply_edit || continue

        # apply node editing
        dn = dep_node
        dag.nodes[dep_idx] = ExprNode(node.id, dn.type, dn.cursor, dn.exprs, dn.adj)
        dag.nodes[i] = ExprNode(node.id, SoftSkip(), node.cursor, node.exprs, node.adj)
        show_info &&
            @info "[DeAnonymize]: adding name $(node.id) to $dep_node"
    end

    return dag
end

"""
    CodegenPreprocessing <: AbstractPass
In this pass, additional info are added into expression nodes for codegen.
"""
mutable struct CodegenPreprocessing <: AbstractPass
    skip_nodes::Vector{Int}
    show_info::Bool
end
CodegenPreprocessing(; info=false) = CodegenPreprocessing(Int[], info)

function (x::CodegenPreprocessing)(dag::ExprDAG, options::Dict)
    general_options = get(options, "general", Dict())
    log_options = get(general_options, "log", Dict())
    show_info = get(log_options, "CodegenPreprocessing_log", x.show_info)

    empty!(x.skip_nodes)
    for (i, node) in enumerate(dag.nodes)
        if skip_check(dag, node)
            skip_mode = is_dup_tagtype(node) ? SoftSkip() : Skip()
            skip_mode == Skip() && push!(x.skip_nodes, i)
            dag.nodes[i] = ExprNode(node.id, skip_mode, node.cursor, node.exprs, node.adj)
            show_info &&
                @info "[CodegenPreprocessing]: skip a $(node.type) node named $(node.id)"
        end
        if attribute_check(dag, node)
            ty = attribute_type(node.type)
            dag.nodes[i] = ExprNode(node.id, ty, node.cursor, node.exprs, node.adj)
            show_info &&
                @info "[CodegenPreprocessing]: mark an attribute $(node.type) node named $(node.id)"
        end
        if nested_anonymous_check(dag, node)
            ty = nested_anonymous_type(node.type)
            dag.nodes[i] = ExprNode(node.id, ty, node.cursor, node.exprs, node.adj)
            show_info &&
                @info "[CodegenPreprocessing]: mark a nested anonymous $(node.type) node named $(node.id)"
        end
        if bitfield_check(dag, node)
            ty = bitfield_type(node.type)
            dag.nodes[i] = ExprNode(node.id, ty, node.cursor, node.exprs, node.adj)
            show_info &&
                @info "[CodegenPreprocessing]: mark a bitfield $(node.type) node named $(node.id)"
        end
    end

    has_new_skip_node = true
    while has_new_skip_node
        has_new_skip_node = false
        for (i, n) in enumerate(dag.nodes)
            is_hardskip(n) && continue
            # if any dependent node is a `Skip` node, mark this node as `Skip`
            for j in n.adj
                dn = dag.nodes[j]
                is_hardskip(dn) || continue
                push!(x.skip_nodes, i)
                dag.nodes[i] = ExprNode(n.id, Skip(), n.cursor, n.exprs, n.adj)
                show_info &&
                    @info "[CodegenPreprocessing]: skip a $(n.type) node named $(n.id)"
                has_new_skip_node = true
            end
        end
    end

    return dag
end

"""
    Codegen <: AbstractPass
In this pass, Julia expressions are emitted to `node.exprs`.
"""
mutable struct Codegen <: AbstractPass
    show_info::Bool
end
Codegen(; info=false) = Codegen(info)

function (x::Codegen)(dag::ExprDAG, options::Dict)
    general_options = get(options, "general", Dict())
    log_options = get(general_options, "log", Dict())
    show_info = get(log_options, "Codegen_log", x.show_info)
    codegen_options = get(options, "codegen", Dict())
    # forward general options
    if haskey(general_options, "library_name")
        codegen_options["library_name"] = general_options["library_name"]
    end
    if haskey(general_options, "library_names")
        codegen_options["library_names"] = general_options["library_names"]
    end

    # store definitions which would be used during codegen
    codegen_options["DAG_tags"] = dag.tags
    codegen_options["DAG_ids"] = dag.ids
    codegen_options["DAG_ids_extra"] = dag.ids_extra
    # collect and map nested anonymous tags
    codegen_options["nested_tags"] = collect_nested_tags(dag)

    for (i, node) in enumerate(dag.nodes)
        !isempty(node.exprs) && empty!(node.exprs)
        emit!(dag, node, codegen_options, idx=i)
        show_info && @info "[Codegen]: emit Julia expression for $(node.id)"
    end

    # clean up
    delete!(codegen_options, "DAG_tags")
    delete!(codegen_options, "DAG_ids")
    delete!(codegen_options, "DAG_ids_extra")
    delete!(codegen_options, "nested_tags")

    return dag
end

"""
    CodegenPostprocessing <: AbstractPass
This pass is reserved for future use.
"""
mutable struct CodegenPostprocessing <: AbstractPass
    show_info::Bool
end
CodegenPostprocessing(; info=false) = CodegenPostprocessing(info)

function (x::CodegenPostprocessing)(dag::ExprDAG, options::Dict)
    # TODO: find a use case
end

"""
    TweakMutability <: AbstractPass
In this pass, the mutability of those structs which are not necessary to be immutable
will be reset to `true` according to the following rules:

if this type is not used as a field type in any other types
    if this type is in the includelist
        then reset

    if this type is in the ignore list
        then skip

    if this type is used as the argument type in some function protos
        if all of the argument type are non-pointer-type
            then reset
        elseif the argument type is pointer-type
            if all other argument types in this function are not integer type (to pass a vector to the C function, both pointer and size are needed)
                then reset

    if this type is not used as the argument type in any functions (but there is an implicit usage)
        then reset
"""
mutable struct TweakMutability <: AbstractPass
    idxs::Vector{Int}
    show_info::Bool
end
TweakMutability(; info=false) = TweakMutability(Int[], info)

function (x::TweakMutability)(dag::ExprDAG, options::Dict)
    general_options = get(options, "general", Dict())
    log_options = get(general_options, "log", Dict())
    show_info = get(log_options, "TweakMutability_log", x.show_info)
    ignorelist = get(general_options, "auto_mutability_ignorelist", get(general_options, "auto_mutability_blacklist", []))
    includelist = get(general_options, "auto_mutability_includelist", get(general_options, "auto_mutability_whitelist", []))
    add_new = get(general_options, "auto_mutability_with_new", true)

    # collect referenced node ids
    empty!(x.idxs)
    for node in dag.nodes
        t = node.type
        t isa StructAnonymous || t isa StructDefinition || t isa StructMutualRef || continue
        for i in node.adj
            find_and_append_deps!(x.idxs, dag.nodes, i)
        end
    end
    unique!(x.idxs)

    for (i, node) in enumerate(dag.nodes)
        t = node.type
        t isa StructAnonymous || t isa StructDefinition || t isa StructMutualRef || continue
        i ∈ x.idxs && continue
        isempty(node.exprs) && continue

        exprs = filter(x->Meta.isexpr(x, :struct), node.exprs)
        @assert length(exprs) == 1
        expr = first(exprs)
        type_name = string(expr.args[2])

        apply_reset = false
        if type_name ∈ includelist
            apply_reset = true
        elseif type_name ∈ ignorelist
            apply_reset = false
        else
            apply_reset = should_tweak(dag.nodes, i)
        end

        if apply_reset
            expr.args[1] = true
            add_new && push!(expr.args[3].args, Expr(:(=), Expr(:call, expr.args[2]), Expr(:call, :new)))
            show_info &&
                @info "[TweakMutability]: reset the mutability of $type_name to mutable"
        end
    end

    return dag
end

"""
    AddFPtrMethods <: AbstractPass
This pass adds a method definition for each function prototype method which `ccall`s into a library.

The generated method allows the use of a function pointer to `ccall` into directly, instead
of relying on a library to give the pointer via a symbol look up. This is useful for libraries that
use runtime loaders to dynamically resolve function pointers for API calls.
"""
struct AddFPtrMethods <: AbstractPass end

function (::AddFPtrMethods)(dag::ExprDAG, options::Dict)
    codegen_options = get(options, "codegen", Dict())
    use_ccall_macro = get(codegen_options, "use_ccall_macro", false)
    for node in dag.nodes
        node.type isa FunctionProto || continue
        ex = copy(first(node.exprs))
        call = ex.args[1]
        push!(call.args, :fptr)
        body = ex.args[findfirst(x -> Base.is_expr(x, :block), ex.args)]
        stmt_idx = if use_ccall_macro
            findfirst(x -> Base.is_expr(x, :macrocall) && x.args[1] == Symbol("@ccall"), body.args)
        else
            findfirst(x -> Base.is_expr(x, :call) && x.args[1] == :ccall, body.args)
        end
        stmt = body.args[stmt_idx]
        if use_ccall_macro
            typeassert = stmt.args[findfirst(x -> Base.is_expr(x, :(::)), stmt.args)]
            call = typeassert.args[findfirst(x -> Base.is_expr(x, :call), typeassert.args)]
            call.args[1] = Expr(:$, :fptr)
        else
            stmt.args[2] = :fptr
        end
        push!(node.exprs, ex)
    end
end

const DEFAULT_AUDIT_FUNCTIONS = [
    audit_library_name,
    sanity_check,
    report_default_tag_types,
]

mutable struct Audit <: AbstractPass
    funcs::Vector{Function}
    show_info::Bool
end
Audit(; show_info=true) = Audit(DEFAULT_AUDIT_FUNCTIONS, show_info)

function (x::Audit)(dag::ExprDAG, options::Dict)
    general_options = get(options, "general", Dict())
    log_options = get(general_options, "log", Dict())
    if !haskey(log_options, "Audit_log")
        log_options["Audit_log"] = x.show_info
    end

    for f in x.funcs
        f(dag, options)
    end

    return dag
end

"""
    AbstractPrinter <: AbstractPass
Supertype for printers.
"""
abstract type AbstractPrinter <: AbstractPass end

"""
    FunctionPrinter <: AbstractPrinter
In this pass, only those functions are dumped to file.
"""
mutable struct FunctionPrinter <: AbstractPrinter
    file::AbstractString
    show_info::Bool
end
FunctionPrinter(file::AbstractString; info=true) = FunctionPrinter(file, info)

function (x::FunctionPrinter)(dag::ExprDAG, options::Dict)
    general_options = get(options, "general", Dict())
    log_options = get(general_options, "log", Dict())
    show_info = get(log_options, "FunctionPrinter_log", x.show_info)
    ignorelist = get(general_options, "output_ignorelist", get(general_options, "printer_blacklist", []))

    show_info && @info "[FunctionPrinter]: print to $(x.file)"
    open(x.file, "w") do io
        for node in dag.nodes
            string(node.id) ∈ ignorelist && continue
            node.type isa AbstractFunctionNodeType || continue
            pretty_print(io, node, general_options)
        end
    end
    return dag
end

"""
    CommonPrinter <: AbstractPrinter
In this pass, only those non-functions are dumped to file.
"""
mutable struct CommonPrinter <: AbstractPrinter
    file::AbstractString
    show_info::Bool
end
CommonPrinter(file::AbstractString; info=true) = CommonPrinter(file, info)

function (x::CommonPrinter)(dag::ExprDAG, options::Dict)
    general_options = get(options, "general", Dict())
    log_options = get(general_options, "log", Dict())
    show_info = get(log_options, "CommonPrinter_log", x.show_info)
    ignorelist = get(general_options, "output_ignorelist", get(general_options, "printer_blacklist", []))

    show_info && @info "[CommonPrinter]: print to $(x.file)"
    open(x.file, "w") do io
        for node in dag.nodes
            string(node.id) ∈ ignorelist && continue
            node.type isa AbstractMacroNodeType && continue
            pretty_print(io, node, general_options)
        end
        # print macros in the bottom of the file
        for node in dag.nodes
            string(node.id) ∈ ignorelist && continue
            node.type isa AbstractMacroNodeType || continue
            pretty_print(io, node, options)
        end
    end
    return dag
end

"""
    GeneralPrinter <: AbstractPrinter
In this pass, all symbols are dumped to file.
"""
mutable struct GeneralPrinter <: AbstractPrinter
    file::AbstractString
    show_info::Bool
end
GeneralPrinter(file::AbstractString; info=true) = GeneralPrinter(file, info)

function (x::GeneralPrinter)(dag::ExprDAG, options::Dict)
    general_options = get(options, "general", Dict())
    log_options = get(general_options, "log", Dict())
    show_info = get(log_options, "GeneralPrinter_log", x.show_info)
    ignorelist = get(general_options, "output_ignorelist", get(general_options, "printer_blacklist", []))
    general_options["DAG_ids"] = merge(dag.ids, dag.tags)

    show_info && @info "[GeneralPrinter]: print to $(x.file)"
    open(x.file, "a") do io
        for node in dag.nodes
            string(node.id) ∈ ignorelist && continue
            node.type isa AbstractMacroNodeType && continue
            pretty_print(io, node, general_options)
        end
        # print macros in the bottom of the file
        for node in dag.nodes
            string(node.id) ∈ ignorelist && continue
            node.type isa AbstractMacroNodeType || continue
            isempty(node.exprs)
            pretty_print(io, node, options)
        end
    end

    delete!(general_options, "DAG_ids")
    return dag
end

"""
   StdPrinter <: AbstractPrinter
In this pass, all symbols are dumped to stdout.
"""
mutable struct StdPrinter <: AbstractPrinter
    show_info::Bool
end
StdPrinter(; info=true) = StdPrinter(info)

function (x::StdPrinter)(dag::ExprDAG, options::Dict)
    general_options = get(options, "general", Dict())
    log_options = get(general_options, "log", Dict())
    show_info = get(log_options, "StdPrinter_log", x.show_info)
    ignorelist = get(general_options, "output_ignorelist", get(general_options, "auto_mutability_blacklist", []))

    for node in dag.nodes
        string(node.id) ∈ ignorelist && continue
        node.type isa AbstractMacroNodeType && continue
        pretty_print(stdout, node, general_options)
    end
    # print macros
    for node in dag.nodes
        string(node.id) ∈ ignorelist && continue
        node.type isa AbstractMacroNodeType || continue
        pretty_print(stdout, node, options)
    end

    return dag
end

"""
    ProloguePrinter <: AbstractPrinter
In this pass, prologues are dumped to file.
"""
mutable struct ProloguePrinter <: AbstractPrinter
    file::AbstractString
    show_info::Bool
end
ProloguePrinter(file::AbstractString; info=true) = ProloguePrinter(file, info)

function (x::ProloguePrinter)(dag::ExprDAG, options::Dict)
    general_options = get(options, "general", Dict())
    codegen_options = get(options, "codegen", Dict())
    log_options = get(general_options, "log", Dict())
    show_info = get(log_options, "ProloguePrinter_log", x.show_info)
    module_name = get(general_options, "module_name", "")
    jll_pkg_name = get(general_options, "jll_pkg_name", "")
    jll_pkg_extra = get(general_options, "jll_pkg_extra", [])
    prologue_file_path = get(general_options, "prologue_file_path", "")
    use_native_enum = get(general_options, "use_julia_native_enum_type", false)
    print_CEnum = get(general_options, "print_using_CEnum", true)
    wrap_variadic_function = get(codegen_options, "wrap_variadic_function", false)

    show_info && @info "[ProloguePrinter]: print to $(x.file)"
    open(x.file, "w") do io
        # print "module name"
        if !isempty(module_name)
            println(io, "module $module_name")
            println(io)
        end
        # print _jll pkgs
        if !isempty(jll_pkg_name)
            println(io, "using $jll_pkg_name")
            println(io, "export $jll_pkg_name")
            println(io)
        end
        # print extra _jll pkgs
        if !isempty(jll_pkg_extra)
            for jll in jll_pkg_extra
                println(io, "using $jll")
                println(io, "export $jll")
                println(io)
            end
        end

        # print "using CEnum"
        if !use_native_enum && print_CEnum
            println(io, "using CEnum")
            println(io)
        end

        if wrap_variadic_function
            println(io, """
            to_c_type(t::Type) = t
            to_c_type_pairs(va_list) = map(enumerate(to_c_type.(va_list))) do (ind, type)
                :(va_list[\$ind]::\$type)
            end
            """)
        end

        # print prelogue patches
        if !isempty(prologue_file_path)
            println(io, read(prologue_file_path, String))
            println(io)
        end
    end
    return dag
end

"""
    EpiloguePrinter <: AbstractPrinter
In this pass, epilogues are dumped to file.
"""
mutable struct EpiloguePrinter <: AbstractPrinter
    file::AbstractString
    show_info::Bool
end
EpiloguePrinter(file::AbstractString; info=true) = EpiloguePrinter(file, info)

function (x::EpiloguePrinter)(dag::ExprDAG, options::Dict)
    general_options = get(options, "general", Dict())
    log_options = get(general_options, "log", Dict())
    show_info = get(log_options, "EpiloguePrinter_log", x.show_info)
    module_name = get(general_options, "module_name", "")
    epilogue_file_path = get(general_options, "epilogue_file_path", "")
    export_prefixes = get(general_options, "export_symbol_prefixes", "")

    show_info && @info "[EpiloguePrinter]: print to $(x.file)"
    open(x.file, "a") do io
        # print epilogue patches
        if !isempty(epilogue_file_path)
            println(io, read(epilogue_file_path, String))
            println(io)
        end
        # print exports
        if !isempty(export_prefixes)
            str = """# exports
const PREFIXES = $export_prefixes
for name in names(@__MODULE__; all=true), prefix in PREFIXES
    if startswith(string(name), prefix)
        @eval export \$name
    end
end"""
            println(io, str)
            println(io)
        end
        # print "end # module"
        if !isempty(module_name)
            println(io, "end # module")
        end
    end
    return dag
end

## EXPERIMENTAL
"""
    CodegenMacro <: AbstractPass
[`Codegen`](@ref) pass for macros.
"""
mutable struct CodegenMacro <: AbstractPass
    show_info::Bool
end
CodegenMacro(; info=false) = CodegenMacro(info)

function (x::CodegenMacro)(dag::ExprDAG, options::Dict)
    general_options = get(options, "general", Dict())
    log_options = get(general_options, "log", Dict())
    show_info = get(log_options, "CodegenMacro_log", x.show_info)
    codegen_options = get(options, "codegen", Dict())
    macro_options = get(codegen_options, "macro", Dict())
    macro_mode = get(macro_options, "macro_mode", "basic")

    (macro_mode == "none" || macro_mode == "disable") && return dag

    for node in dag.nodes
        node.type isa AbstractMacroNodeType || continue
        !isempty(node.exprs) && empty!(node.exprs)
        macro_emit!(dag, node, macro_options)
        show_info && @info "[CodegenMacro]: emit Julia expression for $(node.id)"
    end

    return dag
end

function collect_nested_tags(dag::ExprDAG)
    nested_tags = Dict{Symbol,CLCursor}()
    for (id, i) in dag.tags
        node = dag.nodes[i]
        startswith(string(node.id), "##Ctag") ||
        startswith(string(node.id), "__JL_Ctag") || continue
        if node.type isa NestedRecords
            nested_tags[id] = node.cursor
        end
    end
    nested_tags
end


text:
module DataTypes
export Iterable, Callable,
  Writer, getaccumulator,
  State, getstate, putstate

include("Iterables.jl")
using .Iterables

include("Callable.jl")

include("Writers.jl")
using .Writers

include("States.jl")
using .States
end