text:
sessionsEndpoint = "api/$curApiVersion/users/{1}/robots/{2}/sessions"
sessionEndpoint = "api/$curApiVersion/users/{1}/robots/{2}/sessions/{3}"
variablesEndpoint = "api/$curApiVersion/users/{1}/robots/{2}/sessions/{3}/variables"
variableLabelledEndpoint = "$variablesEndpoint/labelled/{4}"
variableEndpoint = "$variablesEndpoint/{4}"
factorsEndpoint = "api/$curApiVersion/users/{1}/robots/{2}/sessions/{3}/factors"
factorEndpoint = "$factorsEndpoint/{4}"
bigDataEndpoint = "$variablesEndpoint/{4}/data"
bigDataElementEndpoint = "$bigDataEndpoint/{5}"
bigDataRawElementEndpoint = "$bigDataEndpoint/{5}/raw"
sessionReadyEndpoint = "api/$curApiVersion/users/{1}/robots/{2}/sessions/{3}/ready/{4}"
sessionQueueLengthEndpoint = "api/$curApiVersion/users/{1}/robots/{2}/sessions/{3}/queue/status"
sessionDeadQueueLengthEndpoint = "api/$curApiVersion/users/{1}/robots/{2}/sessions/{3}/queue/dead"

odoEndpoint = "api/v0/users/{1}/robots/{2}/sessions/{3}/odometry"
sessionSolveEndpoint = "api/v0/users/{1}/robots/{2}/sessions/{3}/solve"
sessionExportJldEndpoint = "api/v0/users/{1}/robots/{2}/sessions/{3}/export/jld"
bearingRangeEndpoint = "api/v0/users/{1}/robots/{2}/sessions/{3}/factors/bearingrange"

"""
$(SIGNATURES)
Gets all sessions for the current robot.
Return: A vector of sessions for the current robot.
"""
function getSessions(robotId::String;details=false)::Vector{SessionDetailsResponse}
    config = getGraffConfig()
    if config == nothing
        error("Graff config is not set, please call setGraffConfig with a valid configuration.")
    end
    url = "$(config.apiEndpoint)/$(format(sessionsEndpoint, config.userId, robotId))?details=$(details)"
    response = @mock _sendRestRequest(config, HTTP.get, url)
    if(response.status != 200)
        error("Error getting sessions, received $(response.status) with body '$(String(response.body))'.")
    end
    sessions = JSON2.read(String(response.body), Vector{SessionDetailsResponse})
    # #Sort
    sort!(sessions, by=(s -> s.id))
    return sessions
end

"""
$(SIGNATURES)
Gets all sessions for the current robot.
Return: A vector of sessions for the current robot.
"""
function getSessions(;details=false)::Vector{SessionDetailsResponse}

    config = getGraffConfig()
    if config == nothing
        error("Graff config is not set, please call setGraffConfig with a valid configuration.")
    end
    if config.robotId == ""
        error("Your config doesn't have a robot specified, please attach your config to a valid robot by setting the robotId field. Robot = $(config.robotId)")
    end

    return getSessions(config.robotId)
end

"""
$(SIGNATURES)
Return: Returns true if the session exists already.
"""
function isSessionExisting(robotId::String, sessionId::String)::Bool
    config = getGraffConfig()
    if config == nothing
        error("Graff config is not set, please call setGraffConfig with a valid configuration.")
    end

    url = "$(config.apiEndpoint)/$(format(sessionEndpoint, config.userId, robotId, sessionId))/exists"
    response = @mock _sendRestRequest(config, HTTP.get, url)
    body = String(response.body)
    if(response.status != 200)
        error("Error getting session existence, received $(response.status) with body '$body'.")
    end
    return lowercase(body) == "true"
end

"""
$(SIGNATURES)
Return: Returns true if the session exists already.
"""
function isSessionExisting(sessionId::String)::Bool
    config = getGraffConfig()
    if config == nothing
        error("Graff config is not set, please call setGraffConfig with a valid configuration.")
    end
    if config.robotId == ""
        error("Your config doesn't have a robot specified, please attach your config to a valid robot or session by setting the robotId and sessionId fields. Robot = $(config.robotId), Session = $(config.sessionId)")
    end

    return isSessionExisting(config.robotId, sessionId)
end

"""
$(SIGNATURES)
Return: Returns true if the session exists already.
"""
function isSessionExisting()::Bool
    config = getGraffConfig()
    if config == nothing
        error("Graff config is not set, please call setGraffConfig with a valid configuration.")
    end
    if config.robotId == "" || config.sessionId == ""
        error("Your config doesn't have a robot or a session specified, please attach your config to a valid robot or session by setting the robotId and sessionId fields. Robot = $(config.robotId), Session = $(config.sessionId)")
    end

    return isSessionExisting(config.robotId, config.sessionId)
end

"""
$(SIGNATURES)
Get a specific session given a user ID, robot ID, and session ID.
Return: The session details for the provided user ID, robot ID, and session ID.
"""
function getSession(robotId::String, sessionId::String)::SessionDetailsResponse
    config = getGraffConfig()
    if config == nothing
        error("Graff config is not set, please call setGraffConfig with a valid configuration.")
    end
    url = "$(config.apiEndpoint)/$(format(sessionEndpoint, config.userId, robotId, sessionId))"
    response = @mock _sendRestRequest(config, HTTP.get, url)
    if(response.status != 200)
        error("Error getting session, received $(response.status) with body '$(String(response.body))'.")
    end
    return JSON2.read(String(response.body), SessionDetailsResponse)
end

"""
$(SIGNATURES)
Get a specific session given a session response (update).
Return: The session details for the provided user ID, robot ID, and session ID.
"""
function getSession(session::SessionDetailsResponse)::SessionDetailsResponse
    return getSession(session.robotId, session.id)
end

"""
$(SIGNATURES)
Get a specific session given a user ID, robot ID, and session ID.
Return: The session details for the provided user ID, robot ID, and session ID.
"""
function getSession()::SessionDetailsResponse
    config = getGraffConfig()
    if config == nothing
        error("Graff config is not set, please call setGraffConfig with a valid configuration.")
    end
    if config.robotId == "" || config.sessionId == ""
        error("Your config doesn't have a robot or a session specified, please attach your config to a valid robot or session by setting the robotId and sessionId fields. Robot = $(config.robotId), Session = $(config.sessionId)")
    end

    return getSession(config.robotId, config.sessionId)
end

"""
$(SIGNATURES)
Delete a specific session given a user ID, robot ID, and session ID.
Return: Nothing if success, error if failed.
"""
function deleteSession(robotId::String, sessionId::String)::Nothing
    config = getGraffConfig()
    if config == nothing
        error("Graff config is not set, please call setGraffConfig with a valid configuration.")
    end
    url = "$(config.apiEndpoint)/$(format(sessionEndpoint, config.userId, robotId, sessionId))"
    response = @mock _sendRestRequest(config, HTTP.delete, url)
    if(response.status != 200)
        error("Error deleting session, received $(response.status) with body '$(String(response.body))'.")
    end
    return nothing
end

"""
$(SIGNATURES)
Delete a specific session given a session response.
Return: Nothing if success, error if failed.
"""
function deleteSession(session::SessionDetailsResponse)::Nothing
    return deleteSession(session.robotId, session.id)
end

"""
$(SIGNATURES)
Delete a specific session given a user ID, robot ID, and session ID.
Return: Nothing if success, error if failed.
"""
function deleteSession()::Nothing
    config = getGraffConfig()
    if config == nothing
        error("Graff config is not set, please call setGraffConfig with a valid configuration.")
    end
    if config.robotId == "" || config.sessionId == ""
        error("Your config doesn't have a robot or a session specified, please attach your config to a valid robot or session by setting the robotId and sessionId fields. Robot = $(config.robotId), Session = $(config.sessionId)")
    end

    return deleteSession(config.robotId, config.sessionId)
end

"""
$(SIGNATURES)
Create a session in Synchrony and associate it with the given robot+user.
Return: Returns the created session.
"""
function addSession(robotId::String, session::SessionDetailsRequest)::SessionDetailsResponse
    config = getGraffConfig()
    if config == nothing
        error("Graff config is not set, please call setGraffConfig with a valid configuration.")
    end
    url = "$(config.apiEndpoint)/$(format(sessionEndpoint, config.userId, robotId, session.id))"
    body = JSON.json(session)
    response = @mock _sendRestRequest(config, HTTP.post, url, data=body, headers=Dict{String, String}("Content-Type" => "application/json"))
    if(response.status != 200)
        error("Error creating session, received $(response.status) with body '$(String(response.body))'.")
    end
    body = String(response.body)
    return JSON2.read(body, SessionDetailsResponse)
end

"""
$(SIGNATURES)
Create a session in Synchrony and associate it with the given robot+user.
Return: Returns the created session.
"""
function addSession(session::SessionDetailsRequest)::SessionDetailsResponse
    config = getGraffConfig()
    if config == nothing
        error("Graff config is not set, please call setGraffConfig with a valid configuration.")
    end
    if config.robotId == ""
        error("Your config doesn't have a robot specified, please attach your config to a valid robot by setting the robotId fields. Robot = $(config.robotId)")
    end

    return addSession(config.robotId, session)
end

"""
$(SIGNATURES)
Gets all nodes for a given session.
Return: A vector of nodes for a given robot.
"""
function getVariables(robotId::String, sessionId::String; details=false)::Union{Vector{NodeResponse}, Vector{NodeDetailsResponse}}
    config = getGraffConfig()
    if config == nothing
        error("Graff config is not set, please call setGraffConfig with a valid configuration.")
    end
    url = "$(config.apiEndpoint)/$(format(variablesEndpoint, config.userId, robotId, sessionId))$(details ? "?details=true" : "")"
    response = @mock _sendRestRequest(config, HTTP.get, url)
    if(response.status != 200)
        error("Error getting sessions, received $(response.status) with body '$(String(response.body))'.")
    end
    body = String(response.body)
    nodes = JSON2.read(body, details==false ? Vector{NodeResponse} : Vector{NodeDetailsResponse})

    # sort!(nodes; by=(n -> n.label))
    return nodes
end

"""
$(SIGNATURES)
Gets all nodes for a given session.
Return: A vector of nodes for a given robot.
"""
function getVariables(;details=false)::Union{Vector{NodeResponse}, Vector{NodeDetailsResponse}}
    config = getGraffConfig()
    if config == nothing
        error("Graff config is not set, please call setGraffConfig with a valid configuration.")
    end
    if config.robotId == "" || config.sessionId == ""
        error("Your config doesn't have a robot or a session specified, please attach your config to a valid robot or session by setting the robotId and sessionId fields. Robot = $(config.robotId), Session = $(config.sessionId)")
    end

    return getVariables(config.robotId, config.sessionId; details=details)
end

"""
$(SIGNATURES)
Gets all nodes for a given session.
Return: A vector of nodes for a given robot.
Alias for convenience.
"""
function ls(;details=false)::Union{Vector{NodeResponse}, Vector{NodeDetailsResponse}}
    return getVariables(details=details)
end

"""
$(SIGNATURES)
Get data entries for whole session.
Return: Summary of all data associated with a session as a dictionary keyed by label ID.
"""
function getSessionDataEntries(robotId::String, sessionId::String)::Dict{String, Vector{BigDataEntryResponse}}
    config = getGraffConfig()
    if config == nothing
        error("Graff config is not set, please call setGraffConfig with a valid configuration.")
    end

    url = "$(config.apiEndpoint)/$(format(sessionEndpoint, config.userId, robotId, sessionId))/dataentries"
    response = @mock _sendRestRequest(config, HTTP.get, url)
    body = String(response.body)
    if(response.status != 200)
        error("Error data entries for session, received $(response.status) with body '$body'.")
    else
        bigDataEntries = JSON2.read(body, Dict{String, Vector{BigDataEntryResponse}})
        return bigDataEntries
    end
end

"""
$(SIGNATURES)
Get data entries for whole session.
Return: Summary of all data associated with a session as a dictionary keyed by label ID.
"""
function getSessionDataEntries()::Dict{String, Vector{BigDataEntryResponse}}
    config = getGraffConfig()
    if config == nothing
        error("Graff config is not set, please call setGraffConfig with a valid configuration.")
    end
    if config.robotId == "" || config.sessionId == ""
        error("Your config doesn't have a robot or a session specified, please attach your config to a valid robot or session by setting the robotId and sessionId fields. Robot = $(config.robotId), Session = $(config.sessionId)")
    end

    return getSessionDataEntries(config.robotId, config.sessionId)
end

"""
$(SIGNATURES)
Gets a node's details by either its ID or name.
Return: A node's details.
"""
function getVariable(robotId::String, sessionId::String, nodeIdOrLabel::Union{Int, String, Symbol})::NodeDetailsResponse
    config = getGraffConfig()
    if config == nothing
        error("Graff config is not set, please call setGraffConfig with a valid configuration.")
    end
    url = "$(config.apiEndpoint)/$(format(variableEndpoint, config.userId, robotId, sessionId, nodeIdOrLabel))"
    if(typeof(nodeIdOrLabel) in [String, Symbol])
        nodeIdOrLabel = String(nodeIdOrLabel)
        url = "$(config.apiEndpoint)/$(format(variableLabelledEndpoint, config.userId, robotId, sessionId, nodeIdOrLabel))"
    end
    response = @mock _sendRestRequest(config, HTTP.get, url)
    if(response.status != 200)
        error("Error getting node, received $(response.status) with body '$(String(response.body))'.")
    end
    body = String(response.body)
    # Some manual effort done
    node = JSON2.read(body, NodeDetailsResponse)
    return node
end

