text:
using KeywordSearch, Test, UUIDs, Random
using Tables, StringDistances, Suppressor
using Aqua

# easier stateless testing of the global `KeywordSearch.AUTOMATIC_REPLACEMENTS`
# by emptying it, adding replacements, calling `f`, then restoring the former
# behavior.
function with_replacements(f, replaces...)
    former = copy(KeywordSearch.AUTOMATIC_REPLACEMENTS)
    empty!(KeywordSearch.AUTOMATIC_REPLACEMENTS)
    append!(KeywordSearch.AUTOMATIC_REPLACEMENTS, replaces)
    res = f()
    empty!(KeywordSearch.AUTOMATIC_REPLACEMENTS)
    append!(KeywordSearch.AUTOMATIC_REPLACEMENTS, former)
    return res
end

## Search robustness tests

@testset "Mistake 1: misspellings" begin
    @test match(FuzzyQuery("cobra"), Document("The cobre ate a mouse")) !== nothing
    @test match(FuzzyQuery("macaque"), Document("The macqaue ate a crab")) !== nothing
end

@testset "Mistake 2: word conjunctions" begin
    keyword = "ring-tailed lemur"
    text = "The ringtailed lemur sat in the sun."

    # This one matches just by the edit distance
    @test match(FuzzyQuery(keyword), Document(text)) !== nothing

    # It even matches exactly if we augment
    @test match(augment(Query(keyword)), Document(text)) !== nothing
end

@testset "Mistakes 3, 4: hyphenated words conjoined without hyphen or missing hyphen (with space)" begin
    keyword = "giant golden-crowned flying fox"
    # These match exactly due to removing punctuation in the documents
    for document_text in ("A giant golden crowned flying fox slept all day.",
         "A giant-golden-crowned flying fox slept all day.")
        @test match(Query(keyword), Document(document_text)) !== nothing
    end

    # These are 2 edits away, but appear to be 4 due to a possible bug
    for document_text in ("A giantgolden crowned flyingfox slept all day.",
         "A giantgolden crownedflying fox slept all day.")
        @test match(FuzzyQuery(keyword, DamerauLevenshtein(), 1),
                    Document(document_text)) === nothing

        # https://github.com/matthieugomez/StringDistances.jl/issues/43
        @test match(FuzzyQuery(keyword, DamerauLevenshtein(), 4),
                    Document(document_text)) !== nothing

        @test match(augment(Query(keyword)), Document(document_text)) !== nothing
        @test match(augment(FuzzyQuery(keyword)), Document(document_text)) !== nothing
    end

    # This one seems like 3 edits away, but since the same substring can't be edited more than once via the
    # "restricted edit distance" used by `DamerauLevenshtein()`, it comes out as 6 edits.
    document_text = "A giant goldencrownedflying fox slept all day."
    keyword = "giant golden-crowned flying fox"
    @test match(FuzzyQuery(keyword, DamerauLevenshtein(), 1), Document(document_text)) ===
          nothing
    @test match(FuzzyQuery(keyword, DamerauLevenshtein(), 2), Document(document_text)) ===
          nothing
    @test match(FuzzyQuery(keyword, DamerauLevenshtein(), 6), Document(document_text)) !==
          nothing

    # document_text = "A giant goldencrownedflying fox slept all day."

    @test match(augment(Query(keyword)), Document(document_text)) !== nothing
    @test match(augment(FuzzyQuery(keyword)), Document(document_text)) !== nothing
end

@testset "Mistake 5: erroneously redacted terms" begin
    document = with_replacements("xxxs frog" => "Darwins frog") do
        return Document("""
        A xxxs frog sat on a branch.
        """)
    end
    @test match(Query("Darwins frog"), document) !== nothing
    @test match(Query("xxxs frog"), document) === nothing
end

@testset "Mistake 6: Search terms that are subsets of other general terms (like acrynoms)" begin
    @test match(Query(" ant "), Document("This matches ant here")) !== nothing
    @test match(Query(" ant "), Document("This does not match anteater here")) === nothing
    @test match(Query("ant"), Document("This does match anteater")) !== nothing

    @test match(word_boundary(Query("ant")), Document("This does match anteater")) ===
          nothing # part of a word is not OK
    @test match(word_boundary(Query("ant")), Document("This matches ant here")) !== nothing # a separate word is OK
    @test match(word_boundary(Query("ant")), Document("This matches ant")) !== nothing # end of string is OK
    @test match(word_boundary(Query("ant")), Document("This matches ant.")) !== nothing # period is OK
    @test match(word_boundary(Query("ant")), Document("This matches ant?")) !== nothing # other punctuation is OK

    # we count hyphens as a word boundary here (since we remove them from the documents and queries)
    @test match(word_boundary(Query("ant")), Document("This matches-ant")) !== nothing
end

## A more representative test

@testset "Full query" begin
    # this is an actual query we are interested in
    query = augment(FuzzyQuery("leafcutting ant")) |
            augment(FuzzyQuery("leaf slicing ant")) |
            Query("ants") |
            Query("ant's") |
            Query(" ant ")

    # Note that the document has a hyphen and also a mispelling.
    # We still document a match because we replace hyphens with spaces,
    # and the extra `a` is within the threshold difference.
    document = Document("""The leaf-cutting aants walked around.""")
    @test match(query, document) !== nothing
    @test match(Query("ant's"), Document("The ant's exist")) !== nothing
    @test match(query, Document("The ant's exist")) !== nothing
    @test match(Query("ant's"), Document("The aant's exist")) !== nothing
    @test match(Query(" ant's "), Document("The aant's do not exist")) === nothing

    @test match(query, Document("This matches ant here")) !== nothing
    @test match(query, Document("This does not match anteater")) === nothing
end

## Functionality tests

# Some public domain text ("The Picture of Dorian Gray" by Oscar Wilde)
document = Document(lowercase("""
The artist is the creator of beautiful things.  To reveal art and
conceal the artist is art's aim.  The critic is he who can translate
into another manner or a new material his impression of beautiful
things.

The highest as the lowest form of criticism is a mode of autobiography.
Those who find ugly meanings in beautiful things are corrupt without
being charming.  This is a fault.

Those who find beautiful meanings in beautiful things are the
cultivated.  For these there is hope.  They are the elect to whom
beautiful things mean only beauty.

There is no such thing as a moral or an immoral book.  Books are well
written, or badly written.  That is all.

"""))

@testset "`Or`" begin
    has_artist = Query("artist")
    @test match(has_artist, document) !== nothing

    has_abc = Query("abc")
    @test match(has_abc, document) === nothing

    @test match(has_abc | has_artist, document) !== nothing
    @test length(has_abc | has_artist) == 2

    # test nesting
    query = (Query("a") | Query("b")) | (Query("c") | Query("d"))
    @test match(query, document) !== nothing
    @test query isa KeywordSearch.Or{Tuple{Query,Query,Query,Query}}
    @test length(query) == 4
end

@testset "`FuzzyQuery`" begin
    # Try some mispelling of "artist"
    fuzzy_has_altist = FuzzyQuery("altist", DamerauLevenshtein(), 1)
    @test match(fuzzy_has_altist, document) !== nothing

    fuzzy_has_atist = FuzzyQuery("atist", Levenshtein(), 1)
    @test match(fuzzy_has_atist, document) !== nothing
    @test match(Query("atist"), document) === nothing

    @test match(FuzzyQuery("atist", Levenshtein(), 0), document) === nothing
end

@testset "Match objects and `match_all`" begin
    document = Document("""The crab was eaten by a crb-eating macaque.""")
    query = FuzzyQuery("crab-eating macaque")

    m = match(query, document)
    @test m.query === query
    @test m.document === document
    @test m.distance == 2
    @test document.text[m.indices] == " crb eating macaque"

    matches = match_all(query, document)
    @test length(matches) == 1
    @test matches[1] == m

    query = FuzzyQuery("crab-eating macaque") | FuzzyQuery("crabeating macaque")
    matches = match_all(query, document)
    @test length(matches) == 2
    @test matches[1] == m

    @test matches[2].query == FuzzyQuery("crabeating macaque")
    @test matches[2].document === document
    @test matches[2].distance == 2
    @test document.text[matches[2].indices] == "crb eating macaque"
end

@testset "`match_all` for `Query`s" begin
    document = Document("""One crab was eaten.""")
    query = Query("One") | Query("crab") | Query("eat")
    matches = match_all(query, document)
    @test length(matches) == 3
    @test allunique(matches)
end

@testset "`_findmin` and `_findall`" begin
    ## Equal length cases

    # `d` replaced by `x`; 1 away
    str1 = "abcd"
    str2 = "abcx"
    d, inds = KeywordSearch._findmin(str1, str2, Partial(DamerauLevenshtein()); max_dist=1)
    matches = KeywordSearch._findall(str1, str2, Partial(DamerauLevenshtein()); max_dist=1)
    @test d == Partial(DamerauLevenshtein(1))(str1, str2)
    @test matches == [(1, 1:4)] == [(d, inds)]
    @test KeywordSearch.non_overlapping_matches(matches) == matches

    # `cd` replaced by `xy`; 2 away
    str1 = "abcd"
    str2 = "abxy"

    d, inds = KeywordSearch._findmin(str1, str2, Partial(DamerauLevenshtein()); max_dist=1)
    matches = KeywordSearch._findall(str1, str2, Partial(DamerauLevenshtein()); max_dist=1)
    @test d == 2 # `max_dist + 1`
    @test isempty(matches)
    @test KeywordSearch.non_overlapping_matches(matches) == matches

    d, inds = KeywordSearch._findmin(str1, str2, Partial(DamerauLevenshtein()); max_dist=2)
    matches = KeywordSearch._findall(str1, str2, Partial(DamerauLevenshtein()); max_dist=2)
    @test d == Partial(DamerauLevenshtein(1))(str1, str2)
    @test matches == [(2, 1:4)] == [(d, inds)]
    @test KeywordSearch.non_overlapping_matches(matches) == matches

    ## Nonequal length cases

    # `d` replaced by `x`; 1 away
    str1 = "abcdef"
    str2 = "1234abcxef1234"

    d, inds = KeywordSearch._findmin(str1, str2, Partial(DamerauLevenshtein()); max_dist=1)
    matches = KeywordSearch._findall(str1, str2, Partial(DamerauLevenshtein()); max_dist=1)
    @test d == Partial(DamerauLevenshtein(1))(str1, str2)
    @test matches == [(1, 5:10)] == [(d, inds)]
    @test KeywordSearch.non_overlapping_matches(matches) == matches

    d, inds = KeywordSearch._findmin(str1, str2, Partial(DamerauLevenshtein()); max_dist=2)
    matches = KeywordSearch._findall(str1, str2, Partial(DamerauLevenshtein()); max_dist=2)
    @test d == Partial(DamerauLevenshtein(2))(str1, str2)
    @test matches == [(1, 5:10)] == [(d, inds)]
    @test KeywordSearch.non_overlapping_matches(matches) == matches

    d, inds = KeywordSearch._findmin(str1, str2, Partial(DamerauLevenshtein()); max_dist=3)
    matches = KeywordSearch._findall(str1, str2, Partial(DamerauLevenshtein()); max_dist=3)
    @test d == Partial(DamerauLevenshtein(3))(str1, str2)
    @test d == 1
    @test inds == 5:10
    @test matches == [(3, 4:9), (1, 5:10), (3, 6:11)]
    # all the matches overlap, so the following just chooses the best one):
    @test KeywordSearch.non_overlapping_matches(matches) == [(1, 5:10)]

    # `cde` replaced by `xyz`; 3 away
    str1 = "abcdef"
    str2 = "1234abxyzf1234"

    d, inds = KeywordSearch._findmin(str1, str2, Partial(DamerauLevenshtein()); max_dist=1)
    matches = KeywordSearch._findall(str1, str2, Partial(DamerauLevenshtein()); max_dist=1)
    @test d == Partial(DamerauLevenshtein(1))(str1, str2)
    @test d == 2 # max_dist + 1
    # not testing `inds` because they are undefined (since we hit `max_dist`)
    @test isempty(matches)

    d, inds = KeywordSearch._findmin(str1, str2, Partial(DamerauLevenshtein()); max_dist=2)
    matches = KeywordSearch._findall(str1, str2, Partial(DamerauLevenshtein()); max_dist=2)
    @test d == Partial(DamerauLevenshtein(2))(str1, str2)
    @test d == 3 # max_dist + 1
    # not testing `inds`
    @test isempty(matches)

    d, inds = KeywordSearch._findmin(str1, str2, Partial(DamerauLevenshtein()); max_dist=3)
    matches = KeywordSearch._findall(str1, str2, Partial(DamerauLevenshtein()); max_dist=3)
    @test d == Partial(DamerauLevenshtein(3))(str1, str2)
    @test matches == [(3, 5:10)] == [(d, inds)]

    d, inds = KeywordSearch._findmin(str1, str2, Partial(DamerauLevenshtein()); max_dist=4)
    matches = KeywordSearch._findall(str1, str2, Partial(DamerauLevenshtein()); max_dist=4)
    @test d == Partial(DamerauLevenshtein(4))(str1, str2)
    @test matches == [(3, 5:10)] == [(d, inds)]

    # In the first case, `cde` replaced by `xyz` (3 away); in the second, only `e` is replaced by `x` (one away)
    str1 = "abcdef"
    str2 = "1234abxyzf1234abcdxf123"
    for max_dist in (1, 2)
        d, inds = KeywordSearch._findmin(str1, str2, Partial(DamerauLevenshtein());
                                         max_dist=max_dist)
        matches = KeywordSearch._findall(str1, str2, Partial(DamerauLevenshtein());
                                         max_dist=max_dist)
        @test d == Partial(DamerauLevenshtein(max_dist))(str1, str2)
        @test matches == [(1, 15:20)] == [(d, inds)]
        @test KeywordSearch.non_overlapping_matches(matches) == matches
    end
    # Now at 3, we find the other match.
    # We also match at "4abcdx": delete '4', substitute 'x' => 'e', and insert 'f' at the end
    # as well as "bcdxf1": insert 'a' at the start, substitute 'x' => 'e', and delete '1' at the end
    d, inds = KeywordSearch._findmin(str1, str2, Partial(DamerauLevenshtein()); max_dist=3)
    matches = KeywordSearch._findall(str1, str2, Partial(DamerauLevenshtein()); max_dist=3)
    @test d == Partial(DamerauLevenshtein(3))(str1, str2)
    @test (d, inds) == (1, 15:20)
    @test matches == [(3, 5:10), (3, 14:19), (1, 15:20), (3, 16:21)]
    # Now we have a somewhat interesting overlap removal: we have two "runs"
    # One is just `5:10` with no overlaps, so we keep that, and one with indices
    # ranging from `14` to `21`, and we keep the best one.
    @test KeywordSearch.non_overlapping_matches(matches) == [(3, 5:10), (1, 15:20)]
