diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 8b40b454..bc4d8274 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -1,17 +1,4 @@ steps: - - label: ":julia: Run tests on 1.6 LTS" - plugins: - - JuliaCI/julia#v1: - version: "1.6" - - JuliaCI/julia-test#v1: - coverage: false - agents: - queue: "juliagpu" - cuda: "*" - timeout_in_minutes: 60 - # Don't run Buildkite if the commit message includes the text [skip tests] - if: build.message !~ /\[skip tests\]/ - - label: ":julia: Run tests on Julia-latest" plugins: - JuliaCI/julia#v1: diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 700707ce..1e8a051e 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,3 +5,6 @@ updates: directory: "/" # Location of package manifests schedule: interval: "weekly" + ignore: + - dependency-name: "crate-ci/typos" + update-types: ["version-update:semver-patch"] diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 7452a0c8..ff10977c 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -1,70 +1,44 @@ name: CI -env: - DATADEPS_ALWAYS_ACCEPT: true on: - push: - branches: '*' + pull_request: + branches: + - main paths-ignore: - 'docs/**' - tags: '*' - pull_request: + push: + branches: + - main paths-ignore: - 'docs/**' -concurrency: - # Skip intermediate builds: always. - # Cancel intermediate builds: only if it is a pull request build. - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }} + schedule: + - cron: '17 14 * * 5' jobs: test: - name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ github.event_name }} runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: + group: + - Core version: - - '1.6' - '1' - # - 'nightly' os: - ubuntu-latest + - macos-latest - windows-latest - arch: - - x64 steps: - uses: actions/checkout@v4 - uses: julia-actions/setup-julia@v1 with: version: ${{ matrix.version }} - arch: ${{ matrix.arch }} - uses: julia-actions/cache@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} - uses: julia-actions/julia-buildpkg@v1 - uses: julia-actions/julia-runtest@v1 - - uses: julia-actions/julia-processcoverage@v1 - - uses: codecov/codecov-action@v3 - with: - files: lcov.info - docs: - name: Documentation - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - uses: actions/checkout@v4 - - uses: julia-actions/setup-julia@v1 with: - version: '1' - - uses: julia-actions/julia-buildpkg@v1 - - uses: julia-actions/julia-docdeploy@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - run: | - julia --project=docs --code-coverage=user -e ' - using Documenter: DocMeta, doctest - using NeuralOperators - DocMeta.setdocmeta!(NeuralOperators, :DocTestSetup, :(using NeuralOperators); recursive=true) - doctest(NeuralOperators)' + depwarn: error - uses: julia-actions/julia-processcoverage@v1 - uses: codecov/codecov-action@v3 with: - file: lcov.info + file: lcov.info \ No newline at end of file diff --git a/.github/workflows/Documentation.yml b/.github/workflows/Documentation.yml new file mode 100644 index 00000000..2797d6e2 --- /dev/null +++ b/.github/workflows/Documentation.yml @@ -0,0 +1,37 @@ +name: Documentation + +on: + push: + branches: + - main + - 'release-' + tags: '*' + pull_request: + schedule: + - cron: '17 14 * * 5' +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: julia-actions/setup-julia@latest + with: + version: '1' + - name: Install dependencies + run: julia --project=docs/ -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()' + - name: Build and deploy + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # For authentication with GitHub Actions token + DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }} # For authentication with SSH deploy key + run: julia --project=docs/ --code-coverage=user docs/make.jl + - name: Doctest + run: | + julia --project=docs --code-coverage=user -e ' + using Documenter: DocMeta, doctest + using NeuralOperators + DocMeta.setdocmeta!(NeuralOperators, :DocTestSetup, :(using NeuralOperators); recursive=true) + doctest(NeuralOperators)' + - uses: julia-actions/julia-processcoverage@v1 + - uses: codecov/codecov-action@v3 + with: + file: lcov.info diff --git a/.github/workflows/Downgrade.yml b/.github/workflows/Downgrade.yml new file mode 100644 index 00000000..aadf1e62 --- /dev/null +++ b/.github/workflows/Downgrade.yml @@ -0,0 +1,41 @@ +name: Downgrade +on: + pull_request: + branches: + - main + paths-ignore: + - 'docs/**' + push: + branches: + - main + paths-ignore: + - 'docs/**' + schedule: + - cron: '17 14 * * 5' +jobs: + test: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + group: + - Core + version: + - '1' + os: + - ubuntu-latest + - macos-latest + - windows-latest + steps: + - uses: actions/checkout@v4 + - uses: julia-actions/setup-julia@v1 + with: + version: ${{ matrix.version }} + - uses: cjdoris/julia-downgrade-compat-action@v1 + with: + skip: Pkg,TOML + - uses: julia-actions/cache@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + - uses: julia-actions/julia-buildpkg@v1 + - uses: julia-actions/julia-runtest@v1 \ No newline at end of file diff --git a/.github/workflows/SpellCheck.yml b/.github/workflows/SpellCheck.yml new file mode 100644 index 00000000..599253c8 --- /dev/null +++ b/.github/workflows/SpellCheck.yml @@ -0,0 +1,13 @@ +name: Spell Check + +on: [pull_request] + +jobs: + typos-check: + name: Spell Check with Typos + runs-on: ubuntu-latest + steps: + - name: Checkout Actions Repository + uses: actions/checkout@v3 + - name: Check spelling + uses: crate-ci/typos@v1.16.23 \ No newline at end of file diff --git a/.typos.toml b/.typos.toml new file mode 100644 index 00000000..79b68a4c --- /dev/null +++ b/.typos.toml @@ -0,0 +1 @@ +[default.extend-words] \ No newline at end of file diff --git a/Project.toml b/Project.toml index dae1364c..a5a5a2ab 100644 --- a/Project.toml +++ b/Project.toml @@ -5,32 +5,34 @@ version = "0.4.7" [deps] CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" -CUDAKernels = "72cfdca4-0801-4ab0-bf6a-d52aa10adc57" ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" FFTW = "7a1cc6ca-52ef-59f5-83cd-3a7055c09341" Flux = "587475ba-b771-5e3f-ad9e-33799f191a9c" GeometricFlux = "7e08b658-56d3-11e9-2997-919d5b31e4ea" -KernelAbstractions = "63c18a36-062a-441e-b654-da1e3ab1ce7c" Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" Tullio = "bc48ee85-29a4-5162-ae0b-a64e1601d4bc" Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f" [compat] -CUDA = "3, 4, 5" -CUDAKernels = "0.3, 0.4" -ChainRulesCore = "1" -FFTW = "1" -Flux = "0.13" -GeometricFlux = "0.13" -KernelAbstractions = "0.7, 0.8" -Statistics = "1" -Tullio = "0.3" -Zygote = "0.6" -julia = "1.6" +Aqua = "0.8" +CUDA = "3.5, 4, 5" +ChainRulesCore = "1.15" +FFTW = "1.3.0" +Flux = "0.13.4" +GeometricFlux = "0.13.5" +Graphs = "1.6" +SafeTestsets = "0.1" +Statistics = "1.10" +Test = "1" +Tullio = "0.3.1" +Zygote = "0.6.61" +julia = "1.10" [extras] +Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6" +SafeTestsets = "1bc83da4-3b8d-516f-aca4-4fe02f6d838f" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] -test = ["Graphs", "Test"] +test = ["Aqua", "Graphs", "SafeTestsets", "Test"] diff --git a/src/NeuralOperators.jl b/src/NeuralOperators.jl index 22c317f2..fa15c088 100644 --- a/src/NeuralOperators.jl +++ b/src/NeuralOperators.jl @@ -4,8 +4,6 @@ using Flux using FFTW using Tullio using CUDA -using CUDAKernels -using KernelAbstractions using Zygote using ChainRulesCore using GeometricFlux diff --git a/src/Transform/chebyshev_transform.jl b/src/Transform/chebyshev_transform.jl index e83a9fff..9771e609 100644 --- a/src/Transform/chebyshev_transform.jl +++ b/src/Transform/chebyshev_transform.jl @@ -1,14 +1,14 @@ export ChebyshevTransform -struct ChebyshevTransform{N, S} <: AbstractTransform - modes::NTuple{N, S} # N == ndims(x) +struct ChebyshevTransform{nMinus1, S} <: AbstractTransform + modes::Tuple{S,Vararg{S,nMinus1}} # nMinus1 == ndims(x)-1 end -Base.ndims(::ChebyshevTransform{N}) where {N} = N +Base.ndims(::ChebyshevTransform{nMinus1}) where {nMinus1} = nMinus1 + 1 Base.eltype(::Type{ChebyshevTransform}) = Float32 -function transform(t::ChebyshevTransform{N}, 𝐱::AbstractArray) where {N} - return FFTW.r2r(𝐱, FFTW.REDFT10, 1:N) # [size(x)..., in_chs, batch] +function transform(t::ChebyshevTransform{nMinus1}, 𝐱::AbstractArray) where {nMinus1} + return FFTW.r2r(𝐱, FFTW.REDFT10, 1:(nMinus1+1)) # [size(x)..., in_chs, batch] end function truncate_modes(t::ChebyshevTransform, 𝐱̂::AbstractArray) @@ -31,7 +31,7 @@ function ∇r2r(Δ::AbstractArray{T}, kind, dims) where {T} # derivative of r2r turns out to be r2r Δx = FFTW.r2r(Δ, kind, dims) - # rank 4 correction: needs @bischtob to elaborate the reason using this. + # rank 4 correction: needs @bischtob to elaborate the reason using this. # (M,) = size(Δ)[dims] # a1 = fill!(similar(Δ, M), one(T)) # CUDA.@allowscalar a1[1] = a1[end] = zero(T) diff --git a/src/Transform/fourier_transform.jl b/src/Transform/fourier_transform.jl index bab74918..d5ebe777 100644 --- a/src/Transform/fourier_transform.jl +++ b/src/Transform/fourier_transform.jl @@ -1,10 +1,10 @@ export FourierTransform -struct FourierTransform{N, S} <: AbstractTransform - modes::NTuple{N, S} # N == ndims(x) +struct FourierTransform{nMinus1, S} <: AbstractTransform + modes::Tuple{S,Vararg{S,nMinus1}} # nMinus1 == ndims(x)-1 end -Base.ndims(::FourierTransform{N}) where {N} = N +Base.ndims(::FourierTransform{nMinus1}) where {nMinus1} = nMinus1+1 Base.eltype(::Type{FourierTransform}) = ComplexF32 function transform(ft::FourierTransform, 𝐱::AbstractArray) diff --git a/test/DeepONet/DeepONet.jl b/test/DeepONet/DeepONet.jl index 80244ce4..8a7fd558 100644 --- a/test/DeepONet/DeepONet.jl +++ b/test/DeepONet/DeepONet.jl @@ -1,3 +1,6 @@ +using NeuralOperators +using CUDA; CUDA.allowscalar(false) +using Flux @testset "DeepONet" begin @testset "proper construction" begin deeponet = DeepONet((32, 64, 72), (24, 48, 72), σ, tanh) diff --git a/test/FNO/FNO.jl b/test/FNO/FNO.jl index bfad873c..44d9a438 100644 --- a/test/FNO/FNO.jl +++ b/test/FNO/FNO.jl @@ -1,3 +1,5 @@ +using NeuralOperators +using Flux @testset "FourierNeuralOperator" begin m = FourierNeuralOperator() diff --git a/test/NOMAD/NOMAD.jl b/test/NOMAD/NOMAD.jl index 9b46296b..76ad3f99 100644 --- a/test/NOMAD/NOMAD.jl +++ b/test/NOMAD/NOMAD.jl @@ -1,3 +1,5 @@ +using NeuralOperators +using Flux @testset "NOMAD" begin @testset "proper construction" begin nomad = NOMAD((32, 64, 72), (24, 48, 72), σ, tanh) diff --git a/test/Transform/Transform.jl b/test/Transform/Transform.jl deleted file mode 100644 index d5ff9a67..00000000 --- a/test/Transform/Transform.jl +++ /dev/null @@ -1,4 +0,0 @@ -@testset "Transform" begin - include("fourier_transform.jl") - include("chebyshev_transform.jl") -end diff --git a/test/Transform/chebyshev_transform.jl b/test/Transform/chebyshev_transform.jl deleted file mode 100644 index b15ff9df..00000000 --- a/test/Transform/chebyshev_transform.jl +++ /dev/null @@ -1,16 +0,0 @@ -@testset "Chebyshev transform" begin - ch = 6 - batch = 7 - 𝐱 = rand(30, 40, 50, ch, batch) - - t = ChebyshevTransform((3, 4, 5)) - - @test ndims(t) == 3 - @test size(transform(t, 𝐱)) == (30, 40, 50, ch, batch) - @test size(truncate_modes(t, transform(t, 𝐱))) == (3, 4, 5, ch, batch) - @test size(inverse(t, truncate_modes(t, transform(t, 𝐱)), size(𝐱))) == - (3, 4, 5, ch, batch) - - g = gradient(x -> sum(inverse(t, truncate_modes(t, transform(t, x)), size(𝐱))), 𝐱) - @test size(g[1]) == (30, 40, 50, ch, batch) -end diff --git a/test/Transform/fourier_transform.jl b/test/Transform/fourier_transform.jl deleted file mode 100644 index 4583906a..00000000 --- a/test/Transform/fourier_transform.jl +++ /dev/null @@ -1,22 +0,0 @@ -@testset "Fourier transform" begin - ch = 6 - batch = 7 - 𝐱 = rand(30, 40, 50, ch, batch) - - ft = FourierTransform((3, 4, 5)) - - @test size(transform(ft, 𝐱)) == (16, 40, 50, ch, batch) - @test size(truncate_modes(ft, transform(ft, 𝐱))) == (3, 4, 5, ch, batch) - @test size(inverse(ft, - NeuralOperators.pad_modes(truncate_modes(ft, transform(ft, 𝐱)), - size(transform(ft, 𝐱))), - size(𝐱))) == (30, 40, 50, ch, batch) - - g = Zygote.gradient(x -> sum(inverse(ft, - NeuralOperators.pad_modes(truncate_modes(ft, - transform(ft, - x)), - (16, 40, 50, ch, batch)), - (30, 40, 50, ch, batch))), 𝐱) - @test size(g[1]) == (30, 40, 50, ch, batch) -end diff --git a/test/chebyshev_transform.jl b/test/chebyshev_transform.jl new file mode 100644 index 00000000..4ef84b80 --- /dev/null +++ b/test/chebyshev_transform.jl @@ -0,0 +1,13 @@ +using NeuralOperators +using Zygote +ch = 6 +batch = 7 +𝐱 = rand(30, 40, 50, ch, batch) +t = ChebyshevTransform((3, 4, 5)) +@test ndims(t) == 3 +@test size(transform(t, 𝐱)) == (30, 40, 50, ch, batch) +@test size(truncate_modes(t, transform(t, 𝐱))) == (3, 4, 5, ch, batch) +@test size(inverse(t, truncate_modes(t, transform(t, 𝐱)), size(𝐱))) == + (3, 4, 5, ch, batch) +g = gradient(x -> sum(inverse(t, truncate_modes(t, transform(t, x)), size(𝐱))), 𝐱) +@test size(g[1]) == (30, 40, 50, ch, batch) diff --git a/test/fourier_transform.jl b/test/fourier_transform.jl new file mode 100644 index 00000000..77f3e695 --- /dev/null +++ b/test/fourier_transform.jl @@ -0,0 +1,19 @@ +using NeuralOperators +using Zygote +ch = 6 +batch = 7 +𝐱 = rand(30, 40, 50, ch, batch) +ft = FourierTransform((3, 4, 5)) +@test size(transform(ft, 𝐱)) == (16, 40, 50, ch, batch) +@test size(truncate_modes(ft, transform(ft, 𝐱))) == (3, 4, 5, ch, batch) +@test size(inverse(ft, + NeuralOperators.pad_modes(truncate_modes(ft, transform(ft, 𝐱)), + size(transform(ft, 𝐱))), + size(𝐱))) == (30, 40, 50, ch, batch) +g = Zygote.gradient(x -> sum(inverse(ft, + NeuralOperators.pad_modes(truncate_modes(ft, + transform(ft, + x)), + (16, 40, 50, ch, batch)), + (30, 40, 50, ch, batch))), 𝐱) +@test size(g[1]) == (30, 40, 50, ch, batch) diff --git a/test/graph_kernel.jl b/test/graph_kernel.jl index 743257ce..b11b48e8 100644 --- a/test/graph_kernel.jl +++ b/test/graph_kernel.jl @@ -1,24 +1,26 @@ -@testset "GraphKernel" begin - batch_size = 5 - channel = 1 - coord_dim = 2 - N = 10 +using NeuralOperators +using Flux +using Graphs +using GeometricFlux +batch_size = 5 +channel = 1 +coord_dim = 2 +N = 10 - graph = grid([N, N]) - 𝐱 = rand(Float32, channel, nv(graph), batch_size) - κ = Dense(2(coord_dim + channel), abs2(channel), relu) - κ_in_dim, κ_out_dim = 2(coord_dim + channel), abs2(channel) +graph = grid([N, N]) +𝐱 = rand(Float32, channel, nv(graph), batch_size) +κ = Dense(2(coord_dim + channel), abs2(channel), relu) +κ_in_dim, κ_out_dim = 2(coord_dim + channel), abs2(channel) - @testset "layer without graph" begin - pf = rand(Float32, coord_dim, nv(graph), batch_size) - pf = vcat(𝐱, pf) - l = GraphKernel(κ, channel) - fg = FeaturedGraph(graph, nf = 𝐱, pf = pf) - fg_ = l(fg) - @test size(node_feature(fg_)) == (channel, nv(graph), batch_size) - @test_throws MethodError l(𝐱) +@testset "layer without graph" begin + pf = rand(Float32, coord_dim, nv(graph), batch_size) + pf = vcat(𝐱, pf) + l = GraphKernel(κ, channel) + fg = FeaturedGraph(graph, nf = 𝐱, pf = pf) + fg_ = l(fg) + @test size(node_feature(fg_)) == (channel, nv(graph), batch_size) + @test_throws MethodError l(𝐱) - g = gradient(() -> sum(node_feature(l(fg))), Flux.params(l)) - @test length(g.grads) == 5 - end + g = gradient(() -> sum(node_feature(l(fg))), Flux.params(l)) + @test length(g.grads) == 5 end diff --git a/test/loss.jl b/test/loss.jl index bac34f88..525d6762 100644 --- a/test/loss.jl +++ b/test/loss.jl @@ -1,10 +1,7 @@ -@testset "loss" begin - 𝐲 = rand(1, 3, 3, 5) - 𝐲̂ = rand(1, 3, 3, 5) - - feature_dims = 2:3 - loss = sum(.√(sum(abs2, 𝐲̂ - 𝐲, dims = feature_dims))) - y_norm = sum(.√(sum(abs2, 𝐲, dims = feature_dims))) - - @test l₂loss(𝐲̂, 𝐲) ≈ loss / y_norm -end +using NeuralOperators +𝐲 = rand(1, 3, 3, 5) +𝐲̂ = rand(1, 3, 3, 5) +feature_dims = 2:3 +loss = sum(.√(sum(abs2, 𝐲̂ - 𝐲, dims = feature_dims))) +y_norm = sum(.√(sum(abs2, 𝐲, dims = feature_dims))) +@test l₂loss(𝐲̂, 𝐲) ≈ loss / y_norm diff --git a/test/operator_kernel.jl b/test/operator_kernel.jl index c64396bc..33a77e26 100644 --- a/test/operator_kernel.jl +++ b/test/operator_kernel.jl @@ -1,3 +1,5 @@ +using NeuralOperators +using Flux @testset "1D OperatorConv" begin modes = (16,) ch = 64 => 128 diff --git a/test/qa.jl b/test/qa.jl new file mode 100644 index 00000000..6bdd5eda --- /dev/null +++ b/test/qa.jl @@ -0,0 +1,11 @@ +using NeuralOperators, Aqua +@testset "Aqua" begin + Aqua.find_persistent_tasks_deps(NeuralOperators) + Aqua.test_ambiguities(NeuralOperators, recursive = false) + Aqua.test_deps_compat(NeuralOperators) + Aqua.test_piracies(NeuralOperators, broken = true) + Aqua.test_project_extras(NeuralOperators) + Aqua.test_stale_deps(NeuralOperators) + Aqua.test_unbound_args(NeuralOperators) + Aqua.test_undefined_exports(NeuralOperators) +end diff --git a/test/runtests.jl b/test/runtests.jl index 6ffe561a..385c392d 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,24 +1,18 @@ -using NeuralOperators -using CUDA -using Flux -using GeometricFlux -using Graphs -using Zygote -using Test - -CUDA.allowscalar(false) +using SafeTestsets, Test @testset "NeuralOperators.jl" begin + @safetestset "Quality Assurance" include("qa.jl") # kernels - include("Transform/Transform.jl") - include("operator_kernel.jl") - include("graph_kernel.jl") - include("loss.jl") + @safetestset "Fourier Transform" include("fourier_transform.jl") + @safetestset "Chebyshev Transform" include("chebyshev_transform.jl") + @safetestset "operator_kernel.jl" include("operator_kernel.jl") + @safetestset "graph kernel" include("graph_kernel.jl") + @safetestset "loss" include("loss.jl") # models - include("FNO/FNO.jl") - include("DeepONet/DeepONet.jl") - include("NOMAD/NOMAD.jl") + @safetestset "FNO" include("FNO/FNO.jl") + @safetestset "DeepONet" include("DeepONet/DeepONet.jl") + @safetestset "NOMAD" include("NOMAD/NOMAD.jl") end #=