"""
$(SIGNATURES)
Gets a node's details by either its ID or name.
Return: A node's details.
"""
function getVariable(nodeIdOrLabel::Union{Int, String, Symbol})::NodeDetailsResponse
    config = getGraffConfig()
    if config == nothing
        error("Graff config is not set, please call setGraffConfig with a valid configuration.")
    end
    if config.robotId == "" || config.sessionId == ""
        error("Your config doesn't have a robot or a session specified, please attach your config to a valid robot or session by setting the robotId and sessionId fields. Robot = $(config.robotId), Session = $(config.sessionId)")
    end

    return getVariable(config.robotId, config.sessionId, nodeIdOrLabel)
end

"""
$(SIGNATURES)
Gets a node's factors by its ID.
Return: List of factor summaries.
"""
function getVariableFactors(robotId::String, sessionId::String, variableId::Union{Int, NodeDetailsResponse, NodeResponse})::Vector{FactorSummary}
    config = getGraffConfig()
    if config == nothing
        error("Graff config is not set, please call setGraffConfig with a valid configuration.")
    end
    if(typeof(variableId) in [NodeDetailsResponse, NodeResponse])
        variableId = variableId.id
    end
    url = "$(config.apiEndpoint)/$(format(variableEndpoint, config.userId, robotId, sessionId, variableId))/factors"
    response = @mock _sendRestRequest(config, HTTP.get, url)
    if(response.status != 200)
        error("Error getting factors, received $(response.status) with body '$(String(response.body))'.")
    end
    body = String(response.body)
    factors = JSON2.read(body, Vector{FactorSummary})
    return factors
end

"""
$(SIGNATURES)
Gets a node's factors by its ID.
Return: List of factor summaries.
"""
function getVariableFactors(variableId::Union{Int, NodeDetailsResponse, NodeResponse})::Vector{FactorSummary}
    config = getGraffConfig()
    if config == nothing
        error("Graff config is not set, please call setGraffConfig with a valid configuration.")
    end
    if config.robotId == "" || config.sessionId == ""
        error("Your config doesn't have a robot or a session specified, please attach your config to a valid robot or session by setting the robotId and sessionId fields. Robot = $(config.robotId), Session = $(config.sessionId)")
    end

    return getVariableFactors(config.robotId, config.sessionId, variableId)
end

"""
$(SIGNATURES)
Set the ready status for a session.
"""
function putReady(robotId::String, sessionId::String, isReady::Bool)::Nothing
    config = getGraffConfig()
    if config == nothing
        error("Graff config is not set, please call setGraffConfig with a valid configuration.")
    end
    url = "$(config.apiEndpoint)/$(format(sessionReadyEndpoint, config.userId, robotId, sessionId, isReady))"
    response = @mock _sendRestRequest(config, HTTP.put, url, data="")
    if(response.status != 200)
        error("Error updating the ready status of the session, received $(response.status) with body '$(String(response.body))'.")
    end
    return nothing
end

"""
$(SIGNATURES)
Set the ready status for a session.
"""
function putReady(isReady::Bool)::Nothing
    config = getGraffConfig()
    if config == nothing
        error("Graff config is not set, please call setGraffConfig with a valid configuration.")
    end
    if config.robotId == "" || config.sessionId == ""
        error("Your config doesn't have a robot or a session specified, please attach your config to a valid robot or session by setting the robotId and sessionId fields. Robot = $(config.robotId), Session = $(config.sessionId)")
    end

    return putReady(config.robotId, config.sessionId, isReady)
end

"""
$(SIGNATURES)
Manually request that the session is solved in entirety (update whole session).
"""
function requestSessionSolve(robotId::String, sessionId::String)::Nothing
    config = getGraffConfig()
    if config == nothing
        error("Graff config is not set, please call setGraffConfig with a valid configuration.")
    end
    url = "$(config.apiEndpoint)/$(format(sessionSolveEndpoint, config.userId, robotId, sessionId))"
    response = @mock _sendRestRequest(config, HTTP.put, url, data="")
    if(response.status != 200)
        error("Error manually requesting a session solve, received $(response.status) with body '$(String(response.body))'.")
    end
    return nothing
end

"""
$(SIGNATURES)
Request that the session is solved in entirety (update whole session).
"""
function requestSessionSolve()::Nothing
    config = getGraffConfig()
    if config == nothing
        error("Graff config is not set, please call setGraffConfig with a valid configuration.")
    end
    if config.robotId == "" || config.sessionId == ""
        error("Your config doesn't have a robot or a session specified, please attach your config to a valid robot or session by setting the robotId and sessionId fields. Robot = $(config.robotId), Session = $(config.sessionId)")
    end

    return requestSessionSolve(config.robotId, config.sessionId)
end

"""
$(SIGNATURES)
Create a variable in Synchrony.
Return: Returns the ID+label of the created variable.
"""
function addVariable(robotId::String, sessionId::String, variableRequest::VariableRequest)::Nothing
    config = getGraffConfig()
    if config == nothing
        error("Graff config is not set, please call setGraffConfig with a valid configuration.")
    end
    url = "$(config.apiEndpoint)/$(format(variableEndpoint, config.userId, robotId, sessionId, variableRequest.label))"
    response = @mock _sendRestRequest(config, HTTP.post, url, data=JSON.json(variableRequest), headers=Dict{String, String}("Content-Type" => "application/json"))
    if(response.status != 200)
        error("Error creating variable, received $(response.status) with body '$(String(response.body))'.")
    end
    return nothing
    # return _unmarshallWithLinks(String(response.body), NodeResponse)
end

"""
$(SIGNATURES)
Create a variable in Synchrony.
Return: Returns the ID+label of the created variable.
"""
function addVariable(variableRequest::VariableRequest)::Nothing
    config = getGraffConfig()
    if config == nothing
        error("Graff config is not set, please call setGraffConfig with a valid configuration.")
    end
    if config.robotId == "" || config.sessionId == ""
        error("Your config doesn't have a robot or a session specified, please attach your config to a valid robot or session by setting the robotId and sessionId fields. Robot = $(config.robotId), Session = $(config.sessionId)")
    end

    return addVariable(config.robotId, config.sessionId,variableRequest)
end

"""
$(SIGNATURES)
Create a variable in Synchrony.
Return: Returns the ID+label of the created variable.
"""
function addVariable(label::String, varType::String, additionalLabels::Vector{String}=Vector{String}())::Nothing
    return addVariable(VariableRequest(label, varType, additionalLabels))
end

"""
$(SIGNATURES)
Create a variable in Synchrony.
Return: Returns the ID+label of the created variable.
"""
function addVariable(label::Symbol, varType::Type, additionalLabels::Vector{String}=Vector{String}())::Nothing
    return addVariable(VariableRequest(String(label), string(varType), additionalLabels))
end

"""
$(SIGNATURES)
Create a variable in Graff.
"""
function addVariable(config::GraffConfig, variableRequest::VariableRequest)

    if config.robotId == "" || config.sessionId == ""
        error("Your config doesn't have a robot or a session specified, please attach your config to a valid robot or session by setting the robotId and sessionId fields. Robot = $(config.robotId), Session = $(config.sessionId)")
    end

    return addVariable(config.robotId, config.sessionId, variableRequest)
end

"""
$(SIGNATURES)
Create a variable in Graff.
"""
function addVariable(config::GraffConfig, label::String, varType::String, additionalLabels::Vector{String}=Vector{String}())::Nothing
    return addVariable(config,VariableRequest(label, varType, additionalLabels))
end

"""
$(SIGNATURES)
Create a variable in Graff.
"""
function addVariable(config::GraffConfig, label::Symbol, varType::Type, additionalLabels::Vector{String}=Vector{String}())::Nothing
    return addVariable(config, VariableRequest(String(label), string(varType), additionalLabels))
end

"""
$(SIGNATURES)
Create a factor in Graff.
"""
function addFactor(config::GraffConfig, factorRequest::FactorRequest)::Nothing
    if config.robotId == "" || config.sessionId == ""
        error("Your config doesn't have a robot or a session specified, please attach your config to a valid robot or session by setting the robotId and sessionId fields. Robot = $(config.robotId), Session = $(config.sessionId)")
    end
    url = "$(config.apiEndpoint)/$(format(factorsEndpoint, config.userId, config.robotId, config.sessionId))"
    response = @mock _sendRestRequest(config, HTTP.post, url, data=JSON.json(factorRequest), headers=Dict{String, String}("Content-Type" => "application/json"))
    if (response.status != 200)
        error("Error creating factor, received $(response.status) with body '$(String(response.body))'.")
    end
    return nothing
    # return _unmarshallWithLinks(String(response.body), NodeResponse)
end

"""
$(SIGNATURES)
Create a factor in Graff as you do in RoME.
Return: Returns the ID+label of the created factor.
"""
function addFactor(config::GraffConfig, variables::Vector{Symbol}, romeFactor)::Nothing

    if config == nothing
        error("Graff config is not set, please call setGraffConfig with a valid configuration.")
    end
    if config.robotId == "" || config.sessionId == ""
        error("Your config doesn't have a robot or a session specified, please attach your config to a valid robot or session by setting the robotId and sessionId fields. Robot = $(config.robotId), Session = $(config.sessionId)")
    end

    try
        # Try pack quickly here.
        fctType = typeof(romeFactor).name # Simply 'prior', as example
        fctPackedType = getfield(Main, Symbol("Packed$(fctType)") )#eval(Meta.parse("Packed$(fctType)"))
        @info "Trying to encode factor $fctType into PackedType $fctPackedType..."
        fctRequest = FactorRequest(String.(variables), string(fctType), convert(fctPackedType, romeFactor))
        @info "Successfully encoded - sending to Graff..."
        return addFactor(config, fctRequest)
    catch ex
        @info "Unable to pack and send factor - did you include RoME and/or Caesar? Please check your factor type is valid and has a PackedType."
        showerror(stderr, ex, catch_backtrace())
        error(ex)
    end
end

"""
$(SIGNATURES)
Create a factor in Synchrony.
Return: Returns the ID+label of the created factor.
"""
function addFactor(robotId::String, sessionId::String, factorRequest::FactorRequest)::Nothing
    config = getGraffConfig()
    if config == nothing
        error("Graff config is not set, please call setGraffConfig with a valid configuration.")
    end
    url = "$(config.apiEndpoint)/$(format(factorsEndpoint, config.userId, robotId, sessionId))"
    response = @mock _sendRestRequest(config, HTTP.post, url, data=JSON.json(factorRequest), headers=Dict{String, String}("Content-Type" => "application/json"))
    if (response.status != 200)
        error("Error creating factor, received $(response.status) with body '$(String(response.body))'.")
    end
    return nothing
    # return _unmarshallWithLinks(String(response.body), NodeResponse)
end

"""
$(SIGNATURES)
Create a factor in Synchrony.
Return: Returns the ID+label of the created factor.
"""
function addFactor(factorRequest::FactorRequest)::Nothing
    config = getGraffConfig()
    if config == nothing
        error("Graff config is not set, please call setGraffConfig with a valid configuration.")
    end
    if config.robotId == "" || config.sessionId == ""
        error("Your config doesn't have a robot or a session specified, please attach your config to a valid robot or session by setting the robotId and sessionId fields. Robot = $(config.robotId), Session = $(config.sessionId)")
    end

    return addFactor(config.robotId, config.sessionId, factorRequest)
end

"""
$(SIGNATURES)
Create a factor in Graff as you do in RoME.
Return: Returns the ID+label of the created factor.
"""
function addFactor(variables::Vector{Symbol}, romeFactor)::Nothing
    config = getGraffConfig()
    if config == nothing
        error("Graff config is not set, please call setGraffConfig with a valid configuration.")
    end
    if config.robotId == "" || config.sessionId == ""
        error("Your config doesn't have a robot or a session specified, please attach your config to a valid robot or session by setting the robotId and sessionId fields. Robot = $(config.robotId), Session = $(config.sessionId)")
    end

    try
        # Try pack quickly here.
        fctType = typeof(romeFactor).name # Simply 'prior', as example
        fctPackedType = getfield(Main, Symbol("Packed$(fctType)") )#eval(Meta.parse("Packed$(fctType)"))
        @info "Trying to encode factor $fctType into PackedType $fctPackedType..."
        fctRequest = FactorRequest(String.(variables), string(fctType), convert(fctPackedType, romeFactor))
        @info "Successfully encoded - sending to Graff..."
        return addFactor(config.robotId, config.sessionId, fctRequest)
    catch ex
        @info "Unable to pack and send factor - did you include RoME and/or Caesar? Please check your factor type is valid and has a PackedType."
        showerror(stderr, ex, catch_backtrace())
        error(ex)
    end
end

"""
$(SIGNATURES)
Create a variable in Synchrony and associate it with the given robot+user.
Return: Returns ID+label of the created factor.
"""
function addBearingRangeFactor(robotId::String, sessionId::String, bearingRangeRequest::BearingRangeRequest)::Nothing
    config = getGraffConfig()
    if config == nothing
        error("Graff config is not set, please call setGraffConfig with a valid configuration.")
    end
    url = "$(config.apiEndpoint)/$(format(bearingRangeEndpoint, config.userId, robotId, sessionId))"
    response = @mock _sendRestRequest(config, HTTP.post, url, data=JSON.json(bearingRangeRequest), headers=Dict{String, String}("Content-Type" => "application/json"))
    if(response.status != 200)
        error("Error creating bearing range factor, received $(response.status) with body '$(String(response.body))'.")
    end
    return nothing
    # return _unmarshallWithLinks(String(response.body), NodeResponse)
end

"""
$(SIGNATURES)
Create a variable in Synchrony and associate it with the given robot+user.
Return: Returns ID+label of the created factor.
"""
function addBearingRangeFactor(bearingRangeRequest::BearingRangeRequest)::Nothing
    config = getGraffConfig()
    if config == nothing
        error("Graff config is not set, please call setGraffConfig with a valid configuration.")
    end
    if config.robotId == "" || config.sessionId == ""
        error("Your config doesn't have a robot or a session specified, please attach your config to a valid robot or session by setting the robotId and sessionId fields. Robot = $(config.robotId), Session = $(config.sessionId)")
    end

    return addBearingRangeFactor(config.robotId, config.sessionId, bearingRangeRequest)