end

@testset "`Corpus` and `NamedMatch`" begin
    D1 = Document("There were crab eating macqaues!", (; document_uuid=uuid4()))
    D2 = Document("There were lobster eating macqaues!", (; document_uuid=uuid4()))
    C1 = Corpus([D1, D2], (; corpus_uuid=uuid4()))

    @test match(Query("lobster eating"), C1) !== nothing
    @test match(Query("lobster eating"), C1) == match(Query("lobster eating"), D2)
    @test match(Query("crab eating"), C1) !== nothing
    @test match(Query("crab eating"), C1) == match(Query("crab eating"), D1)
    @test match_all(Query("eating"), C1) ==
          [match(Query("eating"), D1), match(Query("eating"), D2)]

    eating_query = NamedQuery(Query("eating"), "eating query")
    matches = match_all(eating_query, C1)
    @test length(matches) == 2
    @test matches[1].metadata ==
          (; query_name="eating query", corpus_uuid=C1.metadata.corpus_uuid,
           document_uuid=D1.metadata.document_uuid)
    @test matches[2].metadata ==
          (; query_name="eating query", corpus_uuid=C1.metadata.corpus_uuid,
           document_uuid=D2.metadata.document_uuid)

    D1_matches = match_all(eating_query, D1)
    @test length(D1_matches) == 1
    @test D1_matches[1].metadata ==
          (; query_name="eating query", document_uuid=D1.metadata.document_uuid)
    @test D1_matches[1].match == matches[1].match

    D3 = Document("There were king cobras", (; document_uuid=uuid4()))
    d_uuid = uuid4()
    D4 = Document("There were other cobras", (; document_uuid=d_uuid))
    C2_uuid = uuid4()
    C2 = Corpus([D3, D4], (; corpus_uuid=C2_uuid))

    Q = NamedQuery(Query(" other"), "other")
    @test match(Q, C1) === nothing
    res = match(Q, C2)
    @test res isa KeywordSearch.NamedMatch
    @test res.metadata == (; query_name="other", corpus_uuid=C2_uuid, document_uuid=d_uuid)
    @test res.match == match(Q.query, D4)

    @test match(Q, D4).match == match(Q.query, D4)
    @test match(Q, D4).metadata == (; query_name="other", document_uuid=d_uuid)

    @testset "Tables interface for `NamedMatch`s" begin
        @test Tables.getcolumn(res, 1) == Tables.getcolumn(res, :query) == Q.query
        @test Tables.getcolumn(res, 2) == Tables.getcolumn(res, :document) == D4
        @test Tables.getcolumn(res, 3) == Tables.getcolumn(res, :distance) == 0
        @test Tables.getcolumn(res, 4) == Tables.getcolumn(res, :indices)
        @test Tables.getcolumn(res, 5) == Tables.getcolumn(res, :query_name) == "other"
        @test Tables.getcolumn(res, 6) == Tables.getcolumn(res, :corpus_uuid) == C2_uuid
        tbl = [res, res]
        @test Tables.isrowtable(tbl)
        @test Tables.columns(tbl).query_name == ["other", "other"]
        @test Tables.columnnames(res) ==
              (:document, :distance, :indices, :query, :query_name, :corpus_uuid,
               :document_uuid)
        @test sprint(explain, (first(Tables.rowtable(tbl)))) ===
              "The query \" other\" exactly matched the text \"There were other cobras \".\n"
    end
end

@testset "`AUTOMATIC_REPLACEMENTS`" begin
    @test with_replacements(() -> Document("abc"), "a" => "b") ==
          with_replacements(() -> Document("bbc"))
end

## Edge cases

@testset "Query vs document lengths" begin
    for Q in (FuzzyQuery, Query)
        # Same query and document length
        @test match(Query("abc"), Document("def")) === nothing
        @test match(Query("abc"), Document("abc")) !== nothing
        @test length(match_all(Query("abc"), Document("abc"))) == 1
        @test length(match_all(Query("abc"), Document("def"))) == 0

        # Zero length query
        @test length(match_all(Query(""), Document("def"))) == 0
        @test match(Query(""), Document("def")) === nothing

        # Document length less than query length
        @test match(Query("abc"), Document("ab")) === nothing
        @test length(match_all(Query("abc"), Document("ab"))) == 0
    end
end

@testset "`text_left_endpoint` and `text_right_endpoint` unit tests" begin
    str = "OpuR5HEDQCWqrVNJHvwa" # from `randstring(20)`
    @test KeywordSearch.text_left_endpoint(str, 15; approx_length=20) == (1, "")
    @test KeywordSearch.text_left_endpoint(str, 15; approx_length=10) == (5, "…")

    @test KeywordSearch.text_right_endpoint(str, 5; approx_length=20) == (20, "")
    @test KeywordSearch.text_right_endpoint(str, 5; approx_length=10) == (15, "…")

    str_space = str[1:4] * " " * str[5:(end - 1)]
    @test KeywordSearch.text_left_endpoint(str_space, 15; approx_length=5) == (6, "…")
    @test KeywordSearch.text_left_endpoint(str_space, 15; approx_length=4) == (11, "…")

    @test KeywordSearch.text_right_endpoint(str_space, 1; approx_length=3) == (4, "…")
    @test KeywordSearch.text_right_endpoint(str_space, 1; approx_length=1) == (2, "…")
end

@testset "Errors" begin
    # Cannot use `match` as a metadata key
    @test_throws ArgumentError Document("abc", (; match=1))
    @test_throws ArgumentError Corpus([Document("abc")], (; match=1))
    @test_throws ArgumentError NamedQuery(Query("abc"), (; match=1))

    # Cannot have the same metadata keys in a corpus and a document it contains
    @test_throws ErrorException Corpus([Document("abc", (; uuid=uuid4()))],
                                       (; uuid=uuid4()))

    # Cannot have the same metadata keys in a `NamedQuery` and an object it is matched to
    @test_throws ErrorException match(NamedQuery(Query("a"), (; uuid=uuid4())),
                                      Document("abc", (; uuid=uuid4())))
    @test_throws ErrorException match(NamedQuery(Query("a"), (; uuid=uuid4())),
                                      Corpus([Document("abc", (; uuid=uuid4()))],
                                             (; corpus_name="a")))
end

@testset "Aqua tests" begin
    Aqua.test_all(KeywordSearch)
end


text:
# demonstrates the use of sixelplot to add a plot
# and overriding the axes limits

using SixelGraphics

x = Array{Float64}(0:0.1:4*pi)
y1 = sin.(x)
y2 = cos.(x)

s = sixelplot(x,y1, typ='b', xlim=[-2,14])

sixelplot(s,x,y2, typ='b')

ext: jl


text:
using  AcuteML
using Dates: Time, Date

export MRDHeader

@enm PatientGender M F O

@aml mutable struct SubjectInformation "~"
    patientName::UN{String}=nothing, "~"
    patientWeight_kg::UN{Float32}=nothing, "~"
    patientID::UN{String}=nothing, "~"
    patientBirthdate::UN{Date}=nothing, "~"
    patientGender::UN{PatientGender.Enm}=nothing, "~"
end
@aml mutable struct StudyInformation "~"
    studyDate::UN{Date}=nothing, "~"
    studyTime::UN{Time}=nothing, "~"
    studyID::UN{String}=nothing, "~"
    accessionNumber::UN{Int64}=nothing, "~"
    referringPhysicianName::UN{String}=nothing, "~"
    studyDescription::UN{String}=nothing, "~"
    studyInstanceUID::UN{String}=nothing, "~"
end

@enm PatientPosition HFP HFS HFDR HFDL FFP FFS FFDR FFDL

@aml mutable struct MeasurementDependency "~"
    dependencyType::String, "~"
    measurementID::String, "~"
end
@aml mutable struct ReferencedImageSequence "~"
    referencedSOPInstanceUID::UN{Vector{String}}=nothing, "~"
end
@aml mutable struct MeasurementInformation "~"
    measurementID::UN{String}=nothing, "~"
    seriesDate::UN{Date}=nothing, "~"
    seriesTime::UN{Time}=nothing, "~"
    patientPosition::PatientPosition.Enm, "~"
    initialSeriesNumber::UN{Int64}=nothing, "~"
    protocolName::UN{String}=nothing, "~"
    seriesDescription::UN{String}=nothing, "~"
    measurementDependency::UN{Vector{MeasurementDependency}}=nothing, "~"
    seriesInstanceUIDRoot::UN{String}=nothing, "~"
    frameOfReferenceUID::UN{String}=nothing, "~"
    referenceImageSequence::UN{ReferencedImageSequence}=nothing, "~"
end

@aml mutable struct CoilLabel "~"
    coilNumber::UInt16,"~"
    coilName::String,"~"
end

@aml mutable struct AcquisitionSystemInformation "~"
    systemVendor::UN{String}=nothing, "~"
    systemModel::UN{String}=nothing, "~"
    systemFieldStrength_T::UN{Float32}=nothing, "~"
    relativeReceiverNoiseBandwidth::UN{Float32}=nothing, "~"
    receiverChannels::UN{UInt16}=nothing, "~"
    coilLabel::UN{Vector{CoilLabel}}=nothing,"~"
    institutionName::UN{String}=nothing, "~"
    stationName::UN{String}=nothing, "~"
    deviceID::UN{String}= nothing, "~"
end
@aml mutable struct ExperimentalConditions "~"
    H1resonanceFrequency_Hz::Int64, "~"
end

@aml mutable struct UserParameter{T} "~"
    name::String, "~"
    value::T, "~"
end

@enm TrajectoryType cartesian epi radial goldenangle spiral other

@aml mutable struct TrajectoryDescription "~"
    identifier::String, "~"
    userParameterLong::UN{Vector{UserParameter{Int64}}}=nothing, "~"
    userParameterDouble::UN{Vector{UserParameter{Float64}}}=nothing, "~"
    comment::UN{String}=nothing, "~"
end

@aml mutable struct AccelerationFactor "~"
    kspace_encoding_step_1::UInt16, "~"
    kspace_encoding_step_2::UInt16, "~"
end

@enm InterleavingDimension phase repetition contrast average other

@enm CalibrationMode embedder interleaved separate external other

@aml mutable struct ParallelImaging  "~"
    accelerationFactor::AccelerationFactor, "~"
    calibrationMode::UN{CalibrationMode.Enm}, "~"
    interleavingDimension::UN{InterleavingDimension.Enm}, "~"
end

@aml mutable struct MatrixSize "~"
    x::UInt16=1, "~"
    y::UInt16=1, "~"
    z::UInt16=1, "~"
end

@aml mutable struct FieldOfView_mm "~"
    x::Float32, "~"
    y::Float32, "~"
    z::Float32, "~"

end

@aml mutable struct EncodingSpace "~"
    matrixSize::MatrixSize, "~"
    fieldOfView_mm::FieldOfView_mm, "~"
end

@aml mutable struct Limit "~"
    minimum::UInt16=0, "~"
    maximum::UInt16=0, "~"
    center::UInt16=0, "~"
end

@aml mutable struct EncodingLimits "~"
    kspace_encoding_step_0::UN{Limit}=nothing, "~"
    kspace_encoding_step_1::UN{Limit}=nothing, "~"
    kspace_encoding_step_2::UN{Limit}=nothing, "~"
    average::UN{Limit}=nothing, "~"
    slice::UN{Limit}=nothing, "~"
    contrast::UN{Limit}=nothing, "~"
    phase::UN{Limit}=nothing, "~"
    repetition::UN{Limit}=nothing, "~"
    set::UN{Limit}=nothing, "~"
    segment::UN{Limit}=nothing, "~"
end

@aml mutable struct Encoding "~"
    encodedSpace::EncodingSpace, "~"
    reconSpace::EncodingSpace, "~"
    encodingLimits::EncodingLimits,"~"
    trajectory::TrajectoryType.Enm,"~"
    trajectoryDescription::UN{TrajectoryDescription}=nothing,"~"
    parallelImaging::UN{ParallelImaging}=nothing,"~"
    echoTrainLength::UN{Int64}, "~"
