From 06f73422ffe134422e503fb0bc37ee75ca529d63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ciar=C3=A1n=20O=27Mara?= Date: Mon, 15 Nov 2021 13:09:05 +1100 Subject: [PATCH 01/16] Loosen types This commit contains breaking changes. --- src/interval.jl | 8 ++++---- src/intervalcollection.jl | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/interval.jl b/src/interval.jl index d1d64c90..bbaf11f5 100644 --- a/src/interval.jl +++ b/src/interval.jl @@ -79,7 +79,7 @@ end IntervalTrees.first(i::Interval) = leftposition(i) IntervalTrees.last(i::Interval) = rightposition(i) -function Base.isless(a::Interval{T}, b::Interval{T}, seqname_isless::Function=isless) where T +function Base.isless(a::Interval, b::Interval, seqname_isless::Function=isless) if seqname(a) != seqname(b) return seqname_isless(seqname(a), seqname(b))::Bool end @@ -104,7 +104,7 @@ Check if two intervals are well ordered. Intervals are considered well ordered if seqname(a) <= seqname(b) and leftposition(a) <= leftposition(b). """ -function isordered(a::Interval{T}, b::Interval{T}, seqname_isless::Function=isless) where T +function isordered(a::Interval, b::Interval, seqname_isless::Function=isless) if seqname(a) != seqname(b) return seqname_isless(seqname(a), seqname(b))::Bool end @@ -119,11 +119,11 @@ end """ Return true if interval `a` entirely precedes `b`. """ -function precedes(a::Interval{T}, b::Interval{T}, seqname_isless::Function=isless) where T +function precedes(a::Interval, b::Interval, seqname_isless::Function=isless) return (rightposition(a) < leftposition(b) && seqname(a) == seqname(b)) || seqname_isless(seqname(a), seqname(b))::Bool end -function Base.:(==)(a::Interval{T}, b::Interval{T}) where T +function Base.:(==)(a::Interval, b::Interval) return seqname(a) == seqname(b) && leftposition(a) == leftposition(b) && rightposition(a) == rightposition(b) && diff --git a/src/intervalcollection.jl b/src/intervalcollection.jl index 6282fed8..247e2c96 100644 --- a/src/intervalcollection.jl +++ b/src/intervalcollection.jl @@ -150,7 +150,7 @@ function Base.eltype(::Type{IntervalCollection{T}}) where T return Interval{T} end -function Base.:(==)(a::IntervalCollection{T}, b::IntervalCollection{T}) where T +function Base.:(==)(a::IntervalCollection, b::IntervalCollection) if length(a) != length(b) return false end From b2787f3bca854e9275bb5180b38e05c7e1325808 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ciar=C3=A1n=20O=27Mara?= Date: Tue, 28 Feb 2023 15:16:55 +1100 Subject: [PATCH 02/16] Add doi to README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 76e9669f..df77ec83 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # GenomicFeatures [![latest release](https://img.shields.io/github/release/BioJulia/GenomicFeatures.jl.svg)](https://github.com/BioJulia/GenomicFeatures.jl/releases/latest) +[![DOI](https://zenodo.org/badge/94160625.svg)](https://zenodo.org/badge/latestdoi/94160625) [![MIT license](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/BioJulia/GenomicFeatures.jl/blob/master/LICENSE) [![Stable documentation](https://img.shields.io/badge/docs-stable-blue.svg)](https://biojulia.github.io/GenomicFeatures.jl/stable) [![Latest documentation](https://img.shields.io/badge/docs-dev-blue.svg)](https://biojulia.github.io/GenomicFeatures.jl/dev/) From 78398581988085122a31cd3630eb5751c3cf9761 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ciar=C3=A1n=20O=27Mara?= Date: Sat, 15 Oct 2022 11:43:40 +1100 Subject: [PATCH 03/16] Add `span` method --- src/GenomicFeatures.jl | 13 ++++++++++++- test/runtests.jl | 3 +++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/GenomicFeatures.jl b/src/GenomicFeatures.jl index 4a70f7b8..8d11a277 100644 --- a/src/GenomicFeatures.jl +++ b/src/GenomicFeatures.jl @@ -24,7 +24,9 @@ export isfilled, hasseqname, hasleftposition, - hasrightposition + hasrightposition, + + span import BioGenerics: BioGenerics, seqname, leftposition, rightposition, isoverlapping, isfilled, hasseqname, hasleftposition, hasrightposition, metadata import DataStructures @@ -38,4 +40,13 @@ include("queue.jl") include("overlap.jl") include("coverage.jl") +""" + span(interval::Interval)::Int + +Get the span of `interval`. +""" +function span(interval::Interval) + return length(leftposition(interval):rightposition(interval)) +end + end # module diff --git a/test/runtests.jl b/test/runtests.jl index 60d55d0a..32fd42c9 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -170,6 +170,9 @@ end @test i1 == i2 @test i1 == Interval("chr2", 5692667:5701385, '+', "SOX11") end + + @test span(Interval("test", 1, 9)) == length(1:9) + end @testset "IntervalCollection" begin From 14ca3d32770b96b8036a3fd3da149cf2cf6d728e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ciar=C3=A1n=20O=27Mara?= Date: Sat, 15 Oct 2022 11:46:05 +1100 Subject: [PATCH 04/16] Add `volume` method --- src/GenomicFeatures.jl | 9 +++++++++ test/runtests.jl | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/GenomicFeatures.jl b/src/GenomicFeatures.jl index 8d11a277..c986fe4e 100644 --- a/src/GenomicFeatures.jl +++ b/src/GenomicFeatures.jl @@ -49,4 +49,13 @@ function span(interval::Interval) return length(leftposition(interval):rightposition(interval)) end +""" + volume(interval::Interval) + +Get the product of the `interval`'s span and metadata. +""" +function volume(interval::Interval) + return span(interval) * GenomicFeatures.metadata(interval) +end + end # module diff --git a/test/runtests.jl b/test/runtests.jl index 32fd42c9..c4351f00 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -172,7 +172,8 @@ end end @test span(Interval("test", 1, 9)) == length(1:9) - + @test GenomicFeatures.volume(Interval("test", 1, 9, '?', 2.5)) == length(1:9) * 2.5 + end @testset "IntervalCollection" begin From 7c9866555c699cfd248dd451818dcb307cf71b48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ciar=C3=A1n=20O=27Mara?= Date: Sat, 27 Nov 2021 21:29:54 +1100 Subject: [PATCH 05/16] Simplify codecov --- .github/workflows/UnitTests.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/UnitTests.yml b/.github/workflows/UnitTests.yml index 252ac577..90788864 100644 --- a/.github/workflows/UnitTests.yml +++ b/.github/workflows/UnitTests.yml @@ -41,8 +41,4 @@ jobs: - name: Upload CodeCov uses: codecov/codecov-action@v2 with: - file: lcov.info - flags: unittests - name: codecov-umbrella - fail_ci_if_error: false - token: ${{ secrets.CODECOV_TOKEN }} + files: lcov.info From 0c5f40d2f346267831408ead996c41273f1b744f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ciar=C3=A1n=20O=27Mara?= Date: Fri, 14 Oct 2022 14:09:12 +1100 Subject: [PATCH 06/16] Remove redundant method --- src/coverage.jl | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/coverage.jl b/src/coverage.jl index 5d8dca89..923fcdcc 100644 --- a/src/coverage.jl +++ b/src/coverage.jl @@ -117,10 +117,6 @@ function coverage(stream, seqname_isless::Function=isless) return cov end -function coverage(ic::IntervalCollection) - return coverage(ic, isless) -end - # Helper function for coverage. Process remaining interval end points after # all intervals have been read. function coverage_process_lasts_heap!(cov::IntervalCollection{UInt32}, current_coverage, coverage_seqname, coverage_first, lasts) From 78204adb8a71dcd142ed7a77aefdb027a2e8651e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ciar=C3=A1n=20O=27Mara?= Date: Fri, 14 Oct 2022 14:07:46 +1100 Subject: [PATCH 07/16] Use `leftposition` and `rightposition` in `coverage` --- src/coverage.jl | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/coverage.jl b/src/coverage.jl index 923fcdcc..78c9f88c 100644 --- a/src/coverage.jl +++ b/src/coverage.jl @@ -70,14 +70,14 @@ function coverage(stream, seqname_isless::Function=isless) error("Intervals must be sorted to compute coverage.") end - if !isempty(lasts) && lasts[1] < first(interval) + if !isempty(lasts) && lasts[1] < leftposition(interval) pos = DataStructures.heappop!(lasts) - if first(interval) == pos + 1 - DataStructures.heappush!(lasts, last(interval)) + if leftposition(interval) == pos + 1 + DataStructures.heappush!(lasts, rightposition(interval)) if stream_next === nothing break end - last_interval_first = first(interval) + last_interval_first = leftposition(interval) interval, stream_state = stream_next stream_next = iterate(stream, stream_state) elseif pos == coverage_first - 1 @@ -90,23 +90,23 @@ function coverage(stream, seqname_isless::Function=isless) end else if coverage_first == 0 - coverage_first = first(interval) + coverage_first = leftposition(interval) current_coverage = 1 - elseif coverage_first == first(interval) + elseif coverage_first == leftposition(interval) current_coverage += 1 else if current_coverage > 0 - push!(cov, Interval{UInt32}(coverage_seqname, coverage_first, first(interval) - 1, STRAND_BOTH, current_coverage)) + push!(cov, Interval{UInt32}(coverage_seqname, coverage_first, leftposition(interval) - 1, STRAND_BOTH, current_coverage)) end current_coverage += 1 - coverage_first = first(interval) + coverage_first = leftposition(interval) end - DataStructures.heappush!(lasts, last(interval)) + DataStructures.heappush!(lasts, rightposition(interval)) if stream_next === nothing break end - last_interval_first = first(interval) + last_interval_first = leftposition(interval) interval, stream_state = stream_next stream_next = iterate(stream, stream_state) end From d64824aa78007e06c74e578fe09f616af732600a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ciar=C3=A1n=20O=27Mara?= Date: Mon, 17 Apr 2023 02:25:34 +1000 Subject: [PATCH 08/16] Provide an `eachoverlap` iterator that accepts reversed parameters --- src/intervalcollection.jl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/intervalcollection.jl b/src/intervalcollection.jl index 247e2c96..69526f18 100644 --- a/src/intervalcollection.jl +++ b/src/intervalcollection.jl @@ -304,6 +304,10 @@ function eachoverlap(a::IntervalCollection{T}, query::Interval; filter::F = true return ICTreeIntervalIntersectionIterator{F,T}(filter, ICTreeIntersection{T}(), ICTree{T}(), query) end +function eachoverlap(query::Interval, b::IntervalCollection{T}; filter::F = true_cmp) where {F,T} + return eachoverlap(b, query; filter = filter) +end + function eachoverlap(a::IntervalCollection, b::IntervalCollection; filter = true_cmp) seqnames = collect(AbstractString, keys(a.trees) ∩ keys(b.trees)) sort!(seqnames, lt = isless) From 9761d57ccce77e7323272deaa60601ebecccfb27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ciar=C3=A1n=20O=27Mara?= Date: Sat, 15 Oct 2022 11:48:54 +1100 Subject: [PATCH 09/16] Add `hasintersection` methods --- src/GenomicFeatures.jl | 1 + src/intervalcollection.jl | 29 +++++++++++++++++++++++++++++ test/runtests.jl | 8 ++++++++ 3 files changed, 38 insertions(+) diff --git a/src/GenomicFeatures.jl b/src/GenomicFeatures.jl index c986fe4e..a7d6d496 100644 --- a/src/GenomicFeatures.jl +++ b/src/GenomicFeatures.jl @@ -20,6 +20,7 @@ export IntervalCollection, eachoverlap, coverage, + hasintersection, isfilled, hasseqname, diff --git a/src/intervalcollection.jl b/src/intervalcollection.jl index 69526f18..278efb18 100644 --- a/src/intervalcollection.jl +++ b/src/intervalcollection.jl @@ -503,3 +503,32 @@ function Base.iterate(it::IntervalCollectionStreamIterator{F,S,T}, state = ()) w end end end + +""" + hasintersection(interval::Interval, col::IntervalCollection)::Bool + +Query whether an `interval` has an intersection with `col`. +""" +function hasintersection(interval::Interval, col::IntervalCollection) + + # Return early if chromosome is not in the interval collection. + if !haskey(col.trees, seqname(interval)) + return false + end + + # Setup intersection iterator. + iter = IntervalTrees.intersect(col.trees[seqname(interval)], (leftposition(interval), rightposition(interval))) + + # Attempt first iteration. + if iterate(iter) === nothing + return false + end + + # Intersection exists. + return true + +end + +function hasintersection(col::IntervalCollection) + return interval -> hasintersection(interval, col) +end diff --git a/test/runtests.jl b/test/runtests.jl index c4351f00..6eadbab9 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -219,6 +219,14 @@ end sort(simple_intersection(intervals_a, intervals_b)) @test sort(collect(eachoverlap(ic_a, ic_b, filter=(a,b) -> isodd(first(a))))) == sort(simple_intersection(intervals_a, intervals_b, filter=(a,b) -> isodd(first(a)))) + + # Check hasintersection method. + @test hasintersection(Interval("test", 1, 1), IntervalCollection([Interval("test", 1,2)])) == true + @test hasintersection(Interval("test", 1, 1), IntervalCollection([Interval("test", 2,2)])) == false + + # Check hasintersection currying. + @test Interval("test", 1, 1) |> hasintersection(IntervalCollection([Interval("test", 1,2)])) == true + @test Interval("test", 1, 1) |> hasintersection(IntervalCollection([Interval("test", 2,2)])) == false end @testset "Show" begin From 9a96ce5b88ca97cc7d6ac12500c0b66707ada96c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ciar=C3=A1n=20O=27Mara?= Date: Mon, 17 Apr 2023 14:00:15 +1000 Subject: [PATCH 10/16] Increment version --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 434addcb..17f7776c 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "GenomicFeatures" uuid = "899a7d2d-5c61-547b-bef9-6698a8d05446" authors = ["Kenta Sato ", "Ben J. Ward ", "Ciarán O’Mara "] -version = "2.0.5" +version = "2.1.0" [deps] BioGenerics = "47718e42-2ac5-11e9-14af-e5595289c2ea" From 8f2e96928af36609c049aeeff08c63e718629fd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ciar=C3=A1n=20O=27Mara?= Date: Mon, 17 Apr 2023 14:01:46 +1000 Subject: [PATCH 11/16] Update authors --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 17f7776c..3e3d72a5 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "GenomicFeatures" uuid = "899a7d2d-5c61-547b-bef9-6698a8d05446" -authors = ["Kenta Sato ", "Ben J. Ward ", "Ciarán O’Mara "] +authors = ["Kenta Sato ", "Sabrina J. Ward ", "Ciarán O’Mara "] version = "2.1.0" [deps] From 84b2d6ef0509a247d55c00fb0db6c2136ff8ef21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ciar=C3=A1n=20O=27Mara?= Date: Mon, 17 Apr 2023 14:05:17 +1000 Subject: [PATCH 12/16] Update TagBot workflow --- .github/workflows/TagBot.yml | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/.github/workflows/TagBot.yml b/.github/workflows/TagBot.yml index 6d2efc1c..2bacdb87 100644 --- a/.github/workflows/TagBot.yml +++ b/.github/workflows/TagBot.yml @@ -1,11 +1,25 @@ name: TagBot - on: issue_comment: types: - created workflow_dispatch: - + inputs: + lookback: + default: 3 +permissions: + actions: read + checks: read + contents: write + deployments: read + issues: read + discussions: read + packages: read + pages: read + pull-requests: read + repository-projects: read + security-events: read + statuses: read jobs: TagBot: if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot' From cd9395b0a4f665a761ed874b9bafab4b44a29729 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ciar=C3=A1n=20O=27Mara?= Date: Mon, 17 Apr 2023 14:12:22 +1000 Subject: [PATCH 13/16] Update UnitTests workflow Reduces the number of OS tested as there is no need to check third-party software or integrations. --- .github/workflows/UnitTests.yml | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/.github/workflows/UnitTests.yml b/.github/workflows/UnitTests.yml index 90788864..c8144623 100644 --- a/.github/workflows/UnitTests.yml +++ b/.github/workflows/UnitTests.yml @@ -8,24 +8,18 @@ jobs: test: name: Julia ${{ matrix.julia-version }} - ${{ matrix.os }} - ${{ matrix.julia-arch }} - ${{ github.event_name }} runs-on: ${{ matrix.os }} - continue-on-error: ${{ matrix.experimental }} + continue-on-error: ${{ matrix.julia-version == 'nightly' }} strategy: fail-fast: false matrix: julia-version: - '1.0' # LTS + - '1.6' # LTS - '1' - julia-arch: [x64, x86] - os: [ubuntu-latest, windows-latest, macOS-latest] - exclude: - - os: macOS-latest - julia-arch: x86 - experimental: [false] - include: - - julia-version: nightly - julia-arch: x64 - os: ubuntu-latest - experimental: true + - 'nightly' + julia-arch: + - x64 + os: [ubuntu-latest] steps: - name: Checkout Repository @@ -34,6 +28,7 @@ jobs: uses: julia-actions/setup-julia@v1 with: version: ${{ matrix.julia-version }} + arch: ${{ matrix.julia-arch }} - name: Run Tests uses: julia-actions/julia-runtest@v1 - name: Create CodeCov From ea7d2ea0abcf8b28273e8275a2bd928d2a89b026 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ciar=C3=A1n=20O=27Mara?= Date: Mon, 17 Apr 2023 14:19:18 +1000 Subject: [PATCH 14/16] Update Documentation workflow --- .github/workflows/Documentation.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/Documentation.yml b/.github/workflows/Documentation.yml index 02e5911e..693e1637 100644 --- a/.github/workflows/Documentation.yml +++ b/.github/workflows/Documentation.yml @@ -10,6 +10,8 @@ on: jobs: Documenter: + permissions: + contents: write name: Documentation runs-on: ubuntu-latest steps: From 751cbd916c84c4d04266bf246934806c15c8c107 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ciar=C3=A1n=20O=27Mara?= Date: Mon, 17 Apr 2023 14:21:39 +1000 Subject: [PATCH 15/16] Update CompatHelper workflow --- .github/workflows/CompatHelper.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.github/workflows/CompatHelper.yml b/.github/workflows/CompatHelper.yml index 9fd9f2b3..ca78383f 100644 --- a/.github/workflows/CompatHelper.yml +++ b/.github/workflows/CompatHelper.yml @@ -3,10 +3,23 @@ on: schedule: - cron: 0 0 * * * workflow_dispatch: +permissions: + contents: write + pull-requests: write jobs: CompatHelper: runs-on: ubuntu-latest steps: + - name: Check if Julia is already available in the PATH + id: julia_in_path + run: which julia + continue-on-error: true + - name: Install Julia, but only if it is not already available in the PATH + uses: julia-actions/setup-julia@v1 + with: + version: '1' + arch: ${{ runner.arch }} + if: steps.julia_in_path.outcome != 'success' - name: "Add the General registry via Git" run: | import Pkg From c7f2cb6bbd1d672f9c1db3a0141d8a70f9e43f43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ciar=C3=A1n=20O=27Mara?= Date: Mon, 17 Apr 2023 14:23:17 +1000 Subject: [PATCH 16/16] Improve slack link --- README.md | 2 +- docs/src/index.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index df77ec83..e4b674de 100644 --- a/README.md +++ b/README.md @@ -70,4 +70,4 @@ Your logo will show up here with a link to your website. ## Questions? -If you have a question about contributing or using BioJulia software, come on over and chat to us on [the Julia Slack workspace](https://julialang.org/slack/), or you can try the [Bio category of the Julia discourse site](https://discourse.julialang.org/c/domain/bio). +If you have a question about contributing or using BioJulia software, come on over and chat to us on [the Julia Slack workspace](https://julialang.slack.com/channels/biology), or you can try the [Bio category of the Julia discourse site](https://discourse.julialang.org/c/domain/bio). diff --git a/docs/src/index.md b/docs/src/index.md index 45d1d498..13a19536 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -69,4 +69,4 @@ Your logo will show up here with a link to your website. ## Questions? -If you have a question about contributing or using BioJulia software, come on over and chat to us on [the Julia Slack workspace](https://julialang.org/slack/), or you can try the [Bio category of the Julia discourse site](https://discourse.julialang.org/c/domain/bio). +If you have a question about contributing or using BioJulia software, come on over and chat to us on [the Julia Slack workspace](https://julialang.slack.com/channels/biology), or you can try the [Bio category of the Julia discourse site](https://discourse.julialang.org/c/domain/bio).