end

"""
$(SIGNATURES)
Create a session in Synchrony and associate it with the given robot+user.
Return: Returns the added odometry information.
"""
function addOdometryMeasurement(robotId::String, sessionId::String, addOdoRequest::AddOdometryRequest)::Nothing
    config = getGraffConfig()
    if config == nothing
        error("Graff config is not set, please call setGraffConfig with a valid configuration.")
    end
    url = "$(config.apiEndpoint)/$(format(odoEndpoint, config.userId, robotId, sessionId))"
    response = @mock _sendRestRequest(config, HTTP.post, url, data=JSON.json(addOdoRequest), headers=Dict{String, String}("Content-Type" => "application/json"))
    if(response.status != 200)
        error("Error creating odometry, received $(response.status) with body '$(String(response.body))'.")
    end
    return nothing
end

"""
$(SIGNATURES)
Create a session in Synchrony and associate it with the given robot+user.
Return: Returns the added odometry information.
"""
function addOdometryMeasurement(addOdoRequest::AddOdometryRequest)::Nothing
    config = getGraffConfig()
    if config == nothing
        error("Graff config is not set, please call setGraffConfig with a valid configuration.")
    end
    if config.robotId == "" || config.sessionId == ""
        error("Your config doesn't have a robot or a session specified, please attach your config to a valid robot or session by setting the robotId and sessionId fields. Robot = $(config.robotId), Session = $(config.sessionId)")
    end

    return addOdometryMeasurement(config.robotId, config.sessionId, addOdoRequest)
end

"""
$(SIGNATURES)
Create a session in Synchrony and associate it with the given robot+user.
Return: Returns the added odometry information.
"""
function addOdometryMeasurement(odoDelta::Vector{Float64}, pOdo::Matrix{Float64})::Nothing
    config = getGraffConfig()
    if config == nothing
        error("Graff config is not set, please call setGraffConfig with a valid configuration.")
    end
    if config.robotId == "" || config.sessionId == ""
        error("Your config doesn't have a robot or a session specified, please attach your config to a valid robot or session by setting the robotId and sessionId fields. Robot = $(config.robotId), Session = $(config.sessionId)")
    end

    return addOdometryMeasurement(config.robotId, config.sessionId, AddOdometryRequest(odoDelta, pOdo))
end

"""
$(SIGNATURES)
Get data entries associated with a node.
Return: Summary of all data associated with a node.
"""
function getDataEntries(robotId::String, sessionId::String, node::Union{Int, NodeResponse, NodeDetailsResponse})::Vector{BigDataEntryResponse}
    config = getGraffConfig()
    if config == nothing
        error("Graff config is not set, please call setGraffConfig with a valid configuration.")
    end
    # Get the node ID.
    nodeId = typeof(node) != Int ? node.id : node;

    url = "$(config.apiEndpoint)/$(format(bigDataEndpoint, config.userId, robotId, sessionId, nodeId))"
    response = @mock _sendRestRequest(config, HTTP.get, url)
    if(response.status != 200)
        error("Error getting node data entries, received $(response.status) with body '$(String(response.body))'.")
    else
        datas = JSON2.read(String(response.body), Vector{BigDataEntryResponse})
        sort(datas; by=(d -> d.id))
        return datas
    end
end

"""
$(SIGNATURES)
Get data entries associated with a node.
Return: Summary of all data associated with a node.
"""
function getDataEntries(node::Union{Int, NodeResponse, NodeDetailsResponse})::Vector{BigDataEntryResponse}
    config = getGraffConfig()
    if config == nothing
        error("Graff config is not set, please call setGraffConfig with a valid configuration.")
    end
    if config.robotId == "" || config.sessionId == ""
        error("Your config doesn't have a robot or a session specified, please attach your config to a valid robot or session by setting the robotId and sessionId fields. Robot = $(config.robotId), Session = $(config.sessionId)")
    end

    return getDataEntries(config.robotId, config.sessionId, node)
end

"""
$(SIGNATURES)
Get data elment associated with a node.
Return: Full data element associated with the specified node.
"""
function getData(robotId::String, sessionId::String, node::Union{Int, NodeResponse, NodeDetailsResponse}, bigDataKey::Union{String, BigDataEntryResponse})::Union{BigDataElementResponse, Nothing}
    config = getGraffConfig()
    if config == nothing
        error("Graff config is not set, please call setGraffConfig with a valid configuration.")
    end
    # Get the node ID.
    nodeId = typeof(node) != Int ? node.id : node;
    bigDataKey = typeof(bigDataKey) == BigDataEntryResponse ? bigDataKey.id : bigDataKey

    # Check if caching enabled,
    if isdefined(GraffSDK, :__localCache)
        cacheKey = "$(config.userId)|$robotId|$sessionId|$nodeId|$bigDataKey"
        @debug "Looking in cache for '$cacheKey'..."
        elem = getElement(cacheKey)
        elem != nothing && @info "Found in cache!"
        if __forceOnlyLocalCache
            elem == nothing && @warn "Element doesn't exist in local cache and forceOnlyLocalCache is true, so returning nothing!"
            return elem
        end
        # Otherwise if cache hit, return it.
        elem != nothing  && return elem
        @debug "Cache miss - retrieving from global server..."
    end

    url = "$(config.apiEndpoint)/$(format(bigDataElementEndpoint, config.userId, robotId, sessionId, nodeId, bigDataKey))"
    response = @mock _sendRestRequest(config, HTTP.get, url)
    if(response.status != 200)
        error("Error getting node data entries, received $(response.status) with body '$(String(response.body))'.")
    end
    contentHeaderIndex = findfirst(v -> v[1] == "Content-Type", response.headers)
    bde = BigDataElementResponse(bigDataKey, response.headers[contentHeaderIndex][2], String(response.body))

    # Update the cache
    if isdefined(GraffSDK, :__localCache)
        @debug "Updating cache..."
        setElement("$(config.userId)|$robotId|$sessionId|$nodeId|$bigDataKey", bde)
    end
    return bde
end

"""
$(SIGNATURES)
Get data elment associated with a node.
Return: Full data element associated with the specified node.
"""
function getData(node::Union{Int, NodeResponse, NodeDetailsResponse}, bigDataKey::Union{String, BigDataEntryResponse})::Union{BigDataElementResponse, Nothing}
    config = getGraffConfig()
    if config == nothing
        error("Graff config is not set, please call setGraffConfig with a valid configuration.")
    end
    if config.robotId == "" || config.sessionId == ""
        error("Your config doesn't have a robot or a session specified, please attach your config to a valid robot or session by setting the robotId and sessionId fields. Robot = $(config.robotId), Session = $(config.sessionId)")
    end

    return getData(config.robotId, config.sessionId, node, bigDataKey)
end

"""
$(SIGNATURES)
Add a data element associated with a node.
Return: Nothing if succeed, error if failed.
"""
function setData(robotId::String, sessionId::String, node::Union{Int, String, Symbol, NodeResponse, NodeDetailsResponse}, bigDataElement::BigDataElementRequest)::Nothing
    config = getGraffConfig()
    if config == nothing
        error("Graff config is not set, please call setGraffConfig with a valid configuration.")
    end
    # Get the node ID.
    nodeId = typeof(node) == NodeResponse || typeof(node) == NodeDetailsResponse ? node.id : node;
    nodeId = typeof(nodeId) == Symbol ? String(nodeId) : nodeId;

    tempData = bigDataElement.data
    if isdefined(GraffSDK, :__localCache)
        if __forceOnlyLocalCache # Emtpy out the data if it's only going local.
            @debug "Cleaning out data because forcing local cache, only saving entry..."
            bigDataElement = deepcopy(bigDataElement)
            bigDataElement.data = Vector{UInt8}
        end
    end

    url = "$(config.apiEndpoint)/$(format(bigDataElementEndpoint, config.userId, robotId, sessionId, nodeId, bigDataElement.id))"
    headers=Dict{String, String}(
        "Content-Type" => bigDataElement.mimeType,
        "description" => bigDataElement.description)
    response = @mock _sendRestRequest(config, HTTP.post, url, data=bigDataElement.data, headers=headers)
    if(response.status != 200)
        error("Error adding data element, received $(response.status) with body '$(String(response.body))'.")
    end

    # TODO: Return the data entry when setting...
    # Update the cache
    if isdefined(GraffSDK, :__localCache)
        cacheKey = "$(config.userId)|$robotId|$sessionId|$nodeId|$(bigDataElement.id)"
        @debug "Updating cache key '$cacheKey'..."
        bde = BigDataElementResponse(bigDataElement.id, bigDataElement.mimeType, bigDataElement.data)
        if __forceOnlyLocalCache
            bde.data = tempData
        end
        setElement(cacheKey, bde)
    end

    return nothing
end

"""
$(SIGNATURES)
Set a data element associated with a node.
Return: Nothing if succeed, error if failed.
"""
function setData(node::Union{Int, String, Symbol, NodeResponse, NodeDetailsResponse}, bigDataElement::BigDataElementRequest)::Nothing
    config = getGraffConfig()
    if config == nothing
        error("Graff config is not set, please call setGraffConfig with a valid configuration.")
    end
    if config.robotId == "" || config.sessionId == ""
        error("Your config doesn't have a robot or a session specified, please attach your config to a valid robot or session by setting the robotId and sessionId fields. Robot = $(config.robotId), Session = $(config.sessionId)")
    end

    return setData(config.robotId, config.sessionId, node, bigDataElement)
end

"""
$(SIGNATURES)
Set a data element associated with a node.
Return: Nothing if succeed, error if failed.
"""
function setData(node::Union{Int, String, Symbol, NodeResponse, NodeDetailsResponse}, elemId::String, data::String; sourceName::String="mongo", description::String="", mimeType::String="application/octet-stream")::Nothing
    config = getGraffConfig()
    if config == nothing
        error("Graff config is not set, please call setGraffConfig with a valid configuration.")
    end
    if config.robotId == "" || config.sessionId == ""
        error("Your config doesn't have a robot or a session specified, please attach your config to a valid robot or session by setting the robotId and sessionId fields. Robot = $(config.robotId), Session = $(config.sessionId)")
    end

    de = BigDataElementRequest(elemId, sourceName, description, data, mimeType)
    return setData(config.robotId, config.sessionId, node, de)
end

"""
$(SIGNATURES)
Delete a data element associated with a node.
Return: Nothing if succeed, error if failed.
"""
function deleteData(robotId::String, sessionId::String, node::Union{Int, String, Symbol, NodeResponse, NodeDetailsResponse}, dataId::String)::Nothing
    config = getGraffConfig()
    if config == nothing
        error("Graff config is not set, please call setGraffConfig with a valid configuration.")
    end
    # Get the node ID.
    nodeId = typeof(node) == NodeResponse || typeof(node) == NodeDetailsResponse ? node.id : node;
    nodeId = typeof(nodeId) == Symbol ? String(nodeId) : nodeId;

    url = "$(config.apiEndpoint)/$(format(bigDataElementEndpoint, config.userId, robotId, sessionId, nodeId, dataId))"
    response = @mock _sendRestRequest(config, HTTP.delete, url)
    if(response.status != 200)
        error("Error deleting data element '$dataId', received $(response.status) with body '$(String(response.body))'.")
    end

    # Update the cache
    if isdefined(GraffSDK, :__localCache)
        cacheKey = "$(config.userId)|$robotId|$sessionId|$nodeId|$dataId"
        @debug "Deleting cache key '$cacheKey'..."
        deleteElement(cacheKey)
    end

    return nothing
end

"""
$(SIGNATURES)
Delete a data element associated with a node.
Return: Nothing if succeed, error if failed.
"""
function deleteData(node::Union{Int, NodeResponse, NodeDetailsResponse}, dataId::String)::Nothing
    config = getGraffConfig()
    if config == nothing
        error("Graff config is not set, please call setGraffConfig with a valid configuration.")
    end
    if config.robotId == "" || config.sessionId == ""
        error("Your config doesn't have a robot or a session specified, please attach your config to a valid robot or session by setting the robotId and sessionId fields. Robot = $(config.robotId), Session = $(config.sessionId)")
    end

    return deleteData(config.robotId, config.sessionId, node, dataId)
end

"""
$(SIGNATURES)
Export a session to a JLD file.
Return: Nothing if succeed, error if failed.
"""
function exportSessionJld(robotId::String, sessionId::String, filename::String)::Nothing
    config = getGraffConfig()
    if config == nothing
        error("Graff config is not set, please call setGraffConfig with a valid configuration.")
    end
    if config.robotId == "" || config.sessionId == ""
        error("Your config doesn't have a robot or a session specified, please attach your config to a valid robot or session by setting the robotId and sessionId fields. Robot = $(config.robotId), Session = $(config.sessionId)")
    end

    url = "$(config.apiEndpoint)/$(format(sessionExportJldEndpoint, config.userId, robotId, sessionId))"
    response = @mock _sendRestRequest(config, HTTP.get, url)
    if(response.status != 200)
        error("Error exporting session, received $(response.status) with body '$(String(response.body))'.")
    end
    out =  open(filename,"w")
    write(out,response.body)
    close(out)

    return nothing
end

"""
$(SIGNATURES)
Export a session to a JLD file.
Return: Nothing if succeed, error if failed.
"""
function exportSessionJld(filename::String)::Nothing
    config = getGraffConfig()
    if config == nothing
        error("Graff config is not set, please call setGraffConfig with a valid configuration.")
    end
    if config.robotId == "" || config.sessionId == ""
        error("Your config doesn't have a robot or a session specified, please attach your config to a valid robot or session by setting the robotId and sessionId fields. Robot = $(config.robotId), Session = $(config.sessionId)")
    end

    return exportSessionJld(config.robotId, config.sessionId, filename)
end


text:
module KhepriUnreal
using KhepriBase
using Sockets

# functions that need specialization
include(khepribase_interface_file())
include("Unreal.jl")