end

@aml mutable struct SequenceParameters "~"
    TR::UN{Vector{Float32}}=nothing, "~"
    TE::UN{Vector{Float32}}=nothing, "~"
    TI::UN{Vector{Float32}}=nothing, "~"
    flipAngle_deg::UN{Vector{Float32}}=nothing, "~"
    sequence_type::UN{String}=nothing, "~"
    echo_spacing::UN{Vector{Float32}}=nothing, "~"
end

@aml mutable struct UserParameters "~"
    userParameterLong::UN{Vector{UserParameter{Int64}}}=nothing,"~"
    userParameterDouble::UN{Vector{UserParameter{Float64}}}=nothing,"~"
    userParameterString::UN{Vector{UserParameter{String}}}=nothing,"~"
    userParameterBase64::UN{Vector{UserParameter{Vector{UInt8}}}}=nothing,"~"
end

@enm WaveformType ecg pulse respiratory trigger gradientwaveform other

@aml mutable struct WaveformInformation "~"
    waveformName::String, "~"
    waveformType::WaveformType.Enm, "~"
    userParameters::UserParameters, "~"
end

@aml mutable struct MRDHeader doc"ismrmrdHeader"
    version::UN{Int}=nothing, "~"
    subjectInformation::UN{SubjectInformation}=nothing, "~"
    studyInformation::UN{StudyInformation}=nothing, "~"
    measurementInformation::UN{MeasurementInformation}=nothing, "~"
    acquisitionSystemInformation::UN{AcquisitionSystemInformation}=nothing, "~"
    experimentalConditions::ExperimentalConditions, "~"
    encoding::UN{Vector{Encoding}}=nothing, "~"
    sequenceParameters::UN{SequenceParameters}=nothing, "~"
    userParameters::UN{UserParameters}=nothing, "~"
    waveformInformation::UN{WaveformInformation}=nothing, "~"
end

MRDHeader(x::AbstractString) = x |> parsexml |> MRDHeader
precompile(Tuple{typeof(MRDHeader),String})


text:
#!/usr/bin/env julia

using ClusterManagers
using Distributed

# this appears to be the point of diminishing returns
max_node_processes = 12

"turns a slurm node list into a list of node names"
function parse_node_names(slurm_node_list::String)
    node_names = String[]
    slurm_node_list_parts = split(slurm_node_list, "[")
    node_name_prefix = slurm_node_list_parts[1]

    #if only one node was found then this length will be 1
    if length(slurm_node_list_parts) == 1
        push!(node_names, node_name_prefix)
    else
        # this assumes all nodes have a common prefix and the nodelist has the form [1-2,4,5-10,12]
        node_ids = split(split(slurm_node_list_parts[2], "]")[1], ",")
        for node_id in node_ids
            node_id_parts = split(node_id, "-")
            start = parse(Int, node_id_parts[1])
            stop = parse(Int, length(node_id_parts) == 1 ? node_id_parts[1] : node_id_parts[2])
            digits = length(node_id_parts[1])
            for i in start:stop
                node_name = "$(node_name_prefix)$(lpad(string(i), digits, "0"))"
                push!(node_names, node_name)
            end
        end
    end
    return node_names
end

function add_local_procs()
    proc_ids = Int[]
    if Distributed.nprocs() <= 2
        node_processes = min(trunc(Int, Sys.CPU_THREADS*0.75), max_node_processes)
        println("local processes: $(node_processes) of $(Sys.CPU_THREADS)")

        proc_ids = Distributed.addprocs(node_processes)
    end
    return proc_ids
end

function add_remote_procs()
    proc_ids = addprocs(SlurmManager(4), partition="scaling")

    return proc_ids
end

function add_procs()
    proc_ids = add_local_procs()
    remote_proc_ids = add_remote_procs()

    for pid in remote_proc_ids
        push!(proc_ids, pid)
    end

    println("process ids: $(proc_ids)")
    return proc_ids
end

println("spin up workers")
add_procs()

println("define hello function on all workers")
@everywhere function hello(task::Int)
    println("I am working on task $(task)")
    return "task $(task) complete"
end

println("execute in parallel")
value = pmap(hello, [i for i in 1:Distributed.nprocs()])
sleep(1) # wait for stdout to catchup
println("result: $(value)")
println("execute complete")

max_stars_repo_name: ccoffrin/julia-examples

max_issues_repo_name: ccoffrin/julia-examples

max_forks_repo_name: ccoffrin/julia-examples


text:
"""
Class for multi-output sparse variational Gaussian Processes

```julia
MOSVGP(X::AbstractArray{T},y::AbstractVector{AbstractArray{T}},kernel::Kernel,
    likelihood::AbstractVector{Likelihoods},inference::InferenceType, nInducingPoints::Int;
    verbose::Int=0,optimizer::Union{Optimizer,Nothing,Bool}=Adam(α=0.01),atfrequency::Int=1,
    mean::Union{<:Real,AbstractVector{<:Real},PriorMean}=ZeroMean(),
    Zoptimizer::Union{Optimizer,Nothing,Bool}=false,
    ArrayType::UnionAll=Vector)
```

Argument list :

**Mandatory arguments**
 - `X` : input features, should be a matrix N×D where N is the number of observation and D the number of dimension
 - `y` : input labels, can be either a vector of labels for multiclass and single output or a matrix for multi-outputs (note that only one likelihood can be applied)
 - `kernel` : covariance function, can be either a single kernel or a collection of kernels for multiclass and multi-outputs models
 - `likelihood` : likelihood of the model, currently implemented : Gaussian, Student-T, Laplace, Bernoulli (with logistic link), Bayesian SVM, Multiclass (softmax or logistic-softmax) see [`Likelihood`](@ref likelihood_user)
 - `inference` : inference for the model, can be analytic, numerical or by sampling, check the model documentation to know what is available for your likelihood see the [`Compatibility table`](@ref compat_table)
 - `nInducingPoints` : number of inducing points
**Optional arguments**
 - `verbose` : How much does the model print (0:nothing, 1:very basic, 2:medium, 3:everything)
 - `optimizer` : Optimizer for kernel hyperparameters (to be selected from [GradDescent.jl](https://github.com/jacobcvt12/GradDescent.jl))
 - `atfrequency` : Choose how many variational parameters iterations are between hyperparameters optimization
 - `mean` : PriorMean object, check the documentation on it [`MeanPrior`](@ref meanprior)
 - `IndependentPriors` : Flag for setting independent or shared parameters among latent GPs
 - `optimizer` : Optimizer for inducing point locations (to be selected from [GradDescent.jl](https://github.com/jacobcvt12/GradDescent.jl))
 - `ArrayType` : Option for using different type of array for storage (allow for GPU usage)
"""
mutable struct MOSVGP{T<:Real,TLikelihood<:Likelihood{T},TInference<:Inference,TGP<:Abstract_GP{T},N,Q} <: AbstractGP{T,TLikelihood,TInference,TGP,N}
    X::Matrix{T} #Feature vectors
    y::Vector #Output (-1,1 for classification, real for regression, matrix for multiclass)
    nSamples::Int64 # Number of data points
    nDim::Int64 # Number of covariates per data point
    nFeatures::Int64 # Number of features of the GP (equal to number of points)
    nLatent::Int64 # Number of latent GPs
    nTask::Int64
    nf_per_task::Vector{Int64}
    f::NTuple{Q,TGP}
    likelihood::Vector{TLikelihood}
    inference::TInference
    A::Array{T,3}
    A_opt::Union{Optimizer,Nothing}
    verbose::Int64
    atfrequency::Int64
    Trained::Bool
end

function MOSVGP(
            X::AbstractArray{T},y::AbstractVector{<:AbstractVector},kernel::Kernel,
            likelihood::TLikelihood,inference::TInference,nLatent::Int,nInducingPoints::Int;
            verbose::Int=0,optimizer::Union{Optimizer,Nothing,Bool}=Adam(α=0.01),atfrequency::Int=1,
            mean::Union{<:Real,AbstractVector{<:Real},PriorMean}=ZeroMean(), variance::Real = 1.0,Aoptimizer::Union{Optimizer,Nothing,Bool}=Adam(α=0.01),
            Zoptimizer::Union{Optimizer,Nothing,Bool}=false,
            ArrayType::UnionAll=Vector) where {T<:Real,TLikelihood<:Likelihood,TInference<:Inference}

            @assert length(y) > 0 "y should not be an empty vector"
            nTask = length(y)
            likelihoods = [deepcopy(likelihood) for _ in 1:nTask]
            nf_per_task = zeros(Int64,nTask)
            corrected_y = Vector(undef,nTask)
            for i in 1:nTask
                X,corrected_y[i],nf_per_task[i],likelihoods[i] = check_data!(X,y[i],likelihoods[i])
            end
            @assert check_implementation(:SVGP,likelihoods[1],inference) "The $likelihood is not compatible or implemented with the $inference"

            nSamples = size(X,1); nDim = size(X,2);
            if isa(optimizer,Bool)
                optimizer = optimizer ? Adam(α=0.01) : nothing
            end
            if isa(AOptimizer,Bool)
                Aoptimizer = Aoptimizer ? Adam(α=0.01) : nothing
            end
            @assert nInducingPoints > 0 && nInducingPoints < nSamples "The number of inducing points is incorrect (negative or bigger than number of samples)"
            Z = KMeansInducingPoints(X,nInducingPoints,nMarkov=10)
            if isa(Zoptimizer,Bool)
                Zoptimizer = Zoptimizer ? Adam(α=0.01) : nothing
            end
            Z = InducingPoints(Z,Zoptimizer)

            nFeatures = nInducingPoints

            if typeof(mean) <: Real
                mean = ConstantMean(mean)
            elseif typeof(mean) <: AbstractVector{<:Real}
                mean = EmpiricalMean(mean)
            end

            nMinibatch = nSamples
            if inference.Stochastic
                @assert inference.nMinibatch > 0 && inference.nMinibatch < nSamples "The size of mini-batch $(inference.nMinibatch) is incorrect (negative or bigger than number of samples), please set nMinibatch correctly in the inference object"
                nMinibatch = inference.nMinibatch
            end

            latent_f = ntuple( _ -> _SVGP{T}(nFeatures,nMinibatch,Z,kernel,mean,variance,optimizer),nLatent)

            dpos = Normal(0.5,0.5)
            dneg = Normal(-0.5,0.5)
            A = zeros(T,nTask,nf_per_task[1],nLatent)
            for i in eachindex(A)
                p = rand(0:1)
                A[i] =  rand(Normal(0.0,1.0))#p*rand(dpos) + (1-p)*rand(dneg)
            end

            likelihoods .= init_likelihood.(likelihoods,inference,nf_per_task,nMinibatch,nFeatures)
            inference = tuple_inference(inference,nLatent,nFeatures,nSamples,nMinibatch)
            inference.xview = view(X,1:nMinibatch,:)
            inference.yview = view(y,:)

            model = MOSVGP{T,TLikelihood,typeof(inference),_SVGP{T},nTask,nLatent}(X,corrected_y,
                    nSamples, nDim, nFeatures, nLatent,
                    nTask, nf_per_task,
                    latent_f,likelihoods,inference,A,Aoptimizer,
                    verbose,atfrequency,false)
            # if isa(inference.optimizer,ALRSVI)
                # init!(model.inference,model)
            # end
            # return model
end

function Base.show(io::IO,model::MOSVGP{T,<:Likelihood,<:Inference}) where {T}
    print(io,"Multioutput Sparse Variational Gaussian Process with the likelihoods $(model.likelihood) infered by $(model.inference) ")
end

function mean_f(model::MOSVGP{T}) where {T}
    μ_q = mean_f.(model.f)
    μ_f = []
    for i in 1:model.nTask
        x = ntuple(_->zeros(T,model.inference.nMinibatch),model.nf_per_task[i])
        for j in 1:model.nf_per_task[i]
            x[j] .= sum(vec(model.A[i,j,:]) .* μ_q)
        end
        push!(μ_f,x)
    end
    return μ_f
end

function diag_cov_f(model::MOSVGP{T}) where {T}
    Σ_q = diag_cov_f.(model.f)
    Σ_f = []
    for i in 1:model.nTask
        x = ntuple(_->zeros(T,model.inference.nMinibatch),model.nf_per_task[i])
        for j in 1:model.nf_per_task[i]
            x[j] .= sum(vec(model.A[i,j,:]).^2 .* Σ_q)
        end
        push!(Σ_f,x)
    end
    return Σ_f
end

get_y(model::MOSVGP) = view_y.(model.likelihood,model.y,[model.inference.MBIndices])

## return the linear sum of the expectation gradient given μ ##
function ∇E_μ(model::MOSVGP{T}) where {T}
    ∇ = [zeros(T,model.inference.nMinibatch) for _ in 1:model.nLatent]
    ∇Eμs = ∇E_μ.(model.likelihood,model.inference.vi_opt[1:1],get_y(model))
    ∇EΣs = ∇E_Σ.(model.likelihood,model.inference.vi_opt[1:1],get_y(model))
    μ_f = mean_f.(model.f)
    for t in 1:model.nTask
        for j in 1:model.nf_per_task[t]
            for q in 1:model.nLatent
                ∇[q] .+= model.A[t,j,q] * (∇Eμs[t][j]  - 2*∇EΣs[t][j].*sum(model.A[t,j,qq]*μ_f[qq] for qq in 1:model.nLatent if qq!=q))
            end
        end
    end
    return ∇
end