function __init__()
  set_backend_family(default_wall_family(), unreal, unreal_material_family("/Game/StarterContent/Materials/M_Basic_Wall.M_Basic_Wall"))
  set_backend_family(default_slab_family(), unreal, unreal_material_family("/Game/StarterContent/Materials/M_Concrete_Tiles.M_Concrete_Tiles"))
  set_backend_family(default_roof_family(), unreal, unreal_material_family("/Game/StarterContent/Materials/M_Concrete_Tiles.M_Concrete_Tiles"))
  set_backend_family(default_beam_family(), unreal, unreal_material_family("/Game/StarterContent/Materials/M_Metal_Steel.M_Metal_Steel"))
  set_backend_family(default_column_family(), unreal, unreal_material_family("/Game/StarterContent/Materials/M_Concrete_Tiles.M_Concrete_Tiles"))
  set_backend_family(default_door_family(), unreal, unreal_material_family("/Game/StarterContent/Materials/M_Wood_Oak.M_Wood_Oak"))
  set_backend_family(default_panel_family(), unreal, unreal_material_family("/Game/StarterContent/Materials/M_Glass.M_Glass"))
  set_backend_family(default_table_family(), unreal, unreal_resource_family("/Game/FreeFurniturePack/Meshes/SM_Modern_Table.SM_Modern_Table"))
  set_backend_family(default_chair_family(), unreal, unreal_resource_family("/Game/FreeFurniturePack/Meshes/SM_Modern_Chair_1.SM_Modern_Chair_1"))
  #set_backend_family(default_table_chair_family(), unreal, unreal_resource_family("Prefabs/TablesChairs/ModernTableChair/ModernTableChair"))

  set_backend_family(default_curtain_wall_family().panel, unreal,
    unreal_material_family("/Game/StarterContent/Materials/M_Glass.M_Glass"))
  set_backend_family(default_curtain_wall_family().boundary_frame, unreal,
      unreal_material_family("/Game/StarterContent/Materials/M_Metal_Steel.M_Metal_Steel"))
  set_backend_family(default_curtain_wall_family().transom_frame, unreal,
    unreal_material_family("/Game/StarterContent/Materials/M_Metal_Steel.M_Metal_Steel"))
  set_backend_family(default_curtain_wall_family().mullion_frame, unreal,
    unreal_material_family("/Game/StarterContent/Materials/M_Metal_Steel.M_Metal_Steel"))
end

end

ext: jl


text:
# A collection of extensions to the DomainSets package.

using DomainSets: inverse_map, forward_map

###########################
# Applying broadcast to in
###########################

# Intercept a broadcasted call to indomain. We assume that the user wants evaluation
# in a set of points (which we call a grid), rather than in a single point.
# TODO: the user may want to evaluate a single point in a sequence of domains...
broadcast(::typeof(in), grid, d::Domain) = indomain_broadcast(grid, d)

# # Default methods for evaluation on a grid: the default is to call eval on the domain with
# # points as arguments. Domains that have faster grid evaluation routines may define their own version.
indomain_broadcast(grid, d::Domain) = indomain_broadcast!(BitArray(undef, size(grid)), grid, d)
# TODO: use BitArray here

function indomain_broadcast!(result, grid::AbstractGrid, domain::Domain)
    for (i,x) in enumerate(grid)
        result[i] = DomainSets.in(x, domain)
    end
    result
end

function indomain_broadcast(grid::AbstractGrid, d::UnionDomain)
    z = indomain_broadcast(grid, element(d,1))
    for i in 2:numelements(d)
        z = z .| indomain_broadcast(grid, element(d,i))
    end
    z
end

function indomain_broadcast(grid::AbstractGrid, d::IntersectionDomain)
    z = indomain_broadcast(grid, element(d,1))
    for i in 2:numelements(d)
        z = z .& indomain_broadcast(grid, element(d,i))
    end
    z
end

function indomain_broadcast(grid::AbstractGrid, d::DifferenceDomain)
    z1 = indomain_broadcast(grid, d.d1)
    z2 = indomain_broadcast(grid, d.d2)
    z1 .& (.~z2)
end


text:
struct Contradiction end
const contradiction = Contradiction()

Base.show(io::IO, ::Contradiction) = print(io, "contradiction")

isunusable(x) = x == nothing || x == contradiction

# Scheduler

const workqueue = WorkQueue()

alert!() = return
alert!(ps...) = push!(workqueue, ps...)

function propagate!()
  try
    while !isempty(workqueue)
      p = pop!(workqueue)
      p.activate!()
    end
  finally
    empty!(workqueue)
  end
end

# Cells

mutable struct Cell
  neighbours
  content
end

Cell() = Cell(Set(), nothing)

function cellmerge(content, increment)
  (content == nothing || increment == contradiction) && return increment
  (increment == nothing || content == contradiction) && return content
  content == increment && return content
  return contradiction
end

function addcontent!(cell, increment)
  answer = cellmerge(cell.content, increment)
  if cell[] == answer
    return
  elseif answer == contradiction
    error("Contradiction between $(cell.content) and $increment")
  else
    cell.content = answer
    alert!(cell.neighbours...)
    return
  end
end

Base.setindex!(c::Cell, value) = addcontent!(c, value)
Base.getindex(c::Cell) = c.content

# Propagators

struct Propagator
  inputs
  activate!
  function Propagator(activate!, inputs)
    me = new(inputs, activate!)
    for cell in inputs
      push!(cell.neighbours, me)
    end
    alert!(me)
    return me
  end
end


text:
using Vann
using NetCDF
using ProgressMeter

# Read forcing data

function read_input(date, variable)

  year  = Dates.format(date, "yyyy")
  month = Dates.format(date, "mm")
  day   = Dates.format(date, "dd")

  filename = Pkg.dir("Vann", "data/senorge/$(variable)_$(year)_$(month)_$(day).nc")

  if variable == "rr"
    data = ncread(filename, "precipitation_amount")
  end

  if variable == "tm"
    data = ncread(filename, "mean_temperature")
  end

  data = squeeze(data, 3)

  data = map(x -> convert(Float64, x), data)

  data = round(data, 2)

  data = data[data .!= -999.99]

  return data

end

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

function run_dist(date_vec)

  # Number of computation elements

  ncells = 606579

  # Initilize state variables

  tstep = 24.0

  time = date_vec[1]

  st_snow  = [TinBasic(tstep, time, [1.0]) for i in 1:ncells]
  st_hydro = [Gr4j(tstep, time) for i in 1:ncells]

  # Allocate output arrays

  q_sim = zeros(Float64, ncells)

  @showprogress 1 "Computing..." for date in date_vec

    # println(date)

    # Load input data

    prec = read_input(date, "rr")
    tair = read_input(date, "tm")
    epot = zeros(Float64, ncells)

    for icell = 1:ncells

      # Get input to snow model

      st_snow[icell].prec[1] = prec[icell]
      st_snow[icell].tair[1] = tair[icell]

      # Run snow model

      run_timestep(st_snow[icell])

      # Get input to hydrological routing model

      st_hydro[icell].prec = st_snow[icell].q_sim
      st_hydro[icell].epot = epot[icell]

      # Run hydrological routing model

      run_timestep(st_hydro[icell])

      q_sim[icell] = st_hydro[icell].q_sim

    end

  end

  return(q_sim)

end

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

# Simulation period

date_vec = DateTime(2010, 1, 1):DateTime(2010, 1, 10)

run_dist(date_vec)

max_stars_repo_name: jmgnve/Vann

max_forks_repo_name: jmgnve/Vann


text:
#=
   -x, abs(x), inv(x),
   sqrt(x), invsqrt(x)
   x+y, x-y, x*y, x/y, hypot(x,y)
   muladd(a,b,c), fma(a,b,c)
=#

function signbit{T<:ArbFloat}(x::T)
    0 != ccall(@libarb(arb_is_negative), Int, (Ptr{T},), &x)
end

function abs{T<:ArbFloat}(x::T)
    lo,hi = bounds(x)

    lo = signbit(lo) ? -lo : lo
    hi = signbit(hi) ? -hi : hi
    if lo > hi
       lo, hi = hi, lo
    end
    return bounds( lo, hi )
end

function abs2{T<:ArbFloat}(x::T)
    lo, hi = bounds(abs(x))
    lo = lo^2
    hi = hi^2
    return bounds( lo, hi )
end

for (op,cfunc) in ((:-,:arb_neg), (:sign, :arb_sgn))
  @eval begin
    function ($op){T<:ArbFloat}(x::T)
      z = T()
      ccall(@libarb($cfunc), Void, (Ptr{T}, Ptr{T}), &z, &x)
      z
    end
  end
end

for (op,cfunc) in ((:inv, :arb_inv), (:sqrt, :arb_sqrt), (:invsqrt, :arb_rsqrt))
  @eval begin
    function ($op){T<:ArbFloat}(x::T)
      P = precision(T)
      z = ArbFloat{P}()
      ccall(@libarb($cfunc), Void, (Ptr{T}, Ptr{T}, Int), &z, &x, P)
      return z
    end
  end
end

for (op,cfunc) in ((:+,:arb_add), (:-, :arb_sub), (:/, :arb_div), (:hypot, :arb_hypot))
  @eval begin
    function ($op){T<:ArbFloat}(x::T, y::T)
      P = precision(T)
      z = ArbFloat{P}()
      ccall(@libarb($cfunc), Void, (Ptr{T}, Ptr{T}, Ptr{T}, Int), &z, &x, &y, P)
      z
    end
    ($op){P,Q}(x::ArbFloat{P}, y::ArbFloat{Q}) = ($op)(promote(x,y)...)
    ($op){T<:ArbFloat, F<:AbstractFloat}(x::T, y::F) = ($op)(x, convert(T, y))
    ($op){T<:ArbFloat, F<:AbstractFloat}(x::F, y::T) = ($op)(convert(T, x), y)
    ($op){T<:ArbFloat, R<:Rational}(x::T, y::R) = ($op)(x, convert(T, y))
    ($op){T<:ArbFloat, R<:Rational}(x::R, y::T) = ($op)(convert(T, x), y)
    ($op){T<:ArbFloat, R<:Real}(x::T, y::R) = ($op)(x, convert(T, y))
    ($op){T<:ArbFloat, R<:Real}(x::R, y::T) = ($op)(convert(T, x), y)
  end
end

function (*){T<:ArbFloat}(x::T, y::T)
    P = precision(T)
    z = ArbFloat{P}()
    ccall(@libarb(arb_mul), Void, (Ptr{T}, Ptr{T}, Ptr{T}, Int), &z, &x, &y, P)
    return z
end
(*){T<:ArbFloat, F<:AbstractFloat}(x::T, y::F) = (*)(x, convert(T, y))
(*){T<:ArbFloat, F<:AbstractFloat}(x::F, y::T) = (*)(convert(T, x), y)
(*){T<:ArbFloat, R<:Rational}(x::T, y::R) = (*)(x, convert(T, y))
(*){T<:ArbFloat, R<:Rational}(x::R, y::T) = (*)(convert(T, x), y)
(*){T<:ArbFloat, R<:Real}(x::T, y::R) = (*)(x, convert(T, y))
(*){T<:ArbFloat, R<:Real}(x::R, y::T) = (*)(convert(T, x), y)

(+){T<:ArbFloat}(x::T, y::T, z::T) = (x + y) + z

(+){T<:ArbFloat,I<:Integer}(x::I, y::T, z::T) = x + (y + z)
(+){T<:ArbFloat,I<:Integer}(x::T, y::T, z::I) = (x + y) + z
(+){T<:ArbFloat,R<:Real}(x::T, y::T, z::R) = (x + y) + z
(+){T<:ArbFloat,R<:Real}(x::R, y::T, z::T) =  x + (y + z)

(*){T<:ArbFloat}(x::T, y::T, z::T) = (x * y) * z

(*){T<:ArbFloat,I<:Integer}(x::I, y::T, z::T) = x * (y * z)
(*){T<:ArbFloat,I<:Integer}(x::T, y::T, z::I) = (x * y) * z
(*){T<:ArbFloat,R<:Real}(x::T, y::T, z::R) = (x * y) * z
(*){T<:ArbFloat,R<:Real}(x::R, y::T, z::T) =  x * (y * z)

(-){T<:ArbFloat}(x::T, y::T, z::T) = (x - y) - z
(/){T<:ArbFloat}(x::T, y::T, z::T) = (x / y) / z

for (op,cfunc) in ((:+,:arb_add_si), (:-, :arb_sub_si), (:*, :arb_mul_si), (:/, :arb_div_si))
  @eval begin
    function ($op){T<:ArbFloat}(x::T, y::Int)
      P = precision(T)
      z = ArbFloat{P}()
      ccall(@libarb($cfunc), Void, (Ptr{T}, Ptr{T}, Int, Int), &z, &x, y, P)
      return z
    end
  end
end

(+){T<:ArbFloat}(x::Int, y::T) = (+)(y,x)
(-){T<:ArbFloat}(x::Int, y::T) = -((-)(y,x))
(*){T<:ArbFloat}(x::Int, y::T) = (*)(y,x)
(/){T<:ArbFloat}(x::Int, y::T) = (/)(T(x),y)

(+){T<:ArbFloat}(x::T, y::Integer) = (+)(x, convert(T, y))
(-){T<:ArbFloat}(x::T, y::Integer) = (-)(x, convert(T, y))
(*){T<:ArbFloat}(x::T, y::Integer) = (*)(x, convert(T, y))
(/){T<:ArbFloat}(x::T, y::Integer) = (/)(x, convert(T, y))

(+){T<:ArbFloat}(x::Integer, y::T) = (+)(convert(T, x), y)
(-){T<:ArbFloat}(x::Integer, y::T) = -((-)(y, convert(T, x)))
(*){T<:ArbFloat}(x::Integer, y::T) = (*)(convert(T,x), y)
(/){T<:ArbFloat}(x::Integer, y::T) = (/)(convert(T,x), y)

for (op,cfunc) in ((:addmul,:arb_addmul), (:submul, :arb_submul))
  @eval begin
    function ($op){T<:ArbFloat}(w::T, x::T, y::T)
      P = precision(T)
      z = ArbFloat{P}()
      ccall(@libarb($cfunc), Void, (Ptr{T}, Ptr{T}, Ptr{T}, Ptr{T}, Int), &z, &w, &x, &y, P)
      z
    end
  end
end

muladd{T<:ArbFloat}(a::T, b::T, c::T) = addmul(c,a,b)
mulsub{T<:ArbFloat}(a::T, b::T, c::T) = addmul(-c,a,b)


text:
using FDA
using Test
using CSV

@testset "FDA.jl" begin
    df = CSV.read("sample.csv")
    y = df[:,2:251];

    temp_basis = gen_fourier_basis([0,1],15)
    argvals = range(0,1;length = 101)
    temp_coefs = Data2fd(argvals, y, temp_basis)

    smooth_curve = eval_fd(argvals, temp_coefs)

    plot(argvals, smooth_curve[:,1], label = "Smoothed curve", lw = 3)
    plot!(argvals, y[:,1], seriestype = :scatter, label = "Discrete observations", lw = 3)
end


text:
const ParameterName=Union{Symbol,Expr}

"""
    Grid(goal=nothing, resolution=10, rng=Random.GLOBAL_RNG, shuffle=true)

Instantiate a Cartesian grid-based hyperparameter tuning strategy with
a specified number of grid points as `goal`, or using a specified
default `resolution` in each numeric dimension.

### Supported ranges:

- A single one-dimensional range (`ParamRange` object) `r`, or pair of
  the form `(r, res)` where `res` specifies a resolution to override
  the default `resolution`.

- Any vector of objects of the above form

`ParamRange` objects are constructed using the `range` method.

Example 1:

    range(model, :hyper1, lower=1, origin=2, unit=1)

Example 2:

    [(range(model, :hyper1, lower=1, upper=10), 15),
      range(model, :hyper2, lower=2, upper=4),
      range(model, :hyper3, values=[:ball, :tree]]

Note: All the `field` values of the `ParamRange` objects (`:hyper1`,
`:hyper2`, `:hyper3` in the precedng example) must refer to field
names a of single model (the `model` specified during `TunedModel`
construction).

### Algorithm

This is a standard grid search with the following specifics: In all
cases all `values` of each specified `NominalRange` are exhausted. If
`goal` is specified, then all resolutions are ignored, and a global
resolution is applied to the `NumericRange` objects that maximizes the
number of grid points, subject to the restriction that this not exceed
`goal`. Otherwise the default `resolution` and any parameter-specific
resolutions apply.

In all cases the models generated are shuffled using `rng`, unless
`shuffle=false`.

See also [TunedModel](@ref), [range](@ref).

"""
mutable struct Grid <: TuningStrategy
    goal::Union{Nothing,Int}
    resolution::Int
    shuffle::Bool
    rng::Random.AbstractRNG
end

# Constructor with keywords
Grid(; goal=nothing, resolution=10, shuffle=true,
     rng=Random.GLOBAL_RNG) =
    Grid(goal, resolution, MLJBase.shuffle_and_rng(shuffle, rng)...)

isnumeric(::Any) = false
isnumeric(::NumericRange) = true

adjusted_resolutions(::Nothing,  ranges, resolutions) = resolutions
function adjusted_resolutions(goal, ranges, resolutions)
    # get the product Π of the lengths of the NominalRanges:
    len(::NumericRange) = 1
    len(r::NominalRange) = length(r.values)
    Π = prod(len.(ranges))

    n_numeric = sum(isnumeric.(ranges))

    # compute new uniform resolution:
    goal = goal/Π
    res = round(Int, goal^(1/n_numeric))
    return  map(eachindex(resolutions)) do j
        isnumeric(ranges[j]) ? res : resolutions[j]
    end
end

function setup(tuning::Grid, model, user_range, verbosity)
    ranges, resolutions =
        process_user_range(user_range, tuning.resolution, verbosity)
    resolutions = adjusted_resolutions(tuning.goal, ranges, resolutions)

    fields = map(r -> r.field, ranges)

    parameter_scales = scale.(ranges)

    if tuning.shuffle
        models = grid(tuning.rng, model, ranges, resolutions)
    else
        models = grid(model, ranges, resolutions)
    end

    state = (models=models,
             fields=fields,
             parameter_scales=parameter_scales)

    return state

end

MLJTuning.models!(tuning::Grid, model, history::Nothing,
                  state, verbosity) = state.models
MLJTuning.models!(tuning::Grid, model, history,
                  state, verbosity) =
    state.models[length(history) + 1:end]

function tuning_report(tuning::Grid, history, state)

    plotting = plotting_report(state.fields, state.parameter_scales, history)

    # todo: remove collects?
    return (history=history, plotting=plotting)

end

function default_n(tuning::Grid, user_range)
    ranges, resolutions =
        process_user_range(user_range, tuning.resolution, -1)

    resolutions = adjusted_resolutions(tuning.goal, ranges, resolutions)
    len(t::Tuple{NumericRange,Integer}) = length(iterator(t[1], t[2]))
    len(t::Tuple{NominalRange,Integer}) = t[2]
    return prod(len.(zip(ranges, resolutions)))

end


text:
export hamiltonian_cycle

"""
`hamiltonian_cycle(G::SimpleGraph)` returns a Hamiltonian cycle in the
graph (if one exists) or an empty array (otherwise). This works
reasonably well for small graphs.
"""
function hamiltonian_cycle(G::SimpleGraph)
    T = vertex_type(G)

    # rule out some simple non-Hamiltonian graphs
    if NV(G) < 3 || minimum(deg(G)) < 2 || !is_connected(G)
        return T[]
    end

    n = NV(G)
    path = Array{T,1}(n)  # we'll put the answer here

    used = Dict{T,Bool}()
    for v in G.V
        used[v] = false
    end

    VV = vlist(G)
    v = VV[1]
    used[v] = true
    path[1] = v

    if ham_extend(G,VV,1,used,path)
        return path
    else
        return T[]
    end
end

function ham_extend(G::SimpleGraph,
                    VV::Array,
                    idx::Int,
                    used::Dict,
                    path::Array)
    # println(path[1:idx])  # debug
    n = NV(G)
    v = path[idx]

    if idx == n
        return has(G,path[1],path[n])
    end

    for w in G[v]
        if !used[w]
            path[idx+1] = w
            used[w] = true
            if ham_extend(G,VV,idx+1,used,path)
                return true
            end
            used[w]=false
        end
    end
    return false
end

function ham_check(G,path::Array)
    n = NV(G)
    if length(path) != n
        return false
    end

    for k=1:n-1
        if !has(G,path[k],path[k+1])
            return false
        end
    end
    return has(G,path[1],path[n])
end


text:
const INLINE_RESULT_LENGTH = 100
const MAX_RESULT_LENGTH = 10_000

# Workaround for https://github.com/julia-vscode/julia-vscode/issues/1940
struct Wrapper
    content
end
wrap(x) = Wrapper(x)
unwrap(x) = x.content

const EVAL_CHANNEL_IN = Channel(0)
const EVAL_CHANNEL_OUT = Channel(0)
const EVAL_BACKEND_TASK = Ref{Any}(nothing)
const IS_BACKEND_WORKING = Ref{Bool}(false)

function is_evaling()
  return IS_BACKEND_WORKING[]
end

function run_with_backend(f, args...)
  put!(EVAL_CHANNEL_IN, (f, args))
  return unwrap(take!(EVAL_CHANNEL_OUT))
end

function start_eval_backend()
  global EVAL_BACKEND_TASK[] = @async begin
    Base.sigatomic_begin()
    while true
      try
        f, args = take!(EVAL_CHANNEL_IN)
        Base.sigatomic_end()
        IS_BACKEND_WORKING[] = true
        res = try
            Base.invokelatest(f, args...)
        catch err
            @static if isdefined(Base, :catch_stack)
                EvalErrorStack(Base.catch_stack())
            else
                EvalError(err, catch_backtrace())
            end
        end
        IS_BACKEND_WORKING[] = false
        Base.sigatomic_begin()
        put!(EVAL_CHANNEL_OUT, wrap(res))
      catch err
        put!(EVAL_CHANNEL_OUT, wrap(err))
      finally
        IS_BACKEND_WORKING[] = false
      end
    end
    Base.sigatomic_end()
  end
end

function repl_interrupt_request(conn, ::Nothing)
    println(stderr, "^C")
    if EVAL_BACKEND_TASK[] !== nothing && !istaskdone(EVAL_BACKEND_TASK[]) && IS_BACKEND_WORKING[]
        schedule(EVAL_BACKEND_TASK[], InterruptException(); error = true)
    end
end

# https://github.com/JuliaLang/julia/blob/53a781d399bfb517b554fb1ae106e6dac99205f1/stdlib/REPL/src/REPL.jl#L547
function add_code_to_repl_history(code)
    code = strip(code)
    isempty(code) && return

    try
        mode = get_main_mode()
        hist = mode.hist
        !isempty(hist.history) &&
            isequal(:julia, hist.modes[end]) && code == hist.history[end] && return

        hist.last_mode = mode
        hist.last_buffer = let
            io = IOBuffer()
            print(io, code)
            io
        end
        push!(hist.modes, :julia)
        push!(hist.history, code)
        hist.history_file === nothing && return
        entry = """
        # time: $(Libc.strftime("%Y-%m-%d %H:%M:%S %Z", time()))
        # mode: julia
        $(replace(code, r"^"ms => "\t"))
        """
        seekend(hist.history_file)
        print(hist.history_file, entry)
        flush(hist.history_file)

        hist.cur_idx = length(hist.history) + 1
    catch err
        @error "writing to history failed" exception=(err, catch_backtrace())
    end
end

ans = nothing
function repl_runcode_request(conn, params::ReplRunCodeRequestParams)
    return run_with_backend() do
        fix_displays()

        source_filename = params.filename
        code_line = params.line
        code_column = params.column
        source_code = params.code
        mod = params.mod

        resolved_mod = try
            module_from_string(mod)
        catch err
            # maybe trigger error reporting here
            Main
        end

        show_code = params.showCodeInREPL
        show_result = params.showResultInREPL
        show_error = params.showErrorInREPL

        JSONRPC.send_notification(conn_endpoint[], "repl/starteval", nothing)

        rendered_result = nothing
        f = () -> hideprompt() do
            if isdefined(Main, :Revise) && isdefined(Main.Revise, :revise) && Main.Revise.revise isa Function
                let mode = get(ENV, "JULIA_REVISE", "auto")
                    mode == "auto" && Main.Revise.revise()
                end
            end
            if show_code
                add_code_to_repl_history(source_code)

                prompt = "julia> "
                prefix = "\e[32m"
                try
                    mode = get_main_mode()
                    prompt = mode.prompt
                    prefix = mode.prompt_prefix
                catch err
                    @debug "getting prompt info failed" exception=(err, catch_backtrace())
                end

                for (i,line) in enumerate(eachline(IOBuffer(source_code)))
                    if i==1
                        print(prefix, prompt, "\e[0m")
                        print(' '^code_column)
                    else
                        # Indent by 7 so that it aligns with the julia> prompt
                        print(' '^length(prompt))
                    end

                    println(line)
                end
            end

            withpath(source_filename) do
                res = try
                    global ans = inlineeval(resolved_mod, source_code, code_line, code_column, source_filename, softscope=params.softscope)
                    @eval Main ans = Main.VSCodeServer.ans
                catch err
                    @static if isdefined(Base, :catch_stack)
                        EvalErrorStack(Base.catch_stack())
                    else
                        EvalError(err, catch_backtrace())
                    end
                finally
                    JSONRPC.send_notification(conn_endpoint[], "repl/finisheval", nothing)
                end

                if show_error && res isa EvalError || res isa EvalErrorStack
                    Base.display_error(stderr, res)
                elseif show_result
                    if res isa EvalError || res isa EvalErrorStack
                        Base.display_error(stderr, res)
                    elseif res !== nothing && !ends_with_semicolon(source_code)
                        try
                            Base.invokelatest(display, res)
                        catch err
                            Base.display_error(stderr, err, catch_backtrace())
                        end
                    end
                else
                    try
                        Base.invokelatest(display, InlineDisplay(), res)
                    catch err
                        if !(err isa MethodError)
                            printstyled(stderr, "Display Error: ", color = Base.error_color(), bold = true)
                            Base.display_error(stderr, err, catch_backtrace())
                        end
                    end
                end

                rendered_result = safe_render(res)
            end
        end
        PROGRESS_ENABLED[] ? Logging.with_logger(f, VSCodeLogger()) : f()

        return rendered_result
    end
end

# don't inline this so we can find it in the stacktrace
@noinline function inlineeval(m, code, code_line, code_column, file; softscope = false)
    code = string('\n' ^ code_line, ' ' ^ code_column, code)
    args = softscope && VERSION >= v"1.5" ? (REPL.softscope, m, code, file) : (m, code, file)
    return Base.invokelatest(include_string, args...)
end

"""
    safe_render(x)

Calls `render`, but catches errors in the display system.
"""
function safe_render(x)
    try
        return render(x)
    catch err
        out = render(EvalError(err, catch_backtrace()))

        return ReplRunCodeRequestReturn(
            string("Display Error: ", out.inline),
            string("Display Error: ", out.all),
            out.stackframe
        )
    end
end