## return the linear sum of the expectation gradient given diag(Σ) ##
function ∇E_Σ(model::MOSVGP{T}) where {T}
    ∇ = [zeros(T,model.inference.nMinibatch) for _ in 1:model.nLatent]
    ∇Es = ∇E_Σ.(model.likelihood,model.inference.vi_opt[1:1],get_y(model))
    for t in 1:model.nTask
        for j in 1:model.nf_per_task[t]
            for q in 1:model.nLatent
                ∇[q] .+= model.A[t,j,q]^2 * ∇Es[t][j]
            end
        end
    end
    return ∇
end

get_Z(model::MOSVGP) = getproperty.(getproperty.(model.f,:Z),:Z)

function update_A!(model::MOSVGP)
    μ_f = mean_f.(model.f) # κμ
    Σ_f = diag_cov_f.(model.f) #Diag(K̃ + κΣκ)
    ∇Eμ = ∇E_μ.(model.likelihood,model.inference.vi_opt[1:1],get_y(model))
    ∇EΣ = ∇E_Σ.(model.likelihood,model.inference.vi_opt[1:1],get_y(model))
    new_A = zero(model.A)
    ∇A = zero(model.A)
    for t in 1:model.nTask
        for j in 1:model.nf_per_task[t]
            for q in 1:model.nLatent
                x1 = dot(∇Eμ[t][j],μ_f[q])-2*dot(∇EΣ[t][j],μ_f[q].*sum(model.A[t,j,qq]*μ_f[qq] for qq in 1:model.nLatent if qq!=q))
                x2 = dot(∇EΣ[t][j],abs2.(μ_f[q])+Σ_f[q])
                new_A[t,j,q] = x1/(2*x2)
                ∇A[t,j,q] = x1 - 2*model.A[t,j,q]*x2
            end
            # model.A[t,j,:]./=sum(model.A[t,j,:])
        end
    end
    model.A .+= update(model.A_opt,∇A)
    # model.A .= model.A./vec(sum(model.A,dims=3))
    # model.A .= new_A
end

function ELBO(model::MOSVGP{T}) where {T}
    tot = zero(T)
    tot += model.inference.ρ*sum(expec_log_likelihood.(model.likelihood,model.inference,get_y(model),mean_f(model),diag_cov_f(model)))
    tot -= GaussianKL(model)
    tot -= model.inference.ρ*sum(AugmentedKL.(model.likelihood,get_y(model)))
end


text:
function check_balances(user)
    balances_all  = Dict()
    # Load BNB settings
    BNB_settings = JSON.parsefile(joinpath(Pinguu.base_dir, "BNB_control/BNB_settings.json"))
    for (k,v) in user
        user_id = parse(Int64, k)
        client = user[k]["client"]

        user_config = glob(joinpath(Pinguu.base_dir, "users/$user_id/*/config.json"));
        user_invest = glob(joinpath(Pinguu.base_dir, "users/$user_id/*/config.json"));
        account = client.get_account()["balances"];
        if account !== nothing
            balances = Dict()

            for bal in account
                balances[bal["asset"]] = Dict()
                balances[bal["asset"]]["free"] = parse(Float64, bal["free"])
                balances[bal["asset"]]["locked"] = parse(Float64, bal["locked"])

                #
                ## Check Pinguus
                coin = bal["asset"]
                total_vol = 0
                locked   = 0
                locked_stopped = 0
                invested  = 0
                for c in user_config
                    tmp_config = JSON.parsefile(c)
                    if tmp_config["status"] != "dead" && tmp_config["pureEMA"] == 0
                        stat = JSON.parsefile(joinpath(dirname(c), "status.json"))
                        if stat["base_coin"] == coin
                            oo = JSON.parsefile(joinpath(dirname(c), "open_orders.json"))
                            co = JSON.parsefile(joinpath(dirname(c), "closed_orders.json"))
                            if length(oo["BUY"]) > 0
                                for (k,v) in oo["BUY"]
                                    id_str = split(k, "_")[end]
                                    if v["status"] != "stopped"
                                        locked += parse(Float64, v["price"]) * parse(Float64, v["origQty"])
                                    else
                                        locked_stopped += parse(Float64, v["price"]) * parse(Float64, v["origQty"])
                                    end
                                    for (k,v) in co[id_str]
                                        if v["side"] == "BUY"
                                            invested += parse(Float64, v["cummulativeQuoteQty"])
                                        else
                                            invested -= parse(Float64, v["cummulativeQuoteQty"])
                                        end
                                    end
                                end
                            elseif length(oo["SELL"]) > 0 && length(oo["BUY"]) == 0
                                #
                                ## Case: Bot is fully invested!
                                for (k,v) in oo["SELL"]
                                    id_str = split(k, "_")[end]
                                    for (k,v) in co[id_str]
                                        if v["side"] == "BUY"
                                            invested += parse(Float64, v["cummulativeQuoteQty"])
                                        else
                                            invested -= parse(Float64, v["cummulativeQuoteQty"])
                                        end
                                    end
                                end
                            end

                            for (k,v) in tmp_config["volume"]
                                total_vol += v
                            end
                            total_vol += tmp_config["thes_vol"]
                        end
                    end
                end
                balances[bal["asset"]]["pinguu_locked"]   = locked
                balances[bal["asset"]]["pinguu_locked_stopped"] = locked_stopped
                balances[bal["asset"]]["pinguu_invested"] = invested
                balances[bal["asset"]]["pinguu_volume"]   = total_vol
                #
                ## Check Pinguu Invest
                user_invest = glob(joinpath(Pinguu.base_dir, "invest/users/$user_id/filled/*/*/" * bal["asset"] * "*.json"))
                user_invest_sold = glob(joinpath(Pinguu.base_dir, "invest/users/$user_id/sold/*/*/" * bal["asset"] * "*.json"))
                balances[bal["asset"]]["invest"] = 0
                for inv in user_invest
                    tmp = JSON.parsefile(inv)
                    balances[bal["asset"]]["invest"] += parse(Float64, tmp["amount"])
                end
                for inv in user_invest_sold
                    tmp = JSON.parsefile(inv)
                    balances[bal["asset"]]["invest"] -= parse(Float64, tmp["amount"])
                end
                balances[bal["asset"]]["estimated_free"]  = balances[bal["asset"]]["free"] - balances[bal["asset"]]["invest"] - locked_stopped - (total_vol - invested - locked - locked_stopped)
            end
        end

        if haskey(BNB_settings, k)
            # Check if user is registered and uses BNB Control
            if BNB_settings[k]["active"] == 1
                # Get current BNB value
                current = parse(Float64, client.get_ticker(symbol="BNBEUR")["lastPrice"])
                # Calculate amount of EUR that should be kept aside for next BNB purchase
                keep_aside = BNB_settings[k]["val"] + BNB_settings[k]["low_limit"] - balances["BNB"]["estimated_free"]*current
                if keep_aside > 0
                    balances["EUR"]["estimated_free"] -= keep_aside
                end
            end
        end

        #
        ## Grid Bots
        grid_config_paths = glob(joinpath(Pinguu.base_dir, "grid_bots/user/$user_id/*/config.json"))
        grid_volume, grid_locked, grid_invested, grid_sub_from_free = 0, 0, 0, 0
        for grid_config_path in grid_config_paths
            tmp = JSON.parsefile(grid_config_path)
            if !(tmp["status"] in ["finished", "prep"]) && tmp["testing"] == "0"
#                 total_value = typeof(tmp["total_value"]) == Float64 ? tmp["total_value"] : parse(Float64, tmp["total_value"])
                total_value = Pinguu.get_current_volume(tmp)
                executedQty = typeof(tmp["init_state"]["executedQty"]) == Float64 ? tmp["init_state"]["executedQty"] : parse(Float64, tmp["init_state"]["executedQty"])
                if typeof(tmp["init_state"]["sold"]) != Int64
                    sold = typeof(tmp["init_state"]["sold"]) == Float64 ? tmp["init_state"]["sold"] : parse(Float64, tmp["init_state"]["sold"])
                else
                    sold = float(tmp["init_state"]["sold"])
                end
                cummulativeQuoteQty = typeof(tmp["init_state"]["cummulativeQuoteQty"]) == Float64 ? tmp["init_state"]["cummulativeQuoteQty"] : parse(Float64, tmp["init_state"]["cummulativeQuoteQty"])
                trade_grid = JSON.parsefile(joinpath(dirname(grid_config_path), "trade_grid.json"))
                invested, locked = 0, 0
                for (price, gridpoint) in trade_grid
                    if haskey(gridpoint, "buy_order")
                        tmp_val = gridpoint["buy_order"]["cummulativeQuoteQty"]
                        invested += typeof(tmp_val) == Float64 ? tmp_val : parse(Float64, tmp_val)
                    end
                    if haskey(gridpoint, "side")
                        if gridpoint["side"] == client.SIDE_BUY
                            if haskey(gridpoint, "order")
                                tmp_price = typeof(gridpoint["order"]["price"]) == Float64 ? gridpoint["order"]["price"] : parse(Float64, gridpoint["order"]["price"])
                                tmp_qty   = typeof(gridpoint["order"]["origQty"]) == Float64 ? gridpoint["order"]["origQty"] : parse(Float64, gridpoint["order"]["origQty"])
                                locked += tmp_price * tmp_qty
                            end
                        end
                    end
                end
                sub_from_free = total_value - cummulativeQuoteQty * (1 - sold/executedQty) - invested - locked
                grid_volume += total_value
                grid_locked += locked
                grid_invested += invested
                grid_sub_from_free += sub_from_free
            end
        end
        balances["EUR"]["estimated_free"] -= grid_sub_from_free
        balances["EUR"]["grid_volume"] = grid_volume
        balances["EUR"]["grid_locked"] = grid_locked
        balances["EUR"]["grid_invested"] = grid_invested

        balances_all[k] = balances
    end
    open(joinpath(Pinguu.base_dir, "account_balances.json"), "w") do f
        JSON.print(f, balances_all, 4)
    end
    return balances_all
end
export check_balances


text:
# out initialized to one(Tropical)
@i function i_gemm!(out!::AbstractMatrix{T}, x::AbstractMatrix{T}, y::AbstractMatrix{T}) where T<:Tropical
	@safe size(x, 2) == size(y, 1) || throw(DimensionMismatch())
	@invcheckoff branch_keeper ← zeros(Bool, size(x,2))
	@invcheckoff for j=1:size(y,2)
		for i=1:size(x,1)
			el ← zero(T)
			@routine @inbounds for k=1:size(x,2)
				x[i,k] *= y[k,j]
				if (el.n < x[i,k].n, branch_keeper[k])
					FLIP(branch_keeper[k])
					NiLang.SWAP(el, x[i,k])
				end
			end
			@inbounds out![i,j] *= el
			~@routine
			el → zero(T)
		end
	end
	@invcheckoff branch_keeper → zeros(Bool, size(x,2))
end

@i function i_gemv!(out!::AbstractVector{T}, x::AbstractMatrix{T}, y::AbstractVector{T}, branch_keeper::AbstractVector{Bool}) where T<:Tropical
	@safe size(x, 2) == size(y, 1) || throw(DimensionMismatch())
	@invcheckoff for i=1:size(x,1)
		el ← zero(T)
		@routine @inbounds for k=1:size(x,2)
			x[i,k] *= y[k]
			if (el.n < x[i,k].n, branch_keeper[k])
				FLIP(branch_keeper[k])
				NiLang.SWAP(el, x[i,k])
			end
		end
		@inbounds out![i] *= el
		~@routine
		el → zero(T)
	end
end

@i function i_sum(out!::T, v::AbstractArray{T}) where T<:Tropical
	@routine @invcheckoff begin
		branch_keeper ← zeros(Bool, length(v))
		anc ← zero(T)
		for i = 1:length(v)
			@inbounds if (anc.n < v[i].n, branch_keeper[i])
				FLIP(branch_keeper[i])
				NiLang.SWAP(anc, v[i])
			end
		end
	end
	out!.n += anc.n
	~@routine
end


text:
module TestClient
using AlphaVantage
using Test

client = AlphaVantage.GLOBAL[]
@testset "Client" begin
    client = AlphaVantageClient(scheme="http", key="demo", host="www.alphavantage.co")
    @test isa(client, AlphaVantageClient)
    @test !isempty(client.scheme)
    @test !isempty(client.key)
    @test !isempty(client.host)
    old_key = client.key;
    client.key = "demo"
    @test client.key === "demo"
    client.key = ""
    @test_logs (:warn, "No API key found") key(client)
    client.key = old_key;
end

end


text:
#=
show_lar:
- Julia version:
- Author: miros
- Date: 2019-01-16
=#
include("../src/LarSurf.jl")


text:
export dataLayer

type dataLayer<:Layer
  bottom ::Array{Layer}
  top    ::Array{Layer}
  topRange ::Vector

  rₒ     ::AFArray{Float32}  #data
  rᵢ     ::AFArray{Float32} #objective

  lock   ::Bool
end
dataLayer(inDim::Int,outDim::Int)=
  dataLayer(Layer[],Layer[],Vector{AFArray}(),rand(AFArray{Float32},outDim,batchsize),rand(AFArray{Float32},inDim,batchsize),false)


text:
include("MeshData.jl")

function createMesh(this::Model, id::Symbol)
	if !haskey(this.meshes, id)
		mesh = MeshManager.Mesh()
		this.meshes[id]=mesh
	else
		mesh=this.meshes[id]
	end
	mesh
end