"""
    render(x)

Produce a representation of `x` that can be displayed by a UI.
Must return a `ReplRunCodeRequestReturn` with the following fields:
- `inline::String`: Short one-line plain text representation of `x`. Typically limited to `INLINE_RESULT_LENGTH` characters.
- `all::String`: Plain text string (that may contain linebreaks and other signficant whitespace) to further describe `x`.
- `stackframe::Vector{Frame}`: Optional, should only be given on an error
"""
function render(x)
    str = sprintlimited(MIME"text/plain"(), x, limit=MAX_RESULT_LENGTH)
    inline = strlimit(first(split(str, "\n")), limit=INLINE_RESULT_LENGTH)
    all = codeblock(str)
    return ReplRunCodeRequestReturn(inline, all)
end

render(::Nothing) = ReplRunCodeRequestReturn("✓", codeblock("nothing"))

indent4(s) = string(' ' ^ 4, s)
codeblock(s) = joinlines(indent4.(splitlines(s)))

struct EvalError
    err
    bt
end

struct EvalErrorStack
    stack
end

sprint_error_unwrap(err::LoadError) = sprint_error(err.error)
sprint_error_unwrap(err) = sprint_error(err)

function sprint_error(err)
    sprintlimited(err, [], func = Base.display_error, limit = MAX_RESULT_LENGTH)
end

function render(err::EvalError)
    bt = crop_backtrace(err.bt)

    errstr = sprint_error_unwrap(err.err)
    inline = strlimit(first(split(errstr, "\n")), limit = INLINE_RESULT_LENGTH)
    all = string('\n', codeblock(errstr), '\n', backtrace_string(bt))

    # handle duplicates e.g. from recursion
    st = unique!(stacktrace(bt))
    # limit number of potential hovers shown in VSCode, just in case
    st = st[1:min(end, 1000)]

    stackframe = Frame.(st)
    return ReplRunCodeRequestReturn(inline, all, stackframe)
end

function render(stack::EvalErrorStack)
    inline = ""
    all = ""
    complete_bt = Union{Base.InterpreterIP,Ptr{Cvoid}}[]
    for (i, (err, bt)) in enumerate(reverse(stack.stack))
        bt = crop_backtrace(bt)
        append!(complete_bt, bt)

        errstr = sprint_error_unwrap(err)
        inline *= strlimit(first(split(errstr, "\n")), limit = INLINE_RESULT_LENGTH)
        all *= string('\n', codeblock(errstr), '\n', backtrace_string(bt))
    end

    # handle duplicates e.g. from recursion
    st = unique!(stacktrace(complete_bt))
    # limit number of potential hovers shown in VSCode, just in case
    st = st[1:min(end, 1000)]

    stackframe = Frame.(st)
    return ReplRunCodeRequestReturn(inline, all, stackframe)
end

function Base.display_error(io::IO, err::EvalError)
    try
        Base.invokelatest(display_repl_error, io, err.err, err.bt)
    catch err
        @error "Error trying to display an error."
    end
end

function Base.display_error(io::IO, err::EvalErrorStack)
    try
        Base.invokelatest(display_repl_error, io, err)
    catch err
        @error "Error trying to display an error."
    end
end

function crop_backtrace(bt)
    i = find_first_topelevel_scope(bt)
    return bt[1:(i === nothing ? end : i)]
end

function backtrace_string(bt)
    io = IOBuffer()

    println(io, "Stacktrace:")
    for (i, frame) in enumerate(stacktrace(bt))
        file = string(frame.file)
        full_file = fullpath(something(Base.find_source_file(file), file))
        cmd = vscode_cmd_uri("language-julia.openFile"; path = full_file, line = frame.line)

        print(io, i, ". `")
        Base.StackTraces.show_spec_linfo(io, frame)
        print(io, "` at [", file, "](", cmd, " \"", file, "\")")
        println(io, "\n")
    end

    return String(take!(io))
end

max_stars_repo_name: FAlobaid/julia-vscode

max_issues_repo_name: FAlobaid/julia-vscode

max_forks_repo_name: FAlobaid/julia-vscode


text:
using LinearAlgebra
using HDF5
using Plasmons

function load()
    h5open("sample.hdf5") do f
        H = read_hamiltonian(f)
        p = read_coordinates(f)
        return H, p
    end
end

function main()
    H, p = load()
    V = coulomb_simple(p...; v0=15.78)
    E, ψ = eigen(H)

    for ħω ∈ [0.45, 0.46, 0.47, 0.48, 0.49, 0.5]
        G = g(ħω + 6e-3im, E; mu=0.4, kT=8.617333262145E-5 * 300)
        χ = polarizability(G=G, ψ=ψ)
        ε = dielectric(χ, V)
        loss = eigvals(ε)
        loss = maximum(-imag(1 ./ loss))
        println(loss)
    end
end

main()


text:
using DiffOpt
import MathOptInterface
const MOI = MathOptInterface
using Test, LinearAlgebra
import OSQP

Q = [
    4.0 1.0
    1.0 2.0
]
q = [1.0, 1.0]
G = [1.0 1.0]
h = [-1.0]

model = diff_optimizer(OSQP.Optimizer)
MOI.set(model, MOI.Silent(), true)
x = MOI.add_variables(model, 2)

# define objective
quad_terms = MOI.ScalarQuadraticTerm{Float64}[]
for i in 1:2
    for j in i:2 # indexes (i,j), (j,i) will be mirrored. specify only one kind
        push!(
            quad_terms,
            MOI.ScalarQuadraticTerm(Q[i,j], x[i], x[j])
        )
    end
end
objective_function = MOI.ScalarQuadraticFunction(
                        MOI.ScalarAffineTerm.(q, x),
                        quad_terms,
                        0.0,
                    )
MOI.set(model, MOI.ObjectiveFunction{MOI.ScalarQuadraticFunction{Float64}}(), objective_function)
MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)

# add constraint
c = MOI.add_constraint(
    model,
    MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(G[1, :], x), 0.0),
    MOI.LessThan(h[1]),
)
MOI.optimize!(model)

@test MOI.get(model, MOI.VariablePrimal(), x) ≈ [-0.25; -0.75] atol=ATOL rtol=RTOL

@test model.gradient_cache === nothing
MOI.set.(model, DiffOpt.BackwardInVariablePrimal(), x, ones(2))
DiffOpt.backward(model)

grad_wrt_h = MOI.constant(MOI.get(model, DiffOpt.BackwardOutConstraint(), c))
# grad_wrt_h = backward(model, ["h"], ones(2))[1]
@test grad_wrt_h ≈ -1.0 atol=2ATOL rtol=RTOL
@test model.gradient_cache !== nothing

# adding two variables invalidates the cache
y = MOI.add_variables(model, 2)
for yi in y
    MOI.delete(model, yi)
end
@test model.gradient_cache === nothing
MOI.optimize!(model)

MOI.set.(model, DiffOpt.BackwardInVariablePrimal(), x, ones(2))
DiffOpt.backward(model)

grad_wrt_h = MOI.constant(MOI.get(model, DiffOpt.BackwardOutConstraint(), c))

# grad_wrt_h = backward(model, ["h"], ones(2))[1]
@test grad_wrt_h ≈ -1.0 atol=1e-3
@test model.gradient_cache !== nothing


text:
function password_enumerator(s, e)::Int
    valid = 0
    for i = s:e
        if length(unique(digits(i))) <= 5 && sum(reverse(digits(i))[2:end] .>= reverse(digits(i))[1:end-1]) == 5
            for j in unique(digits(i))
                if sum(x->x==j, digits(i)) == 2
                    # println(i)
                    valid = valid + 1
                    break
                end
            end
        end
    end
    return valid
end

println(password_enumerator(236491,713787))


text:
include("HelperCode/plot_helpers.jl")
sb.set_style("white")

# # Create alpha we work with
# α1 = -1.0
# α9 = -9.0
# α49 = -49.0

# # Create all the models and interps we care about
# m1, vf_itp1, lλ☆p_itp1, lj1, lu1, lλ☆p1 =
#     build_model_interpolants(α1);
# m9, vf_itp9, lλ☆p_itp9, lj9, lu9, lλ☆p9 =
#     build_model_interpolants(α9);
# m49, vf_itp49, lλ☆p_itp49, lj49, lu49, lλ☆p49 =
#     build_model_interpolants(α49);

# # Find all steady state lambdas
# lλ1_ss = 0.0
# lλ9_ss = find_steadystate_lλ(m9, lλ☆p_itp9; capT=6_000_000)
# lλ49_ss = find_steadystate_lλ(m49, lλ☆p_itp49; capT=6_000_000)

# # Simulate all economies
# srand(61089)
# sim_1 = Exchange.simulate(m1, lλ☆p_itp1; capT=500_000, lλ0=lλ1_ss);
# a1_1, b1_1, c1_1, a2_1, b2_1, c2_1, lλ☆_1, exogvals_1, c2_c1_ratio_1, fxr_1 = sim_1;

# srand(61089)
# sim_9 = Exchange.simulate(m9, lλ☆p_itp9; capT=500_000, lλ0=lλ9_ss);
# a1_9, b1_9, c1_9, a2_9, b2_9, c2_9, lλ☆_9, exogvals_9, c2_c1_ratio_9, fxr_9 = sim_9;

# srand(61089)
# sim_49 = Exchange.simulate(m49, lλ☆p_itp49; capT=500_000, lλ0=lλ49_ss);
# a1_49, b1_49, c1_49, a2_49, b2_49, c2_49, lλ☆_49, exogvals_49, c2_c1_ratio_49, fxr_49 = sim_49;

# @save "plot_data.jld"

@load "plot_data.jld"

get_c1_c2(m) = map(x-> reshape(x, map(length, m.m_ls)), (m.other.c1, m.other.c2))
(c₁1, c₂1), (c₁9, c₂9), (c₁49, c₂49) = map(get_c1_c2, (m1, m9, m49));

function plot_frontiers(lj, lu, c1, c2, α, inds=[7, 10], var=:zhat)
    fig, ax = subplots()
    sb.despine(fig)
    colors = ("#852C64", "#AA6039")
    ls = ("-", "-.")

    # plot baseline
    i = var == :zhat ? (inds[1], 7) : (7, inds[1])
    ax[:plot](exp(getindex(lu, :, i...)), exp(getindex(lj, :, i...)), color=colors[1], ls=ls[1])
    ax[:plot](getindex(c2, :, i...,), getindex(c1, :, i...), color=colors[2], ls=ls[1])

    # plot experiment
    i = var == :zhat ? (inds[2], 7) : (7, inds[2])
    ax[:plot](exp(getindex(lu, :, i...)), exp(getindex(lj, :, i...)), color=colors[1], ls=ls[2], lw=3)
    ax[:plot](getindex(c2, :, i...,), getindex(c1, :, i...), color=colors[2], ls=ls[2], lw=3)

    ax[:set_ylabel]("Agent 1")
    ax[:set_xlabel]("Agent 2")
    ax[:legend](["Value Function", "Consumption"], loc=0)
    fig[:savefig]("images/frontiers_alpha$(α).pdf", dpi=1000)
    fig[:show]()

    fig, ax
end

plot_frontiers(lj9, lu9, c₁9, c₂9, "9_zhat", [length(m9.m_ls[2]), 1])
plot_frontiers(lj9, lu9, c₁9, c₂9, "9_v", [1, length(m9.m_ls[3])], :v)

fig, ax = subplots()
sb.despine(fig)
ax[:plot](exp(lu9[:, 7, 7]), exp(lj9[:, 7, 7]), label=L"v")
ax[:plot](c₁9[:, 7, 7], c₂9[:, 7, 7], label=L"c")

ax[:set_ylabel]("Agent 1")
ax[:set_xlabel]("Agent 2")

ax[:legend](loc=0)

fig[:savefig]("images/frontiers_alpha9.pdf", dpi=1000)
fig[:show]()

fig, ax = subplots()
sb.despine(fig)
ax[:plot](exp(lu49[:, 7, 7]), exp(lj49[:, 7, 7]), label=L"v")
ax[:plot](c₁49[:, 7, 7], c₂49[:, 7, 7], label=L"c")

ax[:set_ylabel]("Agent 1")
ax[:set_xlabel]("Agent 2")

ax[:legend](loc=0)

fig[:savefig]("images/frontiers_alpha49.pdf", dpi=1000)
fig[:show]()

fig, ax = subplots()
sb.despine(fig)
ax[:plot](exp(lu1[:, 7, 7]), exp(lj1[:, 7, 7]), label=L"$\alpha = -1.0$")
# ax[:plot](exp(lu9[:, 7, 7]), exp(lj9[:, 7, 7]), label=L"$\alpha = -9.0$")
ax[:plot](exp(lu49[:, 7, 7]), exp(lj49[:, 7, 7]), label=L"$\alpha = -49.0$")

ax[:set_xlabel](L"$U$")
ax[:set_ylabel](L"$J$")

ax[:legend](loc=0)

fig[:savefig]("images/ValueFunctions_alpha49.pdf", dpi=1000)
fig[:show]()

fig, ax = subplots()
sb.despine(fig)

fig[:suptitle]("Consumption vs Exchange Rate")
ax[:set_ylabel]("Log Exchange Rate")
ax[:set_xlabel]("Log Consumption Ratio")
# Note: We could also make this into multiple subplots and have
# a round robin type comparsion (1v9), (1v49), (9v49)...
plotslice = 3000:15000
plot_c2c1ratio_fxr_single!(α9, c2_c1_ratio_9[plotslice], fxr_9[plotslice], 2500, ax)
# plot_c2c1ratio_fxr_single!(α49, c2_c1_ratio_49[plotslice], fxr_49[plotslice], 2500, ax)
plot_c2c1ratio_fxr_single!(α1, c2_c1_ratio_1[plotslice], fxr_1[plotslice], 2500, ax)
ax[:legend](markerscale=5, framealpha=1.0, loc=2)

fig[:savefig]("images/con_v_fxr_alpha9.pdf", dpi=1000)
fig[:show]()

T = 500_000
st_sz = 1000

fig, ax = plt[:subplots]()
sb.despine(fig)

plot_pareto_history!(α1, lλ☆_1, ax; T=T, stsz=st_sz)
plot_pareto_history!(α9, lλ☆_9, ax; rw=false, T=T, stsz=st_sz)