function addMesh(this::Model, typ::Symbol)
	mesh=createMesh(this, :DEFAULT)
	if MeshManager.isEmpty(mesh) update(mesh, typ) end
	upload(this)
	mesh
end

function update(mesh::Mesh, typ)
	if typ == :PLANE
		MeshManager.update(mesh, typ, MeshDataPlaneQuad)
	elseif typ == :CUBE
		MeshManager.update(mesh, typ, MeshDataCube, 5)
	elseif typ == :IPLANE
		MeshManager.update(mesh, typ, ()->createPlaneData(5,5))
	elseif typ == :ICOSPHERE
		MeshManager.update(mesh, typ, ()->createTetrahedronSphere(1))
	end
end

function prepare(this::Model, mesh::Mesh)
	vlen=length(mesh.vertices.data.values)
	uvlen=length(mesh.uvs.data.values)
	nlen=length(mesh.normals.data.values)
	ilen=length(mesh.indicies.data.values)

	if vlen>0 || uvlen>0 || nlen>0
		va=StorageManager.create(this.group, :VA, :BLOCK, :ARRAY_BLOCK)
		vao=StorageManager.create(va, :VAO, :STORAGE, :ARRAY)

		vb=StorageManager.create(this.group, :VB, :BLOCK, :BUFFER_BLOCK)
		vbo=StorageManager.create(vb, :VBO, :STORAGE, :ARRAY_BUFFER)

		if vlen>0	StorageManager.link(vbo, mesh.vertices, :VERTEX) end
		if uvlen>0 StorageManager.link(vbo, mesh.uvs, :UV) end
		if nlen>0	StorageManager.link(vbo, mesh.normals, :NORMAL)	end
		if ilen==0 setMainBuffer(this,vbo) end
	end

	if ilen>0
		ib=StorageManager.create(this.group, :IB, :BLOCK, :INDEX_BLOCK)
		ibo=StorageManager.create(ib, :IBO, :STORAGE, :ELEMENT_ARRAY_BUFFER)
		StorageManager.link(ibo, mesh.indicies, :INDEX)
		setMainBuffer(this,ibo)
	end
end

max_stars_repo_name: Gilga/JGE

max_issues_repo_name: Gilga/JGE

max_forks_repo_name: Gilga/JGE


text:
doc"""
    set_up_abcmcmc_problem(use_sim_data::Bool=true;nbr_of_unknown_parameters::Int64=2,
  prior_dist::String = "Uniform", adaptiveon::Bool = true, ploton::Bool = false,
  nbr_of_cores::Int64 = 8, T::Int64 = 50,x0::Float64 = 7.,
  print_interval::Int64 = 500, alg::String="abcmcmc", nbr_summary_stats::Int64=5,
  eps::Float64=0.1)

set_up_abcmcmc_problem sets the parameters for the problem defined by the inputs.

# Output
* `problem`: An instance of the type ProblemABCMCMC cointaining all information for the problem
"""
function set_up_abcmcmc_problem(use_sim_data::Bool=true;nbr_of_unknown_parameters::Int64=2,
  prior_dist::String = "Uniform", adaptiveon::Bool = true, ploton::Bool = false,
  nbr_of_cores::Int64 = 8, T::Int64 = 200,x0::Float64 = 7.,
  print_interval::Int64 = 500, alg::String="abcmcmc", nbr_summary_stats::Int64=5,
  start::String = "random")

  # todo: set parameters and objects

  # set algorithm parameters
  theta_true = log([44.7; 10; 0.3]) # r φ σ
  theta_0 =  log([3;3;0.5])
  #prior_parameters = [0 5; 0 4;-10 2]
  prior_parameters = [2 5; 1.61 3;-3 -0.22]
  theta_known = [NaN]

  eps = [30*ones(10000);20*ones(10000);10*ones(10000); 5*ones(10000); 3*ones(10000); 2*ones(10000); 1.5*ones(500000)]

  # create instance of AlgorithmParametersABCMCMC (set parameters to default values)
  alg_param = AlgorithmParametersABCMCMC(length(eps),length(eps)-200000,alg,print_interval,nbr_summary_stats,eps,start, ones(nbr_summary_stats)) # hardcoded nbr of summary stats)

  # create instance of ModelParameters, all theta paramters are on log-scale
  model_param = ModelParameters(theta_true,theta_known,theta_0)

  # set data
  if use_sim_data
    y = generate_data_ricker_model(T, exp(theta_true[1]), exp(theta_true[2]), exp(theta_true[3]), x0, ploton)
  end

  # create instance of Data
  data = Data(y)

  # create instance of PriorDistribution
  prior = PriorDistribution(prior_dist, prior_parameters)

  # create instance of AdaptiveUpdate
  adaptive_update = AMUpdate(eye(length(theta_0)), 2.4/sqrt(length(theta_0)), 1., 0.7, 50)

  # return the an instance of Problem
  return ProblemABCMCMC(data, alg_param, model_param, adaptive_update, prior, "log", "uniform", "original")

end

function generate_data_ricker_model(T::Int64,r::Float64,phi::Float64,sigma::Float64,x0::Float64=7.0,ploton::Bool=false)

  e = rand(Normal(0,sigma),T)
  y = zeros(T)
  x = zeros(T)

  # first iteration
  x[1] = r*x0*exp(-x0+e[1])

  y[1] = rand(Poisson(phi*x[1]))

  @simd for t = 2:T
    @inbounds x[t] = r*x[t-1]*exp(-x[t-1]+e[t])
    y[t] = rand(Poisson(phi*x[t]))
  end

  return y
end

function sample_from_prior(prior_dist::PriorDistribution)

  theta_hat = zeros(size(prior_dist.prior_parameters,1))
  if prior_dist.dist == "Uniform"
    for i = 1:size(prior_dist.prior_parameters,1)
      theta_hat[i] = rand(Uniform(prior_dist.prior_parameters[i,1], prior_dist.prior_parameters[i,2]))
    end
  end

  return theta_hat

end

function  evaluate_prior(theta_star::Vector, prior_parameters::Matrix, dist_type::String = "Uniform")

  # set start value for loglik
  log_likelihood = 0.

  if dist_type == "Uniform"
    for i = 1:length(theta_star)
      # Update loglik, i.e. add the loglik for each model paramter in theta
      log_likelihood = log_likelihood + log_unifpdf( theta_star[i], prior_parameters[i,1], prior_parameters[i,2] )
    end
  else
    # add other priors
  end

  return log_likelihood # return log_lik

end

function generate_data(N::Int64, theta_star::Vector, theta_known::Vector)
  return generate_data_ricker_model(N,exp(theta_star[1]),exp(theta_star[2]),exp(theta_star[3]))
end

function calc_summary(y::Vector)

  s1 = autocor(y,[5],demean=false)[1]

  s2 = log(var(y))

  s3 = mean(y)

  s5 = length(find(x -> x == 0 ,y))

  s6 = maximum(y)

  return [s1;s2;s3;s5;s6]
#  return [s1;s2;s5;s6]

end

max_stars_repo_path: Ricker model/rickermodel_abc.jl

max_stars_repo_name: SamuelWiqvist/abcmcmcrickermodel

max_issues_repo_path: Ricker model/rickermodel_abc.jl

max_issues_repo_name: SamuelWiqvist/abcmcmcrickermodel

max_forks_repo_path: Ricker model/rickermodel_abc.jl

max_forks_repo_name: SamuelWiqvist/abcmcmcrickermodel


text:
# This file is a part of Julia. License is MIT: https://julialang.org/license

module MPFR

export
    BigFloat,
    setprecision

import
    .Base: *, +, -, /, <, <=, ==, >, >=, ^, ceil, cmp, convert, copysign, div,
        inv, exp, exp2, exponent, factorial, floor, fma, hypot, isinteger,
        isfinite, isinf, isnan, ldexp, log, log2, log10, max, min, mod, modf,
        nextfloat, prevfloat, promote_rule, rem, rem2pi, round, show, float,
        sum, sqrt, string, print, trunc, precision, exp10, expm1,
        log1p,
        eps, signbit, sign, sin, cos, sincos, tan, sec, csc, cot, acos, asin, atan,
        cosh, sinh, tanh, sech, csch, coth, acosh, asinh, atanh,
        cbrt, typemax, typemin, unsafe_trunc, floatmin, floatmax, rounding,
        setrounding, maxintfloat, widen, significand, frexp, tryparse, iszero,
        isone, big, _string_n

import .Base.Rounding: rounding_raw, setrounding_raw

import .Base.GMP: ClongMax, CulongMax, CdoubleMax, Limb

import .Base.FastMath.sincos_fast

version() = VersionNumber(unsafe_string(ccall((:mpfr_get_version,:libmpfr), Ptr{Cchar}, ())))
patches() = split(unsafe_string(ccall((:mpfr_get_patches,:libmpfr), Ptr{Cchar}, ())),' ')

function __init__()
    try
        # set exponent to full range by default
        set_emin!(get_emin_min())
        set_emax!(get_emax_max())
    catch ex
        Base.showerror_nostdio(ex, "WARNING: Error during initialization of module MPFR")
    end
    nothing
end

"""
    MPFR.MPFRRoundingMode

Matches the `mpfr_rnd_t` enum provided by MPFR, see
https://www.mpfr.org/mpfr-current/mpfr.html#Rounding-Modes

This is for internal use, and ensures that `ROUNDING_MODE[]` is type-stable.
"""
@enum MPFRRoundingMode begin
    MPFRRoundNearest
    MPFRRoundToZero
    MPFRRoundUp
    MPFRRoundDown
    MPFRRoundFromZero
    MPFRRoundFaithful
end

convert(::Type{MPFRRoundingMode}, ::RoundingMode{:Nearest})  = MPFRRoundNearest
convert(::Type{MPFRRoundingMode}, ::RoundingMode{:ToZero})   = MPFRRoundToZero
convert(::Type{MPFRRoundingMode}, ::RoundingMode{:Up})       = MPFRRoundUp
convert(::Type{MPFRRoundingMode}, ::RoundingMode{:Down})     = MPFRRoundDown
convert(::Type{MPFRRoundingMode}, ::RoundingMode{:FromZero}) = MPFRRoundFromZero

function convert(::Type{RoundingMode}, r::MPFRRoundingMode)
    if r == MPFRRoundNearest
        return RoundNearest
    elseif r == MPFRRoundToZero
        return RoundToZero
    elseif r == MPFRRoundUp
        return RoundUp
    elseif r == MPFRRoundDown
        return RoundDown
    elseif r == MPFRRoundFromZero
        return RoundFromZero
    else
        throw(ArgumentError("invalid MPFR rounding mode code: $r"))
    end
end

const ROUNDING_MODE = Ref{MPFRRoundingMode}(MPFRRoundNearest)
const DEFAULT_PRECISION = Ref{Clong}(256)

# Basic type and initialization definitions

"""
    BigFloat <: AbstractFloat

Arbitrary precision floating point number type.
"""
mutable struct BigFloat <: AbstractFloat
    prec::Clong
    sign::Cint
    exp::Clong
    d::Ptr{Limb}
    # _d::Buffer{Limb} # Julia gc handle for memory @ d
    _d::String # Julia gc handle for memory @ d (optimized)

    # Not recommended for general use:
    # used internally by, e.g. deepcopy
    global function _BigFloat(prec::Clong, sign::Cint, exp::Clong, d::String)
        # ccall-based version, inlined below
        #z = new(zero(Clong), zero(Cint), zero(Clong), C_NULL, d)
        #ccall((:mpfr_custom_init,:libmpfr), Cvoid, (Ptr{Limb}, Clong), d, prec) # currently seems to be a no-op in mpfr
        #NAN_KIND = Cint(0)
        #ccall((:mpfr_custom_init_set,:libmpfr), Cvoid, (Ref{BigFloat}, Cint, Clong, Ptr{Limb}), z, NAN_KIND, prec, d)
        #return z
        return new(prec, sign, exp, pointer(d), d)
    end

    function BigFloat(; precision::Integer=DEFAULT_PRECISION[])
        precision < 1 && throw(DomainError(precision, "`precision` cannot be less than 1."))
        nb = ccall((:mpfr_custom_get_size,:libmpfr), Csize_t, (Clong,), precision)
        nb = (nb + Core.sizeof(Limb) - 1) ÷ Core.sizeof(Limb) # align to number of Limb allocations required for this
        #d = Vector{Limb}(undef, nb)
        d = _string_n(nb * Core.sizeof(Limb))
        EXP_NAN = Clong(1) - Clong(typemax(Culong) >> 1)
        return _BigFloat(Clong(precision), one(Cint), EXP_NAN, d) # +NAN
    end
end

rounding_raw(::Type{BigFloat}) = ROUNDING_MODE[]
setrounding_raw(::Type{BigFloat}, r::MPFRRoundingMode) = ROUNDING_MODE[]=r

rounding(::Type{BigFloat}) = convert(RoundingMode, rounding_raw(BigFloat))
setrounding(::Type{BigFloat}, r::RoundingMode) = setrounding_raw(BigFloat, convert(MPFRRoundingMode, r))

# overload the definition of unsafe_convert to ensure that `x.d` is assigned
# it may have been dropped in the event that the BigFloat was serialized
Base.unsafe_convert(::Type{Ref{BigFloat}}, x::Ptr{BigFloat}) = x
@inline function Base.unsafe_convert(::Type{Ref{BigFloat}}, x::Ref{BigFloat})
    x = x[]
    if x.d == C_NULL
        x.d = pointer(x._d)
    end
    return convert(Ptr{BigFloat}, Base.pointer_from_objref(x))