# plot_pareto_history!(α1, lλ☆_1, ax[1, 1]; T=T, stsz=st_sz)
# plot_pareto_history!(α9, lλ☆_9, ax[1, 1]; rw=false, T=T, stsz=st_sz)

# plot_pareto_history!(α1, lλ☆_1, ax[2, 1]; T=T, stsz=st_sz)
# plot_pareto_history!(α49, lλ☆_49, ax[2, 1]; rw=false, T=T, stsz=st_sz)

# add x/y labels to figure
# fig[:text](0.0, 0.5, "Ratio of Pareto Weights",
#                ha="center", va="center", rotation="vertical")
# fig[:text](0.5, 0.0, "t", ha="center", va="center")
# fig[:tight_layout]()
ax[:set_xlabel]("Time")
ax[:set_ylabel]("Ratio of Pareto Weights")

fig[:savefig]("images/paretoweightstability_alpha9.pdf", dpi=1000)
fig[:show]()

fig, axs = subplots(2, 1)
sb.despine(fig)
acorr_plot!(fxr_1, -1, axs[1], 100, 50)
acorr_plot!(fxr_9, -9, axs[2], 100, 50)
fig[:text](0.05, 0.5, "Autocorrelation Function",
           ha="center", va="center", rotation="vertical")
axs[2][:set_xlabel]("Time")
# map(ax->ax[:grid](alpha=0.4, lw=0.75), axs)
fig[:savefig]("images/acorr_fx_alpha1_9.pdf", dpi=1000)

fig, ax = subplots()
sb.despine(fig)
acorr_plot_one_ax!(fxr_1, -1, ax, 100, 50)
acorr_plot_one_ax!(fxr_9, -9, ax, 100, 50)
# fig[:text](0.05, 0.5, "Autocorrelation Function",
#            ha="center", va="center", rotation="vertical")
ax[:set_ylabel]("Autocorrelation Function")
ax[:set_xlabel]("Lags")
ax[:set_ylim](-0.05, 1.05)
fig[:savefig]("images/acorr_fx_alpha1_9_one_axis.pdf", dpi=1000)

# kde_plot(lλ☆_9, N=50)

# kde_plot(lλ☆_49, N=10)

# Create a matrix of all zeros except for a one
# time shock to volatility in period 2
T_irf = 50
irf_per = collect(1:T_irf)
vshock = zeros(3, T_irf+1)
vshock[3, 2] = 2.5

# Simulate both economies
fig, ax = irf_plotter(m9, lλ☆p_itp9, vshock; capT=T_irf, lλ0=lλ9_ss)
sb.despine(fig)
map(x->x[:set_xlim](0, T_irf), ax)
map(x->x[:set_xticks](0:10:T_irf), ax)

fig[:savefig]("images/irf_wrt_v_alpha9.pdf", dpi=1000)
fig[:show]()

# Create a matrix of all zeros except for a one
# time shock to volatility in period 2
T_irf = 50
irf_per = collect(1:T_irf)
vshock = zeros(3, T_irf+1)
vshock[1, 2] = 0.5
vshock[2, 2] = -0.5

# Simulate both economies
fig, ax = irf_plotter(m9, lλ☆p_itp9, vshock; capT=T_irf, lλ0=lλ9_ss)
sb.despine(fig)
map(x->x[:set_xlim](0, T_irf), ax)
map(x->x[:set_xticks](0:10:T_irf), ax)

fig[:savefig]("images/irf_wrt_zhat_alpha9.pdf", dpi=1000)
fig[:show]()

# Choose time
T_irf = 100
irf_per = collect(1:T_irf)

# Choose how big v3 should be and
v2 = m49.exog.vbar
v3 = 1.5*m49.exog.vbar
v4 = 1.5*m49.exog.vbar
v5 = m49.exog.vbar
ϵ = zeros(3, T_irf+1)
ϵ[3, 2:10:T_irf] = (v3 - (1-m49.exog.φv)*m49.exog.vbar - m49.exog.φv*v2)/m49.exog.τ
ϵ[3, 3:10:T_irf] = (v5 - (1-m49.exog.φv)*m49.exog.vbar - m49.exog.φv*v4)/m49.exog.τ

fig, ax = irf_plotter(m49, lλ☆p_itp49, ϵ; capT=T_irf, lλ0=lλ49_ss)
sb.despine(fig)
fig[:show]()

# Choose time
T_irf = 100
irf_per = collect(1:T_irf)

# Choose how big v3 should be and
lzh2 = 0.01
lzh3 = 0.0

ϵ = zeros(3, T_irf+1)
ϵ[1, 2:10:T_irf] = lzh2 / (sqrt(m49.exog.vbar)/2 + m49.exog.σz2/2)
ϵ[2, 2:10:T_irf] = -lzh2 / (sqrt(m49.exog.vbar)/2 + m49.exog.σz2/2)
ϵ[1, 3:10:T_irf] = -(1-2*m49.exog.γ)*lzh2 / (sqrt(m49.exog.vbar)/2 + m49.exog.σz2/2)
ϵ[2, 3:10:T_irf] = (1-2*m49.exog.γ)*lzh2 / (sqrt(m49.exog.vbar)/2 + m49.exog.σz2/2)

fig, ax = irf_plotter(m49, lλ☆p_itp49, ϵ; capT=T_irf, lλ0=lλ49_ss)
sb.despine(fig)
fig[:show]()

function plot_policy_wrt_v!(m, itp, ax=nothing)
    lλ☆t = 0.0
    lzh = 0.0
    v = linspace(extrema(m.grid[:, 3])..., 50)

    lλ☆p = Array(Float64, 50)

    for i=1:50
        lλ☆p[i] = itp[lλ☆t, lzh, v[i], zeros(3)...]
    end

    if ax === nothing
        fig, ax = subplots()
    end

    ax[:set_ylabel](L"$\lambda_{t+1}^*$")
    ax[:set_xlabel](L"$v_{t}$")
    ax[:plot](v, lλ☆p; plotkwargs[m.agent1.α]...)
    ax
end

fig, (ax1, ax2, ax3) = subplots(3, 1)
sb.despine(fig)
m_itp = [(m1, m9, m49), (lλ☆p_itp1, lλ☆p_itp9, lλ☆p_itp49)]
map(plot_policy_wrt_v!, m_itp..., (ax1, ax2, ax3))
fig[:savefig]("images/policy_subplots.pdf", dpi=1000)

fig, ax = subplots()
sb.despine(fig)
map(plot_policy_wrt_v!, m_itp..., (ax, ax, ax))
ax[:set_title]("Policy with resepct to volatility")
fig[:savefig]("images/policy_one_axis.pdf", dpi=1000)

function plot_policy_at_ss!(m, itp, ss, ax=nothing)

    lλ☆t = linspace(ss-2.5, ss+2.5, 50)
    lzh = 0.0
    v = m.exog.vbar

    ϵ, Π = Exchange.qnwnorm([7, 7, 7], zeros(3), eye(3))
    nshocks = length(Π)
    lλ☆p = Array(Float64, nshocks, 50)

    for i=1:50
        for j=1:nshocks
            lλ☆p[j, i] = itp[lλ☆t[i], lzh, v, ϵ[j, :]...]
        end
    end

    Elλ☆p = vec(Π' * lλ☆p)
    ΔElλ☆p = Elλ☆p - collect(lλ☆t)

    if ax === nothing
        fig, ax = subplots()
    end

    ax[:set_ylabel](L"$E[\lambda_{t+1}^*] - \lambda_{t}^*$")
    ax[:set_xlabel](L"$\lambda_{t}^*$")
    ax[:plot](lλ☆t, ΔElλ☆p; plotkwargs[m.agent1.α]...)
    ax
end

lλ49_ss

# lλ49_ss = find_steadystate_lλ(m49, lλ☆p_itp49; capT=6_000_000)

fig, ax = subplots()
sb.despine(fig)
m_itp2 = [(m1, m9, m49), (lλ☆p_itp1, lλ☆p_itp9, lλ☆p_itp49), (lλ1_ss, lλ9_ss, lλ49_ss)]
map(plot_policy_at_ss!, m_itp2..., (ax, ax, ax))
fig[:savefig]("images/policy_at_ss_one_axis.pdf", dpi=1000)


text:
function InitFileLinks()
    dir = readdir(env.paths[:projects])

    dir = dir[isdir.(dir)]

    if sum(dir.==".DataFileLinks") == 0
        mkdir(".DataFileLinks")
    end
end
InitFileLinks()

function DataFileName(DataSource)
    io = open(joinpath(env.paths[:projects],".DataFileLinks","$DataSource.txt"),"r")
    content = readlines(io)
    close(io)
    paths = Dict()
    [push!(paths, split(cont," => ")[1] => split(cont," => ")[2]) for cont in content]
    return paths
end


text:
"""
	bm3d_thr(img::Array{Float64}, sigma::AbstractFloat, config::bm3d_config)

1st step of BM3D denoising: hard thresholding

img: input noisy image
sigma: known or assumed standard deviation of noise
config: bm3d_config
"""
function bm3d_thr(img::Array{Float64}, sigma::AbstractFloat, config::bm3d_config)

	# parameters
	patchSize = config.thr_patchSize
	searchStride = config.thr_searchStride
	searchWin = config.thr_searchWin
	nMatch = config.thr_nMatch
	thresh3D = config.thr_thresh3D

	# Block matching
	config.show_info && @info "1st get_reference_pixels"
	if config.show_info
		refIndex = @time get_reference_pixels(
			[size(img, 1); size(img, 2)],
			patchSize,
			searchStride,
		)
	else
		refIndex = get_reference_pixels(
			[size(img, 1); size(img, 2)],
			patchSize,
			searchStride,
		)
	end
	config.show_info && @info "1st get_reference_pixels end"
	config.show_info && @info "1st match_patches"
	if config.show_info
		matchTable = @time match_patches(
			img,
			refIndex,
			patchSize,
			searchWin,
			nMatch,
			config.thr_distFunction,
		)
	else
		matchTable = match_patches(
			img,
			refIndex,
			patchSize,
			searchWin,
			nMatch,
			config.thr_distFunction,
		)
	end
	config.show_info && @info "1st match_patches end"

	# Don't use similar(), because it need initialize to 0.0
	Wout = zeros(Float64, size(img)...)
	imgOut = zeros(Float64, size(img)...)

	# 3D filtering
	config.show_info && @info "1st 3D filtering"
	if config.show_info
		@time thr_3D_filtering!(
			Wout,
			imgOut,
			img,
			matchTable,
			refIndex,
			patchSize,
			nMatch,
			thresh3D,
			sigma,
			config,
		)
	else
		thr_3D_filtering!(
			Wout,
			imgOut,
			img,
			matchTable,
			refIndex,
			patchSize,
			nMatch,
			thresh3D,
			sigma,
			config,
		)
	end
	config.show_info && @info "1st 3D filtering end"

	return imgOut ./ Wout
end

"""
3D filtering
"""
function thr_3D_filtering!(
	Wout::AbstractArray{<:AbstractFloat,2},
	imgOut::AbstractArray{<:AbstractFloat,2},
	img::AbstractArray{<:AbstractFloat,2},
	matchTable::Array{CartesianIndex{2},3},
	refIndex::Array{CartesianIndex{2},2},
	patchSize::CartesianIndex{2},
	nMatch::Int,
	thresh3D::AbstractFloat,
	sigma::AbstractFloat,
	config::bm3d_config,
)
	# Each reference block is processed to reduce memory usage
	I_end, J_end = size(refIndex)

	# Preventing conflicts in group_to_image!
	imgLockPool = Array{ReentrantLock}(undef, I_end, J_end)
	for i in 1:length(imgLockPool)
		imgLockPool[i] = ReentrantLock()
	end

	@views @inbounds Threads.@threads for J in 1:J_end
		# Preventing conflicts in parallel computing
		G3D = zeros(Float64, nMatch + 1, patchSize[1], patchSize[2])
		for I in 1:I_end
			form_group!(
				G3D,
				img,
				matchTable,
				refIndex,
				patchSize,
				CartesianIndex(I, J),
				config.thr_group_transform!,
			)

			# Filter 3D groups by hard thresholding
			T = HardThresholding!(G3D, sigma * thresh3D)

			if T > zero(T)
				W = 1.0 / (T * sigma^2)
				G3D .*= W
			else
				W = 1.0
			end

			invert_group!(
				imgOut,
				G3D,
				matchTable,
				refIndex,
				patchSize,
				CartesianIndex(I, J),
				config.thr_group_itransform!,
				imgLockPool,
			)
			group_to_image!(
				Wout,
				W,
				matchTable,
				refIndex,
				patchSize,
				CartesianIndex(I, J),
				imgLockPool,
			)
		end
	end
end

# For color images
function thr_3D_filtering!(
	Wout::Array{<:AbstractFloat,3},
	imgOut::Array{<:AbstractFloat,3},
	img::Array{<:AbstractFloat,3},
	matchTable::Array{CartesianIndex{2},3},
	refIndex::Array{CartesianIndex{2},2},
	patchSize::CartesianIndex{2},
	nMatch::Int,
	thresh3D::AbstractFloat,
	sigma::AbstractFloat,
	config::bm3d_config,
)
	@views @inbounds for i in 1:size(img, 3)
		thr_3D_filtering!(
			Wout[:, :, i],
			imgOut[:, :, i],
			img[:, :, i],
			matchTable,
			refIndex,
			patchSize,
			nMatch,
			thresh3D,
			sigma,
			config,
		)
	end
end


text:
using JuliaFEM, LinearAlgebra, Test

datadir = first(splitext(basename(@__FILE__)))

@testset "Test that mesh coloring provides a good coloring"
    fn = joinpath(datadir, "cube_tet4.inp")
    mesh = JuliaFEM.Mesh(open(parse_abaqus, fn))

    coloring = JuliaFEM.create_coloring(mesh)
    for colors in coloring
        for ele_i in colors
            for ele_j in colors
                if ele_i == ele_j
                    continue
                end
                @test intersect(Set(mesh.elements[ele_i]), Set(mesh.elements[ele_j])) |> isempty
            end
        end
    end
end


text:
module RoMEPlotting

using Reexport
@reexport using Gadfly
@reexport using Colors

using Statistics, LinearAlgebra
using Compose
using Dates
# using Graphs
using DistributedFactorGraphs
using KernelDensityEstimate, KernelDensityEstimatePlotting
using IncrementalInference, RoME
using DocStringExtensions
using ApproxManifoldProducts
# import ApproxManifoldProducts: mmd! # future dependency

# const AMP = ApproxManifoldProducts
# import RoME: AMP

import KernelDensityEstimatePlotting: plot, drawHorDens, plotKDE
import IncrementalInference: CliqGibbsMC, DebugCliqMCMC
import Graphs: plot
import Gadfly: plot

# TODO temporary fix for Compose based plotting in Julia 0.7 (Oct 2018)
# see
# @warn "[TEMPORARY WORKAROUND, pangolayout] for plotting with Compose and Gadfly.jl, see https://github.com/GiovineItalia/Gadfly.jl/issues/1206"
# import Compose: pangolayout
# const pangolayout = PangoLayout()

export
  # Associated with IncrementalInference
  investigateMultidimKDE,
  drawHorDens,
  drawHorBeliefsList,
  spyCliqMat,
  plotKDE,
  plotKDEofnc,
  plotKDEresiduals,
  plotMCMC,
  plotKDE,
  plotUpMsgsAtCliq,
  plotPriorsAtCliq,
  investigateMultidimKDE,
  draw,
  plot,
  whosWith,
  drawUpMsgAtCliq,
  dwnMsgsAtCliq,
  drawPose2DMC!,
  mcmcPose2D!,
  # drawUpMCMCPose2D!,
  # drawDwnMCMCPose2D!,
  drawLbl,
  predCurrFactorBeliefs,
  drawFactorBeliefs,
  localProduct,
  plotLocalProduct,
  plotLocalProductCylinder,
  plotTreeProductUp,
  plotTreeProductDown,
  plotCliqUpMsgs,
  saveplot,
  animateVertexBelief,
  getColorsByLength,

  # Associated with RoME
  togglePrtStbLines,
  plotLsrScanFeats,
  drawFeatTrackers,
  saveImgSeq,
  stbPrtLineLayers!,
  drawPoses,
  drawLandms,
  drawPosesLandms,
  drawSubmaps,
  investigatePoseKDE, # not sure, likely obsolete -- use plotPose instead
  plotPose,
  drawMarginalContour,
  accumulateMarginalContours,
  plotPose3Pairs,
  progressExamplePlot,
  plotTrckStep,
  plotPose2Vels,
  plotProductVsKDE,
  plotVariableGivenFactor,
  plotCliqDownMsgs,
  plotFactor,
  plotFactorMeasurements,
  reportFactors

include("SolverVisualization.jl")
include("RobotViz.jl")
include("PlotHexUtils.jl")
include("PlotFactors.jl")

end


text:
immutable Quaternion{T<:Real} <: Number
    s::T
    v1::T
    v2::T
    v3::T
    norm::Bool
end

Quaternion(s::Real, v1::Real, v2::Real, v3::Real, n::Bool = false) =
    Quaternion( promote(s, v1, v2, v3)..., n)
Quaternion(x::Real) = Quaternion(x, zero(x), zero(x), zero(x), abs(x) == one(x))
Quaternion(z::Complex) = Quaternion(z.re, z.im, zero(z.re), zero(z.re), abs(z) == one(z.re))
Quaternion(s::Real, a::Vector) = Quaternion(s, a[1], a[2], a[3])
Quaternion(a::Vector) = Quaternion(0, a[1], a[2], a[3])

convert{T}(::Type{Quaternion{T}}, x::Real) =
    Quaternion(convert(T,x), convert(T,0), convert(T,0), convert(T,0))
convert{T}(::Type{Quaternion{T}}, z::Complex) =
    Quaternion(convert(T,real(z)), convert(T,imag(z)), convert(T,0), convert(T,0))
convert{T<:Real}(::Type{Quaternion{T}}, q::Quaternion{T}) = q
convert{T}(::Type{Quaternion{T}}, q::Quaternion) =
    Quaternion(convert(T,q.s), convert(T,q.v1), convert(T,q.v2), convert(T,q.v3), q.norm)

promote_rule{T<:Real}(::Type{Quaternion{T}}, ::Type{T}) = Quaternion{T}
promote_rule{T<:Real}(::Type{Quaternion}, ::Type{T}) = Quaternion
promote_rule{T<:Real,S<:Real}(::Type{Quaternion{T}}, ::Type{S}) = Quaternion{promote_type(T,S)}
promote_rule{T<:Real,S<:Real}(::Type{Complex{T}}, ::Type{Quaternion{S}}) = Quaternion{promote_type(T,S)}
promote_rule{T<:Real,S<:Real}(::Type{Quaternion{T}}, ::Type{Quaternion{S}}) = Quaternion{promote_type(T,S)}

quat(p, v1, v2, v3, n = false) = Quaternion(p, v1, v2, v3, n)
quat(x) = Quaternion(x)
quat(s, a) = Quaternion(s, a)

function show(io::IO, q::Quaternion)
    pm(x) = x < 0 ? " - $(-x)" : " + $x"
    print(io, q.s, pm(q.v1), "im", pm(q.v2), "jm", pm(q.v3), "km")
end

real{T}(::Type{Quaternion{T}}) = T
real(q::Quaternion) = q.s
imag(q::Quaternion) = [q.v1, q.v2, q.v3]

(/)(q::Quaternion, x::Real) = Quaternion(q.s/x, q.v1/x, q.v2/x, q.v3/x)

conj(q::Quaternion) = Quaternion(q.s, -q.v1, -q.v2, -q.v3, q.norm)
abs(q::Quaternion) = sqrt(q.s*q.s + q.v1*q.v1 + q.v2*q.v2 + q.v3*q.v3)
abs2(q::Quaternion) = q.s*q.s + q.v1*q.v1 + q.v2*q.v2 + q.v3*q.v3
inv(q::Quaternion) = q.norm ? conj(q) : conj(q)/abs2(q)

isfinite(q::Quaternion) = q.norm ? true : (isfinite(q.s) && isfinite(q.v1) && isfinite(q.v2) && isfinite(q.v3))

function normalize(q::Quaternion)
    if (q.norm)
        return q
    end
    q = q / abs(q)
    Quaternion(q.s, q.v1, q.v2, q.v3, true)
end

function normalizea(q::Quaternion)
    if (q.norm)
        return (q,one(q.s))
    end
    a = abs(q)
    q = q / a
    (Quaternion(q.s, q.v1, q.v2, q.v3, true), a)
end

function normalizeq(q::Quaternion)
    a = abs(q)
    if a > 0
        q = q / a
        Quaternion(q.s, q.v1, q.v2, q.v3, true)
    else
        Quaternion(0.0, 1.0, 0.0, 0.0, true)
    end
end

(-)(q::Quaternion) = Quaternion(-q.s, -q.v1, -q.v2, -q.v3, q.norm)

(+)(q::Quaternion, w::Quaternion) =
    Quaternion(q.s + w.s, q.v1 + w.v1, q.v2 + w.v2, q.v3 + w.v3)

(-)(q::Quaternion, w::Quaternion) =
    Quaternion(q.s - w.s, q.v1 - w.v1, q.v2 - w.v2, q.v3 - w.v3)

(*)(q::Quaternion, w::Quaternion) = Quaternion(q.s*w.s - q.v1*w.v1 - q.v2*w.v2 - q.v3*w.v3,
                                               q.s*w.v1 + q.v1*w.s + q.v2*w.v3 - q.v3*w.v2,
                                               q.s*w.v2 - q.v1*w.v3 + q.v2*w.s + q.v3*w.v1,
                                               q.s*w.v3 + q.v1*w.v2 - q.v2*w.v1 + q.v3*w.s,
                                               q.norm && w.norm)
(/)(q::Quaternion, w::Quaternion) = q * inv(w)

angleaxis(q::Quaternion) = angle(q), axis(q)

angle(q::Quaternion) = 2 * acos(real(normalize(q)))

function axis(q::Quaternion)
    q = normalize(q)
    s = sin(angle(q) / 2)
        abs(s) > 0 ?
        [q.v1, q.v2, q.v3] / s :
        [1.0, 0.0, 0.0]
end

function argq(q::Quaternion)
    q = normalize(q)
    q = Quaternion(imag(q))
    normalizeq(q)
end

function exp(q::Quaternion)
    s = q.s
    se = exp(s)
    scale = se
    th = abs(Quaternion(imag(q)))
    if th > 0
        scale *= sin(th) / th
    end
    Quaternion(se*cos(th), scale*q.v1, scale*q.v2, scale*q.v3, abs(s) < eps(typeof(s)))
end

function log(q::Quaternion)
    q, a = normalizea(q)
    s = q.s
    M = abs(Quaternion(imag(q)))
    th = atan2(M, s)
    if M > 0
        M = th / M
        return Quaternion(log(a), q.v1*M, q.v2*M, q.v3*M)
    else
        return Quaternion(log(a), th , 0.0, 0.0)
    end
end

function sin(q::Quaternion)
    L = argq(q)
    return (exp(L*q) - exp(-L*q))/(2*L)
end

function cos(q::Quaternion)
    L = argq(q)
    return (exp(L*q) + exp(-L*q)) / 2
end

(^)(q::Quaternion, w::Quaternion) = exp(w*log(q))

sqrt(q::Quaternion) = exp(0.5*log(q))

function linpol(p::Quaternion, q::Quaternion, t::Real)
    p = normalize(p)
    q = normalize(q)
    qm = -q
    if abs(p - q) > abs(p - qm)
        q = qm
    end
    c = p.s * q.s + p.v1 * q.v1 + p.v2 * q.v2 + p.v3 * q.v3
    if c > - 1.0
        if c < 1.0
            o = acos(c)
            s = sin(o)
            sp = sin((1 - t)*o)/s
            sq = sin(t*o)/s
        else
        sp = 1 - t
        sq = t
    end
    Quaternion(sp*p.s  + sq*q.s,
               sp*p.v1 + sq*q.v1,
               sp*p.v2 + sq*q.v2,
               sp*p.v3 + sq*q.v3, true)
    else
    s  =  p.v3
    v1 = -p.v2
    v2 =  p.v1
    v3 = -p.s
    sp = sin((0.5 - t)*pi)
    sq = sin(t*pi)
    Quaternion(s,
               sp * p.v1 + sq * v1,
               sp * p.v2 + sq * v2,
               sp * p.v3 + sq * v3, true)
    end
end

quatrand()  = quat(randn(), randn(), randn(), randn())
nquatrand() = normalize(quatrand())

## Rotations

function qrotation{T<:Real}(axis::Vector{T}, theta)
    if length(axis) != 3
        error("Must be a 3-vector")
    end
    u = normalize(axis)
    thetaT = convert(eltype(u), theta)
    s = sin(thetaT/2)
    Quaternion(cos(thetaT/2), s*u[1], s*u[2], s*u[3], true)
end

# Variant of the above where norm(rotvec) encodes theta
function qrotation{T<:Real}(rotvec::Vector{T})
    if length(rotvec) != 3
        error("Must be a 3-vector")
    end
    theta = norm(rotvec)
    if theta > 0
        s = sin(theta/2)/theta  # divide by theta to make rotvec a unit vector
        return Quaternion(cos(theta/2), s*rotvec[1], s*rotvec[2], s*rotvec[3], true)
    end
    Quaternion(one(T), zero(T), zero(T), zero(T), true)
end

rotationmatrix(q::Quaternion) = rotationmatrix_normalized(normalize(q))

function rotationmatrix_normalized(q::Quaternion)
    sx, sy, sz = 2q.s*q.v1, 2q.s*q.v2, 2q.s*q.v3
    xx, xy, xz = 2q.v1^2, 2q.v1*q.v2, 2q.v1*q.v3
    yy, yz, zz = 2q.v2^2, 2q.v2*q.v3, 2q.v3^2
    [1-(yy+zz)     xy-sz     xz+sy;
        xy+sz   1-(xx+zz)    yz-sx;
        xz-sy      yz+sx  1-(xx+yy)]
end

function normalize{T}(v::Vector{T})
    nv = norm(v)
    if nv > 0
        return v/nv
    end
    zeros(promote_type(T,typeof(nv)), length(v))
end

function slerp{T}(qa::Quaternion{T}, qb::Quaternion{T}, t::T)
    # http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/slerp/
    coshalftheta = qa.s * qb.s + qa.v1 * qb.v1 + qa.v2 * qb.v2 + qa.v3 * qb.v3;

    if coshalftheta < 0
        qm = -qb
        coshalftheta = -coshalftheta
    else
        qm = qb
    end
    abs(coshalftheta) >= 1.0 && return qa

    halftheta    = acos(coshalftheta)
    sinhalftheta = sqrt(one(T) - coshalftheta * coshalftheta)

    if abs(sinhalftheta) < 0.001
        return Quaternion(
            T(0.5) * (qa.s  + qb.s),
            T(0.5) * (qa.v1 + qb.v1),
            T(0.5) * (qa.v2 + qb.v2),
            T(0.5) * (qa.v3 + qb.v3),
        )
    end

    ratio_a = sin((one(T) - t) * halftheta) / sinhalftheta
    ratio_b = sin(t * halftheta) / sinhalftheta

    Quaternion(
        qa.s  * ratio_a + qm.s  * ratio_b,
        qa.v1 * ratio_a + qm.v1 * ratio_b,
        qa.v2 * ratio_a + qm.v2 * ratio_b,
        qa.v3 * ratio_a + qm.v3 * ratio_b,
    )
end