end

"""
    BigFloat(x::Union{Real, AbstractString} [, rounding::RoundingMode=rounding(BigFloat)]; [precision::Integer=precision(BigFloat)])

Create an arbitrary precision floating point number from `x`, with precision
`precision`. The `rounding` argument specifies the direction in which the result should be
rounded if the conversion cannot be done exactly. If not provided, these are set by the current global values.

`BigFloat(x::Real)` is the same as `convert(BigFloat,x)`, except if `x` itself is already
`BigFloat`, in which case it will return a value with the precision set to the current
global precision; `convert` will always return `x`.

`BigFloat(x::AbstractString)` is identical to [`parse`](@ref). This is provided for
convenience since decimal literals are converted to `Float64` when parsed, so
`BigFloat(2.1)` may not yield what you expect.

!!! compat "Julia 1.1"
    `precision` as a keyword argument requires at least Julia 1.1.
    In Julia 1.0 `precision` is the second positional argument (`BigFloat(x, precision)`).

# Examples
```jldoctest
julia> BigFloat(2.1) # 2.1 here is a Float64
2.100000000000000088817841970012523233890533447265625

julia> BigFloat("2.1") # the closest BigFloat to 2.1
2.099999999999999999999999999999999999999999999999999999999999999999999999999986

julia> BigFloat("2.1", RoundUp)
2.100000000000000000000000000000000000000000000000000000000000000000000000000021

julia> BigFloat("2.1", RoundUp, precision=128)
2.100000000000000000000000000000000000007
```

# See also
- [`@big_str`](@ref)
- [`rounding`](@ref) and [`setrounding`](@ref)
- [`precision`](@ref) and [`setprecision`](@ref)
"""
BigFloat(x, r::RoundingMode)

widen(::Type{Float64}) = BigFloat
widen(::Type{BigFloat}) = BigFloat

function BigFloat(x::BigFloat, r::MPFRRoundingMode=ROUNDING_MODE[]; precision::Integer=DEFAULT_PRECISION[])
    if precision == MPFR.precision(x)
        return x
    else
        z = BigFloat(;precision=precision)
        ccall((:mpfr_set, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode),
              z, x, r)
        return z
    end
end

function _duplicate(x::BigFloat)
    z = BigFloat(;precision=precision(x))
    ccall((:mpfr_set, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Int32), z, x, 0)
    return z
end

# convert to BigFloat
for (fJ, fC) in ((:si,:Clong), (:ui,:Culong))
    @eval begin
        function BigFloat(x::($fC), r::MPFRRoundingMode=ROUNDING_MODE[]; precision::Integer=DEFAULT_PRECISION[])
            z = BigFloat(;precision=precision)
            ccall(($(string(:mpfr_set_,fJ)), :libmpfr), Int32, (Ref{BigFloat}, $fC, MPFRRoundingMode), z, x, r)
            return z
        end
    end
end

function BigFloat(x::Float64, r::MPFRRoundingMode=ROUNDING_MODE[]; precision::Integer=DEFAULT_PRECISION[])
    z = BigFloat(;precision=precision)
    ccall((:mpfr_set_d, :libmpfr), Int32, (Ref{BigFloat}, Float64, MPFRRoundingMode), z, x, r)
    if isnan(x) && signbit(x) != signbit(z)
        z.sign = -z.sign
    end
    return z
end

function BigFloat(x::BigInt, r::MPFRRoundingMode=ROUNDING_MODE[]; precision::Integer=DEFAULT_PRECISION[])
    z = BigFloat(;precision=precision)
    ccall((:mpfr_set_z, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigInt}, MPFRRoundingMode), z, x, r)
    return z
end

BigFloat(x::Integer, r::MPFRRoundingMode=ROUNDING_MODE[]; precision::Integer=DEFAULT_PRECISION[]) =
    BigFloat(BigInt(x), r; precision=precision)

BigFloat(x::Union{Bool,Int8,Int16,Int32}, r::MPFRRoundingMode=ROUNDING_MODE[]; precision::Integer=DEFAULT_PRECISION[]) =
    BigFloat(convert(Clong, x), r; precision=precision)
BigFloat(x::Union{UInt8,UInt16,UInt32}, r::MPFRRoundingMode=ROUNDING_MODE[]; precision::Integer=DEFAULT_PRECISION[]) =
    BigFloat(convert(Culong, x), r; precision=precision)

BigFloat(x::Union{Float16,Float32}, r::MPFRRoundingMode=ROUNDING_MODE[]; precision::Integer=DEFAULT_PRECISION[]) =
    BigFloat(Float64(x), r; precision=precision)

function BigFloat(x::Rational, r::MPFRRoundingMode=ROUNDING_MODE[]; precision::Integer=DEFAULT_PRECISION[])
    setprecision(BigFloat, precision) do
        setrounding_raw(BigFloat, r) do
            BigFloat(numerator(x)) / BigFloat(denominator(x))
        end
    end
end

function tryparse(::Type{BigFloat}, s::AbstractString; base::Integer=0, precision::Integer=DEFAULT_PRECISION[], rounding::MPFRRoundingMode=ROUNDING_MODE[])
    !isempty(s) && isspace(s[end]) && return tryparse(BigFloat, rstrip(s), base = base)
    z = BigFloat(precision=precision)
    err = ccall((:mpfr_set_str, :libmpfr), Int32, (Ref{BigFloat}, Cstring, Int32, MPFRRoundingMode), z, s, base, rounding)
    err == 0 ? z : nothing
end

BigFloat(x::AbstractString, r::MPFRRoundingMode=ROUNDING_MODE[]; precision::Integer=DEFAULT_PRECISION[]) =
    parse(BigFloat, x; precision=precision, rounding=r)

Rational(x::BigFloat) = convert(Rational{BigInt}, x)
AbstractFloat(x::BigInt) = BigFloat(x)

float(::Type{BigInt}) = BigFloat

BigFloat(x::Real, r::RoundingMode; precision::Integer=DEFAULT_PRECISION[]) =
    BigFloat(x, convert(MPFRRoundingMode, r); precision=precision)
BigFloat(x::AbstractString, r::RoundingMode; precision::Integer=DEFAULT_PRECISION[]) =
    BigFloat(x, convert(MPFRRoundingMode, r); precision=precision)

## BigFloat -> Integer
_unchecked_cast(T, x::BigFloat, r::RoundingMode) = _unchecked_cast(T, x, convert(MPFRRoundingMode, r))

function _unchecked_cast(::Type{Int64}, x::BigFloat, r::MPFRRoundingMode)
    ccall((:__gmpfr_mpfr_get_sj,:libmpfr), Cintmax_t, (Ref{BigFloat}, MPFRRoundingMode), x, r)
end

function _unchecked_cast(::Type{UInt64}, x::BigFloat, r::MPFRRoundingMode)
    ccall((:__gmpfr_mpfr_get_uj,:libmpfr), Cuintmax_t, (Ref{BigFloat}, MPFRRoundingMode), x, r)
end

function _unchecked_cast(::Type{BigInt}, x::BigFloat, r::MPFRRoundingMode)
    z = BigInt()
    ccall((:mpfr_get_z, :libmpfr), Int32, (Ref{BigInt}, Ref{BigFloat}, MPFRRoundingMode), z, x, r)
    return z
end

function _unchecked_cast(::Type{T}, x::BigFloat, r::MPFRRoundingMode) where T<:Union{Signed, Unsigned}
    CT = T <: Signed ? Int64 : UInt64
    typemax(T) < typemax(CT) ? _unchecked_cast(CT, x, r) : _unchecked_cast(BigInt, x, r)
end

function round(::Type{T}, x::BigFloat, r::Union{RoundingMode, MPFRRoundingMode}) where T<:Union{Signed, Unsigned}
    clear_flags()
    res = _unchecked_cast(T, x, r)
    if had_range_exception() || !(typemin(T) <= res <= typemax(T))
        throw(InexactError(:round, T, x))
    end
    return unsafe_trunc(T, res)
end
round(::Type{BigInt}, x::BigFloat, r::Union{RoundingMode, MPFRRoundingMode}) = _unchecked_cast(BigInt, x, r)
round(::Type{T}, x::BigFloat, r::RoundingMode) where T<:Union{Signed, Unsigned} =
    invoke(round, Tuple{Type{<:Union{Signed, Unsigned}}, BigFloat, Union{RoundingMode, MPFRRoundingMode}}, T, x, r)
round(::Type{BigInt}, x::BigFloat, r::RoundingMode) =
    invoke(round, Tuple{Type{BigInt}, BigFloat, Union{RoundingMode, MPFRRoundingMode}}, BigInt, x, r)
round(::Type{<:Integer}, x::BigFloat, r::RoundingMode) = throw(MethodError(round, (Integer, x, r)))

unsafe_trunc(::Type{T}, x::BigFloat) where {T<:Integer} = unsafe_trunc(T, _unchecked_cast(T, x, RoundToZero))
unsafe_trunc(::Type{BigInt}, x::BigFloat) = _unchecked_cast(BigInt, x, RoundToZero)

# TODO: Ideally the base fallbacks for these would already exist
for (f, rnd) in zip((:trunc, :floor, :ceil, :round),
                 (RoundToZero, RoundDown, RoundUp, :(ROUNDING_MODE[])))
    @eval $f(::Type{T}, x::BigFloat) where T<:Union{Unsigned, Signed, BigInt} = round(T, x, $rnd)
    @eval $f(::Type{Integer}, x::BigFloat) = $f(BigInt, x)
end

function Bool(x::BigFloat)
    iszero(x) && return false
    isone(x) && return true
    throw(InexactError(:Bool, Bool, x))
end
function BigInt(x::BigFloat)
    isinteger(x) || throw(InexactError(:BigInt, BigInt, x))
    trunc(BigInt, x)
end

function (::Type{T})(x::BigFloat) where T<:Integer
    isinteger(x) || throw(InexactError(nameof(T), T, x))
    trunc(T,x)
end

## BigFloat -> AbstractFloat
_cpynansgn(x::AbstractFloat, y::BigFloat) = isnan(x) && signbit(x) != signbit(y) ? -x : x

Float64(x::BigFloat, r::MPFRRoundingMode=ROUNDING_MODE[]) =
    _cpynansgn(ccall((:mpfr_get_d,:libmpfr), Float64, (Ref{BigFloat}, MPFRRoundingMode), x, r), x)
Float64(x::BigFloat, r::RoundingMode) = Float64(x, convert(MPFRRoundingMode, r))

Float32(x::BigFloat, r::MPFRRoundingMode=ROUNDING_MODE[]) =
    _cpynansgn(ccall((:mpfr_get_flt,:libmpfr), Float32, (Ref{BigFloat}, MPFRRoundingMode), x, r), x)
Float32(x::BigFloat, r::RoundingMode) = Float32(x, convert(MPFRRoundingMode, r))

# TODO: avoid double rounding
Float16(x::BigFloat) = Float16(Float32(x))

promote_rule(::Type{BigFloat}, ::Type{<:Real}) = BigFloat
promote_rule(::Type{BigInt}, ::Type{<:AbstractFloat}) = BigFloat
promote_rule(::Type{BigFloat}, ::Type{<:AbstractFloat}) = BigFloat

big(::Type{<:AbstractFloat}) = BigFloat

big(x::AbstractFloat) = convert(BigFloat, x)

function (::Type{Rational{BigInt}})(x::AbstractFloat)
    isnan(x) && return zero(BigInt) // zero(BigInt)
    isinf(x) && return copysign(one(BigInt),x) // zero(BigInt)
    iszero(x) && return zero(BigInt) // one(BigInt)
    s = max(precision(x) - exponent(x), 0)
    BigInt(ldexp(x,s)) // (BigInt(1) << s)
end

# Basic arithmetic without promotion
for (fJ, fC) in ((:+,:add), (:*,:mul))
    @eval begin
        # BigFloat
        function ($fJ)(x::BigFloat, y::BigFloat)
            z = BigFloat()
            ccall(($(string(:mpfr_,fC)),:libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, x, y, ROUNDING_MODE[])
            return z
        end

        # Unsigned Integer
        function ($fJ)(x::BigFloat, c::CulongMax)
            z = BigFloat()
            ccall(($(string(:mpfr_,fC,:_ui)), :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Culong, MPFRRoundingMode), z, x, c, ROUNDING_MODE[])
            return z
        end
        ($fJ)(c::CulongMax, x::BigFloat) = ($fJ)(x,c)

        # Signed Integer
        function ($fJ)(x::BigFloat, c::ClongMax)
            z = BigFloat()
            ccall(($(string(:mpfr_,fC,:_si)), :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Clong, MPFRRoundingMode), z, x, c, ROUNDING_MODE[])
            return z
        end
        ($fJ)(c::ClongMax, x::BigFloat) = ($fJ)(x,c)

        # Float32/Float64
        function ($fJ)(x::BigFloat, c::CdoubleMax)
            z = BigFloat()
            ccall(($(string(:mpfr_,fC,:_d)), :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Cdouble, MPFRRoundingMode), z, x, c, ROUNDING_MODE[])
            return z
        end
        ($fJ)(c::CdoubleMax, x::BigFloat) = ($fJ)(x,c)

        # BigInt
        function ($fJ)(x::BigFloat, c::BigInt)
            z = BigFloat()
            ccall(($(string(:mpfr_,fC,:_z)), :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigInt}, MPFRRoundingMode), z, x, c, ROUNDING_MODE[])
            return z
        end
        ($fJ)(c::BigInt, x::BigFloat) = ($fJ)(x,c)
    end
end

for (fJ, fC) in ((:-,:sub), (:/,:div))
    @eval begin
        # BigFloat
        function ($fJ)(x::BigFloat, y::BigFloat)
            z = BigFloat()
            ccall(($(string(:mpfr_,fC)),:libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, x, y, ROUNDING_MODE[])
            return z
        end

        # Unsigned Int
        function ($fJ)(x::BigFloat, c::CulongMax)
            z = BigFloat()
            ccall(($(string(:mpfr_,fC,:_ui)), :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Culong, MPFRRoundingMode), z, x, c, ROUNDING_MODE[])
            return z
        end
        function ($fJ)(c::CulongMax, x::BigFloat)
            z = BigFloat()
            ccall(($(string(:mpfr_,:ui_,fC)), :libmpfr), Int32, (Ref{BigFloat}, Culong, Ref{BigFloat}, MPFRRoundingMode), z, c, x, ROUNDING_MODE[])
            return z
        end

        # Signed Integer
        function ($fJ)(x::BigFloat, c::ClongMax)
            z = BigFloat()
            ccall(($(string(:mpfr_,fC,:_si)), :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Clong, MPFRRoundingMode), z, x, c, ROUNDING_MODE[])
            return z
        end
        function ($fJ)(c::ClongMax, x::BigFloat)
            z = BigFloat()
            ccall(($(string(:mpfr_,:si_,fC)), :libmpfr), Int32, (Ref{BigFloat}, Clong, Ref{BigFloat}, MPFRRoundingMode), z, c, x, ROUNDING_MODE[])
            return z
        end

        # Float32/Float64
        function ($fJ)(x::BigFloat, c::CdoubleMax)
            z = BigFloat()
            ccall(($(string(:mpfr_,fC,:_d)), :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Cdouble, MPFRRoundingMode), z, x, c, ROUNDING_MODE[])
            return z
        end
        function ($fJ)(c::CdoubleMax, x::BigFloat)
            z = BigFloat()
            ccall(($(string(:mpfr_,:d_,fC)), :libmpfr), Int32, (Ref{BigFloat}, Cdouble, Ref{BigFloat}, MPFRRoundingMode), z, c, x, ROUNDING_MODE[])
            return z
        end

        # BigInt
        function ($fJ)(x::BigFloat, c::BigInt)
            z = BigFloat()
            ccall(($(string(:mpfr_,fC,:_z)), :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigInt}, MPFRRoundingMode), z, x, c, ROUNDING_MODE[])
            return z
        end
        # no :mpfr_z_div function
    end
end

function -(c::BigInt, x::BigFloat)
    z = BigFloat()
    ccall((:mpfr_z_sub, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigInt}, Ref{BigFloat}, MPFRRoundingMode), z, c, x, ROUNDING_MODE[])
    return z
end

inv(x::BigFloat) = one(Clong) / x # faster than fallback one(x)/x

function fma(x::BigFloat, y::BigFloat, z::BigFloat)
    r = BigFloat()
    ccall(("mpfr_fma",:libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), r, x, y, z, ROUNDING_MODE[])
    return r
end

# div
# BigFloat
function div(x::BigFloat, y::BigFloat)
    z = BigFloat()
    ccall((:mpfr_div,:libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, x, y, RoundToZero)
    ccall((:mpfr_trunc, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}), z, z)
    return z
end

# Unsigned Int
function div(x::BigFloat, c::CulongMax)
    z = BigFloat()
    ccall((:mpfr_div_ui, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Culong, MPFRRoundingMode), z, x, c, RoundToZero)
    ccall((:mpfr_trunc, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}), z, z)
    return z
end
function div(c::CulongMax, x::BigFloat)
    z = BigFloat()
    ccall((:mpfr_ui_div, :libmpfr), Int32, (Ref{BigFloat}, Culong, Ref{BigFloat}, MPFRRoundingMode), z, c, x, RoundToZero)
    ccall((:mpfr_trunc, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}), z, z)
    return z
end

# Signed Integer
function div(x::BigFloat, c::ClongMax)
    z = BigFloat()
    ccall((:mpfr_div_si, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Clong, MPFRRoundingMode), z, x, c, RoundToZero)
    ccall((:mpfr_trunc, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}), z, z)
    return z
end
function div(c::ClongMax, x::BigFloat)
    z = BigFloat()
    ccall((:mpfr_si_div, :libmpfr), Int32, (Ref{BigFloat}, Clong, Ref{BigFloat}, MPFRRoundingMode), z, c, x, RoundToZero)
    ccall((:mpfr_trunc, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}), z, z)
    return z
end

# Float32/Float64
function div(x::BigFloat, c::CdoubleMax)
    z = BigFloat()
    ccall((:mpfr_div_d, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Cdouble, MPFRRoundingMode), z, x, c, RoundToZero)
    ccall((:mpfr_trunc, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}), z, z)
    return z
end
function div(c::CdoubleMax, x::BigFloat)
    z = BigFloat()
    ccall((:mpfr_d_div, :libmpfr), Int32, (Ref{BigFloat}, Cdouble, Ref{BigFloat}, MPFRRoundingMode), z, c, x, RoundToZero)
    ccall((:mpfr_trunc, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}), z, z)
    return z
end

# BigInt
function div(x::BigFloat, c::BigInt)
    z = BigFloat()
    ccall((:mpfr_div_z, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigInt}, MPFRRoundingMode), z, x, c, RoundToZero)
    ccall((:mpfr_trunc, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}), z, z)
    return z
end

# More efficient commutative operations
for (fJ, fC, fI) in ((:+, :add, 0), (:*, :mul, 1))
    @eval begin
        function ($fJ)(a::BigFloat, b::BigFloat, c::BigFloat)
            z = BigFloat()
            ccall(($(string(:mpfr_,fC)), :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, a, b, ROUNDING_MODE[])
            ccall(($(string(:mpfr_,fC)), :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, z, c, ROUNDING_MODE[])
            return z
        end
        function ($fJ)(a::BigFloat, b::BigFloat, c::BigFloat, d::BigFloat)
            z = BigFloat()
            ccall(($(string(:mpfr_,fC)), :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, a, b, ROUNDING_MODE[])
            ccall(($(string(:mpfr_,fC)), :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, z, c, ROUNDING_MODE[])
            ccall(($(string(:mpfr_,fC)), :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, z, d, ROUNDING_MODE[])
            return z
        end
        function ($fJ)(a::BigFloat, b::BigFloat, c::BigFloat, d::BigFloat, e::BigFloat)
            z = BigFloat()
            ccall(($(string(:mpfr_,fC)), :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, a, b, ROUNDING_MODE[])
            ccall(($(string(:mpfr_,fC)), :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, z, c, ROUNDING_MODE[])
            ccall(($(string(:mpfr_,fC)), :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, z, d, ROUNDING_MODE[])
            ccall(($(string(:mpfr_,fC)), :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, z, e, ROUNDING_MODE[])
            return z
        end
    end
end

function -(x::BigFloat)
    z = BigFloat()
    ccall((:mpfr_neg, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, x, ROUNDING_MODE[])
    return z
end

function sqrt(x::BigFloat)
    isnan(x) && return x
    z = BigFloat()
    ccall((:mpfr_sqrt, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, x, ROUNDING_MODE[])
    isnan(z) && throw(DomainError(x, "NaN result for non-NaN input."))
    return z
end

sqrt(x::BigInt) = sqrt(BigFloat(x))

function ^(x::BigFloat, y::BigFloat)
    z = BigFloat()
    ccall((:mpfr_pow, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, x, y, ROUNDING_MODE[])
    return z
end

function ^(x::BigFloat, y::CulongMax)
    z = BigFloat()
    ccall((:mpfr_pow_ui, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Culong, MPFRRoundingMode), z, x, y, ROUNDING_MODE[])
    return z
end

function ^(x::BigFloat, y::ClongMax)
    z = BigFloat()
    ccall((:mpfr_pow_si, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Clong, MPFRRoundingMode), z, x, y, ROUNDING_MODE[])
    return z
end

function ^(x::BigFloat, y::BigInt)
    z = BigFloat()
    ccall((:mpfr_pow_z, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigInt}, MPFRRoundingMode), z, x, y, ROUNDING_MODE[])
    return z
end

^(x::BigFloat, y::Integer)  = typemin(Clong)  <= y <= typemax(Clong)  ? x^Clong(y)  : x^BigInt(y)
^(x::BigFloat, y::Unsigned) = typemin(Culong) <= y <= typemax(Culong) ? x^Culong(y) : x^BigInt(y)

for f in (:exp, :exp2, :exp10, :expm1, :cosh, :sinh, :tanh, :sech, :csch, :coth, :cbrt)
    @eval function $f(x::BigFloat)
        z = BigFloat()
        ccall(($(string(:mpfr_,f)), :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, x, ROUNDING_MODE[])
        return z
    end
end

function sincos_fast(v::BigFloat)
    s = BigFloat()
    c = BigFloat()
    ccall((:mpfr_sin_cos, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), s, c, v, ROUNDING_MODE[])
    return (s, c)
end
sincos(v::BigFloat) = sincos_fast(v)

# return log(2)
function big_ln2()
    c = BigFloat()
    ccall((:mpfr_const_log2, :libmpfr), Cint, (Ref{BigFloat}, MPFRRoundingMode), c, MPFR.ROUNDING_MODE[])
    return c
end

function ldexp(x::BigFloat, n::Clong)
    z = BigFloat()
    ccall((:mpfr_mul_2si, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Clong, MPFRRoundingMode), z, x, n, ROUNDING_MODE[])
    return z
end
function ldexp(x::BigFloat, n::Culong)
    z = BigFloat()
    ccall((:mpfr_mul_2ui, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Culong, MPFRRoundingMode), z, x, n, ROUNDING_MODE[])
    return z
end
ldexp(x::BigFloat, n::ClongMax) = ldexp(x, convert(Clong, n))
ldexp(x::BigFloat, n::CulongMax) = ldexp(x, convert(Culong, n))
ldexp(x::BigFloat, n::Integer) = x * exp2(BigFloat(n))

function factorial(x::BigFloat)
    if x < 0 || !isinteger(x)
        throw(DomainError(x, "Must be a non-negative integer."))
    end
    ui = convert(Culong, x)
    z = BigFloat()
    ccall((:mpfr_fac_ui, :libmpfr), Int32, (Ref{BigFloat}, Culong, MPFRRoundingMode), z, ui, ROUNDING_MODE[])
    return z
end

function hypot(x::BigFloat, y::BigFloat)
    z = BigFloat()
    ccall((:mpfr_hypot, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, x, y, ROUNDING_MODE[])
    return z
end

for f in (:log, :log2, :log10)
    @eval function $f(x::BigFloat)
        if x < 0
            throw(DomainError(x, string($f, " will only return a complex result if called ",
                              "with a complex argument. Try ", $f, "(complex(x)).")))
        end
        z = BigFloat()
        ccall(($(string(:mpfr_,f)), :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, x, ROUNDING_MODE[])
        return z
    end
end

function log1p(x::BigFloat)
    if x < -1
        throw(DomainError(x, string("log1p will only return a complex result if called ",
                          "with a complex argument. Try log1p(complex(x)).")))
    end
    z = BigFloat()
    ccall((:mpfr_log1p, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, x, ROUNDING_MODE[])
    return z
end

function max(x::BigFloat, y::BigFloat)
    isnan(x) && return x
    isnan(y) && return y
    z = BigFloat()
    ccall((:mpfr_max, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, x, y, ROUNDING_MODE[])
    return z
end

function min(x::BigFloat, y::BigFloat)
    isnan(x) && return x
    isnan(y) && return y
    z = BigFloat()
    ccall((:mpfr_min, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, x, y, ROUNDING_MODE[])
    return z
end

function modf(x::BigFloat)
    isinf(x) && return (BigFloat(NaN), x)
    zint = BigFloat()
    zfloat = BigFloat()
    ccall((:mpfr_modf, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), zint, zfloat, x, ROUNDING_MODE[])
    return (zfloat, zint)
end

function rem(x::BigFloat, y::BigFloat)
    z = BigFloat()
    ccall((:mpfr_fmod, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, x, y, ROUNDING_MODE[])
    return z
end

function rem(x::BigFloat, y::BigFloat, ::RoundingMode{:Nearest})
    z = BigFloat()
    ccall((:mpfr_remainder, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, x, y, ROUNDING_MODE[])
    return z
end

# TODO: use a higher-precision BigFloat(pi) here?
rem2pi(x::BigFloat, r::RoundingMode) = rem(x, 2*BigFloat(pi), r)

function sum(arr::AbstractArray{BigFloat})
    z = BigFloat(0)
    for i in arr
        ccall((:mpfr_add, :libmpfr), Int32,
            (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, z, i, ROUNDING_MODE[])
    end
    return z
end

# Functions for which NaN results are converted to DomainError, following Base
for f in (:sin, :cos, :tan, :sec, :csc, :acos, :asin, :atan, :acosh, :asinh, :atanh)
    @eval begin
        function ($f)(x::BigFloat)
            isnan(x) && return x
            z = BigFloat()
            ccall(($(string(:mpfr_,f)), :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, x, ROUNDING_MODE[])
            isnan(z) && throw(DomainError(x, "NaN result for non-NaN input."))
            return z
        end
    end
end

function atan(y::BigFloat, x::BigFloat)
    z = BigFloat()
    ccall((:mpfr_atan2, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, y, x, ROUNDING_MODE[])
    return z
end

# Utility functions
==(x::BigFloat, y::BigFloat) = ccall((:mpfr_equal_p, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}), x, y) != 0
<=(x::BigFloat, y::BigFloat) = ccall((:mpfr_lessequal_p, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}), x, y) != 0
>=(x::BigFloat, y::BigFloat) = ccall((:mpfr_greaterequal_p, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}), x, y) != 0
<(x::BigFloat, y::BigFloat) = ccall((:mpfr_less_p, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}), x, y) != 0
>(x::BigFloat, y::BigFloat) = ccall((:mpfr_greater_p, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}), x, y) != 0

function cmp(x::BigFloat, y::BigInt)
    isnan(x) && return 1
    ccall((:mpfr_cmp_z, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigInt}), x, y)
end
function cmp(x::BigFloat, y::ClongMax)
    isnan(x) && return 1
    ccall((:mpfr_cmp_si, :libmpfr), Int32, (Ref{BigFloat}, Clong), x, y)
end
function cmp(x::BigFloat, y::CulongMax)
    isnan(x) && return 1
    ccall((:mpfr_cmp_ui, :libmpfr), Int32, (Ref{BigFloat}, Culong), x, y)
end
cmp(x::BigFloat, y::Integer) = cmp(x,big(y))
cmp(x::Integer, y::BigFloat) = -cmp(y,x)

function cmp(x::BigFloat, y::CdoubleMax)
    isnan(x) && return isnan(y) ? 0 : 1
    isnan(y) && return -1
    ccall((:mpfr_cmp_d, :libmpfr), Int32, (Ref{BigFloat}, Cdouble), x, y)
end
cmp(x::CdoubleMax, y::BigFloat) = -cmp(y,x)

==(x::BigFloat, y::Integer)    = !isnan(x) && cmp(x,y) == 0
==(x::Integer, y::BigFloat)    = y == x
==(x::BigFloat, y::CdoubleMax) = !isnan(x) && !isnan(y) && cmp(x,y) == 0
==(x::CdoubleMax, y::BigFloat) = y == x

<(x::BigFloat, y::Integer)    = !isnan(x) && cmp(x,y) < 0
<(x::Integer, y::BigFloat)    = !isnan(y) && cmp(y,x) > 0
<(x::BigFloat, y::CdoubleMax) = !isnan(x) && !isnan(y) && cmp(x,y) < 0
<(x::CdoubleMax, y::BigFloat) = !isnan(x) && !isnan(y) && cmp(y,x) > 0

<=(x::BigFloat, y::Integer)    = !isnan(x) && cmp(x,y) <= 0
<=(x::Integer, y::BigFloat)    = !isnan(y) && cmp(y,x) >= 0
<=(x::BigFloat, y::CdoubleMax) = !isnan(x) && !isnan(y) && cmp(x,y) <= 0
<=(x::CdoubleMax, y::BigFloat) = !isnan(x) && !isnan(y) && cmp(y,x) >= 0

signbit(x::BigFloat) = ccall((:mpfr_signbit, :libmpfr), Int32, (Ref{BigFloat},), x) != 0
function sign(x::BigFloat)
    c = cmp(x, 0)
    (c == 0 || isnan(x)) && return x
    return c < 0 ? -one(x) : one(x)
end

function precision(x::BigFloat)  # precision of an object of type BigFloat
    return ccall((:mpfr_get_prec, :libmpfr), Clong, (Ref{BigFloat},), x)
end

"""
    precision(BigFloat)

Get the precision (in bits) currently used for [`BigFloat`](@ref) arithmetic.
"""
precision(::Type{BigFloat}) = Int(DEFAULT_PRECISION[]) # precision of the type BigFloat itself

"""
    setprecision([T=BigFloat,] precision::Int)

Set the precision (in bits) to be used for `T` arithmetic.

!!! warning

    This function is not thread-safe. It will affect code running on all threads, but
    its behavior is undefined if called concurrently with computations that use the
    setting.
"""
function setprecision(::Type{BigFloat}, precision::Integer)
    if precision < 2
        throw(DomainError(precision, "`precision` cannot be less than 2."))
    end
    DEFAULT_PRECISION[] = precision
    return precision
end

setprecision(precision::Integer) = setprecision(BigFloat, precision)

maxintfloat(x::BigFloat) = BigFloat(2)^precision(x)
maxintfloat(::Type{BigFloat}) = BigFloat(2)^precision(BigFloat)

function copysign(x::BigFloat, y::BigFloat)
    z = BigFloat()
    ccall((:mpfr_copysign, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), z, x, y, ROUNDING_MODE[])
    return z
end

function exponent(x::BigFloat)
    if iszero(x) || !isfinite(x)
        throw(DomainError(x, "`x` must be non-zero and finite."))
    end
    # The '- 1' is to make it work as Base.exponent
    return ccall((:mpfr_get_exp, :libmpfr), Clong, (Ref{BigFloat},), x) - 1
end

function frexp(x::BigFloat)
    z = BigFloat()
    c = Ref{Clong}()
    ccall((:mpfr_frexp, :libmpfr), Int32, (Ptr{Clong}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), c, z, x, ROUNDING_MODE[])
    return (z, c[])
end

function significand(x::BigFloat)
    z = BigFloat()
    c = Ref{Clong}()
    ccall((:mpfr_frexp, :libmpfr), Int32, (Ptr{Clong}, Ref{BigFloat}, Ref{BigFloat}, MPFRRoundingMode), c, z, x, ROUNDING_MODE[])
    # Double the significand to make it work as Base.significand
    ccall((:mpfr_mul_si, :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}, Clong, MPFRRoundingMode), z, z, 2, ROUNDING_MODE[])
    return z
end

function isinteger(x::BigFloat)
    return ccall((:mpfr_integer_p, :libmpfr), Int32, (Ref{BigFloat},), x) != 0
end

for (f,R) in ((:roundeven, :Nearest),
              (:ceil, :Up),
              (:floor, :Down),
              (:trunc, :ToZero),
              (:round, :NearestTiesAway))
    @eval begin
        function round(x::BigFloat, ::RoundingMode{$(QuoteNode(R))})
            z = BigFloat()
            ccall(($(string(:mpfr_,f)), :libmpfr), Int32, (Ref{BigFloat}, Ref{BigFloat}), z, x)
            return z
        end
    end
end

function isinf(x::BigFloat)
    return ccall((:mpfr_inf_p, :libmpfr), Int32, (Ref{BigFloat},), x) != 0
end

function isnan(x::BigFloat)
    return ccall((:mpfr_nan_p, :libmpfr), Int32, (Ref{BigFloat},), x) != 0
end

isfinite(x::BigFloat) = !isinf(x) && !isnan(x)

iszero(x::BigFloat) = x == Clong(0)
isone(x::BigFloat) = x == Clong(1)

@eval typemax(::Type{BigFloat}) = $(BigFloat(Inf))
@eval typemin(::Type{BigFloat}) = $(BigFloat(-Inf))

function nextfloat!(x::BigFloat, n::Integer=1)
    signbit(n) && return prevfloat!(x, abs(n))
    for i = 1:n
        ccall((:mpfr_nextabove, :libmpfr), Int32, (Ref{BigFloat},), x)
    end
    return x
end

function prevfloat!(x::BigFloat, n::Integer=1)
    signbit(n) && return nextfloat!(x, abs(n))
    for i = 1:n
        ccall((:mpfr_nextbelow, :libmpfr), Int32, (Ref{BigFloat},), x)
    end
    return x
end

nextfloat(x::BigFloat, n::Integer=1) = n == 0 ? x : nextfloat!(_duplicate(x), n)
prevfloat(x::BigFloat, n::Integer=1) = n == 0 ? x : prevfloat!(_duplicate(x), n)

eps(::Type{BigFloat}) = nextfloat(BigFloat(1)) - BigFloat(1)

floatmin(::Type{BigFloat}) = nextfloat(zero(BigFloat))
floatmax(::Type{BigFloat}) = prevfloat(BigFloat(Inf))

"""
    setprecision(f::Function, [T=BigFloat,] precision::Integer)

Change the `T` arithmetic precision (in bits) for the duration of `f`.
It is logically equivalent to:

    old = precision(BigFloat)
    setprecision(BigFloat, precision)
    f()
    setprecision(BigFloat, old)

Often used as `setprecision(T, precision) do ... end`

Note: `nextfloat()`, `prevfloat()` do not use the precision mentioned by
`setprecision`
"""
function setprecision(f::Function, ::Type{T}, prec::Integer) where T
    old_prec = precision(T)
    setprecision(T, prec)
    try
        return f()
    finally
        setprecision(T, old_prec)
    end
end

setprecision(f::Function, prec::Integer) = setprecision(f, BigFloat, prec)

function string_mpfr(x::BigFloat, fmt::String)
    pc = Ref{Ptr{UInt8}}()
    n = ccall((:mpfr_asprintf,:libmpfr), Cint,
              (Ptr{Ptr{UInt8}}, Ptr{UInt8}, Ref{BigFloat}...),
              pc, fmt, x)
    p = pc[]
    # convert comma decimal separator to dot
    for i = 1:n
        if unsafe_load(p, i) == UInt8(',')
            unsafe_store!(p, '.', i)
            break
        end
    end
    str = unsafe_string(p)
    ccall((:mpfr_free_str, :libmpfr), Cvoid, (Ptr{UInt8},), p)
    return str
end

function _prettify_bigfloat(s::String)::String
    mantissa, exponent = split(s, 'e')
    if !occursin('.', mantissa)
        mantissa = string(mantissa, '.')
    end
    mantissa = rstrip(mantissa, '0')
    if endswith(mantissa, '.')
        mantissa = string(mantissa, '0')
    end
    expo = parse(Int, exponent)
    if -5 < expo < 6
        expo == 0 && return mantissa
        int, frac = split(mantissa, '.')
        if expo > 0
            expo < length(frac) ?
                string(int, frac[1:expo], '.', frac[expo+1:end]) :
                string(int, frac, '0'^(expo-length(frac)), '.', '0')
        else
            neg = startswith(int, '-')
            neg == true && (int = lstrip(int, '-'))
            @assert length(int) == 1
            string(neg ? '-' : "", '0', '.', '0'^(-expo-1), int, frac)
        end
    else
        string(mantissa, 'e', exponent)
    end
end

function _string(x::BigFloat, fmt::String)::String
    isfinite(x) || return string(Float64(x))
    _prettify_bigfloat(string_mpfr(x, fmt))
end
_string(x::BigFloat) = _string(x, "%.Re")
_string(x::BigFloat, k::Integer) = _string(x, "%.$(k)Re")

string(b::BigFloat) = _string(b)

print(io::IO, b::BigFloat) = print(io, string(b))
function show(io::IO, b::BigFloat)
    if get(io, :compact, false)
        print(io, _string(b, 5))
    else
        print(io, _string(b))
    end
end

# get/set exponent min/max
get_emax() = ccall((:mpfr_get_emax, :libmpfr), Clong, ())
get_emax_min() = ccall((:mpfr_get_emax_min, :libmpfr), Clong, ())
get_emax_max() = ccall((:mpfr_get_emax_max, :libmpfr), Clong, ())

get_emin() = ccall((:mpfr_get_emin, :libmpfr), Clong, ())
get_emin_min() = ccall((:mpfr_get_emin_min, :libmpfr), Clong, ())
get_emin_max() = ccall((:mpfr_get_emin_max, :libmpfr), Clong, ())

check_exponent_err(ret) = ret == 0 || throw(ArgumentError("Invalid MPFR exponent range"))
set_emax!(x) = check_exponent_err(ccall((:mpfr_set_emax, :libmpfr), Cint, (Clong,), x))
set_emin!(x) = check_exponent_err(ccall((:mpfr_set_emin, :libmpfr), Cint, (Clong,), x))

function Base.deepcopy_internal(x::BigFloat, stackdict::IdDict)
    haskey(stackdict, x) && return stackdict[x]
    # d = copy(x._d)
    d = x._d
    d′ = GC.@preserve d unsafe_string(pointer(d), sizeof(d)) # creates a definitely-new String
    y = _BigFloat(x.prec, x.sign, x.exp, d′)
    #ccall((:mpfr_custom_move,:libmpfr), Cvoid, (Ref{BigFloat}, Ptr{Limb}), y, d) # unnecessary
    stackdict[x] = y
    return y
end

function Base.lerpi(j::Integer, d::Integer, a::BigFloat, b::BigFloat)
    t = BigFloat(j)/d
    fma(t, b, fma(-t, a, a))
end

# flags
clear_flags() = ccall((:mpfr_clear_flags, :libmpfr), Cvoid, ())
had_underflow() = ccall((:mpfr_underflow_p, :libmpfr), Cint, ()) != 0
had_overflow() = ccall((:mpfr_underflow_p, :libmpfr), Cint, ()) != 0
had_nan() = ccall((:mpfr_nanflag_p, :libmpfr), Cint, ()) != 0
had_inexact_exception() = ccall((:mpfr_inexflag_p, :libmpfr), Cint, ()) != 0
had_range_exception() = ccall((:mpfr_erangeflag_p, :libmpfr), Cint, ()) != 0

end #module

max_stars_repo_name: rfourquet/julia

max_issues_repo_name: rfourquet/julia

max_forks_repo_name: rfourquet/julia