├── .buildkite └── pipeline.yml ├── .github └── workflows │ ├── CI.yml │ ├── CompatHelper.yml │ └── TagBot.yml ├── .gitignore ├── CITATION.cff ├── CONTRIBUTING.md ├── LICENSE.md ├── Project.toml ├── README.md ├── codecov.yml ├── docs ├── Project.toml ├── make.jl └── src │ ├── aliasing.md │ ├── assets │ └── README.md │ ├── contributing.md │ ├── gpu.md │ ├── index.md │ ├── installation_instructions.md │ ├── lib │ ├── functions.md │ └── types.md │ ├── modules │ ├── barotropicqgql.md │ ├── multilayerqg.md │ ├── singlelayerqg.md │ ├── surfaceqg.md │ └── twodnavierstokes.md │ ├── references.bib │ ├── references.md │ ├── stochastic_forcing.md │ └── visualize.md ├── examples ├── Project.toml ├── README.md ├── barotropicqgql_betaforced.jl ├── multilayerqg_2layer.jl ├── singlelayerqg_betadecay.jl ├── singlelayerqg_betaforced.jl ├── singlelayerqg_decaying_barotropic_equivalentbarotropic.jl ├── singlelayerqg_decaying_topography.jl ├── surfaceqg_decaying.jl ├── twodnavierstokes_decaying.jl ├── twodnavierstokes_stochasticforcing.jl └── twodnavierstokes_stochasticforcing_budgets.jl ├── paper ├── PV_eady_nlayers5.png ├── paper.bib └── paper.md ├── src ├── GeophysicalFlows.jl ├── barotropicqgql.jl ├── multilayerqg.jl ├── singlelayerqg.jl ├── surfaceqg.jl ├── twodnavierstokes.jl └── utils.jl └── test ├── runtests.jl ├── test_barotropicqgql.jl ├── test_multilayerqg.jl ├── test_singlelayerqg.jl ├── test_surfaceqg.jl ├── test_twodnavierstokes.jl └── test_utils.jl /.buildkite/pipeline.yml: -------------------------------------------------------------------------------- 1 | env: 2 | GKSwstype: "100" # See: https://github.com/jheinen/GR.jl/issues/278 3 | SECRET_CODECOV_TOKEN: "Ak2mVTxXnhkPNc096ImDdp7bOc4zGNTqFEEDaGMwAgYPr28g5dyMbslh8B/ad4NQHXVL1MXQ3zrUfGgMBRq+mmqRaAe13FI4Go9uCas6bzdZXE23ExiLzBmqVRNRf8GqEcpGL7BBreohC0cnfI0SVMiIJDCJXX9YsXJtlcpk1glQFMEFI5V6cpFe9K2l5xoUNQ4179ZYoJUMAy/aylQx/UdQuw527FjHQUsi5/dFtWzMqeys0secNa9alLvJCQdIX9OqPjmBYvuIIVXCR7vlZoH8PgXwEj7wbdp8/V31+wlLQI9WePcsJxoOybtLTOlwwfw4jWLAttDZYqnqiLVp3Q==;U2FsdGVkX18sNLCManU1B/jI5kh4LhSi69MFXljHSp9yWrN7u5d196K/XrELwb8ksbamyKeHjIvDIopwD55dbw==" 4 | 5 | steps: 6 | - label: "🥑 Julia 1.10" 7 | plugins: 8 | - JuliaCI/julia#v1: 9 | version: '1.10' 10 | - JuliaCI/julia-test#v1: ~ 11 | agents: 12 | queue: "juliagpu" 13 | cuda: "*" 14 | if: build.message !~ /\[skip tests\]/ 15 | timeout_in_minutes: 60 16 | 17 | - label: "🥝 Julia 1.11" 18 | plugins: 19 | - JuliaCI/julia#v1: 20 | version: '1.11' 21 | - JuliaCI/julia-test#v1: ~ 22 | - JuliaCI/julia-coverage#v1: 23 | codecov: true 24 | agents: 25 | queue: "juliagpu" 26 | cuda: "*" 27 | if: build.message !~ /\[skip tests\]/ 28 | timeout_in_minutes: 60 29 | -------------------------------------------------------------------------------- /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: [main] 5 | tags: ["*"] 6 | pull_request: 7 | jobs: 8 | test: 9 | name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ github.event_name }} 10 | runs-on: ${{ matrix.os }} 11 | strategy: 12 | fail-fast: false 13 | matrix: 14 | version: 15 | - "1.10" # LTS 16 | - "1.11" 17 | os: 18 | - macOS-latest 19 | - windows-latest 20 | arch: 21 | - x64 22 | - aarch64 23 | - arm64 24 | exclude: 25 | # Test 32-bit only on Linux 26 | - os: macOS-latest 27 | arch: x64 28 | - os: windows-latest 29 | arch: aarch64 30 | - os: windows-latest 31 | arch: arm64 32 | steps: 33 | - uses: actions/checkout@v4 34 | - uses: julia-actions/setup-julia@v2 35 | with: 36 | version: ${{ matrix.version }} 37 | arch: ${{ matrix.arch }} 38 | - uses: julia-actions/cache@v2 39 | - uses: julia-actions/julia-buildpkg@v1 40 | - uses: julia-actions/julia-runtest@v1 41 | docs: 42 | name: Documentation 43 | runs-on: ubuntu-latest 44 | steps: 45 | - uses: actions/checkout@v4 46 | - uses: julia-actions/setup-julia@v2 47 | with: 48 | version: '1.11' 49 | - uses: julia-actions/julia-buildpkg@latest 50 | - uses: julia-actions/julia-docdeploy@latest 51 | env: 52 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 53 | DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }} 54 | JULIA_DEBUG: Documenter 55 | -------------------------------------------------------------------------------- /.github/workflows/CompatHelper.yml: -------------------------------------------------------------------------------- 1 | name: CompatHelper 2 | on: 3 | schedule: 4 | - cron: 0 0 * * * 5 | workflow_dispatch: 6 | permissions: 7 | contents: write 8 | pull-requests: write 9 | jobs: 10 | CompatHelper: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Check if Julia is already available in the PATH 14 | id: julia_in_path 15 | run: which julia 16 | continue-on-error: true 17 | - name: Install Julia, but only if it is not already available in the PATH 18 | uses: julia-actions/setup-julia@v2 19 | with: 20 | version: '1' 21 | # arch: ${{ runner.arch }} 22 | if: steps.julia_in_path.outcome != 'success' 23 | - name: "Add the General registry via Git" 24 | run: | 25 | import Pkg 26 | ENV["JULIA_PKG_SERVER"] = "" 27 | Pkg.Registry.add("General") 28 | shell: julia --color=yes {0} 29 | - name: "Install CompatHelper" 30 | run: | 31 | import Pkg 32 | name = "CompatHelper" 33 | uuid = "aa819f21-2bde-4658-8897-bab36330d9b7" 34 | version = "3" 35 | Pkg.add(; name, uuid, version) 36 | shell: julia --color=yes {0} 37 | - name: "Run CompatHelper" 38 | run: | 39 | import CompatHelper 40 | CompatHelper.main() 41 | shell: julia --color=yes {0} 42 | env: 43 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 44 | COMPATHELPER_PRIV: ${{ secrets.DOCUMENTER_KEY }} 45 | # COMPATHELPER_PRIV: ${{ secrets.COMPATHELPER_PRIV }} 46 | -------------------------------------------------------------------------------- /.github/workflows/TagBot.yml: -------------------------------------------------------------------------------- 1 | name: TagBot 2 | on: 3 | issue_comment: 4 | types: 5 | - created 6 | workflow_dispatch: 7 | jobs: 8 | TagBot: 9 | if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot' 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: JuliaRegistries/TagBot@v1 13 | with: 14 | token: ${{ secrets.TAGBOT_PAT }} 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Julia stuff 2 | *.jl.cov 3 | *.jl.*.cov 4 | *.jl.mem 5 | coverage/ 6 | **/.nfs* 7 | 8 | # Swap files. 9 | *.swp 10 | *~ 11 | 12 | # Documenter.jl and MkDocs 13 | docs/build/ 14 | docs/site/ 15 | docs/src/literated 16 | docs/src/examples 17 | docs/Manifest.toml 18 | 19 | # Model output 20 | **/*.jld 21 | **/*.jld2 22 | **/*.mat 23 | 24 | # Plots and movies 25 | **/*.png 26 | **/*.mp4 27 | **/*.pdf 28 | **/.nfs* 29 | **/*.cov 30 | **/Manifest.toml 31 | 32 | docs/build/ 33 | docs/site/ 34 | coverage/ 35 | 36 | # Jupyter Lab stuff and notebooks 37 | *.ipynb 38 | .ipynb_checkpoints 39 | 40 | # MacOS stuff 41 | *.DS_Store 42 | 43 | # VS code 44 | *.vscode -------------------------------------------------------------------------------- /CITATION.cff: -------------------------------------------------------------------------------- 1 | cff-version: 1.2.0 2 | preferred-citation: 3 | type: article 4 | authors: 5 | - family-names: "Constantinou" 6 | given-names: "Navid C." 7 | orcid: "https://orcid.org/0000-0002-8149-4094" 8 | - family-names: "Wagner" 9 | given-names: "Gregory LeClaire" 10 | orcid: "https://orcid.org/0000-0001-5317-2445" 11 | - family-names: "Siegelman" 12 | given-names: "Lia" 13 | orcid: "https://orcid.org/0000-0003-3330-082X" 14 | - family-names: "Pearson" 15 | given-names: "Brodie C." 16 | orcid: "https://orcid.org/0000-0002-0202-0481" 17 | - family-names: "Palóczy" 18 | given-names: "André" 19 | orcid: "https://orcid.org/0000-0001-8231-8298" 20 | title: "GeophysicalFlows.jl: Solvers for geophysical fluid dynamics problems in periodic domains on CPUs & GPUs" 21 | journal: "Journal of Open Source Software" 22 | doi: "10.21105/joss.03053" 23 | volume: 6 24 | issue: 60 25 | start: 3053 26 | year: 2021 27 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributors' Guide 2 | 3 | Thank you for considering contributing to GeophysicalFlows.jl! 4 | 5 | Please feel free to ask us questions and chat with us at any time if you're 6 | unsure about anything. 7 | 8 | Best way to get in touch is to either just open a GitHub [issue](https://github.com/FourierFlows/GeophysicalFlows.jl/issues) 9 | (don't be shy!) or starting a [discussion](https://github.com/FourierFlows/GeophysicalFlows.jl/discussions). 10 | 11 | We follow the [ColPrac guide](https://github.com/SciML/ColPrac) for collaborative 12 | practices. New contributors should make sure to read that guide. 13 | 14 | ## What can I do? 15 | 16 | * Tackle an existing [issue](https://github.com/FourierFlows/GeophysicalFlows.jl/issues). 17 | 18 | * Try to run your favorite GeophysicalFlows.jl module and play around with it to simulate 19 | your particular favorite setup. If you run into any problems or find it difficult 20 | to use, modify, or understand, please [open an issue](https://github.com/FourierFlows/GeophysicalFlows.jl/issues)! 21 | 22 | * Write up an example or tutorial on how to do something useful with one of the current modules 23 | in GeophysicalFlows.jl, like how to set up a new physical configuration. 24 | 25 | * Improve documentation, docstrings, or comments if you found something is hard to use. 26 | 27 | * Implement a new feature (e.g., a new diagnostic into a module). 28 | 29 | * Implement a new module from scratch to solve your favorite partial differential equation with 30 | periodic boundary conditions. 31 | 32 | If you're interested in working on something, let us know by commenting on existing issues or 33 | by opening a new issue if. This is to make sure no one else is working on the same issue and 34 | so we can help and guide you in case there is anything you need to know beforehand. 35 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021: Navid C. Constantinou and Gregory L. Wagner 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Project.toml: -------------------------------------------------------------------------------- 1 | name = "GeophysicalFlows" 2 | uuid = "44ee3b1c-bc02-53fa-8355-8e347616e15e" 3 | license = "MIT" 4 | authors = ["Navid C. Constantinou ", "Gregory L. Wagner ", "and contributors"] 5 | version = "0.16.4" 6 | 7 | [deps] 8 | CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" 9 | DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" 10 | FFTW = "7a1cc6ca-52ef-59f5-83cd-3a7055c09341" 11 | FourierFlows = "2aec4490-903f-5c70-9b11-9bed06a700e1" 12 | JLD2 = "033835bb-8acc-5ee8-8aae-3f567f8a3819" 13 | KernelAbstractions = "63c18a36-062a-441e-b654-da1e3ab1ce7c" 14 | LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" 15 | Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" 16 | Reexport = "189a3867-3050-52da-a836-e630ba90ab69" 17 | SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b" 18 | StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" 19 | Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" 20 | 21 | [compat] 22 | CUDA = "1, 2.4.2, 3.0.0 - 3.6.4, 3.7.1, 4, 5" 23 | DocStringExtensions = "0.8, 0.9" 24 | FFTW = "1" 25 | FourierFlows = "0.10.5" 26 | JLD2 = "0.1, 0.2, 0.3, 0.4, 0.5" 27 | KernelAbstractions = "0.9" 28 | LinearAlgebra = "1.6" 29 | Random = "1.6" 30 | Reexport = "0.2, 1" 31 | SpecialFunctions = "0.10, 1, 2" 32 | StaticArrays = "0.12, 1" 33 | Statistics = "1.6" 34 | julia = "1.6" 35 | 36 | [extras] 37 | BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" 38 | Coverage = "a2441757-f6aa-5fb2-8edb-039e3f45d037" 39 | Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" 40 | 41 | [targets] 42 | test = ["Test", "Coverage", "BenchmarkTools"] 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GeophysicalFlows.jl 2 | 3 | 4 |

5 | 💨🌏🌊 Geophysical fluid dynamics pseudospectral solvers with Julia and FourierFlows.jl. https://fourierflows.github.io/GeophysicalFlowsDocumentation/stable 6 |

7 | 8 | 9 |

10 | 11 | Buildkite CPU+GPU build status 12 | 13 | 14 | CI Status 15 | 16 | 17 | stable docs 18 | 19 | 20 | latest docs 21 | 22 | 23 | 24 | 25 | 26 | DOI 27 | 28 | 29 | ColPrac: Contributor's Guide on Collaborative Practices for Community Packages 30 | 31 | 32 | DOI badge 33 | 34 |

35 | 36 | This package leverages the [FourierFlows.jl] framework to provide modules for solving problems in 37 | Geophysical Fluid Dynamics on periodic domains using Fourier-based pseudospectral methods. 38 | 39 | 40 | ## Installation 41 | 42 | To install, use Julia's built-in package manager (accessed by pressing `]` in the Julia REPL command prompt) to add the package and also to instantiate/build all the required dependencies 43 | 44 | ```julia 45 | julia> ] 46 | (v1.10) pkg> add GeophysicalFlows 47 | (v1.10) pkg> instantiate 48 | ``` 49 | 50 | GeophysicalFlows.jl requires Julia v1.6 or later. However, the package has continuous integration testing on 51 | Julia v1.10 (the current long-term release) and v1.11. _We strongly urge you to use one of these Julia versions._ 52 | 53 | ## Examples 54 | 55 | See `examples/` for example scripts. These examples are best viewed by browsing them within 56 | the package's [documentation]. 57 | 58 | Some animations created with GeophysicalFlows.jl are [online @ youtube]. 59 | 60 | 61 | ## Modules 62 | 63 | * [`TwoDNavierStokes`](https://fourierflows.github.io/GeophysicalFlowsDocumentation/stable/modules/twodnavierstokes/): the 64 | two-dimensional vorticity equation. 65 | * [`SingleLayerQG`](https://fourierflows.github.io/GeophysicalFlowsDocumentation/stable/modules/singlelayerqg/): the barotropic 66 | or equivalent-barotropic quasi-geostrophic equation, which generalizes `TwoDNavierStokes` to cases with topography, Coriolis 67 | parameters of the form `f = f₀ + βy`, and finite Rossby radius of deformation. 68 | * [`MultiLayerQG`](https://fourierflows.github.io/GeophysicalFlowsDocumentation/stable/modules/multilayerqg/): a multi-layer 69 | quasi-geostrophic model over topography allowing to impose a zonal flow `U_n(y)` in each layer. 70 | * [`SurfaceQG`](https://fourierflows.github.io/GeophysicalFlowsDocumentation/stable/modules/surfaceqg/): a surface 71 | quasi-geostrophic model. 72 | * [`BarotropicQGQL`](https://fourierflows.github.io/GeophysicalFlowsDocumentation/stable/modules/barotropicqgql/): the 73 | quasi-linear barotropic quasi-geostrophic equation. 74 | 75 | 76 | ## Scalability 77 | 78 | For now, GeophysicalFlows.jl is restricted to run on either a single CPU or single GPU. These 79 | restrictions come from FourierFlows.jl. Multi-threading can enhance performance for the Fourier 80 | transforms. By default, FourierFlows.jl will use the maximum number of threads available on 81 | your machine. You can set the number of threads used by FourierFlows.jl by setting the 82 | environment variable, e.g., 83 | 84 | ``` 85 | $ export JULIA_NUM_THREADS=4 86 | ``` 87 | 88 | For more information on multi-threading users are directed to the [Julia Documentation](https://docs.julialang.org/en/v1/manual/multi-threading/). 89 | 90 | If your machine has more than one GPU available, then functionality within CUDA.jl package 91 | enables the user to choose the GPU device that FourierFlows.jl should use. The user is referred 92 | to the [CUDA.jl Documentation](https://juliagpu.github.io/CUDA.jl/stable/lib/driver/#Device-Management); 93 | in particular, [`CUDA.devices`](https://juliagpu.github.io/CUDA.jl/stable/lib/driver/#CUDA.devices) 94 | and [`CUDA.CuDevice`](https://juliagpu.github.io/CUDA.jl/stable/lib/driver/#CUDA.CuDevice). 95 | The user is also referred to the [GPU section](https://fourierflows.github.io/FourierFlowsDocumentation/stable/gpu/) in the FourierFlows.jl documentation. 96 | 97 | 98 | ## Getting help 99 | 100 | Interested in GeophysicalFlows.jl or trying to figure out how to use it? Please feel free to 101 | ask us questions and get in touch! Check out the 102 | [examples](https://github.com/FourierFlows/GeophysicalFlows.jl/tree/main/examples) and 103 | [open an issue](https://github.com/FourierFlows/GeophysicalFlows.jl/issues/new) or 104 | [start a discussion](https://github.com/FourierFlows/GeophysicalFlows.jl/discussions/new) 105 | if you have any questions, comments, suggestions, etc. 106 | 107 | 108 | ## Citing 109 | 110 | If you use GeophysicalFlows.jl in research, teaching, or other activities, we would be grateful 111 | if you could mention GeophysicalFlows.jl and cite our paper in JOSS: 112 | 113 | > Constantinou et al., (2021). GeophysicalFlows.jl: Solvers for geophysical fluid dynamics problems in periodic domains on CPUs & GPUs. _Journal of Open Source Software_, **6(60)**, 3053, doi:[10.21105/joss.03053](https://doi.org/10.21105/joss.03053). 114 | 115 | The bibtex entry for the paper is: 116 | 117 | ```bibtex 118 | @article{GeophysicalFlowsJOSS, 119 | doi = {10.21105/joss.03053}, 120 | url = {https://doi.org/10.21105/joss.03053}, 121 | year = {2021}, 122 | publisher = {The Open Journal}, 123 | volume = {6}, 124 | number = {60}, 125 | pages = {3053}, 126 | author = {Navid C. Constantinou and Gregory LeClaire Wagner and Lia Siegelman and Brodie C. Pearson and André Palóczy}, 127 | title = {{GeophysicalFlows.jl: Solvers for geophysical fluid dynamics problems in periodic domains on CPUs \& GPUs}}, 128 | journal = {Journal of Open Source Software} 129 | } 130 | ``` 131 | 132 | 133 | ## Contributing 134 | 135 | If you're interested in contributing to the development of GeophysicalFlows.jl we are excited 136 | to get your help, no matter how big or small a contribution you make! It's always great to have 137 | new people look at the code with fresh eyes: you will see errors that other developers have missed. 138 | 139 | Let us know by [open an issue](https://github.com/FourierFlows/GeophysicalFlows.jl/issues/new) 140 | or [start a discussion](https://github.com/FourierFlows/GeophysicalFlows.jl/discussions/new) 141 | if you'd like to work on a new feature or implement a new module, if you're new to open-source 142 | and want to find a cool little project or issue to work on that fits your interests! We're more 143 | than happy to help along the way. 144 | 145 | For more information, check out our [contributors' guide](https://github.com/FourierFlows/GeophysicalFlows.jl/blob/main/CONTRIBUTING.md). 146 | 147 | [FourierFlows.jl]: https://github.com/FourierFlows/FourierFlows.jl 148 | [documentation]: https://fourierflows.github.io/GeophysicalFlowsDocumentation/dev/ 149 | [online @ youtube]: https://www.youtube.com/channel/UCO_0ugkNUwCsFUMtepwYTqw 150 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | range: 50..90 3 | round: down 4 | precision: 2 5 | 6 | ignore: 7 | - "docs" 8 | - "examples" 9 | - "test" 10 | 11 | status: 12 | project: 13 | default: 14 | target: auto 15 | threshold: 5% 16 | patch: 17 | default: 18 | target: auto 19 | threshold: 20% 20 | 21 | comment: false -------------------------------------------------------------------------------- /docs/Project.toml: -------------------------------------------------------------------------------- 1 | [deps] 2 | CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" 3 | CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" 4 | Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" 5 | DocumenterCitations = "daee34ce-89f3-4625-b898-19384cb65244" 6 | JLD2 = "033835bb-8acc-5ee8-8aae-3f567f8a3819" 7 | Literate = "98b081ad-f1c9-55d3-8b20-4c87d4299306" 8 | Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" 9 | 10 | [compat] 11 | CUDA = "1, 2.4.2, 3.0.0 - 3.6.4, 3.7.1, 4, 5" 12 | CairoMakie = "0.11, 0.12, 0.13" 13 | Documenter = "1" 14 | DocumenterCitations = "1.2" 15 | Literate = "≥2.9.0" 16 | -------------------------------------------------------------------------------- /docs/make.jl: -------------------------------------------------------------------------------- 1 | using Documenter, DocumenterCitations, Literate 2 | 3 | using CairoMakie 4 | 5 | using GeophysicalFlows 6 | 7 | ##### 8 | ##### Generate literated examples 9 | ##### 10 | 11 | const EXAMPLES_DIR = joinpath(@__DIR__, "..", "examples") 12 | const OUTPUT_DIR = joinpath(@__DIR__, "src/literated") 13 | 14 | examples = [ 15 | "twodnavierstokes_decaying.jl", 16 | "twodnavierstokes_stochasticforcing.jl", 17 | "twodnavierstokes_stochasticforcing_budgets.jl", 18 | "singlelayerqg_betadecay.jl", 19 | "singlelayerqg_betaforced.jl", 20 | "singlelayerqg_decaying_topography.jl", 21 | "singlelayerqg_decaying_barotropic_equivalentbarotropic.jl", 22 | "barotropicqgql_betaforced.jl", 23 | "multilayerqg_2layer.jl", 24 | "surfaceqg_decaying.jl", 25 | ] 26 | 27 | for example in examples 28 | withenv("GITHUB_REPOSITORY" => "FourierFlows/GeophysicalFlowsDocumentation") do 29 | example_filepath = joinpath(EXAMPLES_DIR, example) 30 | withenv("JULIA_DEBUG" => "Literate") do 31 | Literate.markdown(example_filepath, OUTPUT_DIR; 32 | flavor = Literate.DocumenterFlavor(), execute = true) 33 | end 34 | end 35 | end 36 | 37 | 38 | ##### 39 | ##### Build and deploy docs 40 | ##### 41 | 42 | format = Documenter.HTML( 43 | collapselevel = 2, 44 | prettyurls = get(ENV, "CI", nothing) == "true", 45 | size_threshold = 2^21, 46 | canonical = "https://fourierflows.github.io/GeophysicalFlowsDocumentation/stable/" 47 | ) 48 | 49 | bib_filepath = joinpath(dirname(@__FILE__), "src/references.bib") 50 | bib = CitationBibliography(bib_filepath, style=:authoryear) 51 | 52 | makedocs( 53 | authors = "Navid C. Constantinou, Gregory L. Wagner, and contributors", 54 | sitename = "GeophysicalFlows.jl", 55 | modules = [GeophysicalFlows], 56 | plugins = [bib], 57 | format = format, 58 | doctest = true, 59 | clean = true, 60 | checkdocs = :all, 61 | pages = Any[ 62 | "Home" => "index.md", 63 | "Installation instructions" => "installation_instructions.md", 64 | "Aliasing" => "aliasing.md", 65 | "GPU" => "gpu.md", 66 | "Visualize output" => "visualize.md", 67 | "Examples" => [ 68 | "TwoDNavierStokes" => Any[ 69 | "literated/twodnavierstokes_decaying.md", 70 | "literated/twodnavierstokes_stochasticforcing.md", 71 | "literated/twodnavierstokes_stochasticforcing_budgets.md", 72 | ], 73 | "SingleLayerQG" => Any[ 74 | "literated/singlelayerqg_betadecay.md", 75 | "literated/singlelayerqg_betaforced.md", 76 | "literated/singlelayerqg_decaying_topography.md", 77 | "literated/singlelayerqg_decaying_barotropic_equivalentbarotropic.md" 78 | ], 79 | "BarotropicQGQL" => Any[ 80 | "literated/barotropicqgql_betaforced.md", 81 | ], 82 | "MultiLayerQG" => Any[ 83 | "literated/multilayerqg_2layer.md" 84 | ], 85 | "SurfaceQG" => Any[ 86 | "literated/surfaceqg_decaying.md" 87 | ] 88 | ], 89 | "Modules" => Any[ 90 | "modules/twodnavierstokes.md", 91 | "modules/singlelayerqg.md", 92 | "modules/barotropicqgql.md", 93 | "modules/multilayerqg.md", 94 | "modules/surfaceqg.md" 95 | ], 96 | "Stochastic forcing" => "stochastic_forcing.md", 97 | "Contributor's guide" => "contributing.md", 98 | "References" => "references.md", 99 | "Library" => Any[ 100 | "lib/types.md", 101 | "lib/functions.md" 102 | ] 103 | ] 104 | ) 105 | 106 | @info "Clean up temporary .jld2 and .nc output created by doctests or literated examples..." 107 | 108 | """ 109 | recursive_find(directory, pattern) 110 | 111 | Return list of filepaths within `directory` that contains the `pattern::Regex`. 112 | """ 113 | recursive_find(directory, pattern) = 114 | mapreduce(vcat, walkdir(directory)) do (root, dirs, files) 115 | joinpath.(root, filter(contains(pattern), files)) 116 | end 117 | 118 | files = [] 119 | for pattern in [r"\.jld2", r"\.nc"] 120 | global files = vcat(files, recursive_find(@__DIR__, pattern)) 121 | end 122 | 123 | for file in files 124 | rm(file) 125 | end 126 | 127 | # Replace with below once https://github.com/JuliaDocs/Documenter.jl/pull/2692 is merged and available. 128 | # deploydocs(repo = "github.com/FourierFlows/FourierFlows.jl", 129 | # deploy_repo = "github.com/FourierFlows/FourierFlowsDocumentation", 130 | # devbranch = "main", 131 | # forcepush = true, 132 | # push_preview = true, 133 | # versions = ["stable" => "v^", "dev" => "dev", "v#.#.#"]) 134 | 135 | if get(ENV, "GITHUB_EVENT_NAME", "") == "pull_request" 136 | deploydocs(repo = "github.com/FourierFlows/GeophysicalFlows.jl", 137 | repo_previews = "github.com/FourierFlows/GeophysicalFlowsDocumentation", 138 | devbranch = "main", 139 | forcepush = true, 140 | push_preview = true, 141 | versions = ["stable" => "v^", "dev" => "dev", "v#.#.#"]) 142 | else 143 | repo = "github.com/FourierFlows/GeophysicalFlowsDocumentation" 144 | withenv("GITHUB_REPOSITORY" => repo) do 145 | deploydocs(; repo, 146 | devbranch = "main", 147 | forcepush = true, 148 | versions = ["stable" => "v^", "dev" => "dev", "v#.#.#"]) 149 | end 150 | end 151 | -------------------------------------------------------------------------------- /docs/src/aliasing.md: -------------------------------------------------------------------------------- 1 | # Aliasing 2 | 3 | 4 | In pseudospectral methods, computing nonlinear terms results in aliasing errors. (Read more about 5 | aliasing errors in the [FourierFlows.jl Documentation](https://fourierflows.github.io/FourierFlowsDocumentation/stable/aliasing/).) To avoid aliasing errors, we need to apply some dealiasing to our fields 6 | in Fourier space before transforming to physical space to compute nonlinear terms. 7 | 8 | !!! info "De-aliasing scheme" 9 | FourierFlows.jl currently implements dealiasing by zeroing out the highest-`aliased_fraction` 10 | wavenumber components on a `grid`. By default in FourierFlows.jl, `aliased_fraction=1/3`. 11 | Users can construct a `grid` with different `aliased_fraction` via 12 | 13 | ```julia 14 | julia> grid = OneDGrid(; nx=64, Lx=2π, aliased_fraction=1/2) 15 | 16 | julia> OneDimensionalGrid 17 | ├─────────── Device: CPU 18 | ├──────── FloatType: Float64 19 | ├────────── size Lx: 6.283185307179586 20 | ├──── resolution nx: 64 21 | ├── grid spacing dx: 0.09817477042468103 22 | ├─────────── domain: x ∈ [-3.141592653589793, 3.0434178831651124] 23 | └─ aliased fraction: 0.5 24 | ``` 25 | or provide the keyword argument `aliased_fraction` to the `Problem()` constructor of each 26 | module, e.g., 27 | 28 | ```julia 29 | julia> prob = GeophysicalFlows.TwoDNavierStokes.Problem(; aliased_fraction=1/2) 30 | Problem 31 | ├─────────── grid: grid (on CPU) 32 | ├───── parameters: params 33 | ├────── variables: vars 34 | ├─── state vector: sol 35 | ├─────── equation: eqn 36 | ├────────── clock: clock 37 | └──── timestepper: RK4TimeStepper 38 | 39 | julia> prob.grid.aliased_fraction 40 | 0.5 41 | ``` 42 | 43 | Currently, all nonlinearities in all modules included in GeophysicalFlows.jl modules are quadratic 44 | nonlinearities. Therefore, the default `aliased_fraction` of 1/3 is appropriate. 45 | 46 | All modules apply de-aliasing by calling, e.g., `dealias!(prob.sol, prob.grid)` both before 47 | computing any nonlinear terms and also during updating all variable, i.e., within `updatevars!`. 48 | 49 | To disable de-aliasing you need to create a problem with a grid that has been constructed with 50 | the keyword `aliased_fraction=0`. 51 | -------------------------------------------------------------------------------- /docs/src/assets/README.md: -------------------------------------------------------------------------------- 1 | ### a placeholder directory for output generated by Docs -------------------------------------------------------------------------------- /docs/src/contributing.md: -------------------------------------------------------------------------------- 1 | # Contributors' Guide 2 | 3 | This is a short guide for potential GeophysicalFlows.jl contributors. 4 | 5 | Please feel free to ask us questions and chat, either by raising an [issue](https://github.com/FourierFlows/GeophysicalFlows.jl/issues) or starting a [discussion](https://github.com/FourierFlows/GeophysicalFlows.jl/discussions). 6 | 7 | We follow the [ColPrac guide](https://github.com/SciML/ColPrac) for collaborative practices. 8 | New contributors should make sure to read that guide. 9 | 10 | ## What can I do? 11 | 12 | * Tackle an existing [issue](https://github.com/FourierFlows/GeophysicalFlows.jl/issues). 13 | 14 | * Try to run your favorite GeophysicalFlows.jl module and play around with it to simulate 15 | your favorite setup. If you run into any problems or find it difficult to use, modify, or 16 | understand, please [open an issue](https://github.com/FourierFlows/GeophysicalFlows.jl/issues)! 17 | 18 | * Write up an example or tutorial on how to do something useful with one of the current modules 19 | in GeophysicalFlows.jl, like how to set up a new physical configuration. 20 | 21 | * Improve documentation, docstrings, or comments if you found something is hard to use. 22 | 23 | * Implement a new feature (e.g., a new diagnostic into a module). 24 | 25 | * Implement a new module from scratch to solve your favorite partial differential equation with 26 | periodic boundary conditions. 27 | 28 | If you're interested in working on something, let us know by commenting on an existing issue 29 | or by opening a new issue. This is to make sure no one else is working on the same issue and 30 | so we can help and guide you in case there is anything you need to know beforehand. 31 | 32 | ## Ground Rules 33 | 34 | * Each pull request should consist of a logical collection of changes. You can 35 | include multiple bug fixes in a single pull request, but they should be related. 36 | For unrelated changes, please submit multiple pull requests. 37 | * Do not commit changes to files that are irrelevant to your feature or bugfix 38 | (e.g., `.gitignore`). 39 | * Be willing to accept criticism and work on improving your code; we don't want 40 | to break other users' code, so care must be taken not to introduce bugs. We 41 | discuss pull requests and keep working on them until we believe we've done a 42 | good job. 43 | * Be aware that the pull request review process is not immediate, and is 44 | generally proportional to the size of the pull request. 45 | 46 | ## Reporting a bug 47 | 48 | The easiest way to get involved is to report issues you encounter when using GeophysicalFlows.jl 49 | or by requesting something you think is missing. 50 | 51 | * Head over to the [issues](https://github.com/FourierFlows/GeophysicalFlows.jl/issues) page. 52 | * Search to see if your issue already exists or has even been solved previously. 53 | * If you indeed have a new issue or request, click the "New Issue" button. 54 | * Please be as specific as possible. Include the version of the code you were using, as 55 | well as what operating system you are running. The output of Julia's `versioninfo()` 56 | and `] status` is helpful to include. If possible, include complete, minimal example 57 | code that reproduces the problem. 58 | 59 | ## Setting up your development environment 60 | 61 | * Install [Julia](https://julialang.org/) on your system. 62 | * Install git on your system if it is not already there (install XCode command line tools on 63 | a Mac or git bash on Windows). 64 | * Login to your GitHub account and make a fork of the 65 | [GeophysicalFlows.jl repository](https://github.com/FourierFlows/GeophysicalFlows.jl) by 66 | clicking the "Fork" button. 67 | * Clone your fork of the GeophysicalFlows.jl repository (in terminal on Mac/Linux or git shell/ 68 | GUI on Windows) in the location you'd like to keep it. 69 | ``` 70 | git clone https://github.com/your-user-name/GeophysicalFlows.jl.git 71 | ``` 72 | * Navigate to that folder in the terminal or in Anaconda Prompt if you're on Windows. 73 | * Connect your repository to the upstream (main project). 74 | ``` 75 | git remote add geophysicalflows https://github.com/FourierFlows/GeophysicalFlows.jl.git 76 | ``` 77 | * Create the development environment by opening Julia via `julia --project` then 78 | typing in `] instantiate`. This will install all the dependencies in the `Project.toml` 79 | file. 80 | * You can test to make sure GeophysicalFlows.jl works by typing in `] test` which will run all 81 | the tests (this can take a while). In an ideal world you should run the tests on a machine 82 | with a GPU capability but if that's not a possibility that is available to you then don't 83 | worry -- simply comment in a PR that you didn't test on GPU. 84 | 85 | Your development environment is now ready! 86 | 87 | ## Pull Requests 88 | 89 | Changes and contributions should be made via GitHub pull requests against the `main` branch. 90 | 91 | When you're done making changes, commit the changes you made. Chris Beams has written 92 | a [guide](https://chris.beams.io/posts/git-commit/) on how to write good commit messages. 93 | 94 | When you think your changes are ready to be merged into the main repository, 95 | push to your fork and [submit a pull request](https://github.com/FourierFlows/GeophysicalFlows.jl/compare/). 96 | 97 | **Working on your first Pull Request?** You can learn how from this _free_ video series 98 | [How to Contribute to an Open Source Project on GitHub](https://egghead.io/courses/how-to-contribute-to-an-open-source-project-on-github), Aaron Meurer's [tutorial on the git workflow](https://www.asmeurer.com/git-workflow/), 99 | or the guide [“How to Contribute to Open Source"](https://opensource.guide/how-to-contribute/). 100 | 101 | ## Documentation 102 | 103 | All PRs that introduce new features or new modules should be accompanied with appropriate 104 | docstrings and documentation. Writing documentation strings is really important to make sure 105 | others use your functionality properly. Didn't write new functions? That's fine, but be sure 106 | that the documentation for the code you touched is still in great shape. It is not uncommon 107 | to find some strange wording or clarification that you can take care of while you are here. 108 | 109 | We encourage using [unicode](https://docs.julialang.org/en/v1/manual/unicode-input/) characters 110 | when writing docstrings, e.g., use `α` instead of `\alpha`. This makes the rendering of the 111 | docstrings in the Documentation and in the Julia REPL's `help?>` mode as similar as possible. 112 | 113 | You can preview how the Documentation will look like after merging by building the documentation 114 | locally. To do that, from the main directory of your local repository call 115 | 116 | ``` 117 | julia --project=docs/ -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()' 118 | julia --project=docs/ docs/make.jl 119 | ``` 120 | 121 | and then open `docs/build/index.html` in your favorite browser. 122 | 123 | ## Credits 124 | 125 | This contributor's guide is heavily based on the [MetPy contributor's guide](https://github.com/Unidata/MetPy/blob/master/CONTRIBUTING.md) 126 | and on its "cover" made by [Oceananigans.jl](https://clima.github.io/OceananigansDocumentation/stable/contributing/). 127 | -------------------------------------------------------------------------------- /docs/src/gpu.md: -------------------------------------------------------------------------------- 1 | # GPU 2 | 3 | GPU-functionality is enabled via `FourierFlows.jl`. For more information on how `FourierFlows.jl` 4 | handled with GPUs we urge you to the corresponding [`FourierFlows.jl` documentation section ](https://fourierflows.github.io/FourierFlowsDocumentation/stable/gpu/). 5 | 6 | All `GeophysicalFlows.jl` modules can be run on GPU by providing `GPU()` as the device (`dev`) 7 | argument in the problem constructors. For example, 8 | 9 | ```julia 10 | julia> GeophysicalFlows.TwoDNavierStokes.Problem(GPU()) 11 | Problem 12 | ├─────────── grid: grid (on GPU) 13 | ├───── parameters: params 14 | ├────── variables: vars 15 | ├─── state vector: sol 16 | ├─────── equation: eqn 17 | ├────────── clock: clock 18 | └──── timestepper: RK4TimeStepper 19 | ``` 20 | 21 | ## Selecting GPU device 22 | 23 | `FourierFlows.jl` can only utilize a single GPU. If your machine has more than one GPU available, 24 | then using functionality within `CUDA.jl` package enables you can choose the GPU device that 25 | `FourierFlows.jl` should use. The user is referred to the [`CUDA.jl` Documentation](https://juliagpu.github.io/CUDA.jl/stable/lib/driver/#Device-Management); in particular, [`CUDA.devices`](https://juliagpu.github.io/CUDA.jl/stable/lib/driver/#CUDA.devices) and [`CUDA.CuDevice`](https://juliagpu.github.io/CUDA.jl/stable/lib/driver/#CUDA.CuDevice). 26 | -------------------------------------------------------------------------------- /docs/src/index.md: -------------------------------------------------------------------------------- 1 | # GeophysicalFlows.jl Documentation 2 | 3 | ## Overview 4 | 5 | `GeophysicalFlows.jl` is a collection of modules which leverage the 6 | [FourierFlows.jl](https://github.com/FourierFlows/FourierFlows.jl) framework to provide 7 | solvers for problems in Geophysical Fluid Dynamics, on periodic domains using Fourier-based pseudospectral methods. 8 | 9 | 10 | ## Examples 11 | 12 | Examples aim to demonstrate the main functionalities of each module. Have a look at our Examples collection! 13 | 14 | 15 | !!! note "Fourier transforms normalization" 16 | 17 | Fourier-based pseudospectral methods rely on Fourier expansions. Throughout the 18 | documentation we denote symbols with hat, e.g., ``\hat{u}``, to be the Fourier transform 19 | of ``u`` like, e.g., 20 | 21 | ```math 22 | u(x) = \sum_{k_x} \hat{u}(k_x) \, e^{i k_x x} . 23 | ``` 24 | 25 | The convention used in the modules is that the Fourier transform of a variable, e.g., `u` 26 | is denoted with `uh` (where the trailing `h` is there to imply "hat"). Note, however, 27 | that `uh` is obtained via a FFT of `u` and due to different normalization factors that the 28 | FFT algorithm uses, `uh` _is not_ exactly the same as ``\hat{u}`` above. Instead, 29 | 30 | ```math 31 | \hat{u}(k_x) = \frac{𝚞𝚑}{n_x e^{i k_x x_0}} , 32 | ``` 33 | 34 | where ``n_x`` is the total number of grid points in ``x`` and ``x_0`` is the left-most 35 | point of our ``x``-grid. 36 | 37 | Read more in the FourierFlows.jl Documentation; see 38 | [Grids](https://fourierflows.github.io/FourierFlowsDocumentation/stable/grids/) section. 39 | 40 | 41 | !!! info "Unicode" 42 | Oftentimes unicode symbols are used in modules for certain variables or parameters. For 43 | example, `ψ` is commonly used to denote the streamfunction of the flow, or `∂` is used 44 | to denote partial differentiation. Unicode symbols can be entered in the Julia REPL by 45 | typing, e.g., `\psi` or `\partial` followed by the `tab` key. 46 | 47 | Read more about Unicode symbols in the 48 | [Julia Documentation](https://docs.julialang.org/en/v1/manual/unicode-input/). 49 | 50 | 51 | ## Developers 52 | 53 | The development of GeophysicalFlows.jl started during the 21st AOFD Meeting 2017 by [Navid C. Constantinou](http://www.navidconstantinou.com) 54 | and [Gregory L. Wagner](https://glwagner.github.io). Since then various people have contributed, including 55 | [Lia Siegelman](https://scholar.google.com/citations?user=BQJtj6sAAAAJ), [Brodie Pearson](https://brodiepearson.github.io), 56 | [André Palóczy](https://scholar.google.com/citations?user=o4tYEH8AAAAJ) (see the 57 | [example in FourierFlows.jl](https://fourierflows.github.io/FourierFlowsDocumentation/stable/literated/OneDShallowWaterGeostrophicAdjustment/)), 58 | and [others](https://github.com/FourierFlows/GeophysicalFlows.jl/graphs/contributors). 59 | 60 | 61 | ## Citing 62 | 63 | If you use GeophysicalFlows.jl in research, teaching, or other activities, we would be grateful 64 | if you could mention GeophysicalFlows.jl and cite our paper in JOSS: 65 | 66 | Constantinou et al., (2021). GeophysicalFlows.jl: Solvers for geophysical fluid dynamics problems in periodic domains on CPUs & GPUs. _Journal of Open Source Software_, **6(60)**, 3053, doi:[10.21105/joss.03053](https://doi.org/10.21105/joss.03053). 67 | 68 | The bibtex entry for the paper is: 69 | 70 | ```bibtex 71 | @article{GeophysicalFlowsJOSS, 72 | doi = {10.21105/joss.03053}, 73 | url = {https://doi.org/10.21105/joss.03053}, 74 | year = {2021}, 75 | publisher = {The Open Journal}, 76 | volume = {6}, 77 | number = {60}, 78 | pages = {3053}, 79 | author = {Navid C. Constantinou and Gregory LeClaire Wagner and Lia Siegelman and Brodie C. Pearson and André Palóczy}, 80 | title = {GeophysicalFlows.jl: Solvers for geophysical fluid dynamics problems in periodic domains on CPUs \& GPUs}, 81 | journal = {Journal of Open Source Software} 82 | } 83 | ``` 84 | 85 | ## Papers using `GeophysicalFlows.jl` 86 | 87 | 1. He, J. and Wang, Y. (2025). Minimum-enstrophy and maximum-entropy equilibrium states in two-dimensional topographic turbulence. arXiv preprint arXiv.2507.00691, doi:[10.48550/arXiv.2507.00691](https://doi.org/10.48550/arXiv.2507.00691). 88 | 89 | 1. Hess, P., Gelbrecht, M., Schötz, C., Aich, M., Huang, Y., Yang, S., and Boers, N. (2025). Generating time-consistent dynamics with discriminator-guided image diffusion models. arXiv preprint arXiv.2410.14402, doi:[10.48550/arXiv.2505.09089](https://doi.org/10.48550/arXiv.2505.09089). 90 | 91 | 1. Crowe, M. N., (2025). QGDipoles.jl: A Julia package for calculating dipolar vortex solutions to the Quasi-Geostrophic equations. _Journal of Open Source Software_, **10(108)**, 7767, doi:[10.21105/joss.07767](https://doi.org/10.21105/joss.07767). 92 | 93 | 1. Lobo, M., Griffies, S. M., and Zhang, W. (2025) Vertical structure of baroclinic instability in a three-layer quasi-geostrophic model over a sloping bottom. _Journal of Physical Oceanography_, in press, doi:[10.1175/JPO-D-24-0130.1](https://doi.org/10.1175/JPO-D-24-0130.1). 94 | 95 | 1. Crowe, M. N. and Sutyrin, G. G. (2025) Symmetry breaking of two-layer eastward propagating dipoles. _Phys. Fluids_, **37**, 021708, doi:[10.1063/5.0251761](https://doi.org/10.1063/5.0251761). 96 | 97 | 1. Pudig, M. and Smith, K. S. (2024) Baroclinic turbulence above rough topography: The vortex gas and topographic turbulence regimes. _ESS Open Archive_, doi:[10.22541/essoar.171995116.60993353/v1](https://doi.org/10.22541/essoar.171995116.60993353/v1). 98 | 99 | 1. Shokar, I. J. S., Haynes, P. H. and Kerswell, R. R. (2024) Extending deep learning emulation across parameter regimes to assess stochastically driven spontaneous transition events. In ICLR 2024 Workshop on AI4DifferentialEquations in Science. url: [https://openreview.net/forum?id=7a5gUX4e5q](https://openreview.net/forum?id=7a5gUX4e5q). 100 | 101 | 1. He, J. and Wang, Y. (2024) Multiple states of two-dimensional turbulence above topography. _Journal of Fluid Mechanics_, **994**, R2, doi:[10.1017/jfm.2024.633](https://doi.org/10.1017/jfm.2024.633). 102 | 103 | 1. Parfenyev, V., Blumenau, M., and Nikitin, I. (2024) Inferring parameters and reconstruction of two-dimensional turbulent flows with physics-informed neural networks. _Jetp Lett._, doi:[10.1134/S0021364024602203](https://doi.org/10.1134/S0021364024602203). 104 | 105 | 1. Shokar, I. J. S., Kerswell, R. R., and Haynes, P. H. (2024) Stochastic latent transformer: Efficient modeling of stochastically forced zonal jets. _Journal of Advances in Modeling Earth Systems_, **16**, e2023MS004177, doi:[10.1029/2023MS004177](https://doi.org/10.1029/2023MS004177). 106 | 107 | 1. Bischoff, T., and Deck, K. (2024) Unpaired downscaling of fluid flows with diffusion bridges. _Artificial Intelligence for the Earth Systems_, doi:[10.1175/AIES-D-23-0039.1](https://doi.org/10.1175/AIES-D-23-0039.1), in press. 108 | 109 | 1. Kolokolov, I. V., Lebedev, V. V., and Parfenyev, V. M. (2024) Correlations in a weakly interacting two-dimensional random flow. _Physical Review E_, **109(3)**, 035103, doi:[10.1103/PhysRevE.109.035103](https://doi.org/10.1103/PhysRevE.109.035103). 110 | 111 | 1. Parfenyev, V. (2024) Statistical analysis of vortex condensate motion in two-dimensional turbulence. _Physics of Fluids_, **36**, 015148, doi:[10.1063/5.0187030](https://doi.org/10.1063/5.0187030). 112 | 113 | 1. LaCasce, J. H., Palóczy, A., and Trodahl, M. (2024). Vortices over bathymetry. _Journal of Fluid Mechanics_, **979**, A32, doi:[10.1017/jfm.2023.1084](https://doi.org/10.1017/jfm.2023.1084). 114 | 115 | 1. Drivas, T. D. and Elgindi, T. M. (2023). Singularity formation in the incompressible Euler equation in finite and infinite time. _EMS Surveys in Mathematical Sciences_, **10(1)**, 1–100, doi:[10.4171/emss/66](https://doi.org/10.4171/emss/66). 116 | 117 | 1. Siegelman, L. and Young, W. R. (2023). Two-dimensional turbulence above topography: Vortices and potential vorticity homogenization. _Proceedings of the National Academy of Sciences_, **120(44)**, e2308018120, doi:[10.1073/pnas.2308018120](https://doi.org/10.1073/pnas.2308018120). 118 | 119 | 1. Bisits, J. I., Stanley G. J., and Zika, J. D. (2023). Can we accurately quantify a lateral diffusivity using a single tracer release? _Journal of Physical Oceanography_, **53(2)**, 647–659, doi:[10.1175/JPO-D-22-0145.1](https://doi.org/10.1175/JPO-D-22-0145.1). 120 | 121 | 1. Parfenyev, V. (2022) Profile of a two-dimensional vortex condensate beyond the universal limit. _Phys. Rev. E_, **106**, 025102, doi:[10.1103/PhysRevE.106.025102](https://doi.org/10.1103/PhysRevE.106.025102). 122 | 123 | 1. Siegelman, L., Young, W. R., and Ingersoll, A. P. (2022). Polar vortex crystals: Emergence and structure _Proceedings of the National Academy of Sciences_, **119(17)**, e2120486119, doi:[10.1073/pnas.2120486119](https://doi.org/10.1073/pnas.2120486119). 124 | 125 | 1. Dolce, M. and Drivas, T. D. (2022). On maximally mixed equilibria of two-dimensional perfect fluids. _Archive for Rational Mechanics and Analysis_, **246**, 735–770, doi:[10.1007/s00205-022-01825-w](https://doi.org/10.1007/s00205-022-01825-w). 126 | 127 | 1. Palóczy, A. and LaCasce, J. H. (2022). Instability of a surface jet over rough topography. _Journal of Physical Oceanography_, **52(11)**, 2725-2740, doi:[10.1175/JPO-D-22-0079.1](https://doi.org/10.1175/JPO-D-22-0079.1). 128 | 129 | 1. Karrasch, D. and Schilling, N. (2020). Fast and robust computation of coherent Lagrangian vortices on very large two-dimensional domains. _The SMAI Journal of Computational Mathematics_, **6**, 101-124, doi:[10.5802/smai-jcm.63](https://doi.org/10.5802/smai-jcm.63). 130 | -------------------------------------------------------------------------------- /docs/src/installation_instructions.md: -------------------------------------------------------------------------------- 1 | # Installation instructions 2 | 3 | You can install the latest version of GeophysicalFlows.jl via the built-in package manager 4 | (accessed by pressing `]` in the Julia REPL command prompt) to add the package and also to 5 | instantiate/build all the required dependencies 6 | 7 | ```julia 8 | julia> ] 9 | (v1.10) pkg> add GeophysicalFlows 10 | (v1.10) pkg> instantiate 11 | ``` 12 | 13 | We recommend installing GeophysicalFlows.jl with the built-in Julia package manager, because 14 | this installs a stable, tagged release. Later on, you can update GeophysicalFlows.jl to the 15 | latest tagged release again via the package manager by typing 16 | 17 | ```julia 18 | (v1.10) pkg> update GeophysicalFlows 19 | ``` 20 | 21 | Note that some releases might induce breaking changes to certain modules. If after anything 22 | happens or your code stops working, please open an [issue](https://github.com/FourierFlows/GeophysicalFlows.jl/issues) 23 | or start a [discussion](https://github.com/FourierFlows/GeophysicalFlows.jl/discussions). We're 24 | more than happy to help with getting your simulations up and running. 25 | 26 | !!! warn "Julia 1.6 or newer required; Julia 1.10 or newer strongly encouraged" 27 | The latest GeophysicalFlows.jl requires at least Julia v1.6 to run. 28 | Installing GeophysicalFlows with an older version of Julia will install an older version 29 | of GeophysicalFlows.jl (the latest version compatible with your version of Julia). 30 | 31 | GeophysicalFlows.jl is continuously tested on Julia v1.10 (the current long-term release) and v1.11. 32 | _We strongly urge using one of the tested Julia versions._ 33 | -------------------------------------------------------------------------------- /docs/src/lib/functions.md: -------------------------------------------------------------------------------- 1 | # Functions 2 | 3 | ## `GeophysicalFlows` 4 | ```@docs 5 | GeophysicalFlows.GeophysicalFlows 6 | ``` 7 | 8 | ### Exported functions 9 | 10 | ```@docs 11 | GeophysicalFlows.lambdipole 12 | GeophysicalFlows.peakedisotropicspectrum 13 | ``` 14 | 15 | 16 | ## `TwoDNavierStokes` 17 | 18 | ### Exported functions 19 | 20 | ```@docs 21 | GeophysicalFlows.TwoDNavierStokes.Problem 22 | GeophysicalFlows.TwoDNavierStokes.energy_dissipation_hyperviscosity 23 | GeophysicalFlows.TwoDNavierStokes.energy_dissipation_hypoviscosity 24 | GeophysicalFlows.TwoDNavierStokes.energy_work 25 | GeophysicalFlows.TwoDNavierStokes.enstrophy_dissipation_hyperviscosity 26 | GeophysicalFlows.TwoDNavierStokes.enstrophy_dissipation_hypoviscosity 27 | GeophysicalFlows.TwoDNavierStokes.enstrophy_work 28 | GeophysicalFlows.TwoDNavierStokes.palinstrophy 29 | ``` 30 | 31 | ### Private functions 32 | 33 | ```@docs 34 | GeophysicalFlows.TwoDNavierStokes.calcN_advection! 35 | GeophysicalFlows.TwoDNavierStokes.addforcing! 36 | GeophysicalFlows.TwoDNavierStokes.energy_dissipation 37 | GeophysicalFlows.TwoDNavierStokes.enstrophy_dissipation 38 | ``` 39 | 40 | 41 | ## `SingleLayerQG` 42 | 43 | ### Exported functions 44 | 45 | ```@docs 46 | GeophysicalFlows.SingleLayerQG.Problem 47 | GeophysicalFlows.SingleLayerQG.streamfunctionfrompv! 48 | GeophysicalFlows.SingleLayerQG.energy_dissipation 49 | GeophysicalFlows.SingleLayerQG.energy_work 50 | GeophysicalFlows.SingleLayerQG.energy_drag 51 | GeophysicalFlows.SingleLayerQG.enstrophy 52 | GeophysicalFlows.SingleLayerQG.enstrophy_dissipation 53 | GeophysicalFlows.SingleLayerQG.enstrophy_work 54 | GeophysicalFlows.SingleLayerQG.enstrophy_drag 55 | ``` 56 | 57 | ### Private functions 58 | 59 | ```@docs 60 | GeophysicalFlows.SingleLayerQG.calcN_advection! 61 | GeophysicalFlows.SingleLayerQG.addforcing! 62 | ``` 63 | 64 | 65 | ## `MultiLayerQG` 66 | 67 | ### Exported functions 68 | 69 | ```@docs 70 | GeophysicalFlows.MultiLayerQG.Problem 71 | GeophysicalFlows.MultiLayerQG.fwdtransform! 72 | GeophysicalFlows.MultiLayerQG.invtransform! 73 | GeophysicalFlows.MultiLayerQG.streamfunctionfrompv! 74 | GeophysicalFlows.MultiLayerQG.pvfromstreamfunction! 75 | ``` 76 | 77 | ### Private functions 78 | 79 | ```@docs 80 | GeophysicalFlows.MultiLayerQG.LinearEquation 81 | GeophysicalFlows.MultiLayerQG.calcS! 82 | GeophysicalFlows.MultiLayerQG.calcS⁻¹! 83 | GeophysicalFlows.MultiLayerQG.calcNlinear! 84 | GeophysicalFlows.MultiLayerQG.calcN_advection! 85 | GeophysicalFlows.MultiLayerQG.calcN_linearadvection! 86 | GeophysicalFlows.MultiLayerQG.addforcing! 87 | GeophysicalFlows.MultiLayerQG.pv_streamfunction_kernel! 88 | ``` 89 | 90 | 91 | ## `SurfaceQG` 92 | 93 | ### Exported functions 94 | 95 | ```@docs 96 | GeophysicalFlows.SurfaceQG.Problem 97 | GeophysicalFlows.SurfaceQG.get_streamfunction 98 | GeophysicalFlows.SurfaceQG.buoyancy_dissipation 99 | GeophysicalFlows.SurfaceQG.buoyancy_work 100 | ``` 101 | 102 | ### Private functions 103 | 104 | ```@docs 105 | GeophysicalFlows.SurfaceQG.calcN_advection! 106 | GeophysicalFlows.SurfaceQG.addforcing! 107 | ``` 108 | 109 | 110 | ## `BarotropicQGQL` 111 | 112 | ### Exported functions 113 | 114 | ```@docs 115 | GeophysicalFlows.BarotropicQGQL.Problem 116 | GeophysicalFlows.BarotropicQGQL.dissipation 117 | GeophysicalFlows.BarotropicQGQL.work 118 | GeophysicalFlows.BarotropicQGQL.drag 119 | ``` 120 | 121 | ### Private functions 122 | 123 | ```@docs 124 | GeophysicalFlows.BarotropicQGQL.calcN_advection! 125 | GeophysicalFlows.BarotropicQGQL.addforcing! 126 | ``` 127 | -------------------------------------------------------------------------------- /docs/src/lib/types.md: -------------------------------------------------------------------------------- 1 | # Private types 2 | 3 | ## `TwoDNavierStokes` 4 | 5 | ```@docs 6 | GeophysicalFlows.TwoDNavierStokes.Params 7 | GeophysicalFlows.TwoDNavierStokes.Vars 8 | GeophysicalFlows.TwoDNavierStokes.DecayingVars 9 | GeophysicalFlows.TwoDNavierStokes.ForcedVars 10 | GeophysicalFlows.TwoDNavierStokes.StochasticForcedVars 11 | ``` 12 | 13 | 14 | ## `SingleLayerQG` 15 | 16 | ```@docs 17 | GeophysicalFlows.SingleLayerQG.Params 18 | GeophysicalFlows.SingleLayerQG.BarotropicQGParams 19 | GeophysicalFlows.SingleLayerQG.EquivalentBarotropicQGParams 20 | GeophysicalFlows.SingleLayerQG.Vars 21 | GeophysicalFlows.SingleLayerQG.DecayingVars 22 | GeophysicalFlows.SingleLayerQG.ForcedVars 23 | GeophysicalFlows.SingleLayerQG.StochasticForcedVars 24 | ``` 25 | 26 | 27 | ## `MultiLayerQG` 28 | 29 | ```@docs 30 | GeophysicalFlows.MultiLayerQG.Params 31 | GeophysicalFlows.MultiLayerQG.SingleLayerParams 32 | GeophysicalFlows.MultiLayerQG.Vars 33 | GeophysicalFlows.MultiLayerQG.DecayingVars 34 | GeophysicalFlows.MultiLayerQG.ForcedVars 35 | GeophysicalFlows.MultiLayerQG.StochasticForcedVars 36 | ``` 37 | 38 | 39 | ## `SurfaceQG` 40 | 41 | ```@docs 42 | GeophysicalFlows.SurfaceQG.Params 43 | GeophysicalFlows.SurfaceQG.Vars 44 | GeophysicalFlows.SurfaceQG.DecayingVars 45 | GeophysicalFlows.SurfaceQG.ForcedVars 46 | GeophysicalFlows.SurfaceQG.StochasticForcedVars 47 | ``` 48 | 49 | 50 | ## `BarotropicQGQL` 51 | 52 | ```@docs 53 | GeophysicalFlows.BarotropicQGQL.Params 54 | GeophysicalFlows.BarotropicQGQL.Vars 55 | GeophysicalFlows.BarotropicQGQL.DecayingVars 56 | GeophysicalFlows.BarotropicQGQL.ForcedVars 57 | GeophysicalFlows.BarotropicQGQL.StochasticForcedVars 58 | ``` 59 | -------------------------------------------------------------------------------- /docs/src/modules/barotropicqgql.md: -------------------------------------------------------------------------------- 1 | # BarotropicQGQL 2 | 3 | ### Basic Equations 4 | 5 | This module solves the *quasi-linear* quasi-geostrophic barotropic vorticity equation on a beta 6 | plane of variable fluid depth ``H - h(x, y)``. Quasi-linear refers to the dynamics that *neglects* 7 | the eddy--eddy interactions in the eddy evolution equation after an eddy--mean flow decomposition, 8 | e.g., 9 | 10 | ```math 11 | \phi(x, y, t) = \overline{\phi}(y, t) + \phi'(x, y, t) , 12 | ``` 13 | 14 | where overline above denotes a zonal mean, ``\overline{\phi}(y, t) = \int \phi(x, y, t) \, 𝖽x / L_x``, and prime denotes deviations from the zonal mean. This approximation is used in many process-model studies of zonation, e.g., [Farrell-Ioannou-2003](@citet), [Srinivasan-Young-2012](@citet), and [Constantinou-etal-2014](@citet). 15 | 16 | As in the [SingleLayerQG module](singlelayerqg.md), the flow is obtained through a 17 | streamfunction ``\psi`` as ``(u, v) = (-\partial_y \psi, \partial_x \psi)``. All flow fields 18 | can be obtained from the quasi-geostrophic potential vorticity (QGPV). Here, the QGPV is 19 | 20 | ```math 21 | \underbrace{f_0 + \beta y}_{\text{planetary PV}} + \underbrace{\partial_x v 22 | - \partial_y u}_{\text{relative vorticity}} + \underbrace{\frac{f_0 h}{H}}_{\text{topographic PV}} . 23 | ``` 24 | 25 | The dynamical variable is the component of the vorticity of the flow normal to the plane of 26 | motion, ``\zeta \equiv \partial_x v - \partial_y u = \nabla^2 \psi``. Also, we denote the 27 | topographic PV with ``\eta \equiv f_0 h / H``. After we apply the eddy-mean flow decomposition 28 | above, the QGPV dynamics are: 29 | 30 | ```math 31 | \begin{aligned} 32 | \partial_t \overline{\zeta} + \mathsf{J}(\overline{\psi}, \overline{\zeta} + \overline{\eta}) + \overline{\mathsf{J}(\psi', \zeta' + \eta')} & = \underbrace{- \left[\mu + \nu(-1)^{n_\nu} \nabla^{2n_\nu} 33 | \right] \overline{\zeta} }_{\textrm{dissipation}} , \\ 34 | \partial_t \zeta' + \mathsf{J}(\psi', \overline{\zeta} + \overline{\eta}) + \mathsf{J}(\overline{\psi}, \zeta' + \eta') + & \underbrace{\mathsf{J}(\psi', \zeta' + \eta') - \overline{\mathsf{J}(\psi', \zeta' + \eta')}}_{\textrm{EENL}} + \beta \partial_x \psi' = \\ 35 | & = \underbrace{-\left[\mu + \nu(-1)^{n_\nu} \nabla^{2n_\nu} \right] \zeta'}_{\textrm{dissipation}} + F . 36 | \end{aligned} 37 | ``` 38 | 39 | where ``\mathsf{J}(a, b) = (\partial_x a)(\partial_y b) - (\partial_y a)(\partial_x b)``. On 40 | the right hand side, ``F(x, y, t)`` is forcing (which is assumed to have zero zonal mean, 41 | ``\overline{F} = 0``), ``\mu`` is linear drag, and ``\nu`` is hyperviscosity. Plain old 42 | viscosity corresponds to ``n_{\nu} = 1``. 43 | 44 | *Quasi-linear* dynamics **neglects the term eddy-eddy nonlinearity (EENL) term** above. 45 | 46 | 47 | ### Implementation 48 | 49 | The equation is time-stepped forward in Fourier space: 50 | 51 | ```math 52 | \partial_t \widehat{\zeta} = - \widehat{\mathsf{J}(\psi, \zeta + \eta)}^{\textrm{QL}} + \beta \frac{i k_x}{|𝐤|^2} \widehat{\zeta} - \left ( \mu + \nu |𝐤|^{2n_\nu} \right ) \widehat{\zeta} + \widehat{F} . 53 | ``` 54 | 55 | The state variable `sol` is the Fourier transform of vorticity, [`ζh`](@ref GeophysicalFlows.BarotropicQGQL.Vars). 56 | 57 | The Jacobian is computed in the conservative form: ``\mathsf{J}(f, g) = \partial_y 58 | [ (\partial_x f) g] - \partial_x [ (\partial_y f) g]``. The superscript QL on the Jacobian term 59 | above denotes that triad interactions that correspond to the EENL term are removed. 60 | 61 | The linear operator is constructed in `Equation` 62 | 63 | ```@docs 64 | GeophysicalFlows.BarotropicQGQL.Equation 65 | ``` 66 | 67 | and the nonlinear terms are computed via 68 | 69 | ```@docs 70 | GeophysicalFlows.BarotropicQGQL.calcN! 71 | ``` 72 | 73 | which in turn calls [`calcN_advection!`](@ref GeophysicalFlows.BarotropicQGQL.calcN_advection!) 74 | and [`addforcing!`](@ref GeophysicalFlows.BarotropicQGQL.addforcing!). 75 | 76 | 77 | ### Parameters and Variables 78 | 79 | All required parameters are included inside [`Params`](@ref GeophysicalFlows.BarotropicQGQL.Params) 80 | and all module variables are included inside [`Vars`](@ref GeophysicalFlows.BarotropicQGQL.Vars). 81 | 82 | For the decaying case (no forcing, ``F = 0``), variables are constructed with [`Vars`](@ref GeophysicalFlows.BarotropicQGQL.Vars). 83 | For the forced case (``F \ne 0``) variables are constructed with either [`ForcedVars`](@ref GeophysicalFlows.BarotropicQGQL.ForcedVars) 84 | or [`StochasticForcedVars`](@ref GeophysicalFlows.BarotropicQGQL.StochasticForcedVars). 85 | 86 | 87 | ### Helper functions 88 | 89 | ```@docs 90 | GeophysicalFlows.BarotropicQGQL.updatevars! 91 | GeophysicalFlows.BarotropicQGQL.set_zeta! 92 | ``` 93 | 94 | 95 | ### Diagnostics 96 | 97 | The kinetic energy of the fluid is obtained via: 98 | 99 | ```@docs 100 | GeophysicalFlows.BarotropicQGQL.energy 101 | ``` 102 | 103 | while the enstrophy via: 104 | 105 | ```@docs 106 | GeophysicalFlows.BarotropicQGQL.enstrophy 107 | ``` 108 | 109 | Other diagnostic include: [`dissipation`](@ref GeophysicalFlows.BarotropicQGQL.dissipation), 110 | [`drag`](@ref GeophysicalFlows.BarotropicQGQL.drag), and [`work`](@ref GeophysicalFlows.BarotropicQGQL.work). 111 | 112 | 113 | ## Examples 114 | 115 | - [`examples/barotropicqgql_betaforced.jl`](@ref barotropicqgql_betaforced_example): Simulate forced-dissipative quasi-linear 116 | quasi-geostrophic flow on a beta plane demonstrating zonation. The forcing is temporally delta-correlated and its spatial 117 | structure is isotropic with power in a narrow annulus of total radius ``k_f`` in wavenumber space. This example demonstrates 118 | that the anisotropic inverse energy cascade is not required for zonation. 119 | -------------------------------------------------------------------------------- /docs/src/modules/multilayerqg.md: -------------------------------------------------------------------------------- 1 | # MultiLayerQG 2 | 3 | ### Basic Equations 4 | 5 | This module solves the layered Boussinesq quasi-geostrophic equations on a beta plane of variable-fluid 6 | depth ``H - h(x, y)``. The flow in each layer is obtained through a streamfunction ``\psi_j`` as 7 | ``(u_j, v_j) = (-\partial_y \psi_j, \partial_x \psi_j)``, ``j = 1, \dots, n``, where ``n`` 8 | is the number of fluid layers, with the bottom-most layer is the ``n``-th layer. 9 | 10 | The QGPV in each layer is 11 | 12 | ```math 13 | \mathrm{QGPV}_j = q_j + \underbrace{f_0 + \beta y}_{\textrm{planetary PV}} + \delta_{j, n} \underbrace{\frac{f_0 h}{H_n}}_{\textrm{topographic PV}}, \quad j = 1, \dots, n . 14 | ``` 15 | 16 | where ``q_j`` incorporates both the relative vorticity in each layer ``\nabla^2 \psi_j`` and the 17 | vortex stretching terms, i.e., 18 | 19 | ```math 20 | \begin{aligned} 21 | q_1 &= \nabla^2 \psi_1 + F_{3/2, 1} (\psi_2 - \psi_1) ,\\ 22 | q_j &= \nabla^2 \psi_j + F_{j-1/2, j} (\psi_{j-1} - \psi_j) + F_{j+1/2, j} (\psi_{j+1} - \psi_j) , \quad j = 2, \dots, n-1 ,\\ 23 | q_n &= \nabla^2 \psi_n + F_{n-1/2, n} (\psi_{n-1} - \psi_n) . 24 | \end{aligned} 25 | ``` 26 | 27 | with 28 | 29 | ```math 30 | F_{j+1/2, k} = \frac{f_0^2}{g'_{j+1/2} H_k} \quad \text{and} \quad 31 | g'_{j+1/2} = b_j - b_{j+1} , 32 | ``` 33 | 34 | where 35 | 36 | ```math 37 | b_{j} = - g \frac{\delta \rho_j}{\rho_0} 38 | ``` 39 | 40 | is the Boussinesq buoyancy in each layer, with ``\rho = \rho_0 + \delta \rho`` the total density, 41 | ``\rho_0`` a constant reference density, and ``|\delta \rho| \ll \rho_0`` the perturbation density. 42 | 43 | In view of the relationships above, when we convert to Fourier space the ``q``'s and ``\psi``'s are 44 | related via the matrix equation 45 | 46 | ```math 47 | \begin{pmatrix} \widehat{q}_{𝐤, 1}\\\vdots\\\widehat{q}_{𝐤, n} \end{pmatrix} = 48 | \underbrace{\left(-|𝐤|^2 \mathbb{1} + \mathbb{F} \right)}_{\equiv \mathbb{S}_{𝐤}} 49 | \begin{pmatrix} \widehat{\psi}_{𝐤, 1}\\\vdots\\\widehat{\psi}_{𝐤, n} \end{pmatrix} 50 | ``` 51 | 52 | where 53 | 54 | ```math 55 | \mathbb{F} \equiv \begin{pmatrix} 56 | -F_{3/2, 1} & F_{3/2, 1} & 0 & \cdots & 0\\ 57 | F_{3/2, 2} & -(F_{3/2, 2}+F_{5/2, 2}) & F_{5/2, 2} & & \vdots\\ 58 | 0 & \ddots & \ddots & \ddots & \\ 59 | \vdots & & & & 0 \\ 60 | 0 & \cdots & 0 & F_{n-1/2, n} & -F_{n-1/2, n} 61 | \end{pmatrix} . 62 | ``` 63 | 64 | Including an imposed zonal flow ``U_j(y)`` in each layer, the equations of motion are: 65 | 66 | ```math 67 | \partial_t q_j + \mathsf{J}(\psi_j, q_j ) + (U_j - \partial_y\psi_j) \partial_x Q_j + U_j \partial_x q_j + (\partial_y Q_j)(\partial_x \psi_j) = -\delta_{j, n} \mu \nabla^2 \psi_n - \nu (-1)^{n_\nu} \nabla^{2 n_\nu} q_j , 68 | ``` 69 | 70 | with 71 | 72 | ```math 73 | \begin{aligned} 74 | \partial_y Q_j &\equiv \beta - \partial_y^2 U_j - (1 - \delta_{j, 1}) F_{j-1/2, j} (U_{j-1} - U_j) - (1 - \delta_{j,n}) F_{j+1/2, j} (U_{j+1} - U_j) + \delta_{j, n} \partial_y \eta , \\ 75 | \partial_x Q_j & \equiv \delta_{j, n} \partial_x \eta , 76 | \end{aligned} 77 | ``` 78 | 79 | the background PV gradient components in each layer and where 80 | ``\mathsf{J}(a, b) = (\partial_x a)(\partial_y b)-(\partial_y a)(\partial_x b)`` is the 81 | two-dimensional Jacobian. On the right hand side, ``\mu`` is linear bottom drag, and ``\nu`` is 82 | hyperviscosity of order ``n_\nu``. Plain old viscosity corresponds to ``n_\nu = 1``. 83 | 84 | ### Implementation 85 | 86 | Matrices ``\mathbb{S}_{𝐤}`` as well as ``\mathbb{S}^{-1}_{𝐤}`` are included in 87 | [`Params`](@ref GeophysicalFlows.MultiLayerQG.Params) as `S` and `S⁻¹` respectively. 88 | Additionally, the background PV gradients ``\partial_x Q`` and ``\partial_y Q`` are also 89 | included in [`Params`](@ref GeophysicalFlows.MultiLayerQG.Params) as `Qx` and `Qy` respectively. 90 | 91 | One can get the ``\widehat{\psi}_j`` from ``\widehat{q}_j`` via 92 | [`streamfunctionfrompv!`](@ref GeophysicalFlows.MultiLayerQG.streamfunctionfrompv!). The inverse, 93 | i.e., to obtain ``\widehat{q}_j`` from ``\widehat{\psi}_j``, is done via 94 | [`pvfromstreamfunction!`](@ref GeophysicalFlows.MultiLayerQG.pvfromstreamfunction!). 95 | 96 | The equations of motion are time-stepped forward in Fourier space: 97 | 98 | ```math 99 | \partial_t \widehat{q}_j = - \widehat{\mathsf{J}(\psi_j, q_j)} - \widehat{U_j \partial_x Q_j} - \widehat{U_j \partial_x q_j} 100 | + \widehat{(\partial_y \psi_j) \partial_x Q_j} - \widehat{(\partial_x \psi_j)(\partial_y Q_j)} + \delta_{j, n} \mu |𝐤|^{2} \widehat{\psi}_n - \nu |𝐤|^{2n_\nu} \widehat{q}_j . 101 | ``` 102 | 103 | In doing so the Jacobian is computed in the conservative form: ``\mathsf{J}(f, g) = 104 | \partial_y [(\partial_x f) g] - \partial_x [(\partial_y f) g]``. 105 | 106 | The state variable `sol` consists of the Fourier transforms of ``q_j`` at each layer, i.e., 107 | [`qh`](@ref GeophysicalFlows.MultiLayerQG.Vars). 108 | 109 | The linear operator is constructed in `Equation` 110 | 111 | ```@docs 112 | GeophysicalFlows.MultiLayerQG.Equation 113 | GeophysicalFlows.MultiLayerQG.hyperviscosity 114 | ``` 115 | 116 | The nonlinear terms are computed via 117 | 118 | ```@docs 119 | GeophysicalFlows.MultiLayerQG.calcN! 120 | ``` 121 | 122 | which in turn calls [`calcN_advection!`](@ref GeophysicalFlows.MultiLayerQG.calcN_advection!) 123 | and [`addforcing!`](@ref GeophysicalFlows.MultiLayerQG.addforcing!). 124 | 125 | !!! tip "Linearized MultiLayerQG dynamics" 126 | The `MultiLayerQG` module includes also a linearized version of the dynamics about a base 127 | flow ``U_j(y)``, ``j = 1, \dots, n``; see [`LinearEquation`](@ref GeophysicalFlows.MultiLayerQG.LinearEquation), 128 | [`calcNlinear!`](@ref GeophysicalFlows.MultiLayerQG.calcNlinear!), and 129 | [`calcN_linearadvection!`](@ref GeophysicalFlows.MultiLayerQG.calcN_linearadvection!). 130 | 131 | 132 | ### Parameters and Variables 133 | 134 | All required parameters are included inside [`Params`](@ref GeophysicalFlows.MultiLayerQG.Params) 135 | and all module variables are included inside [`Vars`](@ref GeophysicalFlows.MultiLayerQG.Vars). 136 | 137 | For the decaying case (no forcing, ``F=0``), `vars` can be constructed with [`DecayingVars`](@ref GeophysicalFlows.MultiLayerQG.DecayingVars). 138 | For the forced case (``F \ne 0``) the `vars` struct is with [`ForcedVars`](@ref GeophysicalFlows.MultiLayerQG.ForcedVars) or [`StochasticForcedVars`](@ref GeophysicalFlows.MultiLayerQG.StochasticForcedVars). 139 | 140 | 141 | ### Helper functions 142 | 143 | ```@docs 144 | GeophysicalFlows.MultiLayerQG.set_q! 145 | GeophysicalFlows.MultiLayerQG.set_ψ! 146 | GeophysicalFlows.MultiLayerQG.updatevars! 147 | ``` 148 | 149 | ### Diagnostics 150 | 151 | The eddy kinetic energy in each layer and the eddy potential energy that corresponds to each 152 | fluid interface is computed via `energies`: 153 | 154 | ```@docs 155 | GeophysicalFlows.MultiLayerQG.energies 156 | ``` 157 | 158 | The lateral eddy fluxes in each layer and the vertical fluxes across fluid interfaces are 159 | computed via `fluxes`: 160 | 161 | ```@docs 162 | GeophysicalFlows.MultiLayerQG.fluxes 163 | ``` 164 | 165 | 166 | ## Examples 167 | 168 | - [`examples/multilayerqg_2layer.jl`](@ref multilayerqg_2layer_example): Simulate the growth and equilibration of baroclinic 169 | eddy turbulence in the Phillips 2-layer model. 170 | -------------------------------------------------------------------------------- /docs/src/modules/singlelayerqg.md: -------------------------------------------------------------------------------- 1 | # SingleLayerQG 2 | 3 | ### Basic Equations 4 | 5 | This module solves the barotropic or equivalent-barotropic quasi-geostrophic vorticity equation 6 | on a beta plane of variable fluid depth ``H - h(x, y)``. The flow is obtained through a 7 | streamfunction ``\psi`` as ``(u, v) = (-\partial_y \psi, \partial_x \psi)``. All flow fields 8 | can be obtained from the quasi-geostrophic potential vorticity (QGPV). Here, the QGPV is 9 | 10 | ```math 11 | \underbrace{f_0 + \beta y}_{\text{planetary PV}} + \underbrace{\partial_x v 12 | - \partial_y u}_{\text{relative vorticity}} 13 | \underbrace{ - \frac{1}{\ell^2} \psi}_{\text{vortex stretching}} + 14 | \underbrace{\frac{f_0 h}{H}}_{\text{topographic PV}} , 15 | ``` 16 | 17 | where ``\ell`` is the Rossby radius of deformation. Purely barotropic dynamics corresponds to 18 | infinite Rossby radius of deformation (``\ell = \infty``), while a flow with a finite Rossby 19 | radius follows is said to obey equivalent-barotropic dynamics. We denote the sum of the relative 20 | vorticity and the vortex stretching contributions to the QGPV with ``q \equiv \nabla^2 \psi - \psi / \ell^2``. 21 | Also, we denote the topographic PV with ``\eta \equiv f_0 h / H``. 22 | 23 | The dynamical variable is ``q``. Including an imposed zonal flow ``U(y)``, the equation of motion is: 24 | 25 | ```math 26 | \partial_t q + \mathsf{J}(\psi, q) + (U - \partial_y \psi) \partial_x Q + U \partial_x q + (\partial_y Q)(\partial_x \psi) = \underbrace{-\left[\mu + \nu(-1)^{n_\nu} \nabla^{2n_\nu} \right] q}_{\textrm{dissipation}} + F , 27 | ``` 28 | 29 | with 30 | 31 | ```math 32 | \begin{aligned} 33 | \partial_y Q & \equiv \beta - \partial_y^2 U + \partial_y \eta , \\ 34 | \partial_x Q & \equiv \partial_x \eta , 35 | \end{aligned} 36 | ``` 37 | 38 | the background PV gradient components, and with 39 | ``\mathsf{J}(a, b) = (\partial_x a)(\partial_y b) - (\partial_y a)(\partial_x b)`` 40 | the two-dimensional Jacobian. On the right hand side, ``F(x, y, t)`` is forcing, ``\mu`` is 41 | linear drag, and ``\nu`` is hyperviscosity of order ``n_\nu``. Plain old viscosity corresponds 42 | to ``n_\nu = 1``. 43 | 44 | If the imposed background zonal flow is constant over the whole domain (doesn't depend on ``y``), 45 | the above simplifies to: 46 | 47 | ```math 48 | \partial_t q + \mathsf{J}(\psi, q + \eta) + U \partial_x (q + \eta) + β \partial_x \psi = \underbrace{-\left[\mu + \nu(-1)^{n_\nu} \nabla^{2n_\nu} \right] q}_{\textrm{dissipation}} + F , 49 | ``` 50 | 51 | and thus the mean advection of ``q`` by ``U`` is incorporated in the linear term ``L``. 52 | 53 | ### Implementation 54 | 55 | The equation is time-stepped forward in Fourier space: 56 | 57 | ```math 58 | \partial_t \widehat{q} = - \widehat{\mathsf{J}(\psi, q)} - \widehat{U \partial_x Q} - \widehat{U \partial_x q} 59 | + \widehat{(\partial_y \psi) (\partial_x Q)} - \widehat{(\partial_x \psi)(\partial_y Q)} 60 | - \left(\mu + \nu |𝐤|^{2n_\nu} \right) \widehat{q} + \widehat{F} . 61 | ``` 62 | 63 | In doing so the Jacobian is computed in the conservative form: ``\mathsf{J}(f, g) = 64 | \partial_y [(\partial_x f) g] - \partial_x [(\partial_y f) g]``. 65 | 66 | The state variable `sol` is the Fourier transform of the sum of relative vorticity and vortex 67 | stretching (when the latter is applicable), [`qh`](@ref GeophysicalFlows.SingleLayerQG.Vars). 68 | 69 | The linear operator is constructed in `Equation` 70 | 71 | ```@docs 72 | GeophysicalFlows.SingleLayerQG.Equation 73 | ``` 74 | 75 | The nonlinear terms are computed via 76 | 77 | ```@docs 78 | GeophysicalFlows.SingleLayerQG.calcN! 79 | ``` 80 | 81 | which in turn calls [`calcN_advection!`](@ref GeophysicalFlows.SingleLayerQG.calcN_advection!) 82 | and [`addforcing!`](@ref GeophysicalFlows.SingleLayerQG.addforcing!). 83 | 84 | 85 | ### Parameters and Variables 86 | 87 | All required parameters are included inside [`Params`](@ref GeophysicalFlows.SingleLayerQG.Params) 88 | and all module variables are included inside [`Vars`](@ref GeophysicalFlows.SingleLayerQG.Vars). 89 | 90 | For the decaying case (no forcing, ``F = 0``), variables are constructed with [`Vars`](@ref GeophysicalFlows.SingleLayerQG.Vars). 91 | For the forced case (``F \ne 0``) variables are constructed with either [`ForcedVars`](@ref GeophysicalFlows.SingleLayerQG.ForcedVars) 92 | or [`StochasticForcedVars`](@ref GeophysicalFlows.SingleLayerQG.StochasticForcedVars). 93 | 94 | 95 | ### Helper functions 96 | 97 | Some helper functions included in the module are: 98 | 99 | ```@docs 100 | GeophysicalFlows.SingleLayerQG.updatevars! 101 | GeophysicalFlows.SingleLayerQG.set_q! 102 | ``` 103 | 104 | 105 | ### Diagnostics 106 | 107 | The kinetic energy of the fluid is computed via: 108 | 109 | ```@docs 110 | GeophysicalFlows.SingleLayerQG.kinetic_energy 111 | ``` 112 | 113 | while the potential energy, for an equivalent barotropic fluid, is computed via: 114 | 115 | ```@docs 116 | GeophysicalFlows.SingleLayerQG.potential_energy 117 | ``` 118 | 119 | The total energy is: 120 | 121 | ```@docs 122 | GeophysicalFlows.SingleLayerQG.energy 123 | ``` 124 | 125 | Other diagnostic include: [`energy_dissipation`](@ref GeophysicalFlows.SingleLayerQG.energy_dissipation), 126 | [`energy_drag`](@ref GeophysicalFlows.SingleLayerQG.energy_drag), [`energy_work`](@ref GeophysicalFlows.SingleLayerQG.energy_work), 127 | [`enstrophy_dissipation`](@ref GeophysicalFlows.SingleLayerQG.enstrophy_dissipation), and 128 | [`enstrophy_drag`](@ref GeophysicalFlows.SingleLayerQG.enstrophy_drag), [`enstrophy_work`](@ref GeophysicalFlows.SingleLayerQG.enstrophy_work). 129 | 130 | 131 | ## Examples 132 | 133 | - [`examples/singlelayerqg_betadecay.jl`](@ref singlelayerqg_betadecay_example): Simulate decaying quasi-geostrophic flow on 134 | a beta plane demonstrating zonation. 135 | 136 | - [`examples/singlelayerqg_betaforced.jl`](@ref singlelayerqg_betaforced_example): Simulate forced-dissipative quasi-geostrophic 137 | flow on a beta plane demonstrating zonation. The forcing is temporally delta-correlated with isotropic spatial structure with 138 | power in a narrow annulus in wavenumber space with total wavenumber ``k_f``. 139 | 140 | - [`examples/singlelayerqg_decay_topography.jl`](@ref singlelayerqg_decay_topography_example): Simulate two dimensional turbulence 141 | (barotropic quasi-geostrophic flow with ``\beta=0``) above topography. 142 | 143 | - [`examples/singlelayerqg_decaying_barotropic_equivalentbarotropic.jl`](@ref singlelayerqg_decaying_barotropic_equivalentbarotropic_example): 144 | Simulate two dimensional turbulence (``\beta=0``) with both infinite and finite Rossby radius of deformation and compares the evolution of the two. 145 | -------------------------------------------------------------------------------- /docs/src/modules/surfaceqg.md: -------------------------------------------------------------------------------- 1 | # SurfaceQG 2 | 3 | ### Basic Equations 4 | 5 | This module solves the non-dimensional surface quasi-geostrophic (SQG) equation for surface 6 | buoyancy ``b_s = b(x, y, z=0)``, as described by [Capet-etal-2008](@citet). The buoyancy and the fluid 7 | velocity at the surface are related through a streamfunction ``\psi`` via: 8 | 9 | ```math 10 | (u_s, v_s, b_s) = (-\partial_y \psi, \partial_x \psi, -\partial_z \psi) . 11 | ``` 12 | 13 | The SQG model evolves the surface buoyancy, 14 | 15 | ```math 16 | \partial_t b_s + \mathsf{J}(\psi, b_s) = \underbrace{-\nu(-1)^{n_\nu} \nabla^{2n_\nu} b_s}_{\textrm{buoyancy diffusion}} + \underbrace{F}_{\textrm{forcing}} . 17 | ``` 18 | 19 | Above, ``\mathsf{J}(\psi, b) = (\partial_x \psi)(\partial_y b) - (\partial_y \psi)(\partial_x b)`` 20 | is the two-dimensional Jacobian. The evolution of buoyancy is only solved for the surface 21 | layer, but ``b_s`` is a function of the vertical gradient of ``\psi``. In the SQG system, the 22 | potential vorticity in the interior of the flow is identically zero. That is, relative vorticity 23 | is equal and opposite to the vertical stretching of the buoyancy layers, 24 | 25 | ```math 26 | \underbrace{\left(\partial_x^2 + \partial_y^2 \right) \psi}_{\textrm{relative vorticity}} + \underbrace{\partial_z^2 \psi}_{\textrm{stretching term}} = 0 , 27 | ``` 28 | 29 | with the boundary conditions ``b_s = - \partial_z \psi|_{z=0}`` and ``\partial_z \psi|_{z=-H} = 0``, where ``H`` is the layer depth. In the case of infinite depth, ``H \rightarrow \infty``, the bottom boundary condition becomes ``\psi \rightarrow 0`` as ``z \rightarrow -\infty``. (We take here the oceanographic convention: ``z \le 0``.) 30 | 31 | These equations describe a system where the streamfunction (and hence the dynamics) at all depths is prescribed entirely by the surface buoyancy. By taking the Fourier transform in the horizontal (``x`` and ``y``), the streamfunction-buoyancy relation is: 32 | 33 | ```math 34 | \widehat{\psi}(k_x, k_y, z, t) = - \frac{\widehat{b_s}}{|𝐤|} \, \frac{\cosh[|𝐤|(z+H)]}{\sinh(|𝐤|H)} , 35 | ``` 36 | 37 | for finite ``H`` and 38 | 39 | ```math 40 | \widehat{\psi}(k_x, k_y, z, t) = - \frac{\widehat{b_s}}{|𝐤|} \, e^{|𝐤|z} , 41 | ``` 42 | 43 | for a fluid of infinite depth (``H \rightarrow \infty``). 44 | Above, ``|𝐤| = \sqrt{k_x^2 + k_y^2}`` is the total horizontal wavenumber. 45 | 46 | ### Implementation 47 | 48 | The buoyancy equation is time-stepped forward in Fourier space: 49 | 50 | ```math 51 | \partial_t \widehat{b_s} = - \widehat{\mathsf{J}(\psi, b_s)} - \nu |𝐤|^{2 n_\nu} \widehat{b_s} + \widehat{F} . 52 | ``` 53 | 54 | The surface buoyancy is [`b`](@ref GeophysicalFlows.SurfaceQG.Vars). The state variable 55 | `sol` is the Fourier transform of the surface buoyancy, [`bh`](@ref GeophysicalFlows.SurfaceQG.Vars). 56 | 57 | The Jacobian is computed in the conservative form: ``\mathsf{J}(f, g) = 58 | \partial_y [(\partial_x f) g] - \partial_x [(\partial_y f) g]``. 59 | 60 | The linear operator is constructed in `Equation` 61 | 62 | ```@docs 63 | GeophysicalFlows.SurfaceQG.Equation 64 | ``` 65 | 66 | while the nonlinear terms via 67 | 68 | ```@docs 69 | GeophysicalFlows.SurfaceQG.calcN! 70 | ``` 71 | 72 | which in turn calls [`calcN_advection!`](@ref GeophysicalFlows.SurfaceQG.calcN_advection!) 73 | and [`addforcing!`](@ref GeophysicalFlows.SurfaceQG.addforcing!). 74 | 75 | 76 | ### Parameters and Variables 77 | 78 | All required parameters are included inside [`Params`](@ref GeophysicalFlows.SurfaceQG.Params) 79 | and all module variables are included inside [`Vars`](@ref GeophysicalFlows.SurfaceQG.Vars). 80 | 81 | For the decaying case (no forcing, ``F = 0``), variables are constructed with [`Vars`](@ref GeophysicalFlows.SurfaceQG.Vars). 82 | For the forced case (``F \ne 0``) variables are constructed with either [`ForcedVars`](@ref GeophysicalFlows.SurfaceQG.ForcedVars) 83 | or [`StochasticForcedVars`](@ref GeophysicalFlows.SurfaceQG.StochasticForcedVars). 84 | 85 | 86 | ### Helper functions 87 | 88 | ```@docs 89 | GeophysicalFlows.SurfaceQG.updatevars! 90 | GeophysicalFlows.SurfaceQG.set_b! 91 | ``` 92 | 93 | 94 | ### Diagnostics 95 | 96 | Some useful diagnostics are kinetic energy and buoyancy variance. 97 | 98 | ```@docs 99 | GeophysicalFlows.SurfaceQG.kinetic_energy 100 | GeophysicalFlows.SurfaceQG.buoyancy_variance 101 | GeophysicalFlows.SurfaceQG.total_3D_energy 102 | ``` 103 | 104 | Other diagnostic include: [`buoyancy_dissipation`](@ref GeophysicalFlows.SurfaceQG.buoyancy_dissipation) and 105 | [`buoyancy_work`](@ref GeophysicalFlows.SurfaceQG.buoyancy_work). 106 | 107 | 108 | ## Examples 109 | 110 | - [`examples/surfaceqg_decaying.jl`](@ref surfaceqg_decaying_example): Simulate decaying surface quasi-geostrophic flow 111 | with a prescribed initial buoyancy field. 112 | -------------------------------------------------------------------------------- /docs/src/modules/twodnavierstokes.md: -------------------------------------------------------------------------------- 1 | # TwoDNavierStokes 2 | 3 | 4 | ### Basic Equations 5 | 6 | This module solves two-dimensional incompressible Navier-Stokes equations using the 7 | vorticity-streamfunction formulation. The flow ``\bm{u} = (u, v)`` is obtained through a 8 | streamfunction ``\psi`` as ``(u, v) = (-\partial_y \psi, \partial_x \psi)``. The only non-zero 9 | component of vorticity is that normal to the plane of motion, 10 | ``\partial_x v - \partial_y u = \nabla^2 \psi``. The module solves the two-dimensional 11 | vorticity equation: 12 | 13 | ```math 14 | \partial_t \zeta + \mathsf{J}(\psi, \zeta) = \underbrace{-\left [ \mu (-\nabla^2)^{n_\mu} 15 | + \nu (-\nabla^2)^{n_\nu} \right ] \zeta}_{\textrm{dissipation}} + F , 16 | ``` 17 | 18 | where ``\mathsf{J}(\psi, \zeta) = (\partial_x \psi)(\partial_y \zeta) - (\partial_y \psi)(\partial_x \zeta)`` 19 | is the two-dimensional Jacobian and ``F(x, y, t)`` is forcing. The Jacobian term is the advection 20 | of relative vorticity, ``\mathsf{J}(ψ, ζ) = \bm{u \cdot \nabla} \zeta``. Both ``ν`` and ``μ`` 21 | terms are viscosities; typically the former is chosen to act at small scales (``n_ν ≥ 1``), 22 | while the latter at large scales (``n_ν ≤ 0``). Plain old viscosity corresponds to ``n_ν=1`` 23 | while ``n_μ=0`` corresponds to linear drag. Values of ``n_ν ≥ 2`` or ``n_μ ≤ -1`` are referred 24 | to as hyper- or hypo-viscosities, respectively. 25 | 26 | 27 | ### Implementation 28 | 29 | The equation is time-stepped forward in Fourier space: 30 | 31 | ```math 32 | \partial_t \widehat{\zeta} = - \widehat{\mathsf{J}(\psi, \zeta)} - \left ( \mu |𝐤|^{2n_\mu} 33 | + \nu |𝐤|^{2n_\nu} \right ) \widehat{\zeta} + \widehat{F} . 34 | ``` 35 | 36 | The state variable `sol` is the Fourier transform of vorticity, [`ζh`](@ref GeophysicalFlows.TwoDNavierStokes.Vars). 37 | 38 | The Jacobian is computed in the conservative form: ``\mathsf{J}(a, b) = 39 | \partial_y [(\partial_x a) b] - \partial_x[(\partial_y a) b]``. 40 | 41 | The linear operator is constructed in `Equation` 42 | 43 | ```@docs 44 | GeophysicalFlows.TwoDNavierStokes.Equation 45 | ``` 46 | 47 | The nonlinear terms are computed via `calcN!`, 48 | 49 | ```@docs 50 | GeophysicalFlows.TwoDNavierStokes.calcN! 51 | ``` 52 | 53 | which in turn calls [`calcN_advection!`](@ref GeophysicalFlows.TwoDNavierStokes.calcN_advection!) 54 | and [`addforcing!`](@ref GeophysicalFlows.TwoDNavierStokes.addforcing!). 55 | 56 | 57 | ### Parameters and Variables 58 | 59 | All required parameters are included inside [`Params`](@ref GeophysicalFlows.TwoDNavierStokes.Params) 60 | and all module variables are included inside [`Vars`](@ref GeophysicalFlows.TwoDNavierStokes.Vars). 61 | 62 | For the decaying case (no forcing, ``F = 0``), variables are constructed with [`Vars`](@ref GeophysicalFlows.TwoDNavierStokes.Vars). 63 | For the forced case (``F \ne 0``) variables are constructed with either [`ForcedVars`](@ref GeophysicalFlows.TwoDNavierStokes.ForcedVars) 64 | or [`StochasticForcedVars`](@ref GeophysicalFlows.TwoDNavierStokes.StochasticForcedVars). 65 | 66 | 67 | ### Helper functions 68 | 69 | Some helper functions included in the module are: 70 | 71 | ```@docs 72 | GeophysicalFlows.TwoDNavierStokes.updatevars! 73 | GeophysicalFlows.TwoDNavierStokes.set_ζ! 74 | ``` 75 | 76 | 77 | ### Diagnostics 78 | 79 | Some useful diagnostics are: 80 | 81 | ```@docs 82 | GeophysicalFlows.TwoDNavierStokes.energy 83 | GeophysicalFlows.TwoDNavierStokes.enstrophy 84 | ``` 85 | 86 | Other diagnostic include: [`energy_dissipation`](@ref GeophysicalFlows.TwoDNavierStokes.energy_dissipation), 87 | [`energy_work`](@ref GeophysicalFlows.TwoDNavierStokes.energy_work), 88 | [`enstrophy_dissipation`](@ref GeophysicalFlows.TwoDNavierStokes.enstrophy_dissipation), and 89 | [`enstrophy_work`](@ref GeophysicalFlows.TwoDNavierStokes.enstrophy_work). 90 | 91 | 92 | ## Examples 93 | 94 | - [`examples/twodnavierstokes_decaying.jl`](@ref twodnavierstokes_decaying_example): Simulates decaying two-dimensional 95 | turbulence reproducing the results by: 96 | 97 | - [`examples/twodnavierstokes_stochasticforcing.jl`](@ref twodnavierstokes_stochasticforcing_example): Simulate forced-dissipative 98 | two-dimensional turbulence with isotropic temporally delta-correlated stochastic forcing. 99 | 100 | - [`examples/twodnavierstokes_stochasticforcing_budgets.jl`](@ref twodnavierstokes_stochasticforcing_budgets_example): Simulate 101 | forced-dissipative two-dimensional turbulence demonstrating how we can compute the energy and enstrophy budgets. 102 | -------------------------------------------------------------------------------- /docs/src/references.bib: -------------------------------------------------------------------------------- 1 | @article{Held-etal-1995, 2 | title={Surface quasi-geostrophic dynamics}, 3 | author={Held, Isaac M. and Pierrehumbert, Raymond T. and Garner, Stephen T. and Swanson, Kyle L.}, 4 | journal={Journal of Fluid Mechanics}, 5 | volume={282}, 6 | pages={1--20}, 7 | year={1995}, 8 | doi={10.1017/S0022112095000012} 9 | } 10 | 11 | @article{Capet-etal-2008, 12 | title={Surface kinetic energy transfer in surface quasi-geostrophic flows}, 13 | author={Capet, Xavier and Klein, Patrice and Hua, Bach Lien and Lapeyre, Guillaume and Mcwilliams, James C.}, 14 | journal={Journal of Fluid Mechanics}, 15 | volume={604}, 16 | pages={165--174}, 17 | year={2008}, 18 | doi={10.1017/S0022112008001110} 19 | } 20 | 21 | @article{McWilliams-1984, 22 | title={The emergence of isolated coherent vortices in turbulent flow}, 23 | author={McWilliams, James C.}, 24 | journal={Journal of Fluid Mechanics}, 25 | volume={146}, 26 | pages={21--43}, 27 | year={1984}, 28 | doi={10.1017/S0022112084001750} 29 | } 30 | 31 | @article{Farrell-Ioannou-2003, 32 | title = {Structural stability of turbulent jets}, 33 | author = {Farrell, Brian F. and Ioannou, Petros J.}, 34 | journal = {Journal of the Atmospheric Sciences}, 35 | pages = {2101--2118}, 36 | volume = 60, 37 | year = 2003, 38 | doi = {10.1175/1520-0469(2003)060<2101:SSOTJ>2.0.CO;2}, 39 | } 40 | 41 | @article{Constantinou-etal-2014, 42 | title = {Emergence and equilibration of jets in beta-plane turbulence: applications of Stochastic Structural Stability Theory}, 43 | author = {Constantinou, Navid C. and Farrell, Brian F. and Ioannou, Petros J.}, 44 | journal = {Journal of the Atmospheric Sciences}, 45 | volume = {71}, 46 | number = {5}, 47 | pages = {1818--1842}, 48 | year = {2014}, 49 | doi = {10.1175/JAS-D-13-076.1}, 50 | } 51 | 52 | @article{Srinivasan-Young-2012, 53 | title = {Zonostrophic instability}, 54 | author = {Srinivasan, Kaushik and Young, William R.}, 55 | journal = {Journal of the Atmospheric Sciences}, 56 | volume = {69}, 57 | number = {5}, 58 | pages = {1633--1656}, 59 | year = {2012}, 60 | doi = {10.1175/JAS-D-11-0200.1}, 61 | } 62 | 63 | @article{vanKampen-1981, 64 | title={Itô versus Stratonovich}, 65 | author={Van Kampen, Nicolaas G}, 66 | journal={Journal of Statistical Physics}, 67 | volume={24}, 68 | pages={175--187}, 69 | year={1981}, 70 | doi={10.1007/BF01007642} 71 | } 72 | 73 | @phdthesis{Constantinou-2015-phd, 74 | title = {Formation of large-scale structures by turbulence in rotating planets}, 75 | author = {Constantinou, N. C.}, 76 | school = {National and Kapodistrian University of Athens}, 77 | address = {Athens}, 78 | url = {http://www.didaktorika.gr/eadd/handle/10442/35501?locale=en}, 79 | year = {2015}, 80 | note = {(also available at arXiv:1503.07644)} 81 | } 82 | -------------------------------------------------------------------------------- /docs/src/references.md: -------------------------------------------------------------------------------- 1 | # References 2 | 3 | ```@bibliography 4 | ``` 5 | -------------------------------------------------------------------------------- /docs/src/visualize.md: -------------------------------------------------------------------------------- 1 | # Visualize output 2 | 3 | In the examples we use [Makie.jl](https://docs.makie.org/stable/) for plotting. 4 | 5 | Makie comes with a few [backends](https://docs.makie.org/stable/#makie_ecosystem). In the documented examples 6 | we use [CairoMakie](https://docs.makie.org/stable/documentation/backends/cairomakie/) since this backend 7 | works well on headless devices, that is, devices without monitor. Since the documentation is automatically 8 | built via GitHub actions the CairoMakie backend is necessary. Users that run GeophysicalFlows.jl on 9 | devices with a monitor might want to change to [GLMakie](https://docs.makie.org/stable/documentation/backends/glmakie/) 10 | that displays figures in an interactive window. 11 | 12 | In GeophysicalFlows.jl simulations, we can either visualize the fields on-the-fly as the problem is stepped forward or 13 | we can save output onto a `.jld2` file and after simulation is done load the output and visualize it. Most examples do 14 | the former. For a demonstration for how one can save output and load later to process and visualize it have a look at the 15 | [`SingeLayerQG` beta-plane forced-dissipative example](@ref singlelayerqg_betaforced_example). For more information about 16 | saving output to `.jld2` files, files see the 17 | [Output section](https://fourierflows.github.io/FourierFlowsDocumentation/stable/output/) in FourierFlows.jl Documentation 18 | might be useful. 19 | -------------------------------------------------------------------------------- /examples/Project.toml: -------------------------------------------------------------------------------- 1 | [deps] 2 | CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" 3 | CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" 4 | GeophysicalFlows = "44ee3b1c-bc02-53fa-8355-8e347616e15e" 5 | JLD2 = "033835bb-8acc-5ee8-8aae-3f567f8a3819" 6 | Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" 7 | Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" 8 | Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" 9 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # GeophysicalFlows.jl/examples 2 | 3 | These are some basic examples of the various modules included in GeophysicalFlows.jl. The best way to go through the examples by browsing them within the package's documentation . 4 | 5 | ## Run the examples 6 | 7 | You can run the examples in an executable environment via [Binder](https://mybinder.org) by clicking on the [![badge](https://img.shields.io/badge/binder-badge-579ACA.svg?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFkAAABZCAMAAABi1XidAAAB8lBMVEX///9XmsrmZYH1olJXmsr1olJXmsrmZYH1olJXmsr1olJXmsrmZYH1olL1olJXmsr1olJXmsrmZYH1olL1olJXmsrmZYH1olJXmsr1olL1olJXmsrmZYH1olL1olJXmsrmZYH1olL1olL0nFf1olJXmsrmZYH1olJXmsq8dZb1olJXmsrmZYH1olJXmspXmspXmsr1olL1olJXmsrmZYH1olJXmsr1olL1olJXmsrmZYH1olL1olLeaIVXmsrmZYH1olL1olL1olJXmsrmZYH1olLna31Xmsr1olJXmsr1olJXmsrmZYH1olLqoVr1olJXmsr1olJXmsrmZYH1olL1olKkfaPobXvviGabgadXmsqThKuofKHmZ4Dobnr1olJXmsr1olJXmspXmsr1olJXmsrfZ4TuhWn1olL1olJXmsqBi7X1olJXmspZmslbmMhbmsdemsVfl8ZgmsNim8Jpk8F0m7R4m7F5nLB6jbh7jbiDirOEibOGnKaMhq+PnaCVg6qWg6qegKaff6WhnpKofKGtnomxeZy3noG6dZi+n3vCcpPDcpPGn3bLb4/Mb47UbIrVa4rYoGjdaIbeaIXhoWHmZYHobXvpcHjqdHXreHLroVrsfG/uhGnuh2bwj2Hxk17yl1vzmljzm1j0nlX1olL3AJXWAAAAbXRSTlMAEBAQHx8gICAuLjAwMDw9PUBAQEpQUFBXV1hgYGBkcHBwcXl8gICAgoiIkJCQlJicnJ2goKCmqK+wsLC4usDAwMjP0NDQ1NbW3Nzg4ODi5+3v8PDw8/T09PX29vb39/f5+fr7+/z8/Pz9/v7+zczCxgAABC5JREFUeAHN1ul3k0UUBvCb1CTVpmpaitAGSLSpSuKCLWpbTKNJFGlcSMAFF63iUmRccNG6gLbuxkXU66JAUef/9LSpmXnyLr3T5AO/rzl5zj137p136BISy44fKJXuGN/d19PUfYeO67Znqtf2KH33Id1psXoFdW30sPZ1sMvs2D060AHqws4FHeJojLZqnw53cmfvg+XR8mC0OEjuxrXEkX5ydeVJLVIlV0e10PXk5k7dYeHu7Cj1j+49uKg7uLU61tGLw1lq27ugQYlclHC4bgv7VQ+TAyj5Zc/UjsPvs1sd5cWryWObtvWT2EPa4rtnWW3JkpjggEpbOsPr7F7EyNewtpBIslA7p43HCsnwooXTEc3UmPmCNn5lrqTJxy6nRmcavGZVt/3Da2pD5NHvsOHJCrdc1G2r3DITpU7yic7w/7Rxnjc0kt5GC4djiv2Sz3Fb2iEZg41/ddsFDoyuYrIkmFehz0HR2thPgQqMyQYb2OtB0WxsZ3BeG3+wpRb1vzl2UYBog8FfGhttFKjtAclnZYrRo9ryG9uG/FZQU4AEg8ZE9LjGMzTmqKXPLnlWVnIlQQTvxJf8ip7VgjZjyVPrjw1te5otM7RmP7xm+sK2Gv9I8Gi++BRbEkR9EBw8zRUcKxwp73xkaLiqQb+kGduJTNHG72zcW9LoJgqQxpP3/Tj//c3yB0tqzaml05/+orHLksVO+95kX7/7qgJvnjlrfr2Ggsyx0eoy9uPzN5SPd86aXggOsEKW2Prz7du3VID3/tzs/sSRs2w7ovVHKtjrX2pd7ZMlTxAYfBAL9jiDwfLkq55Tm7ifhMlTGPyCAs7RFRhn47JnlcB9RM5T97ASuZXIcVNuUDIndpDbdsfrqsOppeXl5Y+XVKdjFCTh+zGaVuj0d9zy05PPK3QzBamxdwtTCrzyg/2Rvf2EstUjordGwa/kx9mSJLr8mLLtCW8HHGJc2R5hS219IiF6PnTusOqcMl57gm0Z8kanKMAQg0qSyuZfn7zItsbGyO9QlnxY0eCuD1XL2ys/MsrQhltE7Ug0uFOzufJFE2PxBo/YAx8XPPdDwWN0MrDRYIZF0mSMKCNHgaIVFoBbNoLJ7tEQDKxGF0kcLQimojCZopv0OkNOyWCCg9XMVAi7ARJzQdM2QUh0gmBozjc3Skg6dSBRqDGYSUOu66Zg+I2fNZs/M3/f/Grl/XnyF1Gw3VKCez0PN5IUfFLqvgUN4C0qNqYs5YhPL+aVZYDE4IpUk57oSFnJm4FyCqqOE0jhY2SMyLFoo56zyo6becOS5UVDdj7Vih0zp+tcMhwRpBeLyqtIjlJKAIZSbI8SGSF3k0pA3mR5tHuwPFoa7N7reoq2bqCsAk1HqCu5uvI1n6JuRXI+S1Mco54YmYTwcn6Aeic+kssXi8XpXC4V3t7/ADuTNKaQJdScAAAAAElFTkSuQmCC)](https://mybinder.org) 8 | at the top of each example page in the documentation. 9 | 10 | Alternatively, you can run these scripts directly using the `.toml` files in this directory. 11 | To do that, first open julia and activate this directory's project, e.g., 12 | ``` 13 | $ julia --project="path/to/examples" 14 | ``` 15 | Then instantiate the project in this directory, i.e., 16 | ```julia 17 | ] instantiate 18 | ``` 19 | to install dependencies. Then run any of the examples by 20 | ```julia 21 | include("path/to/examples/example_script.jl") 22 | ``` 23 | -------------------------------------------------------------------------------- /examples/barotropicqgql_betaforced.jl: -------------------------------------------------------------------------------- 1 | # # [Quasi-Linear forced-dissipative barotropic QG beta-plane turbulence](@id barotropicqgql_betaforced_example) 2 | # 3 | # A simulation of forced-dissipative barotropic quasi-geostrophic turbulence on 4 | # a beta plane under the *quasi-linear approximation*. The dynamics include 5 | # linear drag and stochastic excitation. 6 | # 7 | # ## Install dependencies 8 | # 9 | # First let's make sure we have all required packages installed. 10 | 11 | # ```julia 12 | # using Pkg 13 | # pkg"add GeophysicalFlows, CUDA, CairoMakie" 14 | # ``` 15 | 16 | # ## Let's begin 17 | # Let's load `GeophysicalFlows.jl` and some other packages we need. 18 | 19 | using GeophysicalFlows, CUDA, Random, Printf, CairoMakie 20 | 21 | using Statistics: mean 22 | 23 | parsevalsum = FourierFlows.parsevalsum 24 | record = CairoMakie.record # disambiguate between CairoMakie.record and CUDA.record 25 | nothing #hide 26 | 27 | # ## Choosing a device: CPU or GPU 28 | 29 | dev = CPU() # Device (CPU/GPU) 30 | nothing #hide 31 | 32 | 33 | # ## Numerical parameters and time-stepping parameters 34 | 35 | n = 128 # 2D resolution = n^2 36 | stepper = "FilteredRK4" # timestepper 37 | dt = 0.05 # timestep 38 | nsteps = 8000 # total number of time-steps 39 | nsubs = 10 # number of time-steps for intermediate logging/plotting (nsteps must be multiple of nsubs) 40 | nothing #hide 41 | 42 | 43 | # ## Physical parameters 44 | 45 | L = 2π # domain size 46 | β = 10.0 # planetary PV gradient 47 | μ = 0.01 # bottom drag 48 | nothing #hide 49 | 50 | 51 | # ## Forcing 52 | 53 | # We force the vorticity equation with stochastic excitation that is delta-correlated in time 54 | # and while spatially homogeneously and isotropically correlated. The forcing has a spectrum 55 | # with power in a ring in wavenumber space of radius ``k_f`` (`forcing_wavenumber`) and width 56 | # ``δ_f`` (`forcing_bandwidth`), and it injects energy per unit area and per unit time 57 | # equal to ``\varepsilon``. That is, the forcing covariance spectrum is proportional to 58 | # ``\exp{[-(|\bm{k}| - k_f)^2 / (2 δ_f^2)]}``. 59 | 60 | forcing_wavenumber = 14.0 * 2π/L # the forcing wavenumber, `k_f`, for a spectrum that is a ring in wavenumber space 61 | forcing_bandwidth = 1.5 * 2π/L # the width of the forcing spectrum, `δ_f` 62 | ε = 0.001 # energy input rate by the forcing 63 | 64 | grid = TwoDGrid(dev; nx=n, Lx=L) 65 | 66 | K = @. sqrt(grid.Krsq) # a 2D array with the total wavenumber 67 | 68 | forcing_spectrum = @. exp(-(K - forcing_wavenumber)^2 / (2 * forcing_bandwidth^2)) 69 | @CUDA.allowscalar forcing_spectrum[grid.Krsq .== 0] .= 0 # ensure forcing has zero domain-average 70 | 71 | ε0 = parsevalsum(forcing_spectrum .* grid.invKrsq / 2, grid) / (grid.Lx * grid.Ly) 72 | @. forcing_spectrum *= ε/ε0 # normalize forcing to inject energy at rate ε 73 | nothing #hide 74 | 75 | 76 | # We reset of the random number generator for reproducibility 77 | if dev==CPU(); Random.seed!(1234); else; CUDA.seed!(1234); end 78 | nothing #hide 79 | 80 | 81 | # Next we construct function `calcF!` that computes a forcing realization every timestep. 82 | # For that, we call `randn!` to obtain complex numbers whose real and imaginary part 83 | # are normally-distributed with zero mean and variance 1/2. 84 | 85 | function calcF!(Fh, sol, t, clock, vars, params, grid) 86 | randn!(Fh) 87 | @. Fh *= sqrt(forcing_spectrum) / sqrt(clock.dt) 88 | return nothing 89 | end 90 | nothing #hide 91 | 92 | 93 | # ## Problem setup 94 | # We initialize a `Problem` by providing a set of keyword arguments. 95 | # We use `stepper = "FilteredRK4"`. Filtered timesteppers apply a wavenumber-filter 96 | # at every time-step that removes enstrophy at high wavenumbers and, thereby, 97 | # stabilize the problem, despite that we use the default viscosity coefficient `ν=0`. 98 | # Thus, we choose not to do any dealiasing by providing `aliased_fraction=0`. 99 | prob = BarotropicQGQL.Problem(dev; nx=n, Lx=L, β, μ, dt, stepper, 100 | calcF=calcF!, stochastic=true, aliased_fraction=0) 101 | nothing #hide 102 | 103 | # and define some shortcuts. 104 | sol, clock, vars, params, grid = prob.sol, prob.clock, prob.vars, prob.params, prob.grid 105 | x, y = grid.x, grid.y 106 | Lx, Ly = grid.Lx, grid.Ly 107 | nothing #hide 108 | 109 | 110 | # First let's see how a forcing realization looks like. Note that when plotting, we decorate 111 | # the variable to be plotted with `Array()` to make sure it is brought back on the CPU when 112 | # `vars` live on the GPU. 113 | 114 | calcF!(vars.Fh, sol, 0.0, clock, vars, params, grid) 115 | 116 | fig = Figure() 117 | 118 | ax = Axis(fig[1, 1], 119 | xlabel = "x", 120 | ylabel = "y", 121 | aspect = 1, 122 | title = "a forcing realization", 123 | limits = ((-Lx/2, Lx/2), (-Ly/2, Ly/2))) 124 | 125 | heatmap!(ax, x, y, Array(irfft(vars.Fh, grid.nx)); 126 | colormap = :balance, colorrange = (-8, 8)) 127 | 128 | fig 129 | 130 | 131 | # ## Setting initial conditions 132 | 133 | # Our initial condition is simply fluid at rest. 134 | BarotropicQGQL.set_zeta!(prob, device_array(dev)(zeros(grid.nx, grid.ny))) 135 | nothing #hide 136 | 137 | # ## Diagnostics 138 | 139 | # Create Diagnostics -- `energy` and `enstrophy` are functions imported at the top. 140 | E = Diagnostic(BarotropicQGQL.energy, prob; nsteps) 141 | Z = Diagnostic(BarotropicQGQL.enstrophy, prob; nsteps) 142 | nothing #hide 143 | 144 | # We can also define our custom diagnostics via functions. 145 | zetaMean(prob) = prob.sol[1, :] 146 | 147 | zMean = Diagnostic(zetaMean, prob; nsteps, freq=10) # the zonal-mean vorticity 148 | nothing #hide 149 | 150 | # We combile all diags in a list. 151 | diags = [E, Z, zMean] # A list of Diagnostics types passed to "stepforward!" will be updated every timestep. 152 | nothing #hide 153 | 154 | 155 | # ## Output 156 | 157 | # We choose folder for outputing `.jld2` files and snapshots (`.png` files). 158 | filepath = "." 159 | plotpath = "./plots_forcedbetaQLturb" 160 | plotname = "snapshots" 161 | filename = joinpath(filepath, "forcedbetaQLturb.jld2") 162 | nothing #hide 163 | 164 | # Do some basic file management, 165 | if isfile(filename); rm(filename); end 166 | if !isdir(plotpath); mkdir(plotpath); end 167 | nothing #hide 168 | 169 | # and then create Output. 170 | get_sol(prob) = prob.sol # extracts the Fourier-transformed solution 171 | 172 | function get_u(prob) 173 | grid, vars = prob.grid, prob.vars 174 | 175 | @. vars.uh = im * grid.l * grid.invKrsq * sol 176 | ldiv!(vars.u, grid.rfftplan, deepcopy(vars.uh)) 177 | 178 | return vars.u 179 | end 180 | 181 | out = Output(prob, filename, (:sol, get_sol), (:u, get_u)) 182 | 183 | 184 | # ## Visualizing the simulation 185 | 186 | # We define a function that plots the vorticity and streamfunction fields, the 187 | # corresponding zonal-mean vorticity and zonal-mean zonal velocity and timeseries 188 | # of energy and enstrophy. 189 | 190 | title_ζ = Observable(@sprintf("vorticity, μt = %.2f", μ * clock.t)) 191 | title_ψ = "streamfunction ψ" 192 | 193 | fig = Figure(size = (1000, 600)) 194 | 195 | axis_kwargs = (xlabel = "x", 196 | ylabel = "y", 197 | aspect = 1, 198 | limits = ((-Lx/2, Lx/2), (-Ly/2, Ly/2))) 199 | 200 | axζ = Axis(fig[1, 1]; title = title_ζ, axis_kwargs...) 201 | 202 | axψ = Axis(fig[2, 1]; title = title_ψ, axis_kwargs...) 203 | 204 | axζ̄ = Axis(fig[1, 2], 205 | xlabel = "zonal mean ζ", 206 | ylabel = "y", 207 | aspect = 1, 208 | limits = ((-3, 3), (-Ly/2, Ly/2))) 209 | 210 | axū = Axis(fig[2, 2], 211 | xlabel = "zonal mean u", 212 | ylabel = "y", 213 | aspect = 1, 214 | limits = ((-0.5, 0.5), (-Ly/2, Ly/2))) 215 | 216 | axE = Axis(fig[1, 3], 217 | xlabel = "μ t", 218 | ylabel = "energy", 219 | aspect = 1, 220 | limits = ((-0.1, 4.1), (0, 0.05))) 221 | 222 | axZ = Axis(fig[2, 3], 223 | xlabel = "μ t", 224 | ylabel = "enstrophy", 225 | aspect = 1, 226 | limits = ((-0.1, 4.1), (0, 5))) 227 | 228 | ζ̄, ζ′= prob.vars.Zeta, prob.vars.zeta 229 | ζ = Observable(Array(@. ζ̄ + ζ′)) 230 | ψ̄, ψ′= prob.vars.Psi, prob.vars.psi 231 | ψ = Observable(Array(@. ψ̄ + ψ′)) 232 | ζ̄ₘ = Observable(Array(vec(mean(ζ̄, dims=1)))) 233 | ūₘ = Observable(Array(vec(mean(prob.vars.U, dims=1)))) 234 | 235 | μt = Observable(μ * E.t[1:1]) 236 | energy = Observable(E.data[1:1]) 237 | enstrophy = Observable(Z.data[1:1]) 238 | 239 | heatmap!(axζ, x, y, ζ; 240 | colormap = :balance, colorrange = (-8, 8)) 241 | 242 | heatmap!(axψ, x, y, ψ; 243 | colormap = :viridis, colorrange = (-0.22, 0.22)) 244 | 245 | lines!(axζ̄, ζ̄ₘ, y; linewidth = 3) 246 | lines!(axζ̄, 0y, y; linewidth = 1, linestyle=:dash) 247 | 248 | lines!(axū, ūₘ, y; linewidth = 3) 249 | lines!(axū, 0y, y; linewidth = 1, linestyle=:dash) 250 | 251 | lines!(axE, μt, energy; linewidth = 3) 252 | lines!(axZ, μt, enstrophy; linewidth = 3, color = :red) 253 | 254 | nothing #hide 255 | 256 | 257 | # ## Time-stepping the `Problem` forward 258 | 259 | # We step the `Problem` forward in time. 260 | 261 | startwalltime = time() 262 | 263 | frames = 0:round(Int, nsteps / nsubs) 264 | 265 | record(fig, "barotropicqgql_betaforced.mp4", frames, framerate = 18) do j 266 | if j % (1000 / nsubs) == 0 267 | cfl = clock.dt * maximum([maximum(vars.u .+ vars.U) / grid.dx, maximum(vars.v) / grid.dy]) 268 | 269 | log = @sprintf("step: %04d, t: %d, cfl: %.2f, E: %.4f, Q: %.4f, walltime: %.2f min", 270 | clock.step, clock.t, cfl, E.data[E.i], Z.data[Z.i], 271 | (time()-startwalltime)/60) 272 | 273 | println(log) 274 | end 275 | 276 | ζ[] = @. ζ̄ + ζ′ 277 | ψ[] = @. ψ̄ + ψ′ 278 | ζ̄ₘ[] = vec(mean(ζ̄, dims=1)) 279 | ūₘ[] = vec(mean(prob.vars.U, dims=1)) 280 | 281 | μt.val = μ * E.t[1:E.i] 282 | energy[] = E.data[1:E.i] 283 | enstrophy[] = Z.data[1:E.i] 284 | 285 | title_ζ[] = @sprintf("vorticity, μt = %.2f", μ * clock.t) 286 | 287 | stepforward!(prob, diags, nsubs) 288 | BarotropicQGQL.updatevars!(prob) 289 | end 290 | nothing #hide 291 | 292 | # ![](barotropicqgql_betaforced.mp4) 293 | 294 | 295 | # ## Save 296 | 297 | # Finally, we can save, e.g., the last snapshot via 298 | # ```julia 299 | # savename = @sprintf("%s_%09d.png", joinpath(plotpath, plotname), clock.step) 300 | # savefig(savename) 301 | # ``` 302 | -------------------------------------------------------------------------------- /examples/multilayerqg_2layer.jl: -------------------------------------------------------------------------------- 1 | # # [Phillips model of Baroclinic Instability](@id multilayerqg_2layer_example) 2 | # 3 | # A simulation of the growth of barolinic instability in the Phillips 2-layer model 4 | # when we impose a vertical mean flow shear as a difference ``\Delta U`` in the 5 | # imposed, domain-averaged, zonal flow at each layer. 6 | # 7 | # ## Install dependencies 8 | # 9 | # First let's make sure we have all required packages installed. 10 | 11 | # ```julia 12 | # using Pkg 13 | # pkg"add GeophysicalFlows, CairoMakie, Printf" 14 | # ``` 15 | 16 | # ## Let's begin 17 | # Let's load `GeophysicalFlows.jl` and some other packages we need. 18 | # 19 | using GeophysicalFlows, CairoMakie, Printf 20 | 21 | using Random: seed! 22 | 23 | 24 | # ## Choosing a device: CPU or GPU 25 | 26 | dev = CPU() # Device (CPU/GPU) 27 | nothing #hide 28 | 29 | 30 | # ## Numerical parameters and time-stepping parameters 31 | 32 | n = 128 # 2D resolution = n² 33 | stepper = "FilteredRK4" # timestepper 34 | dt = 2.5e-3 # timestep 35 | nsteps = 20000 # total number of time-steps 36 | nsubs = 50 # number of time-steps for plotting (nsteps must be multiple of nsubs) 37 | nothing #hide 38 | 39 | 40 | # ## Physical parameters 41 | L = 2π # domain size 42 | μ = 5e-2 # bottom drag 43 | β = 5 # the y-gradient of planetary PV 44 | 45 | nlayers = 2 # number of layers 46 | f₀ = 1 # Coriolis parameter 47 | H = [0.2, 0.8] # the rest depths of each layer 48 | b = [-1.0, -1.2] # Boussinesq buoyancy of each layer 49 | 50 | U = zeros(nlayers) # the imposed mean zonal flow in each layer 51 | U[1] = 1.0 52 | U[2] = 0.0 53 | nothing #hide 54 | 55 | 56 | # ## Problem setup 57 | # We initialize a `Problem` by providing a set of keyword arguments. 58 | # We use `stepper = "FilteredRK4"`. Filtered timesteppers apply a wavenumber-filter 59 | # at every time-step that removes enstrophy at high wavenumbers and, thereby, 60 | # stabilize the problem, despite that we use the default viscosity coefficient `ν=0`. 61 | 62 | prob = MultiLayerQG.Problem(nlayers, dev; nx=n, Lx=L, f₀, H, b, U, μ, β, 63 | dt, stepper, aliased_fraction=0) 64 | nothing #hide 65 | 66 | # and define some shortcuts. 67 | sol, clock, params, vars, grid = prob.sol, prob.clock, prob.params, prob.vars, prob.grid 68 | x, y = grid.x, grid.y 69 | nothing #hide 70 | 71 | 72 | # ## Setting initial conditions 73 | 74 | # Our initial condition is some small-amplitude random noise. We smooth our initial 75 | # condidtion using the `timestepper`'s high-wavenumber `filter`. 76 | # 77 | # `device_array()` function returns the array type appropriate for the device, i.e., `Array` for 78 | # `dev = CPU()` and `CuArray` for `dev = GPU()`. 79 | 80 | seed!(1234) # reset of the random number generator for reproducibility 81 | q₀ = 1e-2 * device_array(dev)(randn((grid.nx, grid.ny, nlayers))) 82 | q₀h = prob.timestepper.filter .* rfft(q₀, (1, 2)) # apply rfft only in dims=1, 2 83 | q₀ = irfft(q₀h, grid.nx, (1, 2)) # apply irfft only in dims=1, 2 84 | 85 | MultiLayerQG.set_q!(prob, q₀) 86 | nothing #hide 87 | 88 | 89 | # ## Diagnostics 90 | 91 | # Create Diagnostics -- `energies` function is imported at the top. 92 | E = Diagnostic(MultiLayerQG.energies, prob; nsteps) 93 | diags = [E] # A list of Diagnostics types passed to "stepforward!" will be updated every timestep. 94 | nothing #hide 95 | 96 | 97 | # ## Output 98 | 99 | # We choose folder for outputing `.jld2` files and snapshots (`.png` files). 100 | filepath = "." 101 | plotpath = "./plots_2layer" 102 | plotname = "snapshots" 103 | filename = joinpath(filepath, "2layer.jld2") 104 | nothing #hide 105 | 106 | # Do some basic file management 107 | if isfile(filename); rm(filename); end 108 | if !isdir(plotpath); mkdir(plotpath); end 109 | nothing #hide 110 | 111 | # And then create Output 112 | 113 | get_sol(prob) = prob.sol # extracts the Fourier-transformed solution 114 | 115 | function get_u(prob) 116 | sol, params, vars, grid = prob.sol, prob.params, prob.vars, prob.grid 117 | 118 | @. vars.qh = sol 119 | streamfunctionfrompv!(vars.ψh, vars.qh, params, grid) 120 | @. vars.uh = -im * grid.l * vars.ψh 121 | invtransform!(vars.u, vars.uh, params) 122 | 123 | return vars.u 124 | end 125 | 126 | out = Output(prob, filename, (:sol, get_sol), (:u, get_u)) 127 | nothing #hide 128 | 129 | 130 | # ## Visualizing the simulation 131 | 132 | # We create a figure using Makie's [`Observable`](https://makie.juliaplots.org/stable/documentation/nodes/)s 133 | # that plots the potential vorticity field and the evolution of energy and enstrophy. 134 | # Note that when plotting, we decorate the variable to be plotted with `Array()` 135 | # to make sure it is brought back on the CPU when `vars` live on the GPU. 136 | 137 | Lx, Ly = grid.Lx, grid.Ly 138 | 139 | title_KE = Observable(@sprintf("μt = %.2f", μ * clock.t)) 140 | 141 | q₁ = Observable(Array(vars.q[:, :, 1])) 142 | ψ₁ = Observable(Array(vars.ψ[:, :, 1])) 143 | q₂ = Observable(Array(vars.q[:, :, 2])) 144 | ψ₂ = Observable(Array(vars.ψ[:, :, 2])) 145 | 146 | function compute_levels(maxf, nlevels=8) 147 | ## -max(|f|):...:max(|f|) 148 | levelsf = @lift collect(range(-$maxf, stop = $maxf, length=nlevels)) 149 | 150 | ## only positive 151 | levelsf⁺ = @lift collect(range($maxf/(nlevels-1), stop = $maxf, length=Int(nlevels/2))) 152 | 153 | ## only negative 154 | levelsf⁻ = @lift collect(range(-$maxf, stop = -$maxf/(nlevels-1), length=Int(nlevels/2))) 155 | 156 | return levelsf, levelsf⁺, levelsf⁻ 157 | end 158 | 159 | maxψ₁ = Observable(maximum(abs, vars.ψ[:, :, 1])) 160 | maxψ₂ = Observable(maximum(abs, vars.ψ[:, :, 2])) 161 | 162 | levelsψ₁, levelsψ₁⁺, levelsψ₁⁻ = compute_levels(maxψ₁) 163 | levelsψ₂, levelsψ₂⁺, levelsψ₂⁻ = compute_levels(maxψ₂) 164 | 165 | KE₁ = Observable(Point2f[(μ * E.t[1], E.data[1][1][1])]) 166 | KE₂ = Observable(Point2f[(μ * E.t[1], E.data[1][1][2])]) 167 | PE = Observable(Point2f[(μ * E.t[1], E.data[1][2][1])]) 168 | 169 | fig = Figure(size = (1000, 600)) 170 | 171 | axis_kwargs = (xlabel = "x", 172 | ylabel = "y", 173 | aspect = 1, 174 | limits = ((-Lx/2, Lx/2), (-Ly/2, Ly/2))) 175 | 176 | axq₁ = Axis(fig[1, 1]; title = "q₁", axis_kwargs...) 177 | 178 | axψ₁ = Axis(fig[2, 1]; title = "ψ₁", axis_kwargs...) 179 | 180 | axq₂ = Axis(fig[1, 2]; title = "q₂", axis_kwargs...) 181 | 182 | axψ₂ = Axis(fig[2, 2]; title = "ψ₂", axis_kwargs...) 183 | 184 | axKE = Axis(fig[1, 3], 185 | xlabel = "μ t", 186 | ylabel = "KE", 187 | title = title_KE, 188 | yscale = log10, 189 | limits = ((-0.1, 2.6), (1e-9, 5))) 190 | 191 | axPE = Axis(fig[2, 3], 192 | xlabel = "μ t", 193 | ylabel = "PE", 194 | yscale = log10, 195 | limits = ((-0.1, 2.6), (1e-9, 5))) 196 | 197 | heatmap!(axq₁, x, y, q₁; colormap = :balance) 198 | 199 | heatmap!(axq₂, x, y, q₂; colormap = :balance) 200 | 201 | contourf!(axψ₁, x, y, ψ₁; 202 | levels = levelsψ₁, colormap = :viridis, extendlow = :auto, extendhigh = :auto) 203 | contour!(axψ₁, x, y, ψ₁; 204 | levels = levelsψ₁⁺, color=:black) 205 | contour!(axψ₁, x, y, ψ₁; 206 | levels = levelsψ₁⁻, color=:black, linestyle = :dash) 207 | 208 | contourf!(axψ₂, x, y, ψ₂; 209 | levels = levelsψ₂, colormap = :viridis, extendlow = :auto, extendhigh = :auto) 210 | contour!(axψ₂, x, y, ψ₂; 211 | levels = levelsψ₂⁺, color=:black) 212 | contour!(axψ₂, x, y, ψ₂; 213 | levels = levelsψ₂⁻, color=:black, linestyle = :dash) 214 | 215 | ke₁ = lines!(axKE, KE₁; linewidth = 3) 216 | ke₂ = lines!(axKE, KE₂; linewidth = 3) 217 | Legend(fig[1, 4], [ke₁, ke₂,], ["KE₁", "KE₂"]) 218 | 219 | lines!(axPE, PE; linewidth = 3) 220 | 221 | fig 222 | 223 | 224 | # ## Time-stepping the `Problem` forward 225 | 226 | # Finally, we time-step the `Problem` forward in time. 227 | 228 | startwalltime = time() 229 | 230 | frames = 0:round(Int, nsteps / nsubs) 231 | 232 | record(fig, "multilayerqg_2layer.mp4", frames, framerate = 18) do j 233 | if j % (1000 / nsubs) == 0 234 | cfl = clock.dt * maximum([maximum(vars.u) / grid.dx, maximum(vars.v) / grid.dy]) 235 | 236 | log = @sprintf("step: %04d, t: %.1f, cfl: %.2f, KE₁: %.3e, KE₂: %.3e, PE: %.3e, walltime: %.2f min", 237 | clock.step, clock.t, cfl, E.data[E.i][1][1], E.data[E.i][1][2], E.data[E.i][2][1], (time()-startwalltime)/60) 238 | 239 | println(log) 240 | end 241 | 242 | q₁[] = vars.q[:, :, 1] 243 | ψ₁[] = vars.ψ[:, :, 1] 244 | q₂[] = vars.q[:, :, 2] 245 | ψ₂[] = vars.ψ[:, :, 2] 246 | 247 | maxψ₁[] = maximum(abs, vars.ψ[:, :, 1]) 248 | maxψ₂[] = maximum(abs, vars.ψ[:, :, 2]) 249 | 250 | KE₁[] = push!(KE₁[], Point2f(μ * E.t[E.i], E.data[E.i][1][1])) 251 | KE₂[] = push!(KE₂[], Point2f(μ * E.t[E.i], E.data[E.i][1][2])) 252 | PE[] = push!(PE[] , Point2f(μ * E.t[E.i], E.data[E.i][2][1])) 253 | 254 | title_KE[] = @sprintf("μ t = %.2f", μ * clock.t) 255 | 256 | stepforward!(prob, diags, nsubs) 257 | MultiLayerQG.updatevars!(prob) 258 | end 259 | nothing #hide 260 | 261 | # ![](multilayerqg_2layer.mp4) 262 | 263 | 264 | # ## Save 265 | # Finally, we can save, e.g., the last snapshot via 266 | # ```julia 267 | # savename = @sprintf("%s_%09d.png", joinpath(plotpath, plotname), clock.step) 268 | # savefig(savename) 269 | # ``` 270 | -------------------------------------------------------------------------------- /examples/singlelayerqg_betadecay.jl: -------------------------------------------------------------------------------- 1 | # # [Decaying barotropic QG beta-plane turbulence](@id singlelayerqg_betadecay_example) 2 | # 3 | # An example of decaying barotropic quasi-geostrophic turbulence on a beta plane. 4 | # 5 | # ## Install dependencies 6 | # 7 | # First let's make sure we have all required packages installed. 8 | 9 | # ```julia 10 | # using Pkg 11 | # pkg"add GeophysicalFlows, CairoMakie, Printf, Statistics, Random" 12 | # ``` 13 | 14 | # ## Let's begin 15 | # Let's load `GeophysicalFlows.jl` and some other packages we need. 16 | # 17 | using GeophysicalFlows, CairoMakie, Printf, Random 18 | 19 | using Statistics: mean 20 | 21 | 22 | # ## Choosing a device: CPU or GPU 23 | 24 | dev = CPU() # Device (CPU/GPU) 25 | nothing #hide 26 | 27 | 28 | # ## Numerical parameters and time-stepping parameters 29 | 30 | n = 128 # 2D resolution: n² grid points 31 | stepper = "FilteredRK4" # timestepper 32 | dt = 0.04 # timestep 33 | nsteps = 2000 # total number of time-steps 34 | nsubs = 10 # number of time-steps for intermediate logging/plotting (nsteps must be multiple of nsubs) 35 | nothing #hide 36 | 37 | 38 | # ## Physical parameters 39 | 40 | L = 2π # domain size 41 | β = 10.0 # planetary PV gradient 42 | μ = 0.0 # bottom drag 43 | nothing #hide 44 | 45 | # ## Problem setup 46 | # We initialize a `Problem` by providing a set of keyword arguments. 47 | # We use `stepper = "FilteredRK4"`. Filtered timesteppers apply a wavenumber-filter 48 | # at every time-step that removes enstrophy at high wavenumbers and, thereby, 49 | # stabilize the problem, despite that we use the default viscosity coefficient `ν=0`. 50 | # Thus, we choose not to do any dealiasing by providing `aliased_fraction=0`. 51 | 52 | prob = SingleLayerQG.Problem(dev; nx=n, Lx=L, β, μ, dt, stepper, aliased_fraction=0) 53 | nothing #hide 54 | 55 | # and define some shortcuts 56 | sol, clock, vars, params, grid = prob.sol, prob.clock, prob.vars, prob.params, prob.grid 57 | x, y = grid.x, grid.y 58 | nothing #hide 59 | 60 | 61 | # ## Setting initial conditions 62 | 63 | # Our initial condition consist of a flow that has power only at wavenumbers with 64 | # ``6 < \frac{L}{2\pi} \sqrt{k_x^2 + k_y^2} < 10`` and initial energy ``E_0``. 65 | # `device_array()` function returns the array type appropriate for the device, i.e., `Array` for 66 | # `dev = CPU()` and `CuArray` for `dev = GPU()`. 67 | 68 | E₀ = 0.08 # energy of initial condition 69 | 70 | K = @. sqrt(grid.Krsq) # a 2D array with the total wavenumber 71 | 72 | Random.seed!(1234) 73 | q₀h = device_array(dev)(randn(Complex{eltype(grid)}, size(sol))) 74 | @. q₀h = ifelse(K < 6 * 2π/L, 0, q₀h) 75 | @. q₀h = ifelse(K > 10 * 2π/L, 0, q₀h) 76 | @. q₀h[1, :] = 0 # remove any power from zonal wavenumber k=0 77 | q₀h *= sqrt(E₀ / SingleLayerQG.energy(q₀h, vars, params, grid)) # normalize q₀ to have energy E₀ 78 | q₀ = irfft(q₀h, grid.nx) 79 | 80 | SingleLayerQG.set_q!(prob, q₀) 81 | nothing #hide 82 | 83 | # Let's plot the initial vorticity and streamfunction. Note that when plotting, we decorate 84 | # the variable to be plotted with `Array()` to make sure it is brought back on the CPU when 85 | # `vars` live on the GPU. 86 | 87 | fig = Figure(size = (800, 360)) 88 | 89 | axq = Axis(fig[1, 1]; 90 | xlabel = "x", 91 | ylabel = "y", 92 | title = "initial vorticity ∂v/∂x-∂u/∂y", 93 | aspect = 1, 94 | limits = ((-grid.Lx/2, grid.Lx/2), (-grid.Ly/2, grid.Ly/2)) 95 | ) 96 | 97 | axψ = Axis(fig[1, 2]; 98 | xlabel = "x", 99 | ylabel = "y", 100 | title = "initial streamfunction ψ", 101 | aspect = 1, 102 | limits = ((-grid.Lx/2, grid.Lx/2), (-grid.Ly/2, grid.Ly/2)) 103 | ) 104 | 105 | heatmap!(axq, x, y, Array(vars.q); colormap = :balance) 106 | 107 | contourf!(axψ, x, y, Array(vars.ψ); colormap = :viridis) 108 | 109 | fig 110 | 111 | # ## Diagnostics 112 | 113 | # Create Diagnostics -- `energy` and `enstrophy` functions are imported at the top. 114 | E = Diagnostic(SingleLayerQG.energy, prob; nsteps) 115 | Z = Diagnostic(SingleLayerQG.enstrophy, prob; nsteps) 116 | diags = [E, Z] # A list of Diagnostics types passed to "stepforward!" will be updated every timestep. 117 | nothing #hide 118 | 119 | 120 | # ## Output 121 | 122 | # We choose folder for outputing `.jld2` files and snapshots (`.png` files). 123 | filepath = "." 124 | plotpath = "./plots_decayingbetaturb" 125 | plotname = "snapshots" 126 | filename = joinpath(filepath, "decayingbetaturb.jld2") 127 | nothing #hide 128 | 129 | # Do some basic file management, 130 | if isfile(filename); rm(filename); end 131 | if !isdir(plotpath); mkdir(plotpath); end 132 | nothing #hide 133 | 134 | # and then create Output. 135 | get_sol(prob) = prob.sol # extracts the Fourier-transformed solution 136 | out = Output(prob, filename, (:sol, get_sol)) 137 | nothing #hide 138 | 139 | 140 | # ## Visualizing the simulation 141 | 142 | # We plot the vorticity and streamfunction and their corresponding zonal mean structure. 143 | 144 | Lx, Ly = grid.Lx, grid.Ly 145 | 146 | title_q = Observable(@sprintf("vorticity, t = %.2f", clock.t)) 147 | title_ψ = "streamfunction ψ" 148 | 149 | fig = Figure(size = (800, 720)) 150 | 151 | axis_kwargs = (xlabel = "x", 152 | ylabel = "y", 153 | aspect = 1, 154 | limits = ((-Lx/2, Lx/2), (-Ly/2, Ly/2))) 155 | 156 | axq = Axis(fig[1, 1]; title = title_q, axis_kwargs...) 157 | 158 | axψ = Axis(fig[2, 1]; title = title_ψ, axis_kwargs...) 159 | 160 | axq̄ = Axis(fig[1, 2], 161 | xlabel = "zonal mean vorticity", 162 | ylabel = "y", 163 | aspect = 1, 164 | limits = ((-2.1, 2.1), (-Ly/2, Ly/2))) 165 | 166 | axū = Axis(fig[2, 2], 167 | xlabel = "zonal mean u", 168 | ylabel = "y", 169 | aspect = 1, 170 | limits = ((-0.5, 0.5), (-Ly/2, Ly/2))) 171 | 172 | q = Observable(Array(vars.q)) 173 | ψ = Observable(Array(vars.ψ)) 174 | q̄ₘ = Observable(Array(vec(mean(vars.q, dims=1)))) 175 | ūₘ = Observable(Array(vec(mean(vars.u, dims=1)))) 176 | 177 | heatmap!(axq, x, y, q; 178 | colormap = :balance, colorrange = (-12, 12)) 179 | 180 | levels = collect(range(-0.7, stop=0.7, length=20)) 181 | 182 | contourf!(axψ, x, y, ψ; 183 | levels, colormap = :viridis) 184 | contour!(axψ, x, y, ψ; 185 | levels, color = :black) 186 | 187 | lines!(axq̄, q̄ₘ, y; linewidth = 3) 188 | lines!(axq̄, 0y, y; linewidth = 1, linestyle = :dash) 189 | 190 | lines!(axū, ūₘ, y; linewidth = 3) 191 | lines!(axū, 0y, y; linewidth = 1, linestyle = :dash) 192 | 193 | fig 194 | 195 | 196 | # ## Time-stepping the `Problem` forward 197 | 198 | # We step the `Problem` forward in time. 199 | 200 | startwalltime = time() 201 | 202 | frames = 0:round(Int, nsteps / nsubs) 203 | 204 | record(fig, "singlelayerqg_betadecay.mp4", frames, framerate = 12) do j 205 | if j % round(Int, nsteps/nsubs / 4) == 0 206 | cfl = clock.dt * maximum([maximum(vars.u) / grid.dx, maximum(vars.v) / grid.dy]) 207 | 208 | log = @sprintf("step: %04d, t: %d, cfl: %.2f, E: %.4f, Q: %.4f, walltime: %.2f min", 209 | clock.step, clock.t, cfl, E.data[E.i], Z.data[Z.i], (time()-startwalltime)/60) 210 | 211 | println(log) 212 | end 213 | 214 | q[] = vars.q 215 | ψ[] = vars.ψ 216 | q̄ₘ[] = vec(mean(vars.q, dims=1)) 217 | ūₘ[] = vec(mean(vars.u, dims=1)) 218 | 219 | title_q[] = @sprintf("vorticity, t = %.2f", clock.t) 220 | 221 | stepforward!(prob, diags, nsubs) 222 | SingleLayerQG.updatevars!(prob) 223 | end 224 | nothing #hide 225 | 226 | # ![](singlelayerqg_betadecay.mp4) 227 | 228 | 229 | # ## Save 230 | 231 | # Finally, we can save, e.g., the last snapshot via 232 | # ```julia 233 | # savename = @sprintf("%s_%09d.png", joinpath(plotpath, plotname), clock.step) 234 | # savefig(savename) 235 | # ``` 236 | -------------------------------------------------------------------------------- /examples/singlelayerqg_betaforced.jl: -------------------------------------------------------------------------------- 1 | # # [Forced-dissipative barotropic QG beta-plane turbulence](@id singlelayerqg_betaforced_example) 2 | # 3 | # A simulation of forced-dissipative barotropic quasi-geostrophic turbulence on 4 | # a beta plane. The dynamics include linear drag and stochastic excitation. 5 | # 6 | # ## Install dependencies 7 | # 8 | # First let's make sure we have all required packages installed. 9 | 10 | # ```julia 11 | # using Pkg 12 | # pkg"add GeophysicalFlows, CUDA, JLD2, CairoMakie, Statistics" 13 | # ``` 14 | 15 | # ## Let's begin 16 | # Let's load `GeophysicalFlows.jl` and some other packages we need. 17 | # 18 | using GeophysicalFlows, CUDA, JLD2, CairoMakie, Random, Printf 19 | 20 | using Statistics: mean 21 | using LinearAlgebra: ldiv! 22 | 23 | parsevalsum = FourierFlows.parsevalsum 24 | record = CairoMakie.record # disambiguate between CairoMakie.record and CUDA.record 25 | nothing #hide 26 | 27 | # ## Choosing a device: CPU or GPU 28 | 29 | dev = CPU() # Device (CPU/GPU) 30 | nothing #hide 31 | 32 | 33 | # ## Numerical parameters and time-stepping parameters 34 | 35 | n = 128 # 2D resolution: n² grid points 36 | stepper = "FilteredRK4" # timestepper 37 | dt = 0.05 # timestep 38 | nsteps = 8000 # total number of timesteps 39 | save_substeps = 10 # number of timesteps after which output is saved 40 | 41 | nothing #hide 42 | 43 | 44 | # ## Physical parameters 45 | 46 | L = 2π # domain size 47 | β = 10.0 # planetary PV gradient 48 | μ = 0.01 # bottom drag 49 | nothing #hide 50 | 51 | 52 | # ## Forcing 53 | # 54 | # We force the vorticity equation with stochastic excitation that is delta-correlated in time 55 | # and while spatially homogeneously and isotropically correlated. The forcing has a spectrum 56 | # with power in a ring in wavenumber space of radius ``k_f`` (`forcing_wavenumber`) and width 57 | # ``δ_f`` (`forcing_bandwidth`), and it injects energy per unit area and per unit time 58 | # equal to ``\varepsilon``. That is, the forcing covariance spectrum is proportional to 59 | # ``\exp{[-(|\bm{k}| - k_f)^2 / (2 δ_f^2)]}``. 60 | 61 | forcing_wavenumber = 14.0 * 2π/L # the forcing wavenumber, `k_f`, for a spectrum that is a ring in wavenumber space 62 | forcing_bandwidth = 1.5 * 2π/L # the width of the forcing spectrum, `δ_f` 63 | ε = 0.001 # energy input rate by the forcing 64 | 65 | grid = TwoDGrid(dev; nx=n, Lx=L) 66 | 67 | K = @. sqrt(grid.Krsq) # a 2D array with the total wavenumber 68 | 69 | forcing_spectrum = @. exp(-(K - forcing_wavenumber)^2 / (2 * forcing_bandwidth^2)) 70 | @CUDA.allowscalar forcing_spectrum[grid.Krsq .== 0] .= 0 # ensure forcing has zero domain-average 71 | 72 | ε0 = parsevalsum(forcing_spectrum .* grid.invKrsq / 2, grid) / (grid.Lx * grid.Ly) 73 | @. forcing_spectrum *= ε/ε0 # normalize forcing to inject energy at rate ε 74 | nothing #hide 75 | 76 | 77 | # We reset of the random number generator for reproducibility 78 | if dev==CPU(); Random.seed!(1234); else; CUDA.seed!(1234); end 79 | nothing #hide 80 | 81 | 82 | # Next we construct function `calcF!` that computes a forcing realization every timestep. 83 | # For that, we call `randn!` to obtain complex numbers whose real and imaginary part 84 | # are normally-distributed with zero mean and variance 1/2. 85 | 86 | function calcF!(Fh, sol, t, clock, vars, params, grid) 87 | randn!(Fh) 88 | @. Fh *= sqrt(forcing_spectrum) / sqrt(clock.dt) 89 | return nothing 90 | end 91 | nothing #hide 92 | 93 | 94 | # ## Problem setup 95 | # We initialize a `Problem` by providing a set of keyword arguments. 96 | # We use `stepper = "FilteredRK4"`. Filtered timesteppers apply a wavenumber-filter 97 | # at every time-step that removes enstrophy at high wavenumbers and, thereby, 98 | # stabilize the problem, despite that we use the default viscosity coefficient `ν=0`. 99 | prob = SingleLayerQG.Problem(dev; nx=n, Lx=L, β, μ, dt, stepper, 100 | calcF=calcF!, stochastic=true) 101 | nothing #hide 102 | 103 | # Let's define some shortcuts. 104 | sol, clock, vars, params, grid = prob.sol, prob.clock, prob.vars, prob.params, prob.grid 105 | x, y = grid.x, grid.y 106 | Lx, Ly = grid.Lx, grid.Ly 107 | nothing #hide 108 | 109 | 110 | # First let's see how a forcing realization looks like. Note that when plotting, we decorate 111 | # the variable to be plotted with `Array()` to make sure it is brought back on the CPU when 112 | # `vars` live on the GPU. 113 | calcF!(vars.Fh, sol, 0.0, clock, vars, params, grid) 114 | 115 | fig = Figure() 116 | 117 | ax = Axis(fig[1, 1], 118 | xlabel = "x", 119 | ylabel = "y", 120 | aspect = 1, 121 | title = "a forcing realization", 122 | limits = ((-Lx/2, Lx/2), (-Ly/2, Ly/2))) 123 | 124 | heatmap!(ax, x, y, Array(irfft(vars.Fh, grid.nx)); 125 | colormap = :balance, colorrange = (-8, 8)) 126 | 127 | fig 128 | 129 | 130 | # ## Setting initial conditions 131 | 132 | # Our initial condition is simply fluid at rest. 133 | SingleLayerQG.set_q!(prob, device_array(dev)(zeros(grid.nx, grid.ny))) 134 | 135 | 136 | # ## Diagnostics 137 | 138 | # Create Diagnostic -- `energy` and `enstrophy` are functions imported at the top. 139 | E = Diagnostic(SingleLayerQG.energy, prob; nsteps, freq=save_substeps) 140 | Z = Diagnostic(SingleLayerQG.enstrophy, prob; nsteps, freq=save_substeps) 141 | diags = [E, Z] # A list of Diagnostics types passed to "stepforward!" will be updated every timestep. 142 | nothing #hide 143 | 144 | 145 | # ## Output 146 | 147 | # We choose folder for outputing `.jld2` files and snapshots (`.png` files). 148 | filepath = "." 149 | plotpath = "./plots_forcedbetaturb" 150 | plotname = "snapshots" 151 | filename = joinpath(filepath, "singlelayerqg_forcedbeta.jld2") 152 | nothing #hide 153 | 154 | # Do some basic file management, 155 | if isfile(filename); rm(filename); end 156 | if !isdir(plotpath); mkdir(plotpath); end 157 | nothing #hide 158 | 159 | # and then create Output. 160 | get_sol(prob) = Array(prob.sol) # extracts the Fourier-transformed solution 161 | 162 | function get_u(prob) 163 | vars, grid, sol = prob.vars, prob.grid, prob.sol 164 | 165 | @. vars.qh = sol 166 | 167 | SingleLayerQG.streamfunctionfrompv!(vars.ψh, vars.qh, params, grid) 168 | 169 | ldiv!(vars.u, grid.rfftplan, -im * grid.l .* vars.ψh) 170 | 171 | return Array(vars.u) 172 | end 173 | 174 | output = Output(prob, filename, (:qh, get_sol), (:u, get_u)) 175 | nothing #hide 176 | 177 | # We first save the problem's grid and other parameters so we can use them later. 178 | saveproblem(output) 179 | 180 | # and then call `saveoutput(output)` once to save the initial state. 181 | saveoutput(output) 182 | 183 | # ## Time-stepping the `Problem` forward 184 | 185 | # We time-step the `Problem` forward in time. 186 | 187 | startwalltime = time() 188 | 189 | while clock.step <= nsteps 190 | if clock.step % 50save_substeps == 0 191 | cfl = clock.dt * maximum([maximum(vars.u) / grid.dx, maximum(vars.v) / grid.dy]) 192 | 193 | log = @sprintf("step: %04d, t: %d, cfl: %.2f, E: %.4f, Q: %.4f, walltime: %.2f min", 194 | clock.step, clock.t, cfl, E.data[E.i], Z.data[Z.i], (time()-startwalltime)/60) 195 | 196 | println(log) 197 | end 198 | 199 | stepforward!(prob, diags, save_substeps) 200 | SingleLayerQG.updatevars!(prob) 201 | 202 | if clock.step % save_substeps == 0 203 | saveoutput(output) 204 | end 205 | end 206 | 207 | savediagnostic(E, "energy", output.path) 208 | savediagnostic(Z, "enstrophy", output.path) 209 | 210 | 211 | # ## Load saved output and visualize 212 | 213 | # We now have output from our simulation saved in `singlelayerqg_forcedbeta.jld2` which 214 | # we can load to create a time series for the fields we are interested in. 215 | 216 | file = jldopen(output.path) 217 | 218 | iterations = parse.(Int, keys(file["snapshots/t"])) 219 | t = [file["snapshots/t/$i"] for i ∈ iterations] 220 | 221 | qh = [file["snapshots/qh/$i"] for i ∈ iterations] 222 | u = [file["snapshots/u/$i"] for i ∈ iterations] 223 | 224 | E_t = file["diagnostics/energy/t"] 225 | Z_t = file["diagnostics/enstrophy/t"] 226 | E_data = file["diagnostics/energy/data"] 227 | Z_data = file["diagnostics/enstrophy/data"] 228 | 229 | x, y = file["grid/x"], file["grid/y"] 230 | nx, ny = file["grid/nx"], file["grid/ny"] 231 | Lx, Ly = file["grid/Lx"], file["grid/Ly"] 232 | 233 | close(file) 234 | 235 | 236 | # We create a figure using Makie's [`Observable`](https://makie.juliaplots.org/stable/documentation/nodes/)s 237 | 238 | n = Observable(1) 239 | 240 | qₙ = @lift irfft(qh[$n], nx) 241 | ψₙ = @lift irfft(- Array(grid.invKrsq) .* qh[$n], nx) 242 | q̄ₙ = @lift real(ifft(qh[$n][1, :] / ny)) 243 | ūₙ = @lift vec(mean(u[$n], dims=1)) 244 | 245 | title_q = @lift @sprintf("vorticity, μt = %.2f", μ * t[$n]) 246 | 247 | energy = Observable([Point2f(E_t[1], E_data[1])]) 248 | enstrophy = Observable([Point2f(Z_t[1], Z_data[1])]) 249 | 250 | fig = Figure(size = (1000, 600)) 251 | 252 | axis_kwargs = (xlabel = "x", 253 | ylabel = "y", 254 | aspect = 1, 255 | limits = ((-Lx/2, Lx/2), (-Ly/2, Ly/2))) 256 | 257 | axq = Axis(fig[1, 1]; title = title_q, axis_kwargs...) 258 | 259 | axψ = Axis(fig[2, 1]; title = "streamfunction ψ", axis_kwargs...) 260 | 261 | axq̄ = Axis(fig[1, 2], 262 | xlabel = "zonal mean vorticity", 263 | ylabel = "y", 264 | aspect = 1, 265 | limits = ((-3, 3), (-Ly/2, Ly/2))) 266 | 267 | axū = Axis(fig[2, 2], 268 | xlabel = "zonal mean u", 269 | ylabel = "y", 270 | aspect = 1, 271 | limits = ((-0.5, 0.5), (-Ly/2, Ly/2))) 272 | 273 | axE = Axis(fig[1, 3], 274 | xlabel = "μ t", 275 | ylabel = "energy", 276 | aspect = 1, 277 | limits = ((-0.1, 4.1), (0, 0.055))) 278 | 279 | axZ = Axis(fig[2, 3], 280 | xlabel = "μ t", 281 | ylabel = "enstrophy", 282 | aspect = 1, 283 | limits = ((-0.1, 4.1), (0, 3.1))) 284 | 285 | heatmap!(axq, x, y, qₙ; 286 | colormap = :balance, colorrange = (-8, 8)) 287 | 288 | levels = collect(-0.32:0.04:0.32) 289 | 290 | contourf!(axψ, x, y, ψₙ; 291 | levels, colormap = :viridis) 292 | contour!(axψ, x, y, ψₙ; 293 | levels, color = :black) 294 | 295 | lines!(axq̄, q̄ₙ, y; linewidth = 3) 296 | lines!(axq̄, 0y, y; linewidth = 1, linestyle=:dash) 297 | 298 | lines!(axū, ūₙ, y; linewidth = 3) 299 | lines!(axū, 0y, y; linewidth = 1, linestyle=:dash) 300 | 301 | lines!(axE, energy; linewidth = 3) 302 | lines!(axZ, enstrophy; linewidth = 3, color = :red) 303 | 304 | fig 305 | 306 | 307 | # We are now ready to animate all saved output. 308 | 309 | frames = 1:length(t) 310 | record(fig, "singlelayerqg_betaforced.mp4", frames, framerate = 16) do i 311 | n[] = i 312 | 313 | energy[] = push!(energy[], Point2f(μ * E_t[i], E_data[i])) 314 | enstrophy[] = push!(enstrophy[], Point2f(μ * Z_t[i], Z_data[i])) 315 | end 316 | nothing #hide 317 | 318 | # ![](singlelayerqg_betaforced.mp4) 319 | 320 | ## we delete the .jld2 file before deploying the docs (takes too much space) #hide 321 | rm(output.path) #hide 322 | -------------------------------------------------------------------------------- /examples/singlelayerqg_decaying_barotropic_equivalentbarotropic.jl: -------------------------------------------------------------------------------- 1 | # # [SingleLayerQG decaying 2D turbulence with and without finite Rossby radius of deformation](@id singlelayerqg_decaying_barotropic_equivalentbarotropic_example) 2 | # 3 | # We use here the `SingleLayerQG` module to simulate decaying two-dimensional turbulence and 4 | # investigate how does a finite Rossby radius of deformation affects its evolution. 5 | # 6 | # ## Install dependencies 7 | # 8 | # First let's make sure we have all required packages installed. 9 | 10 | # ```julia 11 | # using Pkg 12 | # pkg"add GeophysicalFlows, Printf, Random, CairoMakie" 13 | # ``` 14 | 15 | # ## Let's begin 16 | # Let's load `GeophysicalFlows.jl` and some other packages we need. 17 | # 18 | using GeophysicalFlows, Printf, Random, CairoMakie 19 | 20 | using GeophysicalFlows: peakedisotropicspectrum 21 | using LinearAlgebra: ldiv! 22 | using Random: seed! 23 | 24 | 25 | # ## Choosing a device: CPU or GPU 26 | 27 | dev = CPU() # Device (CPU/GPU) 28 | nothing #hide 29 | 30 | 31 | # ## Numerical, domain, and simulation parameters 32 | # 33 | # First, we pick some numerical and physical parameters for our model. 34 | 35 | n, L = 128, 2π # grid resolution and domain length 36 | deformation_radius = 0.35 # the deformation radius 37 | nothing #hide 38 | 39 | ## Then we pick the time-stepper parameters 40 | dt = 1e-2 # timestep 41 | nsteps = 4000 # total number of steps 42 | nsubs = 20 # number of steps between each plot 43 | nothing #hide 44 | 45 | 46 | # ## Problem setup 47 | # We initialize two problems by providing a set of keyword arguments to the `Problem` constructor. 48 | # The two problems are otherwise the same, except one has an infinite deformation radius, `prob_bqg`, 49 | # and the other has finite deformation radius, `prob_eqbqg`. 50 | 51 | # For both problems we use `stepper = "FilteredRK4"`. Filtered timesteppers apply a 52 | # wavenumber-filter at every time-step that removes enstrophy at high wavenumbers and, 53 | # thereby, stabilize the problem, despite that we use the default viscosity coefficient `ν=0`. 54 | # Thus, we choose not to do any dealiasing by providing `aliased_fraction=0`. 55 | 56 | stepper="FilteredRK4" 57 | 58 | prob_bqg = SingleLayerQG.Problem(dev; nx=n, Lx=L, dt, stepper, aliased_fraction=0) 59 | prob_eqbqg = SingleLayerQG.Problem(dev; nx=n, Lx=L, deformation_radius, dt, stepper, aliased_fraction=0) 60 | nothing #hide 61 | 62 | 63 | # ## Setting initial conditions 64 | 65 | # For initial condition we construct a relative vorticity with energy most energy around total 66 | # wavenumber ``k_0``. 67 | seed!(1234) 68 | k₀, E₀ = 6, 0.5 69 | ∇²ψ₀ = peakedisotropicspectrum(prob_bqg.grid, k₀, E₀, mask=prob_bqg.timestepper.filter) 70 | nothing #hide 71 | 72 | # `SingleLayerQG` allows us to set up the initial ``q`` for each problem via `set_q!()` function. 73 | # To initialize both `prob_bqg` and `prob_eqbqg` with the same flow, we first use function 74 | # `SingleLayerQG.streamfunctionfrompv!` to get the streamfunction that corresponds to the 75 | # relative vorticity we computed above. This works in the purely barotropic problem, `prob_bqg` 76 | # since in that case the QGPV is simply the relative vorticity. 77 | ∇²ψ₀h = rfft(∇²ψ₀) 78 | ψ₀h = @. 0 * ∇²ψ₀h 79 | SingleLayerQG.streamfunctionfrompv!(ψ₀h, ∇²ψ₀h, prob_bqg.params, prob_bqg.grid) 80 | nothing #hide 81 | 82 | # and then use the streamfunction to compute the corresponding ``q_0`` for each problem, 83 | q₀_bqg = irfft(-prob_bqg.grid.Krsq .* ψ₀h, prob_bqg.grid.nx) 84 | q₀_eqbqg = irfft(-(prob_eqbqg.grid.Krsq .+ 1/prob_eqbqg.params.deformation_radius^2) .* ψ₀h, prob_bqg.grid.nx) 85 | nothing #hide 86 | 87 | # Now we can initialize our problems with the same flow. 88 | SingleLayerQG.set_q!(prob_bqg, q₀_bqg) 89 | SingleLayerQG.set_q!(prob_eqbqg, q₀_eqbqg) 90 | nothing #hide 91 | 92 | 93 | # Let's plot the initial vorticity field for each problem. Note that when plotting, we decorate 94 | # the variable to be plotted with `Array()` to make sure it is brought back on the CPU when 95 | # `vars` live on the GPU. 96 | 97 | function relativevorticity(prob) 98 | vars, grid = prob.vars, prob.grid 99 | 100 | ldiv!(vars.q, grid.rfftplan, - grid.Krsq .* vars.ψh) 101 | 102 | return vars.q 103 | end 104 | 105 | x, y = prob_bqg.grid.x, prob_bqg.grid.y 106 | Lx, Ly = prob_bqg.grid.Lx, prob_bqg.grid.Ly 107 | 108 | fig = Figure(size = (800, 380)) 109 | 110 | axis_kwargs = (xlabel = "x", 111 | ylabel = "y", 112 | aspect = 1, 113 | limits = ((-Lx/2, Lx/2), (-Ly/2, Ly/2))) 114 | 115 | t_bqg = Observable(prob_bqg.clock.t) 116 | t_eqbqg = Observable(prob_eqbqg.clock.t) 117 | 118 | title_bqg = @lift "barotropic\n ∇²ψ, t=" * @sprintf("%.2f", $t_bqg) 119 | title_eqbqg = @lift "equivalent barotropic; deformation radius: " * @sprintf("%.2f", prob_eqbqg.params.deformation_radius) * "\n ∇²ψ, t=" * @sprintf("%.2f", $t_eqbqg) 120 | 121 | ax1 = Axis(fig[1, 1]; title = title_bqg, axis_kwargs...) 122 | ax2 = Axis(fig[1, 2]; title = title_eqbqg, axis_kwargs...) 123 | 124 | ζ_bqg = Observable(Array(relativevorticity(prob_bqg))) 125 | ζ_eqbqg = Observable(Array(relativevorticity(prob_eqbqg))) 126 | 127 | heatmap!(ax1, x, y, ζ_bqg; 128 | colormap = :balance, colorrange = (-40, 40)) 129 | 130 | heatmap!(ax2, x, y, ζ_eqbqg; 131 | colormap = :balance, colorrange = (-40, 40)) 132 | 133 | fig 134 | 135 | # ## Time-stepping the `Problem` forward 136 | 137 | # Now we time-step both problems forward and animate the relative vorticity in each case. 138 | 139 | startwalltime = time() 140 | 141 | cfl(prob) = prob.clock.dt * maximum([maximum(prob.vars.u) / prob.grid.dx, maximum(prob.vars.v) / prob.grid.dy]) 142 | 143 | record(fig, "singlelayerqg_barotropic_equivalentbarotropic.mp4", 0:Int(nsteps/nsubs), framerate = 18) do j 144 | if j % (1000 / nsubs) == 0 145 | log_bqg = @sprintf("barotropic; step: %04d, t: %d, cfl: %.2f, walltime: %.2f min", 146 | prob_bqg.clock.step, prob_bqg.clock.t, cfl(prob_bqg), (time()-startwalltime)/60) 147 | println(log_bqg) 148 | 149 | log_eqbqg = @sprintf("equivalent barotropic; step: %04d, t: %d, cfl: %.2f, walltime: %.2f min", 150 | prob_eqbqg.clock.step, prob_eqbqg.clock.t, cfl(prob_eqbqg), (time()-startwalltime)/60) 151 | println(log_eqbqg) 152 | end 153 | 154 | stepforward!(prob_bqg, nsubs) 155 | SingleLayerQG.updatevars!(prob_bqg) 156 | 157 | stepforward!(prob_eqbqg, nsubs) 158 | SingleLayerQG.updatevars!(prob_eqbqg) 159 | 160 | t_bqg[] = prob_bqg.clock.t 161 | t_eqbqg[] = prob_eqbqg.clock.t 162 | ζ_bqg[] = relativevorticity(prob_bqg) 163 | ζ_eqbqg[] = relativevorticity(prob_eqbqg) 164 | end 165 | nothing #hide 166 | 167 | # ![](singlelayerqg_barotropic_equivalentbarotropic.mp4) 168 | -------------------------------------------------------------------------------- /examples/singlelayerqg_decaying_topography.jl: -------------------------------------------------------------------------------- 1 | # # [Decaying barotropic QG turbulence over topography](@id singlelayerqg_decay_topography_example) 2 | # 3 | # An example of decaying barotropic quasi-geostrophic turbulence over topography. 4 | # 5 | # ## Install dependencies 6 | # 7 | # First let's make sure we have all required packages installed. 8 | 9 | # ```julia 10 | # using Pkg 11 | # pkg"add GeophysicalFlows, CairoMakie" 12 | # ``` 13 | 14 | # ## Let's begin 15 | # Let's load `GeophysicalFlows.jl` and some other packages we need. 16 | # 17 | using GeophysicalFlows, CairoMakie, Printf, Random 18 | 19 | using Statistics: mean 20 | 21 | 22 | # ## Choosing a device: CPU or GPU 23 | 24 | dev = CPU() # Device (CPU/GPU) 25 | nothing #hide 26 | 27 | 28 | # ## Numerical parameters and time-stepping parameters 29 | 30 | n = 128 # 2D resolution = n² 31 | stepper = "FilteredRK4" # timestepper 32 | dt = 0.05 # timestep 33 | nsteps = 2000 # total number of time-steps 34 | nsubs = 10 # number of time-steps for intermediate logging/plotting (nsteps must be multiple of nsubs) 35 | nothing #hide 36 | 37 | 38 | # ## Physical parameters 39 | 40 | L = 2π # domain size 41 | nothing #hide 42 | 43 | # Define the topographic potential vorticity, ``\eta = f_0 h(x, y)/H``. The topography here is 44 | # an elliptical mount at ``(x, y) = (1, 1)``, and an elliptical depression at ``(x, y) = (-1, -1)``. 45 | σx, σy = 0.4, 0.8 46 | topographicPV(x, y) = 3exp(-(x - 1)^2 / 2σx^2 - (y - 1)^2 / 2σy^2) - 2exp(- (x + 1)^2 / 2σx^2 - (y + 1)^2 / 2σy^2) 47 | nothing #hide 48 | 49 | # ## Problem setup 50 | # We initialize a `Problem` by providing a set of keyword arguments. 51 | # We use `stepper = "FilteredRK4"`. Filtered timesteppers apply a wavenumber-filter 52 | # at every time-step that removes enstrophy at high wavenumbers and, thereby, 53 | # stabilize the problem, despite that we use the default viscosity coefficient `ν=0`. 54 | # Thus, we choose not to do any dealiasing by providing `aliased_fraction=0`. 55 | # 56 | # The topophic PV is prescribed via keyword argument `eta`. 57 | prob = SingleLayerQG.Problem(dev; nx=n, Lx=L, eta=topographicPV, 58 | dt, stepper, aliased_fraction=0) 59 | nothing #hide 60 | 61 | # and define some shortcuts 62 | sol, clock, vars, params, grid = prob.sol, prob.clock, prob.vars, prob.params, prob.grid 63 | x, y = grid.x, grid.y 64 | Lx, Ly = grid.Lx, grid.Ly 65 | nothing #hide 66 | 67 | # and let's plot the topographic PV. Note that when plotting, we decorate the variable to be 68 | # plotted with `Array()` to make sure it is brought back on the CPU when the variable lives 69 | # on the GPU. 70 | 71 | η = Array(params.eta) 72 | 73 | fig = Figure() 74 | ax = Axis(fig[1, 1]; 75 | xlabel = "x", 76 | ylabel = "y", 77 | title = "topographic PV η=f₀h/H", 78 | limits = ((-Lx/2, Lx/2), (-Ly/2, Ly/2))) 79 | 80 | contourf!(ax, x, y, η; 81 | levels = collect(-3:0.4:3), colormap = :balance) 82 | 83 | fig 84 | 85 | # ## Setting initial conditions 86 | 87 | # Our initial condition consist of a flow that has power only at wavenumbers with 88 | # ``6 < \frac{L}{2\pi} \sqrt{k_x^2 + k_y^2} < 12`` and initial energy ``E_0``. 89 | # `device_array()` function returns the array type appropriate for the device, i.e., `Array` for 90 | # `dev = CPU()` and `CuArray` for `dev = GPU()`. 91 | 92 | E₀ = 0.04 # energy of initial condition 93 | 94 | K = @. sqrt(grid.Krsq) # a 2D array with the total wavenumber 95 | 96 | Random.seed!(1234) 97 | qih = device_array(dev)(randn(Complex{eltype(grid)}, size(sol))) 98 | @. qih = ifelse(K < 6 * 2π/L, 0, qih) 99 | @. qih = ifelse(K > 12 * 2π/L, 0, qih) 100 | qih *= sqrt(E₀ / SingleLayerQG.energy(qih, vars, params, grid)) # normalize qi to have energy E₀ 101 | qi = irfft(qih, grid.nx) 102 | 103 | SingleLayerQG.set_q!(prob, qi) 104 | nothing #hide 105 | 106 | # Let's plot the initial vorticity and streamfunction. 107 | 108 | q = Observable(Array(vars.q)) 109 | ψ = Observable(Array(vars.ψ)) 110 | 111 | fig = Figure(size = (800, 380)) 112 | 113 | axis_kwargs = (xlabel = "x", 114 | ylabel = "y", 115 | aspect = 1, 116 | limits = ((-Lx/2, Lx/2), (-Ly/2, Ly/2))) 117 | 118 | title_q = Observable("initial vorticity ∂v/∂x-∂u/∂y") 119 | axq = Axis(fig[1, 1]; title = title_q, axis_kwargs...) 120 | 121 | title_ψ = Observable("initial streamfunction ψ") 122 | axψ = Axis(fig[1, 3]; title = title_ψ, axis_kwargs...) 123 | 124 | hm = heatmap!(axq, x, y, q; 125 | colormap = :balance, colorrange = (-8, 8)) 126 | 127 | Colorbar(fig[1, 2], hm) 128 | 129 | levels = collect(range(-0.28, stop=0.28, length=11)) 130 | 131 | hc = contourf!(axψ, x, y, ψ; 132 | levels, colormap = :viridis, 133 | extendlow = :auto, extendhigh = :auto) 134 | contour!(axψ, x, y, ψ; 135 | levels, color = :black) 136 | 137 | Colorbar(fig[1, 4], hc) 138 | 139 | fig 140 | 141 | 142 | # ## Diagnostics 143 | 144 | # Create Diagnostics -- `energy` and `enstrophy` functions are imported at the top. 145 | E = Diagnostic(SingleLayerQG.energy, prob; nsteps) 146 | Z = Diagnostic(SingleLayerQG.enstrophy, prob; nsteps) 147 | diags = [E, Z] # A list of Diagnostics types passed to "stepforward!" will be updated every timestep. 148 | nothing #hide 149 | 150 | 151 | # ## Output 152 | 153 | # We choose folder for outputing `.jld2` files. 154 | filepath = "." 155 | filename = joinpath(filepath, "decayingbetaturb.jld2") 156 | nothing #hide 157 | 158 | # Do some basic file management, 159 | if isfile(filename); rm(filename); end 160 | nothing #hide 161 | 162 | # and then create Output. 163 | get_sol(prob) = prob.sol # extracts the Fourier-transformed solution 164 | out = Output(prob, filename, (:sol, get_sol)) 165 | nothing #hide 166 | 167 | 168 | # ## Visualizing the simulation 169 | 170 | # We modify the figure with the initial state slightly by adding the topography contours 171 | # and mark the time in the title. 172 | 173 | contour!(axq, x, y, η; 174 | levels = collect(0.5:0.5:3), linewidth = 2, color = (:black, 0.5)) 175 | 176 | contour!(axq, x, y, η; 177 | levels = collect(-2:0.5:-0.5), linewidth = 2, color = (:grey, 0.7), linestyle = :dash) 178 | 179 | title_q[] = "vorticity, t=" * @sprintf("%.2f", clock.t) 180 | title_ψ[] = "streamfunction ψ" 181 | 182 | nothing #hide 183 | 184 | 185 | # ## Time-stepping the `Problem` forward 186 | 187 | # We step the `Problem` forward in time. 188 | 189 | startwalltime = time() 190 | 191 | record(fig, "singlelayerqg_decaying_topography.mp4", 0:round(Int, nsteps/nsubs), framerate = 12) do j 192 | if j % (1000 / nsubs) == 0 193 | cfl = clock.dt * maximum([maximum(vars.u) / grid.dx, maximum(vars.v) / grid.dy]) 194 | 195 | log = @sprintf("step: %04d, t: %d, cfl: %.2f, E: %.4f, Q: %.4f, walltime: %.2f min", 196 | clock.step, clock.t, cfl, E.data[E.i], Z.data[Z.i], (time()-startwalltime)/60) 197 | 198 | println(log) 199 | end 200 | 201 | q[] = vars.q 202 | ψ[] = vars.ψ 203 | 204 | title_q[] = "vorticity, t="*@sprintf("%.2f", clock.t) 205 | title_ψ[] = "streamfunction ψ" 206 | 207 | stepforward!(prob, diags, nsubs) 208 | SingleLayerQG.updatevars!(prob) 209 | end 210 | nothing #hide 211 | 212 | # ![](singlelayerqg_decaying_topography.mp4) 213 | -------------------------------------------------------------------------------- /examples/surfaceqg_decaying.jl: -------------------------------------------------------------------------------- 1 | # # [Decaying Surface QG turbulence](@id surfaceqg_decaying_example) 2 | # 3 | # A simulation of decaying surface quasi-geostrophic turbulence. 4 | # We reproduce here the initial value problem for an elliptical 5 | # vortex as done by [Held-etal-1995](@citet). 6 | # 7 | # An example of decaying barotropic quasi-geostrophic turbulence over topography. 8 | # 9 | # ## Install dependencies 10 | # 11 | # First let's make sure we have all required packages installed. 12 | 13 | # ```julia 14 | # using Pkg 15 | # pkg"add GeophysicalFlows, CairoMakie" 16 | # ``` 17 | 18 | # ## Let's begin 19 | # Let's load `GeophysicalFlows.jl` and some other packages we need. 20 | # 21 | using GeophysicalFlows, CairoMakie, Printf, Random 22 | 23 | using Statistics: mean 24 | using Random: seed! 25 | 26 | 27 | # ## Choosing a device: CPU or GPU 28 | 29 | dev = CPU() # Device (CPU/GPU) 30 | nothing #hide 31 | 32 | 33 | # ## Numerical parameters and time-stepping parameters 34 | 35 | n = 256 # 2D resolution = n² 36 | stepper = "FilteredETDRK4" # timestepper 37 | dt = 0.03 # timestep 38 | tf = 60 # length of time for simulation 39 | nsteps = Int(tf / dt) # total number of time-steps 40 | nsubs = round(Int, nsteps/100) # number of time-steps for intermediate logging/plotting (nsteps must be multiple of nsubs) 41 | nothing #hide 42 | 43 | 44 | # ## Physical parameters 45 | 46 | L = 2π # domain size 47 | ν = 1e-19 # hyper-viscosity coefficient 48 | nν = 4 # hyper-viscosity order 49 | nothing #hide 50 | 51 | 52 | # ## Problem setup 53 | # We initialize a `Problem` by providing a set of keyword arguments. 54 | # We use `stepper = "FilteredRK4"`. Filtered timesteppers apply a wavenumber-filter 55 | # at every time-step that removes enstrophy at high wavenumbers and, thereby, 56 | # stabilize the problem, despite that we use the default viscosity coefficient `ν=0`. 57 | 58 | prob = SurfaceQG.Problem(dev; nx=n, Lx=L, dt, stepper, ν, nν) 59 | nothing #hide 60 | 61 | # Let's define some shortcuts. 62 | sol, clock, vars, params, grid = prob.sol, prob.clock, prob.vars, prob.params, prob.grid 63 | x, y = grid.x, grid.y 64 | Lx, Ly = grid.Lx, grid.Ly 65 | #md nothing #hide 66 | 67 | 68 | # ## Setting initial conditions 69 | # 70 | # We initialize the buoyancy equation with an elliptical vortex. 71 | X, Y = gridpoints(grid) 72 | b₀ = @. exp(-(X^2 + 4Y^2)) 73 | 74 | SurfaceQG.set_b!(prob, b₀) 75 | nothing #hide 76 | 77 | # Let's plot the initial condition. Note that when plotting, we decorate the variable to be 78 | # plotted with `Array()` to make sure it is brought back on the CPU when `vars` live on the GPU. 79 | 80 | fig = Figure(size = (500, 500)) 81 | 82 | ax = Axis(fig[1, 1], 83 | xlabel = "x", 84 | ylabel = "y", 85 | aspect = 1, 86 | title = "buoyancy bₛ", 87 | limits = ((-Lx/2, Lx/2), (-Ly/2, Ly/2))) 88 | 89 | hm = heatmap!(ax, x, y, Array(vars.b); 90 | colormap = :deep, colorrange = (0, 1)) 91 | 92 | Colorbar(fig[1, 2], hm) 93 | 94 | fig 95 | 96 | 97 | # ## Diagnostics 98 | 99 | # Create Diagnostics; `buoyancy_variance`, `kinetic_energy` and `buoyancy_dissipation` 100 | # functions were imported at the top. 101 | B = Diagnostic(SurfaceQG.buoyancy_variance, prob; nsteps) 102 | KE = Diagnostic(SurfaceQG.kinetic_energy, prob; nsteps) 103 | Dᵇ = Diagnostic(SurfaceQG.buoyancy_dissipation, prob; nsteps) 104 | diags = [B, KE, Dᵇ] # A list of Diagnostics types passed to `stepforward!`. Diagnostics are updated every timestep. 105 | nothing #hidenothing #hide 106 | 107 | 108 | # ## Output 109 | 110 | # We choose folder for outputing `.jld2` files and snapshots (`.png` files). 111 | # Define base filename so saved data can be distinguished from other runs 112 | base_filename = string("SurfaceQG_decaying_n_", n) 113 | # We choose folder for outputing `.jld2` files and snapshots (`.png` files). 114 | datapath = "./" 115 | plotpath = "./" 116 | 117 | dataname = joinpath(datapath, base_filename) 118 | plotname = joinpath(plotpath, base_filename) 119 | nothing #hide 120 | 121 | # Do some basic file management, 122 | if !isdir(plotpath); mkdir(plotpath); end 123 | if !isdir(datapath); mkdir(datapath); end 124 | nothing #hide 125 | 126 | # and then create Output. 127 | get_sol(prob) = prob.sol # extracts the Fourier-transformed solution 128 | get_u(prob) = irfft(im * prob.grid.l .* sqrt.(prob.grid.invKrsq) .* prob.sol, prob.grid.nx) 129 | 130 | out = Output(prob, dataname, (:sol, get_sol), (:u, get_u)) 131 | nothing #hide 132 | 133 | 134 | # ## Visualizing the simulation 135 | 136 | # We define a function that plots the buoyancy field and the time evolution of kinetic energy 137 | # and buoyancy variance. 138 | 139 | b = Observable(Array(vars.b)) 140 | 141 | ke = Observable([Point2f(KE.t[1], KE.data[1])]) 142 | b² = Observable([Point2f(B.t[1], B.data[1])]) 143 | 144 | title_b = Observable("buoyancy, t=" * @sprintf("%.2f", clock.t)) 145 | 146 | fig = Figure(size = (900, 600)) 147 | 148 | axb = Axis(fig[1:2, 1]; 149 | xlabel = "x", 150 | ylabel = "y", 151 | title = title_b, 152 | aspect = 1, 153 | limits = ((-Lx/2, Lx/2), (-Ly/2, Ly/2))) 154 | 155 | axE = Axis(fig[1, 2]; 156 | xlabel = "t", 157 | limits = ((0, tf), (0, 2e-2))) 158 | 159 | heatmap!(axb, x, y, b; 160 | colormap = :deep, colorrange = (0, 1)) 161 | 162 | hE = lines!(axE, ke; linewidth = 3) 163 | hb² = lines!(axE, b²; linewidth = 3) 164 | 165 | Legend(fig[2, 2], [hE, hb²], ["kinetic energy ∫½(uₛ²+vₛ²)dxdy/L²", "buoyancy variance ∫bₛ²dxdy/L²"]) 166 | 167 | fig 168 | 169 | 170 | # ## Time-stepping the `Problem` forward and create animation by updating the plot. 171 | 172 | startwalltime = time() 173 | 174 | record(fig, "sqg_ellipticalvortex.mp4", 0:round(Int, nsteps/nsubs), framerate = 14) do j 175 | if j % (500 / nsubs) == 0 176 | cfl = clock.dt * maximum([maximum(vars.u) / grid.dx, maximum(vars.v) / grid.dy]) 177 | 178 | log1 = @sprintf("step: %04d, t: %.1f, cfl: %.3f, walltime: %.2f min", 179 | clock.step, clock.t, cfl, (time()-startwalltime)/60) 180 | 181 | log2 = @sprintf("buoyancy variance: %.2e, buoyancy variance dissipation: %.2e", 182 | B.data[B.i], Dᵇ.data[Dᵇ.i]) 183 | 184 | println(log1) 185 | 186 | println(log2) 187 | end 188 | 189 | b[] = vars.b 190 | 191 | ke[] = push!(ke[], Point2f(KE.t[KE.i], KE.data[KE.i])) 192 | b²[] = push!(b²[], Point2f(B.t[B.i], B.data[B.i])) 193 | 194 | title_b[] = "buoyancy, t=" * @sprintf("%.2f", clock.t) 195 | 196 | stepforward!(prob, diags, nsubs) 197 | SurfaceQG.updatevars!(prob) 198 | end 199 | nothing #hide 200 | 201 | # ![](sqg_ellipticalvortex.mp4) 202 | 203 | 204 | # Let's see how all flow fields look like at the end of the simulation. 205 | 206 | fig = Figure(size = (800, 380)) 207 | 208 | axis_kwargs = (xlabel = "x", 209 | ylabel = "y", 210 | aspect = 1, 211 | limits = ((-Lx/2, Lx/2), (-Ly/2, Ly/2))) 212 | 213 | axb = Axis(fig[1, 1]; title = "bₛ(x, y, t=" * @sprintf("%.2f", clock.t) * ")", axis_kwargs...) 214 | axu = Axis(fig[1, 2]; title = "uₛ(x, y, t=" * @sprintf("%.2f", clock.t) * ")", axis_kwargs...) 215 | axv = Axis(fig[1, 3]; title = "vₛ(x, y, t=" * @sprintf("%.2f", clock.t) * ")", axis_kwargs...) 216 | 217 | hb = heatmap!(axb, x, y, Array(vars.b); 218 | colormap = :deep, colorrange = (0, 1)) 219 | 220 | Colorbar(fig[2, 1], hb, vertical = false) 221 | 222 | hu = heatmap!(axu, x, y, Array(vars.u); 223 | colormap = :balance, colorrange = (-maximum(abs.(vars.u)), maximum(abs.(vars.u)))) 224 | 225 | Colorbar(fig[2, 2], hu, vertical = false) 226 | 227 | hv = heatmap!(axv, x, y, Array(vars.v); 228 | colormap = :balance, colorrange = (-maximum(abs.(vars.v)), maximum(abs.(vars.v)))) 229 | 230 | Colorbar(fig[2, 3], hv, vertical = false) 231 | 232 | fig 233 | 234 | 235 | # ## Save 236 | 237 | # We can save the output at the end of the simulation by calling 238 | # ```julia 239 | # saveoutput(out) 240 | # ``` 241 | -------------------------------------------------------------------------------- /examples/twodnavierstokes_decaying.jl: -------------------------------------------------------------------------------- 1 | # # [2D decaying turbulence](@id twodnavierstokes_decaying_example) 2 | # 3 | # A simulation of decaying two-dimensional turbulence closely following 4 | # the paper by [McWilliams-1984](@citet). 5 | # 6 | # ## Install dependencies 7 | # 8 | # First let's make sure we have all required packages installed. 9 | 10 | # ```julia 11 | # using Pkg 12 | # pkg"add GeophysicalFlows, CairoMakie" 13 | # ``` 14 | 15 | # ## Let's begin 16 | # Let's load `GeophysicalFlows.jl` and some other packages we need. 17 | # 18 | using GeophysicalFlows, Printf, Random, CairoMakie 19 | 20 | using Random: seed! 21 | using GeophysicalFlows: peakedisotropicspectrum 22 | 23 | 24 | # ## Choosing a device: CPU or GPU 25 | 26 | dev = CPU() # Device (CPU/GPU) 27 | nothing #hide 28 | 29 | 30 | # ## Numerical, domain, and simulation parameters 31 | # 32 | # First, we pick some numerical and physical parameters for our model. 33 | 34 | n, L = 128, 2π # grid resolution and domain length 35 | nothing #hide 36 | 37 | # Then we pick the time-stepper parameters 38 | dt = 1e-2 # timestep 39 | nsteps = 4000 # total number of steps 40 | nsubs = 20 # number of steps between each plot 41 | nothing #hide 42 | 43 | 44 | # ## Problem setup 45 | # We initialize a `Problem` by providing a set of keyword arguments. 46 | # We use `stepper = "FilteredRK4"`. Filtered timesteppers apply a wavenumber-filter 47 | # at every time-step that removes enstrophy at high wavenumbers and, thereby, 48 | # stabilize the problem, despite that we use the default viscosity coefficient `ν=0`. 49 | 50 | prob = TwoDNavierStokes.Problem(dev; nx=n, Lx=L, ny=n, Ly=L, dt, stepper="FilteredRK4") 51 | nothing #hide 52 | 53 | # Next we define some shortcuts for convenience. 54 | sol, clock, vars, grid = prob.sol, prob.clock, prob.vars, prob.grid 55 | x, y = grid.x, grid.y 56 | Lx, Ly = grid.Lx, grid.Ly 57 | nothing #hide 58 | 59 | 60 | # ## Setting initial conditions 61 | 62 | # Our initial condition tries to reproduce the initial condition used by [McWilliams-1984](@citet). 63 | seed!(1234) 64 | k₀, E₀ = 6, 0.5 65 | ζ₀ = peakedisotropicspectrum(grid, k₀, E₀, mask=prob.timestepper.filter) 66 | TwoDNavierStokes.set_ζ!(prob, ζ₀) 67 | nothing #hide 68 | 69 | # Let's plot the initial vorticity field. Note that when plotting, we decorate the variable 70 | # to be plotted with `Array()` to make sure it is brought back on the CPU when `vars` live on 71 | # the GPU. 72 | 73 | fig = Figure() 74 | ax = Axis(fig[1, 1]; 75 | xlabel = "x", 76 | ylabel = "y", 77 | title = "initial vorticity", 78 | aspect = 1, 79 | limits = ((-L/2, L/2), (-L/2, L/2))) 80 | 81 | heatmap!(ax, x, y, Array(vars.ζ'); 82 | colormap = :balance, colorrange = (-40, 40)) 83 | 84 | fig 85 | 86 | 87 | # ## Diagnostics 88 | 89 | # Create Diagnostics -- `energy` and `enstrophy` functions are imported at the top. 90 | E = Diagnostic(TwoDNavierStokes.energy, prob; nsteps) 91 | Z = Diagnostic(TwoDNavierStokes.enstrophy, prob; nsteps) 92 | diags = [E, Z] # A list of Diagnostics types passed to "stepforward!" will be updated every timestep. 93 | nothing #hide 94 | 95 | 96 | # ## Output 97 | 98 | # We choose folder for outputing `.jld2` files and snapshots (`.png` files). 99 | filepath = "." 100 | plotpath = "./plots_decayingTwoDNavierStokes" 101 | plotname = "snapshots" 102 | filename = joinpath(filepath, "decayingTwoDNavierStokes.jld2") 103 | nothing #hide 104 | 105 | # Do some basic file management 106 | if isfile(filename); rm(filename); end 107 | if !isdir(plotpath); mkdir(plotpath); end 108 | nothing #hide 109 | 110 | # And then create Output 111 | get_sol(prob) = prob.sol # extracts the Fourier-transformed solution 112 | get_u(prob) = irfft(im * prob.grid.l .* prob.grid.invKrsq .* prob.sol, prob.grid.nx) 113 | 114 | out = Output(prob, filename, (:sol, get_sol), (:u, get_u)) 115 | saveproblem(out) 116 | nothing #hide 117 | 118 | 119 | # ## Visualizing the simulation 120 | 121 | # We initialize a plot with the vorticity field and the time-series of 122 | # energy and enstrophy diagnostics. 123 | 124 | ζ = Observable(Array(vars.ζ)) 125 | title_ζ = Observable("vorticity, t=" * @sprintf("%.2f", clock.t)) 126 | 127 | energy = Observable(Point2f[(E.t[1], E.data[1] / E.data[1])]) 128 | enstrophy = Observable(Point2f[(Z.t[1], Z.data[1] / Z.data[1])]) 129 | 130 | fig = Figure(size = (800, 360)) 131 | 132 | axζ = Axis(fig[1, 1]; 133 | xlabel = "x", 134 | ylabel = "y", 135 | title = title_ζ, 136 | aspect = 1, 137 | limits = ((-L/2, L/2), (-L/2, L/2))) 138 | 139 | ax2 = Axis(fig[1, 2], 140 | xlabel = "t", 141 | limits = ((-0.5, 40.5), (0, 1.05))) 142 | 143 | heatmap!(axζ, x, y, ζ; 144 | colormap = :balance, colorrange = (-40, 40)) 145 | 146 | hE = lines!(ax2, energy; linewidth = 3) 147 | hZ = lines!(ax2, enstrophy; linewidth = 3, color = :red) 148 | Legend(fig[1, 3], [hE, hZ], ["E(t)/E(0)", "Z(t)/Z(0)"]) 149 | 150 | fig 151 | 152 | # ## Time-stepping the `Problem` forward 153 | 154 | # We time-step the `Problem` forward in time. 155 | 156 | startwalltime = time() 157 | 158 | record(fig, "twodturb.mp4", 0:Int(nsteps/nsubs), framerate = 18) do j 159 | if j % (1000 / nsubs) == 0 160 | cfl = clock.dt * maximum([maximum(vars.u) / grid.dx, maximum(vars.v) / grid.dy]) 161 | 162 | log = @sprintf("step: %04d, t: %d, cfl: %.2f, ΔE: %.4f, ΔZ: %.4f, walltime: %.2f min", 163 | clock.step, clock.t, cfl, E.data[E.i]/E.data[1], Z.data[Z.i]/Z.data[1], (time()-startwalltime)/60) 164 | 165 | println(log) 166 | end 167 | 168 | ζ[] = vars.ζ 169 | 170 | energy[] = push!(energy[], Point2f(E.t[E.i], E.data[E.i] / E.data[1])) 171 | enstrophy[] = push!(enstrophy[], Point2f(Z.t[E.i], Z.data[Z.i] / Z.data[1])) 172 | 173 | title_ζ[] = "vorticity, t=" * @sprintf("%.2f", clock.t) 174 | 175 | stepforward!(prob, diags, nsubs) 176 | TwoDNavierStokes.updatevars!(prob) 177 | end 178 | nothing #hide 179 | 180 | # ![](twodturb.mp4) 181 | 182 | 183 | # ## Radial energy spectrum 184 | 185 | # After the simulation is done we plot the instantaneous radial energy spectrum to illustrate 186 | # how `FourierFlows.radialspectrum` can be used, 187 | 188 | E = @. 0.5 * (vars.u^2 + vars.v^2) # energy density 189 | Eh = rfft(E) # Fourier transform of energy density 190 | 191 | ## compute radial specturm of `Eh` 192 | kr, Ehr = FourierFlows.radialspectrum(Eh, grid, refinement = 1) 193 | nothing #hide 194 | 195 | # and we plot it. 196 | lines(kr, vec(abs.(Ehr)); 197 | linewidth = 2, 198 | axis = (xlabel = L"k_r", 199 | ylabel = L"\int |\hat{E}| k_r \mathrm{d}k_\theta", 200 | xscale = log10, 201 | yscale = log10, 202 | title = "Radial energy spectrum", 203 | limits = ((0.3, 1e2), (1e0, 1e5)))) 204 | -------------------------------------------------------------------------------- /examples/twodnavierstokes_stochasticforcing.jl: -------------------------------------------------------------------------------- 1 | # # [2D forced-dissipative turbulence](@id twodnavierstokes_stochasticforcing_example) 2 | # 3 | # A simulation of forced-dissipative two-dimensional turbulence. We solve the 4 | # two-dimensional vorticity equation with stochastic excitation and dissipation in 5 | # the form of linear drag and hyperviscosity. 6 | # 7 | # ## Install dependencies 8 | # 9 | # First let's make sure we have all required packages installed. 10 | 11 | # ```julia 12 | # using Pkg 13 | # pkg"add GeophysicalFlows, CUDA, CairoMakie" 14 | # ``` 15 | 16 | # ## Let's begin 17 | # Let's load `GeophysicalFlows.jl` and some other packages we need. 18 | # 19 | using GeophysicalFlows, CUDA, Random, Printf, CairoMakie 20 | 21 | parsevalsum = FourierFlows.parsevalsum 22 | record = CairoMakie.record # disambiguate between CairoMakie.record and CUDA.record 23 | nothing #hide 24 | 25 | # ## Choosing a device: CPU or GPU 26 | 27 | dev = CPU() # Device (CPU/GPU) 28 | nothing #hide 29 | 30 | 31 | # ## Numerical, domain, and simulation parameters 32 | # 33 | # First, we pick some numerical and physical parameters for our model. 34 | 35 | n, L = 256, 2π # grid resolution and domain length 36 | ν, nν = 2e-7, 2 # hyperviscosity coefficient and hyperviscosity order 37 | μ, nμ = 1e-1, 0 # linear drag coefficient 38 | dt = 0.005 # timestep 39 | nsteps = 4000 # total number of steps 40 | nsubs = 20 # number of steps between each plot 41 | nothing #hide 42 | 43 | 44 | # ## Forcing 45 | 46 | # We force the vorticity equation with stochastic excitation that is delta-correlated in time 47 | # and while spatially homogeneously and isotropically correlated. The forcing has a spectrum 48 | # with power in a ring in wavenumber space of radius ``k_f`` (`forcing_wavenumber`) and width 49 | # ``δ_f`` (`forcing_bandwidth`), and it injects energy per unit area and per unit time 50 | # equal to ``\varepsilon``. That is, the forcing covariance spectrum is proportional to 51 | # ``\exp{[-(|\bm{k}| - k_f)^2 / (2 δ_f^2)]}``. 52 | 53 | forcing_wavenumber = 14.0 * 2π/L # the forcing wavenumber, `k_f`, for a spectrum that is a ring in wavenumber space 54 | forcing_bandwidth = 1.5 * 2π/L # the width of the forcing spectrum, `δ_f` 55 | ε = 0.1 # energy input rate by the forcing 56 | 57 | grid = TwoDGrid(dev; nx=n, Lx=L) 58 | 59 | K = @. sqrt(grid.Krsq) # a 2D array with the total wavenumber 60 | 61 | forcing_spectrum = @. exp(-(K - forcing_wavenumber)^2 / (2 * forcing_bandwidth^2)) 62 | @CUDA.allowscalar forcing_spectrum[grid.Krsq .== 0] .= 0 # ensure forcing has zero domain-average 63 | 64 | ε0 = parsevalsum(forcing_spectrum .* grid.invKrsq / 2, grid) / (grid.Lx * grid.Ly) 65 | @. forcing_spectrum *= ε/ε0 # normalize forcing to inject energy at rate ε 66 | nothing #hide 67 | 68 | 69 | # We reset of the random number generator for reproducibility 70 | if dev==CPU(); Random.seed!(1234); else; CUDA.seed!(1234); end 71 | nothing #hide 72 | 73 | 74 | # Next we construct function `calcF!` that computes a forcing realization every timestep. 75 | # For that, we call `randn!` to obtain complex numbers whose real and imaginary part 76 | # are normally-distributed with zero mean and variance 1/2. 77 | 78 | function calcF!(Fh, sol, t, clock, vars, params, grid) 79 | randn!(Fh) 80 | @. Fh *= sqrt(forcing_spectrum) / sqrt(clock.dt) 81 | return nothing 82 | end 83 | nothing #hide 84 | 85 | 86 | # ## Problem setup 87 | # We initialize a `Problem` by providing a set of keyword arguments. The 88 | # `stepper` keyword defines the time-stepper to be used. 89 | prob = TwoDNavierStokes.Problem(dev; nx=n, Lx=L, ν, nν, μ, nμ, dt, stepper="ETDRK4", 90 | calcF=calcF!, stochastic=true) 91 | nothing #hide 92 | 93 | # Define some shortcuts for convenience. 94 | sol, clock, vars, params, grid = prob.sol, prob.clock, prob.vars, prob.params, prob.grid 95 | 96 | x, y = grid.x, grid.y 97 | nothing #hide 98 | 99 | 100 | # First let's see how a forcing realization looks like. Function `calcF!()` computes 101 | # the forcing in Fourier space and saves it into variable `vars.Fh`, so we first need to 102 | # go back to physical space. 103 | 104 | # Note that when plotting, we decorate the variable to be plotted with `Array()` to make sure 105 | # it is brought back on the CPU when the variable lives on the GPU. 106 | calcF!(vars.Fh, sol, 0.0, clock, vars, params, grid) 107 | 108 | fig = Figure() 109 | 110 | ax = Axis(fig[1, 1], 111 | xlabel = "x", 112 | ylabel = "y", 113 | aspect = 1, 114 | title = "a forcing realization", 115 | limits = ((-L/2, L/2), (-L/2, L/2))) 116 | 117 | heatmap!(ax, x, y, Array(irfft(vars.Fh, grid.nx)); 118 | colormap = :balance, colorrange = (-200, 200)) 119 | 120 | fig 121 | 122 | 123 | # ## Setting initial conditions 124 | 125 | # Our initial condition is a fluid at rest. 126 | TwoDNavierStokes.set_ζ!(prob, device_array(dev)(zeros(grid.nx, grid.ny))) 127 | 128 | 129 | # ## Diagnostics 130 | 131 | # Create Diagnostics; the diagnostics are aimed to probe the energy budget. 132 | E = Diagnostic(TwoDNavierStokes.energy, prob; nsteps) # energy 133 | Z = Diagnostic(TwoDNavierStokes.enstrophy, prob; nsteps) # enstrophy 134 | diags = [E, Z] # a list of Diagnostics passed to `stepforward!` will be updated every timestep. 135 | nothing #hide 136 | 137 | 138 | # ## Visualizing the simulation 139 | 140 | # We initialize a plot with the vorticity field and the time-series of 141 | # energy and enstrophy diagnostics. To plot energy and enstrophy on the 142 | # same axes we scale enstrophy with ``k_f^2``. 143 | 144 | ζ = Observable(Array(vars.ζ)) 145 | title_ζ = Observable("vorticity, μ t=" * @sprintf("%.2f", μ * clock.t)) 146 | 147 | energy = Observable(Point2f[(μ * E.t[1], E.data[1])]) 148 | enstrophy = Observable(Point2f[(μ * Z.t[1], Z.data[1] / forcing_wavenumber^2)]) 149 | 150 | fig = Figure(size = (800, 360)) 151 | 152 | axζ = Axis(fig[1, 1]; 153 | xlabel = "x", 154 | ylabel = "y", 155 | title = title_ζ, 156 | aspect = 1, 157 | limits = ((-L/2, L/2), (-L/2, L/2))) 158 | 159 | ax2 = Axis(fig[1, 2], 160 | xlabel = "μ t", 161 | limits = ((0, 1.1 * μ * nsteps * dt), (0, 0.55))) 162 | 163 | heatmap!(axζ, x, y, ζ; 164 | colormap = :balance, colorrange = (-40, 40)) 165 | 166 | hE = lines!(ax2, energy; linewidth = 3) 167 | hZ = lines!(ax2, enstrophy; linewidth = 3, color = :red) 168 | Legend(fig[1, 3], [hE, hZ], ["energy E(t)", "enstrophy Z(t) / k_f²"]) 169 | 170 | fig 171 | 172 | 173 | # ## Time-stepping the `Problem` forward 174 | 175 | # We step the `Problem` forward in time. 176 | 177 | startwalltime = time() 178 | 179 | record(fig, "twodturb_forced.mp4", 0:round(Int, nsteps / nsubs), framerate = 18) do j 180 | if j % (1000/nsubs) == 0 181 | cfl = clock.dt * maximum([maximum(vars.u) / grid.dx, maximum(vars.v) / grid.dy]) 182 | 183 | log = @sprintf("step: %04d, t: %d, cfl: %.2f, E: %.4f, Z: %.4f, walltime: %.2f min", 184 | clock.step, clock.t, cfl, E.data[E.i], Z.data[Z.i], (time()-startwalltime)/60) 185 | println(log) 186 | end 187 | 188 | ζ[] = vars.ζ 189 | 190 | energy[] = push!(energy[], Point2f(μ * E.t[E.i], E.data[E.i])) 191 | enstrophy[] = push!(enstrophy[], Point2f(μ * Z.t[E.i], Z.data[Z.i] / forcing_wavenumber^2)) 192 | 193 | title_ζ[] = "vorticity, μ t=" * @sprintf("%.2f", μ * clock.t) 194 | 195 | stepforward!(prob, diags, nsubs) 196 | TwoDNavierStokes.updatevars!(prob) 197 | end 198 | nothing #hide 199 | 200 | # ![](twodturb_forced.mp4) 201 | -------------------------------------------------------------------------------- /examples/twodnavierstokes_stochasticforcing_budgets.jl: -------------------------------------------------------------------------------- 1 | # # [2D forced-dissipative turbulence budgets](@id twodnavierstokes_stochasticforcing_budgets_example) 2 | # 3 | # A simulation of forced-dissipative two-dimensional turbulence. We solve the 4 | # two-dimensional vorticity equation with stochastic excitation and dissipation in 5 | # the form of linear drag and hyperviscosity. As a demonstration, we compute how 6 | # each of the forcing and dissipation terms contribute to the energy and the 7 | # enstrophy budgets. 8 | # 9 | # ## Install dependencies 10 | # 11 | # First let's make sure we have all required packages installed. 12 | 13 | # ```julia 14 | # using Pkg 15 | # pkg"add GeophysicalFlows, CUDA, Random, Printf, CairoMakie" 16 | # ``` 17 | 18 | # ## Let's begin 19 | # Let's load `GeophysicalFlows.jl` and some other packages we need. 20 | # 21 | using GeophysicalFlows, CUDA, Random, Printf, CairoMakie 22 | 23 | parsevalsum = FourierFlows.parsevalsum 24 | record = CairoMakie.record # disambiguate between CairoMakie.record and CUDA.record 25 | nothing #hide 26 | 27 | # ## Choosing a device: CPU or GPU 28 | 29 | dev = CPU() # Device (CPU/GPU) 30 | nothing #hide 31 | 32 | 33 | # ## Numerical, domain, and simulation parameters 34 | # 35 | # First, we pick some numerical and physical parameters for our model. 36 | 37 | n, L = 256, 2π # grid resolution and domain length 38 | ν, nν = 2e-7, 2 # hyperviscosity coefficient and hyperviscosity order 39 | μ, nμ = 1e-1, 0 # linear drag coefficient 40 | dt, tf = 0.005, 0.2 / μ # timestep and final time 41 | nt = round(Int, tf / dt) # total timesteps 42 | ns = 4 # how many intermediate times we want to plot 43 | nothing #hide 44 | 45 | 46 | # ## Forcing 47 | 48 | # We force the vorticity equation with stochastic excitation that is delta-correlated in time 49 | # and while spatially homogeneously and isotropically correlated. The forcing has a spectrum 50 | # with power in a ring in wavenumber space of radius ``k_f`` (`forcing_wavenumber`) and width 51 | # ``δ_f`` (`forcing_bandwidth`), and it injects energy per unit area and per unit time 52 | # equal to ``\varepsilon``. That is, the forcing covariance spectrum is proportional to 53 | # ``\exp{[-(|\bm{k}| - k_f)^2 / (2 δ_f^2)]}``. 54 | 55 | forcing_wavenumber = 14.0 * 2π/L # the forcing wavenumber, `k_f`, for a spectrum that is a ring in wavenumber space 56 | forcing_bandwidth = 1.5 * 2π/L # the width of the forcing spectrum, `δ_f` 57 | ε = 0.1 # energy input rate by the forcing 58 | 59 | grid = TwoDGrid(dev; nx=n, Lx=L) 60 | 61 | K = @. sqrt(grid.Krsq) # a 2D array with the total wavenumber 62 | 63 | forcing_spectrum = @. exp(-(K - forcing_wavenumber)^2 / (2 * forcing_bandwidth^2)) 64 | @CUDA.allowscalar forcing_spectrum[grid.Krsq .== 0] .= 0 # ensure forcing has zero domain-average 65 | 66 | ε0 = parsevalsum(forcing_spectrum .* grid.invKrsq / 2, grid) / (grid.Lx * grid.Ly) 67 | @. forcing_spectrum *= ε/ε0 # normalize forcing to inject energy at rate ε 68 | nothing #hide 69 | 70 | 71 | # We reset of the random number generator for reproducibility 72 | if dev==CPU(); Random.seed!(1234); else; CUDA.seed!(1234); end 73 | nothing #hide 74 | 75 | 76 | # Next we construct function `calcF!` that computes a forcing realization every timestep. 77 | # For that, we call `randn!` to obtain complex numbers whose real and imaginary part 78 | # are normally-distributed with zero mean and variance 1/2. 79 | 80 | function calcF!(Fh, sol, t, clock, vars, params, grid) 81 | randn!(Fh) 82 | @. Fh *= sqrt(forcing_spectrum) / sqrt(clock.dt) 83 | return nothing 84 | end 85 | nothing #hide 86 | 87 | 88 | # ## Problem setup 89 | # We initialize a `Problem` by providing a set of keyword arguments. The 90 | # `stepper` keyword defines the time-stepper to be used. 91 | prob = TwoDNavierStokes.Problem(dev; nx=n, Lx=L, ν, nν, μ, nμ, dt, stepper="ETDRK4", 92 | calcF=calcF!, stochastic=true) 93 | nothing #hide 94 | 95 | # Define some shortcuts for convenience. 96 | sol, clock, vars, params, grid = prob.sol, prob.clock, prob.vars, prob.params, prob.grid 97 | 98 | x, y = grid.x, grid.y 99 | Lx, Ly = grid.Lx, grid.Ly 100 | nothing #hide 101 | 102 | 103 | # First let's see how a forcing realization looks like. Function `calcF!()` computes 104 | # the forcing in Fourier space and saves it into variable `vars.Fh`, so we first need to 105 | # go back to physical space. 106 | # 107 | # Note that when plotting, we decorate the variable to be plotted with `Array()` to make sure 108 | # it is brought back on the CPU when the variable lives on the GPU. 109 | calcF!(vars.Fh, sol, 0.0, clock, vars, params, grid) 110 | 111 | fig = Figure() 112 | 113 | ax = Axis(fig[1, 1], 114 | xlabel = "x", 115 | ylabel = "y", 116 | aspect = 1, 117 | title = "a forcing realization", 118 | limits = ((-Lx/2, Lx/2), (-Ly/2, Ly/2))) 119 | 120 | heatmap!(ax, x, y, Array(irfft(vars.Fh, grid.nx)); 121 | colormap = :balance, colorrange = (-200, 200)) 122 | 123 | fig 124 | 125 | 126 | # ## Setting initial conditions 127 | 128 | # Our initial condition is a fluid at rest. 129 | TwoDNavierStokes.set_ζ!(prob, device_array(dev)(zeros(grid.nx, grid.ny))) 130 | 131 | 132 | # ## Diagnostics 133 | 134 | # Create Diagnostics; the diagnostics are aimed to probe the energy and enstrophy budgets. 135 | E = Diagnostic(TwoDNavierStokes.energy, prob, nsteps=nt) # energy 136 | Rᵋ = Diagnostic(TwoDNavierStokes.energy_dissipation_hypoviscosity, prob, nsteps=nt) # energy dissipation by drag μ 137 | Dᵋ = Diagnostic(TwoDNavierStokes.energy_dissipation_hyperviscosity, prob, nsteps=nt) # energy dissipation by drag μ 138 | Wᵋ = Diagnostic(TwoDNavierStokes.energy_work, prob, nsteps=nt) # energy work input by forcing 139 | Z = Diagnostic(TwoDNavierStokes.enstrophy, prob, nsteps=nt) # enstrophy 140 | Rᶻ = Diagnostic(TwoDNavierStokes.enstrophy_dissipation_hypoviscosity, prob, nsteps=nt) # enstrophy dissipation by drag μ 141 | Dᶻ = Diagnostic(TwoDNavierStokes.enstrophy_dissipation_hyperviscosity, prob, nsteps=nt) # enstrophy dissipation by drag μ 142 | Wᶻ = Diagnostic(TwoDNavierStokes.enstrophy_work, prob, nsteps=nt) # enstrophy work input by forcing 143 | diags = [E, Dᵋ, Wᵋ, Rᵋ, Z, Dᶻ, Wᶻ, Rᶻ] # a list of Diagnostics passed to `stepforward!` will be updated every timestep. 144 | nothing #hide 145 | 146 | # ## Time-stepping the `Problem` forward 147 | 148 | # We step the `Problem` forward in time. 149 | 150 | startwalltime = time() 151 | for i = 1:ns 152 | stepforward!(prob, diags, round(Int, nt/ns)) 153 | 154 | TwoDNavierStokes.updatevars!(prob) 155 | 156 | cfl = clock.dt * maximum([maximum(vars.u) / grid.dx, maximum(vars.v) / grid.dy]) 157 | 158 | log = @sprintf("step: %04d, t: %.1f, cfl: %.3f, walltime: %.2f min", clock.step, clock.t, 159 | cfl, (time()-startwalltime)/60) 160 | 161 | println(log) 162 | end 163 | 164 | 165 | # ## Plot 166 | # Now let's see the final snapshot of the vorticity. 167 | 168 | fig = Figure(size = (400, 400)) 169 | 170 | ax = Axis(fig[1, 1]; 171 | xlabel = "x", 172 | ylabel = "y", 173 | title = "∇²ψ(x, y, μt=" * @sprintf("%.2f", μ * clock.t) * ")", 174 | aspect = 1, 175 | limits = ((-L/2, L/2), (-L/2, L/2))) 176 | 177 | heatmap!(ax, x, y, Array(vars.ζ); 178 | colormap = :viridis, colorrange = (-25, 25)) 179 | 180 | fig 181 | 182 | # And finally, we plot the evolution of the energy and enstrophy diagnostics and all terms 183 | # involved in the energy and enstrophy budgets. Last, we also check (by plotting) whether 184 | # the energy and enstrophy budgets are accurately computed, e.g., ``\mathrm{d}E/\mathrm{d}t = W^\varepsilon - 185 | # R^\varepsilon - D^\varepsilon``. 186 | 187 | sol, clock, vars, params, grid = prob.sol, prob.clock, prob.vars, prob.params, prob.grid 188 | 189 | TwoDNavierStokes.updatevars!(prob) 190 | 191 | E, Dᵋ, Wᵋ, Rᵋ, Z, Dᶻ, Wᶻ, Rᶻ = diags 192 | 193 | clocktime = round(μ * clock.t, digits=2) 194 | 195 | dEdt_numerical = (E[2:E.i] - E[1:E.i-1]) / clock.dt # numerical first-order approximation of energy tendency 196 | dZdt_numerical = (Z[2:Z.i] - Z[1:Z.i-1]) / clock.dt # numerical first-order approximation of enstrophy tendency 197 | 198 | dEdt_computed = Wᵋ[2:E.i] + Dᵋ[1:E.i-1] + Rᵋ[1:E.i-1] 199 | dZdt_computed = Wᶻ[2:Z.i] + Dᶻ[1:Z.i-1] + Rᶻ[1:Z.i-1] 200 | 201 | residual_E = dEdt_computed - dEdt_numerical 202 | residual_Z = dZdt_computed - dZdt_numerical 203 | 204 | εᶻ = parsevalsum(forcing_spectrum / 2, grid) / (grid.Lx * grid.Ly) 205 | 206 | t = E.t[2:E.i] 207 | 208 | fig = Figure(size = (800, 1100)) 209 | 210 | axis_kwargs = (xlabel = "μ t", ) 211 | 212 | ax1E = Axis(fig[1, 1]; ylabel = "energy sources/sinks", axis_kwargs...) 213 | ax2E = Axis(fig[3, 1]; ylabel = "dE/dt", axis_kwargs...) 214 | ax3E = Axis(fig[5, 1]; axis_kwargs...) 215 | 216 | ax1Z = Axis(fig[1, 2]; axis_kwargs...) 217 | ax2Z = Axis(fig[3, 2]; axis_kwargs...) 218 | ax3Z = Axis(fig[5, 2]; axis_kwargs...) 219 | 220 | hWᵋ = lines!(ax1E, t, Wᵋ[2:E.i]; linestyle = :solid) 221 | hε = lines!(ax1E, t, ε .+ 0t; linestyle = :dash) 222 | hDᵋ = lines!(ax1E, t, Dᵋ[1:E.i-1]; linestyle = :solid) 223 | hRᵋ = lines!(ax1E, t, Rᵋ[1:E.i-1]; linestyle = :solid) 224 | 225 | Legend(fig[2, 1], 226 | [hWᵋ, hε, hDᵋ, hRᵋ], 227 | ["energy work, Wᵋ", "ensemble mean energy work, ", "dissipation, Dᵋ", "drag, Rᵋ = - 2μE"]) 228 | 229 | hc = lines!(ax2E, t, dEdt_computed; linestyle = :solid) 230 | hn = lines!(ax2E, t, dEdt_numerical; linestyle = :dash) 231 | 232 | Legend(fig[4, 1], 233 | [hc, hn], 234 | ["computed Wᵋ-Dᵋ", "numerical dE/dt"]) 235 | 236 | hr = lines!(ax3E, t, residual_E) 237 | 238 | Legend(fig[6, 1], 239 | [hr], 240 | ["residual"]) 241 | 242 | hWᶻ = lines!(ax1Z, t, Wᶻ[2:Z.i]; linestyle = :solid) 243 | hεᶻ = lines!(ax1Z, t, εᶻ .+ 0t; linestyle = :dash) 244 | hDᶻ = lines!(ax1Z, t, Dᶻ[1:Z.i-1]; linestyle = :solid) 245 | hRᶻ = lines!(ax1Z, t, Rᶻ[1:Z.i-1]; linestyle = :solid) 246 | 247 | Legend(fig[2, 2], 248 | [hWᶻ, hεᶻ, hDᶻ, hRᶻ], 249 | ["enstrophy work, Wᶻ", "ensemble mean enstophy work, ", "dissipation, Dᶻ", "drag, Rᶻ = - 2μZ"]) 250 | 251 | hcᶻ = lines!(ax2Z, t, dZdt_computed; linestyle = :solid) 252 | hnᶻ = lines!(ax2Z, t, dZdt_numerical; linestyle = :dash) 253 | 254 | Legend(fig[4, 2], 255 | [hcᶻ, hnᶻ], 256 | ["computed Wᶻ-Dᶻ", "numerical dZ/dt"]) 257 | 258 | hrᶻ = lines!(ax3Z, t, residual_Z) 259 | 260 | Legend(fig[6, 2], 261 | [hr], 262 | ["residual"]) 263 | 264 | fig 265 | -------------------------------------------------------------------------------- /paper/PV_eady_nlayers5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FourierFlows/GeophysicalFlows.jl/cde06dbe5cffe6cdbfba1d7b0b218bdf057cff51/paper/PV_eady_nlayers5.png -------------------------------------------------------------------------------- /paper/paper.bib: -------------------------------------------------------------------------------- 1 | @article{Bezanson2017, 2 | title = {Julia: {A} fresh approach to numerical computing}, 3 | author = {Bezanson, Jeff and Edelman, Alan and Karpinski, Stefan and Shah, Viral B.}, 4 | journal = {SIAM Review}, 5 | volume = {59}, 6 | number = {1}, 7 | pages = {65--98}, 8 | year = {2017}, 9 | doi = {10.1137/141000671} 10 | } 11 | 12 | @article{Oceananigans, 13 | title={Oceananigans.jl: {Fast} and friendly geophysical fluid dynamics on {GPUs}}, 14 | author={Ramadhan, Ali and Wagner, Gregory LeClaire and Hill, Chris and Campin, Jean-Michel and Churavy, Valentin and Besard, Tim and Souza, Andre and Edelman, Alan and Ferrari, Raffaele and Marshall, John}, 15 | journal={Journal of Open Source Software}, 16 | volume={5}, 17 | number={53}, 18 | pages={2018}, 19 | year={2020}, 20 | doi={10.21105/joss.02018} 21 | } 22 | 23 | @article{MAOOAM, 24 | author = {De Cruz, L. and Demaeyer, J. and Vannitsem, S.}, 25 | doi = {10.5194/gmd-9-2793-2016}, 26 | journal = {Geoscientific Model Development}, 27 | volume = {9}, 28 | number = {8}, 29 | pages = {2793--2808}, 30 | title = {The modular arbitrary-order ocean-atmosphere model: \textsc{maooam}~v1.0}, 31 | year = {2016}} 32 | 33 | @article{qgs, 34 | author = {Demaeyer, J. and De Cruz, L. and Vannitsem, S.}, 35 | doi = {10.21105/joss.02597}, 36 | journal = {Journal of Open Source Software}, 37 | volume = {5}, 38 | number = {56}, 39 | pages = {2597}, 40 | title = {{qgs}: {A} flexible {Python} framework of reduced-order multiscale climate models}, 41 | year = {2020}} 42 | 43 | @software{FourierFlows, 44 | author = {Constantinou, N. C. and 45 | Wagner, L. C. and 46 | Palóczy, A.}, 47 | title = {FourierFlows/FourierFlows.jl: v0.6.17}, 48 | year = {2021}, 49 | publisher = {Zenodo}, 50 | version = {v0.6.17}, 51 | doi = {10.5281/zenodo.4686348}, 52 | url = {https://doi.org/10.5281/zenodo.4686348} 53 | } 54 | 55 | @article{Thyng2016, 56 | title = {True colors of oceanography: {Guidelines} for effective and accurate colormap selection}, 57 | author = {Thyng, K. M. and Greene, C. A. and Hetland, R. D. and Zimmerle, H. M. and DiMarco, S. F.}, 58 | journal = {Oceanography}, 59 | volume = {29}, 60 | number = {3}, 61 | pages = {9--13}, 62 | year = {2016}, 63 | doi = {10.5670/oceanog.2016.66} 64 | } 65 | 66 | @article{Burns2020, 67 | title = {Dedalus: {A} flexible framework for numerical simulations with spectral methods}, 68 | author = {Burns, K. J. and Vasil, G. M. and Oishi, J. S. and Lecoanet, D. and Brown, B. P.}, 69 | journal = {Physical Review Research}, 70 | volume = {2}, 71 | issue = {2}, 72 | pages = {023068}, 73 | numpages = {39}, 74 | year = {2020}, 75 | publisher = {American Physical Society}, 76 | doi = {10.1103/PhysRevResearch.2.023068} 77 | } 78 | 79 | @software{pyqg, 80 | author = {Abernathey, R. and 81 | Rocha, C. B. and 82 | Jansen, M. and 83 | Poulin, F. J. and 84 | Constantinou, N. C. and 85 | Balwada, D. and 86 | Sinha, A. and 87 | Bueti, M. and 88 | Penn, J. and 89 | Wolfe, C. L. P. and 90 | Boas, B. V.}, 91 | title = {{pyqg/pyqg}: v0.3.0}, 92 | year = {2019}, 93 | publisher = {Zenodo}, 94 | version = {v0.3.0}, 95 | doi = {10.5281/zenodo.3551326}, 96 | url = {https://doi.org/10.5281/zenodo.3551326} 97 | } 98 | 99 | @article{Pearson2021, 100 | title = {Advective structure functions in anisotropic two-dimensional turbulence}, 101 | author = {Pearson, B. C. and Pearson, J. L. and Fox-Kemper, B.}, 102 | journal = {Journal of Fluid Mechanics}, 103 | year = {2021}, 104 | volume = {916}, 105 | pages = {A49}, 106 | doi = {10.1017/jfm.2021.247} 107 | } 108 | 109 | @article{Karrasch2020, 110 | title = {Fast and robust computation of coherent {Lagrangian} vortices on very large two-dimensional domains}, 111 | author = {Karrasch, D. and Schilling, N.}, 112 | journal = {SMAI Journal of Computational Mathematics}, 113 | volume = {6}, 114 | pages = {101--124}, 115 | year = {2020}, 116 | doi = {10.5802/smai-jcm.63} 117 | } 118 | 119 | @misc{KolmogorovFlow, 120 | author = {Constantinou, N. C. and Drivas, T. D.}, 121 | title = {KolmogorovFlow}, 122 | year = {2020}, 123 | publisher = {GitHub}, 124 | journal = {GitHub repository}, 125 | url = {https://github.com/navidcy/KolmogorovFlow} 126 | } 127 | 128 | @misc{QG_tracer_advection, 129 | author = {Bisits, J. and Constantinou, N. C.}, 130 | title = {QG_tracer_advection}, 131 | year = {2021}, 132 | publisher = {GitHub}, 133 | journal = {GitHub repository}, 134 | url = {https://github.com/jbisits/QG_tracer_advection} 135 | } 136 | 137 | @misc{GeophysicalFlows-Examples, 138 | author = {Constantinou, N. C. and Wagner, G. L.}, 139 | title = {GeophysicalFlows-Examples}, 140 | year = {2020}, 141 | publisher = {GitHub}, 142 | journal = {GitHub repository}, 143 | url = {https://github.com/FourierFlows/GeophysicalFlows-Examples} 144 | } 145 | 146 | @misc{CLExWinterSchool2020, 147 | author = {Constantinou, N. C.}, 148 | title = {CLExWinterSchool2020}, 149 | year = {2020}, 150 | publisher = {GitHub}, 151 | journal = {GitHub repository}, 152 | url = {https://github.com/navidcy/CLExWinterSchool2020} 153 | } 154 | -------------------------------------------------------------------------------- /paper/paper.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'GeophysicalFlows.jl: Solvers for geophysical fluid dynamics problems in periodic domains on CPUs & GPUs' 3 | tags: 4 | - geophysical fluid dynamics 5 | - computational fluid dynamics 6 | - Fourier methods 7 | - pseudospectral 8 | - Julia 9 | - gpu 10 | authors: 11 | - name: Navid C. Constantinou 12 | orcid: 0000-0002-8149-4094 13 | affiliation: "1, 2" 14 | - name: Gregory LeClaire Wagner 15 | orcid: 0000-0001-5317-2445 16 | affiliation: 3 17 | - name: Lia Siegelman 18 | orcid: 0000-0003-3330-082X 19 | affiliation: 4 20 | - name: Brodie C. Pearson 21 | orcid: 0000-0002-0202-0481 22 | affiliation: 5 23 | - name: André Palóczy 24 | orcid: 0000-0001-8231-8298 25 | affiliation: 6 26 | affiliations: 27 | - name: Australian National University 28 | index: 1 29 | - name: ARC Centre of Excellence for Climate Extremes 30 | index: 2 31 | - name: Massachussetts Institute of Technology 32 | index: 3 33 | - name: University of California San Diego 34 | index: 4 35 | - name: Oregon State University 36 | index: 5 37 | - name: University of Oslo 38 | index: 6 39 | date: 7 April 2021 40 | bibliography: paper.bib 41 | --- 42 | 43 | 44 | # Summary 45 | 46 | `GeophysicalFlows.jl` is a Julia [@Bezanson2017] package that contains partial differential 47 | equations solvers for a collection of geophysical fluid systems in periodic domains. All 48 | modules use Fourier-based pseudospectral numerical methods and leverage the framework provided 49 | by the `FourierFlows.jl` [@FourierFlows] Julia package for time-stepping, custom diagnostics, 50 | and saving output. 51 | 52 | 53 | # Statement of need 54 | 55 | Conceptual models in simple domains often provide stepping stones for better understanding geophysical and astrophysical systems, particularly the atmospheres and oceans of Earth and other planets. These conceptual models are used in research but also are of great value for helping students in class to grasp new concepts and phenomena. Oftentimes people end up coding their own versions of solvers for the same partial differential equations for research or classwork. `GeophysicalFlows.jl` package is designed to be easily utilized and adaptable for a wide variety of both research and pedagogical purposes. 56 | 57 | On top of the above-mentioned needs, the recent explosion of machine-learning applications in atmospheric and oceanic sciences advocates for the need that solvers for partial differential equations can be run on GPUs. 58 | 59 | `GeophysicalFlows.jl` provides a collection of modules for solving sets of partial differential equations often used as conceptual models. These modules are continuously tested (unit tests and tests for the physics involved) and are well-documented. `GeophysicalFlows.jl` utilizes Julia's functionality and abstraction to enable all modules to run on CPUs or GPUs, and to provide a high level of customizability within modules. The abstractions allow simulations to be tailored for specific research questions, via the choice of parameters, domain properties, and schemes for damping, forcing, time-stepping etc. Simulations can easily be carried out on different computing architectures. Selection of the architecture on which equations are solved is done by providing the argument `CPU()` or `GPU()` during the construction of a particular problem. 60 | 61 | Documented examples for each geophysical system (module) appear in the package's documentation, 62 | providing a starting point for new users and for the development of new or customized modules. 63 | Current modules include two-dimensional flow and a variety of quasi-geostrophic (QG) dynamical 64 | systems, which provide analogues to the large-scale dynamics of atmospheres and oceans. The QG 65 | systems currently in `GeophysicalFlows.jl` extend two-dimensional dynamics to include the leading 66 | order effects of a third dimension through planetary rotation, topography, surface boundary 67 | conditions, stratification and quasi-two-dimensional layering. A community-based collection 68 | of diagnostics throughout the modules are used to compute quantities like energy, enstrophy, 69 | dissipation, etc. 70 | 71 | ![Potential vorticity snapshots from a nonlinearly equilibrated simulation of the Eady instability 72 | over a meridional ridge. Simulation used `MultiLayerQG` module of `GeophysicalFlows.jl`. The Eady 73 | problem was approximated here using 5 fluid layers stacked up in the vertical. Each layer was 74 | simulated with 512² grid-points. Plots were made with the `Plots.jl` Julia package, which 75 | utilizes the `cmocean` colormaps collection [@Thyng2016]. Scripts to reproduce the simulation 76 | reside in the repository `github.com/FourierFlows/MultilayerQG-example`. 77 | \label{fig1}](PV_eady_nlayers5.png) 78 | 79 | 80 | # State of the field 81 | 82 | `GeophysicalFlows.jl` is a unique Julia package that shares some features and similarities with 83 | other packages. In particular: 84 | 85 | - `pyqg` [@pyqg] (Python) 86 | 87 | Beyond their base language, the major differences between `GeophysicalFlows.jl` and `pyqg` 88 | is that `GeophysicalFlows.jl` can be run on GPUs or CPUs and leverages a separate package (`FourierFlows.jl`; which is continuously developed) to solve differential equations and compute diagnostics, while `pyqg` can only be run on CPUs and uses a self-contained kernel. 89 | 90 | - Dedalus [@Burns2020] (Python) 91 | 92 | Dedalus is a Python package with an intuitive script-based interface that uses spectral methods 93 | to solve general partial differential equations, such as the ones within `GeophysicalFlows.jl`. 94 | Dedalus allows for more general boundary conditions in one of the dimensions. It only runs on 95 | CPUs (not on GPUs) but can be MPI-parallelized. 96 | 97 | - `Oceananigans.jl` [@Oceananigans] (Julia) 98 | 99 | `Oceananigans.jl` is a fluid solver focussed on the Navier-Stokes equations under the Boussinesq 100 | approximation. `Oceananigans.jl` also runs on GPUs, and it allows for more variety of boundary 101 | conditions but it does not have spectral accuracy as it uses finite-volume discretization methods. 102 | 103 | - `MAOOAM` [@MAOOAM] (Fortran, Python, and Lua) and its expanded Python implementation `qgs` [@qgs] 104 | 105 | `MAOOAM` and `qgs` simulate two atmospheric layers with QG dynamics, above either land or 106 | an oceanic fluid layer with reduced-gravity QG dynamics. The dynamics of individual layers 107 | have overlap with the `MultiLayerQG` and `SingleLayerQG` modules, however the layer configuration 108 | of `MOAAM` and `qgs` is specifically designed to study the dynamics of Earth's mid-latitude 109 | atmosphere. Neither `MAOOAM` nor `qgs` can run on GPUs. 110 | 111 | - Isolated codes/scripts 112 | 113 | Several codes/scripts exist in personal websites and in open-source public repositories with 114 | similar functionality as some `GeophysicalFlows.jl` modules (e.g., `TwoDNavierStokes` or 115 | `SingleLayerQG`). Usually, though, these codes come without any or poor documentation and 116 | typically they are not continuously tested. 117 | 118 | `GeophysicalFlows.jl` can be used to investigate a variety of scientific research questions 119 | thanks to its various modules and high customizability, and its ease-of-use makes it an ideal 120 | teaching tool for fluids courses [@GeophysicalFlows-Examples; @CLExWinterSchool2020]. 121 | `GeophysicalFlows.jl` has been used in developing Lagrangian vortices identification algorithms 122 | [@Karrasch2020] and to test new theories for diagnosing turbulent energy transfers in geophysical 123 | flows [@Pearson2021]. Currently, `GeophysicalFlows.jl` is being used, e.g., (i) to compare 124 | different observational sampling techniques in these flows, (ii) to study the bifurcation properties 125 | of Kolmogorov flows [@KolmogorovFlow], (iii) to study the genesis and persistence of the polygons 126 | of vortices present at Jovian high latitudes (Siegelman, Young, and Ingersoll; in prep), and 127 | (iv) to study how mesoscale macroturbulence affects mixing of tracers [@QG_tracer_advection]. 128 | 129 | 130 | # Acknowledgements 131 | 132 | We acknowledge discussions with Keaton Burns, Valentin Churavy, Theodore Drivas, Cesar Rocha, 133 | and William Young. B. C. P. was supported by the National Science Foundation under Grant 134 | No. 2023721. We would also like to take a moment to remember our friend and colleague 135 | Sean R. Haney (February 1987 - January 2021) who left us a bit too early. 136 | 137 | 138 | # References 139 | -------------------------------------------------------------------------------- /src/GeophysicalFlows.jl: -------------------------------------------------------------------------------- 1 | """ 2 | Main module for `GeophysicalFlows.jl` -- a collection of solvers for geophysical fluid dynamics 3 | problems in periodic domains on CPUs and GPUs. All modules use Fourier-based pseudospectral 4 | methods and leverage the functionality of `FourierFlows.jl` ecosystem. 5 | """ 6 | module GeophysicalFlows 7 | 8 | using 9 | CUDA, 10 | Statistics, 11 | SpecialFunctions, 12 | Reexport, 13 | DocStringExtensions 14 | 15 | @reexport using FourierFlows 16 | 17 | include("utils.jl") 18 | include("twodnavierstokes.jl") 19 | include("singlelayerqg.jl") 20 | include("multilayerqg.jl") 21 | include("surfaceqg.jl") 22 | include("barotropicqgql.jl") 23 | 24 | @reexport using GeophysicalFlows.TwoDNavierStokes 25 | @reexport using GeophysicalFlows.SingleLayerQG 26 | @reexport using GeophysicalFlows.MultiLayerQG 27 | @reexport using GeophysicalFlows.SurfaceQG 28 | @reexport using GeophysicalFlows.BarotropicQGQL 29 | 30 | end # module 31 | -------------------------------------------------------------------------------- /src/utils.jl: -------------------------------------------------------------------------------- 1 | """ 2 | lambdipole(U, R, grid::TwoDGrid; center=(mean(grid.x), mean(grid.y)) 3 | 4 | Return the two-dimensional vorticity field of the Lamb dipole with strength `U` and radius `R`, 5 | on `grid`. The dipole is centered at `center = (xc, yc)`; default value for `center` is the center 6 | of the domain. 7 | """ 8 | function lambdipole(U, R, grid::TwoDGrid{T, A}; center=(mean(grid.x), mean(grid.y))) where {T, A} 9 | firstzero = 3.8317059702075123156 # first zero of besselj1 10 | k = firstzero / R # dipole wavenumber for radius R in terms of first zero of besselj 11 | q₀ = -2U * k / besselj(0, k * R) # dipole amplitude for strength U and radius R 12 | x, y = gridpoints(grid) 13 | xc, yc = center 14 | r = @. sqrt((x - xc)^2 + (y - yc)^2) 15 | besseljorder1 = CUDA.@allowscalar A([besselj1(k * r[i, j]) for j=1:grid.ny, i=1:grid.nx]) 16 | q = @. q₀ * besseljorder1 * (y - yc) / r 17 | @. q = ifelse(r ≥ R, 0, q) 18 | 19 | return q 20 | end 21 | 22 | """ 23 | peakedisotropicspectrum(grid, kpeak, E₀; mask = ones(size(grid.Krsq)), allones = false) 24 | 25 | Generate a random two-dimensional relative vorticity field ``q(x, y)`` with Fourier spectrum 26 | peaked around a central non-dimensional wavenumber `kpeak` and normalized so that its total 27 | kinetic energy is `E₀`. 28 | """ 29 | function peakedisotropicspectrum(grid::TwoDGrid{T, A}, kpeak::Real, E₀::Real; 30 | mask = ones(size(grid.Krsq)), allones = false) where {T, A} 31 | 32 | grid.Lx !== grid.Ly && error("the domain is not square") 33 | 34 | k₀ = kpeak * 2π / grid.Lx 35 | modk = sqrt.(grid.Krsq) 36 | modψ = A(zeros(T, (grid.nk, grid.nl))) 37 | modψ = @. (modk^2 * (1 + (modk / k₀)^4))^(-0.5) 38 | CUDA.@allowscalar modψ[1, 1] = 0.0 39 | 40 | if allones 41 | ψh = modψ 42 | else 43 | phases = randn(Complex{T}, size(grid.Krsq)) 44 | phases_real, phases_imag = real.(phases), imag.(phases) 45 | phases = A(phases_real) + im * A(phases_imag) 46 | ψh = @. phases * modψ 47 | end 48 | 49 | ψh .*= A(mask) 50 | energy_initial = sum(grid.Krsq .* abs2.(ψh)) / (grid.nx * grid.ny)^2 51 | ψh *= sqrt(E₀ / energy_initial) 52 | 53 | return A(irfft(- grid.Krsq .* ψh, grid.nx)) 54 | end 55 | -------------------------------------------------------------------------------- /test/runtests.jl: -------------------------------------------------------------------------------- 1 | using 2 | CUDA, 3 | GeophysicalFlows, 4 | Statistics, 5 | Random, 6 | Test 7 | 8 | import # use 'import' rather than 'using' for submodules to keep namespace clean 9 | GeophysicalFlows.TwoDNavierStokes, 10 | GeophysicalFlows.SingleLayerQG, 11 | GeophysicalFlows.BarotropicQGQL, 12 | GeophysicalFlows.MultiLayerQG, 13 | GeophysicalFlows.SurfaceQG 14 | 15 | using FourierFlows: parsevalsum 16 | using GeophysicalFlows: lambdipole, peakedisotropicspectrum 17 | 18 | # the devices on which tests will run 19 | devices = CUDA.functional() ? (CPU(), GPU()) : (CPU(),) 20 | 21 | const rtol_lambdipole = 1e-2 # tolerance for lamb dipole tests 22 | const rtol_twodnavierstokes = 1e-13 # tolerance for twodnavierstokes forcing tests 23 | const rtol_singlelayerqg = 1e-13 # tolerance for singlelayerqg forcing tests 24 | const rtol_multilayerqg = 1e-13 # tolerance for multilayerqg forcing tests 25 | const rtol_surfaceqg = 1e-13 # tolerance for surfaceqg forcing tests 26 | 27 | # Run tests 28 | testtime = @elapsed begin 29 | for dev in devices 30 | 31 | @info "testing on " * string(typeof(dev)) 32 | 33 | @testset "Utils" begin 34 | include("test_utils.jl") 35 | 36 | @test testpeakedisotropicspectrum(dev) 37 | @test_throws ErrorException("the domain is not square") testpeakedisotropicspectrum_rectangledomain() 38 | end 39 | 40 | @testset "TwoDNavierStokes" begin 41 | include("test_twodnavierstokes.jl") 42 | 43 | @test test_twodnavierstokes_advection(0.0005, "ForwardEuler", dev) 44 | @test test_twodnavierstokes_lambdipole(256, 1e-3, dev) 45 | @test test_twodnavierstokes_deterministicforcing_energybudget(dev) 46 | @test test_twodnavierstokes_stochasticforcing_energybudget(dev) 47 | @test test_twodnavierstokes_deterministicforcing_enstrophybudget(dev) 48 | @test test_twodnavierstokes_stochasticforcing_enstrophybudget(dev) 49 | @test test_twodnavierstokes_energyenstrophypalinstrophy(dev) 50 | @test test_twodnavierstokes_problemtype(dev, Float32) 51 | @test TwoDNavierStokes.nothingfunction() === nothing 52 | end 53 | 54 | @testset "SingleLayerQG" begin 55 | include("test_singlelayerqg.jl") 56 | 57 | for deformation_radius in (Inf, 1.23), U₀ in (0, 0.3) 58 | for (timestepper, dt, nsteps) in zip(("ETDRK4", "FilteredETDRK4", "RK4", "FilteredRK4", "AB3", "FilteredAB3", "ForwardEuler", "FilteredForwardEuler",), 59 | (1e-2, 1e-2, 1e-2, 1e-2, 1e-3, 1e-3, 1e-4, 1e-4,), 60 | (20, 20, 20, 20, 200, 200, 2000, 2000,)) 61 | 62 | nx = 64 63 | @test test_1layerqg_rossbywave(timestepper, dt, nsteps, dev, nx; deformation_radius, U=U₀) 64 | @test test_1layerqg_rossbywave(timestepper, dt, nsteps, dev, nx; deformation_radius, U=U₀*ones((nx,))) 65 | end 66 | @test test_1layerqg_problemtype(dev, Float32; deformation_radius, U=U₀) 67 | end 68 | @test test_1layerqg_nonlinearadvection(0.0005, "ForwardEuler", dev, add_background_flow = false, add_topography = false) 69 | @test test_1layerqg_nonlinearadvection(0.0005, "ForwardEuler", dev, add_background_flow = false, add_topography = true) 70 | @test test_1layerqg_nonlinearadvection(0.0005, "ForwardEuler", dev, add_background_flow = true, background_flow_vary_in_y = false) 71 | @test test_1layerqg_nonlinearadvection(0.0005, "ForwardEuler", dev, add_background_flow = true, background_flow_vary_in_y = false, add_topography = true) 72 | @test test_1layerqg_nonlinearadvection(0.0005, "ForwardEuler", dev, add_background_flow = true, background_flow_vary_in_y = true) 73 | @test test_1layerqg_nonlinearadvection_deformation(0.0005, "ForwardEuler", dev) 74 | @test test_streamfunctionfrompv(dev; deformation_radius=1.23) 75 | @test test_1layerqg_energyenstrophy_BarotropicQG(dev) 76 | @test test_1layerqg_energies_EquivalentBarotropicQG(dev; deformation_radius=1.23) 77 | @test test_1layerqg_deterministicforcing_energybudget(dev) 78 | @test test_1layerqg_stochasticforcing_energybudget(dev) 79 | @test test_1layerqg_deterministicforcing_enstrophybudget(dev) 80 | @test test_1layerqg_stochasticforcing_enstrophybudget(dev) 81 | @test SingleLayerQG.nothingfunction() === nothing 82 | @test_throws ErrorException("not implemented for finite deformation radius") test_1layerqg_energy_dissipation(dev; deformation_radius=2.23) 83 | @test_throws ErrorException("not implemented for finite deformation radius") test_1layerqg_enstrophy_dissipation(dev; deformation_radius=2.23) 84 | @test_throws ErrorException("not implemented for finite deformation radius") test_1layerqg_energy_work(dev; deformation_radius=2.23) 85 | @test_throws ErrorException("not implemented for finite deformation radius") test_1layerqg_enstrophy_work(dev; deformation_radius=2.23) 86 | @test_throws ErrorException("not implemented for finite deformation radius") test_1layerqg_energy_drag(dev; deformation_radius=2.23) 87 | @test_throws ErrorException("not implemented for finite deformation radius") test_1layerqg_enstrophy_drag(dev; deformation_radius=2.23) 88 | end 89 | 90 | @testset "BarotropicQGQL" begin 91 | include("test_barotropicqgql.jl") 92 | 93 | @test test_bqgql_rossbywave("ETDRK4", 1e-2, 20, dev) 94 | @test test_bqgql_rossbywave("FilteredETDRK4", 1e-2, 20, dev) 95 | @test test_bqgql_rossbywave("RK4", 1e-2, 20, dev) 96 | @test test_bqgql_rossbywave("FilteredRK4", 1e-2, 20, dev) 97 | @test test_bqgql_rossbywave("AB3", 1e-3, 200, dev) 98 | @test test_bqgql_rossbywave("FilteredAB3", 1e-3, 200, dev) 99 | @test test_bqgql_rossbywave("ForwardEuler", 1e-4, 2000, dev) 100 | @test test_bqgql_rossbywave("FilteredForwardEuler", 1e-4, 2000, dev) 101 | @test test_bqgql_deterministicforcingbudgets(dev) 102 | @test test_bqgql_stochasticforcingbudgets(dev) 103 | @test test_bqgql_advection(0.0005, "ForwardEuler", dev) 104 | @test test_bqgql_energyenstrophy(dev) 105 | @test test_bqgql_problemtype(dev, Float32) 106 | @test BarotropicQGQL.nothingfunction() === nothing 107 | end 108 | 109 | @testset "SurfaceQG" begin 110 | include("test_surfaceqg.jl") 111 | 112 | @test test_sqg_deterministicforcing_buoyancy_variance_budget(dev) 113 | @test test_sqg_stochasticforcing_buoyancy_variance_budget(dev) 114 | @test test_sqg_stochasticforcedproblemconstructor(dev) 115 | @test test_sqg_problemtype(dev, Float32) 116 | @test test_sqg_noforcing(dev) 117 | @test SurfaceQG.nothingfunction() === nothing 118 | 119 | for H in (Inf, 1.0) 120 | @test test_sqg_energy_buoyancyvariance(dev, H) 121 | @test test_sqg_advection(0.0005, "ForwardEuler", dev, H) 122 | @test test_sqg_paramsconstructor(dev, H) 123 | 124 | end 125 | end 126 | 127 | @testset "MultiLayerQG" begin 128 | include("test_multilayerqg.jl") 129 | 130 | @test test_pvtofromstreamfunction_2layer(dev) 131 | @test test_pvtofromstreamfunction_3layer(dev) 132 | @test test_mqg_rossbywave("RK4", 1e-2, 20, dev) 133 | @test test_mqg_nonlinearadvection_2layers(0.005, "ForwardEuler", dev) 134 | @test test_mqg_nonlinearadvection_3layers(0.005, "ForwardEuler", dev) 135 | @test test_mqg_linearadvection(0.005, "ForwardEuler", dev) 136 | @test test_mqg_energies(dev) 137 | @test test_mqg_energysinglelayer(dev) 138 | @test test_mqg_fluxes(dev; nlayers=2) 139 | @test test_mqg_fluxes(dev; nlayers=3) 140 | @test test_mqg_fluxessinglelayer(dev) 141 | @test test_mqg_setqsetψ(dev) 142 | @test test_mqg_set_topographicPV_largescale_gradient(dev) 143 | @test test_mqg_paramsconstructor(dev) 144 | @test test_mqg_stochasticforcedproblemconstructor(dev) 145 | @test test_mqg_problemtype(dev, Float32) 146 | @test MultiLayerQG.nothingfunction() === nothing 147 | end 148 | end 149 | end # time 150 | 151 | println("Total test time: ", testtime) 152 | -------------------------------------------------------------------------------- /test/test_barotropicqgql.jl: -------------------------------------------------------------------------------- 1 | """ 2 | test_bqgql_rossbywave(; kwargs...) 3 | 4 | Evolvesa a Rossby wave and compares with the analytic solution. 5 | """ 6 | function test_bqgql_rossbywave(stepper, dt, nsteps, dev::Device=CPU()) 7 | nx = 64 8 | β = 2.0 9 | Lx = 2π 10 | μ = 0.0 11 | ν = 0.0 12 | T = Float64 13 | 14 | # the following if statement is called so that all the cases of 15 | # Problem() function are tested 16 | if stepper=="ForwardEuler" 17 | eta = zeros(dev, T, (nx, nx)) 18 | else 19 | eta(x, y) = 0*x 20 | end 21 | 22 | prob = BarotropicQGQL.Problem(dev; nx=nx, Lx=Lx, eta=eta, β=β, μ=μ, ν=ν, stepper=stepper, dt=dt) 23 | sol, clock, vars, params, grid = prob.sol, prob.clock, prob.vars, prob.params, prob.grid 24 | 25 | x, y = gridpoints(grid) 26 | 27 | # the Rossby wave initial condition 28 | ampl = 1e-2 29 | kwave, lwave = 3 * 2π / grid.Lx, 2 * 2π / grid.Ly 30 | ω = - params.β * kwave / (kwave^2 + lwave^2) 31 | ζ0 = @. ampl * cos(kwave * x) * cos(lwave * y) 32 | ζ0h = rfft(ζ0) 33 | 34 | BarotropicQGQL.set_zeta!(prob, ζ0) 35 | 36 | stepforward!(prob, nsteps) 37 | dealias!(sol, grid) 38 | BarotropicQGQL.updatevars!(prob) 39 | 40 | ζ_theory = @. ampl * cos(kwave * (x - ω / kwave * clock.t)) * cos(lwave * y) 41 | 42 | return isapprox(ζ_theory, vars.zeta, rtol = grid.nx * grid.ny * nsteps * 1e-12) 43 | end 44 | 45 | """ 46 | test_stochasticforcingbudgets(dev; kwargs...) 47 | 48 | Tests if the energy budget is closed for BarotropicQG problem with stochastic forcing. 49 | """ 50 | function test_bqgql_stochasticforcingbudgets(dev::Device=CPU(); n=256, dt=0.01, L=2π, ν=1e-7, nν=2, μ=1e-1, T=Float64) 51 | n, L = 256, 2π 52 | ν, nν = 1e-7, 2 53 | μ = 1e-1 54 | dt, tf = 0.005, 0.1/μ 55 | nt = round(Int, tf/dt) 56 | 57 | grid = TwoDGrid(dev; nx=n, Lx=L) 58 | x, y = gridpoints(grid) 59 | 60 | # Forcing 61 | kf, dkf = 12.0, 2.0 62 | ε = 0.1 63 | 64 | Kr = CUDA.@allowscalar device_array(dev)([ grid.kr[i] for i=1:grid.nkr, j=1:grid.nl]) 65 | 66 | forcing_spectrum = zeros(dev, T, (grid.nkr, grid.nl)) 67 | @. forcing_spectrum = exp(-(sqrt(grid.Krsq) - kf)^2 / (2 * dkf^2)) 68 | @. forcing_spectrum = ifelse(grid.Krsq < 2^2, 0, forcing_spectrum) 69 | @. forcing_spectrum = ifelse(grid.Krsq > 20^2, 0, forcing_spectrum) 70 | @. forcing_spectrum = ifelse(Kr < 2π/L, 0, forcing_spectrum) 71 | ε0 = parsevalsum(forcing_spectrum .* grid.invKrsq / 2, grid) / (grid.Lx * grid.Ly) 72 | forcing_spectrum .= ε / ε0 * forcing_spectrum 73 | 74 | Random.seed!(1234) 75 | 76 | function calcF!(F, sol, t, clock, vars, params, grid) 77 | eta = device_array(dev)(exp.(2π * im * rand(T, size(sol))) / sqrt(clock.dt)) 78 | CUDA.@allowscalar eta[1, 1] = 0 79 | @. F = eta * sqrt(forcing_spectrum) 80 | return nothing 81 | end 82 | 83 | prob = BarotropicQGQL.Problem(dev; nx=n, Lx=L, ν=ν, nν=nν, μ=μ, dt=dt, 84 | stepper="RK4", calcF=calcF!, stochastic=true) 85 | 86 | BarotropicQGQL.set_zeta!(prob, 0*x) 87 | E = Diagnostic(BarotropicQGQL.energy, prob, nsteps=nt) 88 | D = Diagnostic(BarotropicQGQL.dissipation, prob, nsteps=nt) 89 | R = Diagnostic(BarotropicQGQL.drag, prob, nsteps=nt) 90 | W = Diagnostic(BarotropicQGQL.work, prob, nsteps=nt) 91 | diags = [E, D, W, R] 92 | 93 | stepforward!(prob, diags, round(Int, nt)) 94 | 95 | BarotropicQGQL.updatevars!(prob) 96 | 97 | dEdt_numerical = (E[2:E.i] - E[1:E.i-1]) / prob.clock.dt 98 | 99 | dEdt_computed = W[2:E.i] - D[1:E.i-1] - R[1:E.i-1] 100 | 101 | return isapprox(dEdt_numerical, dEdt_computed, rtol=1e-3) 102 | end 103 | 104 | """ 105 | test_bqgql_deterministicforcingbudgets(dev ; kwargs...) 106 | 107 | Tests if the energy budget is closed for BarotropicQGQL problem with deterministic forcing. 108 | """ 109 | function test_bqgql_deterministicforcingbudgets(dev::Device=CPU(); n=256, dt=0.01, L=2π, ν=1e-7, nν=2, μ=1e-1) 110 | n, L = 256, 2π 111 | ν, nν = 1e-7, 2 112 | μ = 1e-1 113 | dt, tf = 0.005, 0.1/μ 114 | nt = round(Int, tf/dt) 115 | 116 | grid = TwoDGrid(dev; nx=n, Lx=L) 117 | x, y = gridpoints(grid) 118 | k₀, l₀ = 2π/grid.Lx, 2π/grid.Ly 119 | 120 | # Forcing = 0.01cos(4x)cos(5y)cos(2t) 121 | f = @. 0.01 * cos(4k₀*x) * cos(5l₀*y) 122 | fh = rfft(f) 123 | 124 | function calcF!(Fh, sol, t, clock, vars, params, grid) 125 | @. Fh = fh*cos(2t) 126 | return nothing 127 | end 128 | 129 | prob = BarotropicQGQL.Problem(dev; nx=n, Lx=L, ν=ν, nν=nν, μ=μ, dt=dt, 130 | stepper="RK4", calcF=calcF!, stochastic=false) 131 | 132 | BarotropicQGQL.set_zeta!(prob, 0*x) 133 | 134 | E = Diagnostic(BarotropicQGQL.energy, prob, nsteps=nt) 135 | D = Diagnostic(BarotropicQGQL.dissipation, prob, nsteps=nt) 136 | R = Diagnostic(BarotropicQGQL.drag, prob, nsteps=nt) 137 | W = Diagnostic(BarotropicQGQL.work, prob, nsteps=nt) 138 | diags = [E, D, W, R] 139 | 140 | stepforward!(prob, diags, nt) 141 | 142 | BarotropicQGQL.updatevars!(prob) 143 | 144 | dEdt_numerical = (E[3:E.i] - E[1:E.i-2]) / (2 * prob.clock.dt) 145 | dEdt_computed = W[2:E.i-1] - D[2:E.i-1] - R[2:E.i-1] 146 | 147 | residual = dEdt_numerical - dEdt_computed 148 | 149 | return isapprox(dEdt_numerical, dEdt_computed, atol=1e-10) 150 | end 151 | 152 | """ 153 | test_bqgql_nonlinearadvection(dt, stepper, dev; kwargs...) 154 | 155 | Tests the advection term by timestepping a test problem with timestep `dt` and timestepper 156 | `stepper`. The test problem is derived by picking a solution ζf (with associated 157 | streamfunction ψf) for which the advection term J(ψf, ζf) is non-zero. Next, a 158 | forcing Ff is derived according to Ff = ∂ζf/∂t + J(ψf, ζf) - νΔζf. One solution 159 | to the vorticity equation forced by this Ff is then ζf. (This solution may not 160 | be realized, at least at long times, if it is unstable.) 161 | """ 162 | function test_bqgql_advection(dt, stepper, dev::Device=CPU(); n=128, L=2π, ν=1e-2, nν=1, μ=0.0) 163 | n, L = 128, 2π 164 | ν, nν = 1e-2, 1 165 | μ = 0.0 166 | tf = 1.0 167 | nt = round(Int, tf/dt) 168 | 169 | grid = TwoDGrid(dev; nx=n, Lx=L) 170 | x, y = gridpoints(grid) 171 | 172 | ψf = @. cos(3y) + sin(2x)*cos(2y) + 2sin(x)*cos(3y) 173 | qf = @. - 9cos(3y) - 8sin(2x)*cos(2y) - 20sin(x)*cos(3y) 174 | 175 | Ff = @. ν*( 81cos(3y) + 200cos(3y)*sin(x) + 64cos(2y)*sin(2x) ) - 176 | 3sin(3y)*(-16cos(2x)*cos(2y) - 20cos(x)*cos(3y)) - 177 | 27sin(3y)*(2cos(2x)*cos(2y) + 2cos(x)*cos(3y)) + 0*(-8cos(x)*cos(3y)*sin(2x)*sin(2y) + 178 | 24*cos(2x)*cos(2y)*sin(x)*sin(3y)) 179 | 180 | Ffh = -rfft(Ff) 181 | 182 | # Forcing 183 | function calcF!(Fh, sol, t, cl, v, p, g) 184 | Fh .= Ffh 185 | return nothing 186 | end 187 | 188 | prob = BarotropicQGQL.Problem(dev; nx=n, Lx=L, ν=ν, nν=nν, μ=μ, dt=dt, stepper=stepper, calcF=calcF!) 189 | 190 | BarotropicQGQL.set_zeta!(prob, qf) 191 | 192 | stepforward!(prob, round(Int, nt)) 193 | 194 | BarotropicQGQL.updatevars!(prob) 195 | 196 | return isapprox(prob.vars.zeta + prob.vars.Zeta, qf, rtol = 1e-13) 197 | end 198 | 199 | """ 200 | test_bqgql_energyenstrophy(dev) 201 | 202 | Tests the energy and enstrophy function for a BarotropicQGQL problem. 203 | """ 204 | function test_bqgql_energyenstrophy(dev::Device=CPU()) 205 | nx, Lx = 64, 2π 206 | ny, Ly = 64, 3π 207 | grid = TwoDGrid(dev; nx, Lx, ny, Ly) 208 | k₀, l₀ = 2π/Lx, 2π/Ly # fundamental wavenumbers 209 | x, y = gridpoints(grid) 210 | 211 | energy_calc = 29/9 212 | enstrophy_calc = 2701/162 213 | 214 | eta = @. cos(10k₀*x)*cos(10l₀*y) 215 | ψ₀ = @. sin(2k₀*x)*cos(2l₀*y) + 2sin(k₀*x)*cos(3l₀*y) 216 | ζ₀ = @. -((2k₀)^2+(2l₀)^2)*sin(2k₀*x)*cos(2l₀*y) - (k₀^2+(3l₀)^2)*2sin(k₀*x)*cos(3l₀*y) 217 | 218 | prob = BarotropicQGQL.Problem(dev; nx=nx, Lx=Lx, ny=ny, Ly=Ly, eta=eta, stepper="ForwardEuler") 219 | sol, clock, vars, params, grid = prob.sol, prob.clock, prob.vars, prob.params, prob.grid 220 | 221 | BarotropicQGQL.set_zeta!(prob, ζ₀) 222 | BarotropicQGQL.updatevars!(prob) 223 | 224 | energyζ₀ = BarotropicQGQL.energy(prob) 225 | enstrophyζ₀ = BarotropicQGQL.enstrophy(prob) 226 | 227 | return isapprox(energyζ₀, energy_calc, rtol=1e-13) && isapprox(enstrophyζ₀, enstrophy_calc, rtol=1e-13) && BarotropicQGQL.addforcing!(prob.timestepper.N, sol, clock.t, clock, vars, params, grid)==nothing 228 | end 229 | 230 | function test_bqgql_problemtype(dev, T) 231 | prob = BarotropicQGQL.Problem(dev; T=T) 232 | 233 | A = device_array(dev) 234 | 235 | return (typeof(prob.sol)<:A{Complex{T}, 2} && typeof(prob.grid.Lx)==T && eltype(prob.grid.x)==T && typeof(prob.vars.u)<:A{T, 2}) 236 | end 237 | -------------------------------------------------------------------------------- /test/test_surfaceqg.jl: -------------------------------------------------------------------------------- 1 | function test_sqg_problemtype(dev, T) 2 | prob = SurfaceQG.Problem(dev; T=T) 3 | 4 | A = device_array(dev) 5 | 6 | return (typeof(prob.sol)<:A{Complex{T},2} && 7 | typeof(prob.grid.Lx)==T && 8 | eltype(prob.grid.x)==T && 9 | typeof(prob.vars.u)<:A{T,2}) 10 | end 11 | 12 | function test_sqg_stochasticforcedproblemconstructor(dev::Device=CPU()) 13 | 14 | function calcF!(Fqh, sol, t, clock, vars, params, grid) 15 | Fqh .= Ffh 16 | return nothing 17 | end 18 | 19 | prob = SurfaceQG.Problem(dev; calcF=calcF!, stochastic=true) 20 | 21 | return typeof(prob.vars.prevsol) == typeof(prob.sol) 22 | end 23 | 24 | """ 25 | test_sqg_advection(dt, stepper; kwargs...) 26 | 27 | Tests the advection term in the SurfaceQG module by timestepping a 28 | test problem with timestep dt and timestepper identified by the string stepper. 29 | The test problem is derived by picking a solution bf (with associated 30 | streamfunction ψf) for which the advection term J(ψf, bf) is non-zero. Next, a 31 | forcing Ff is derived according to Ff = ∂bf/∂t + J(ψf, bf) - ν∇²bf. One solution 32 | to the buoyancy equation forced by this Ff is then bf. (This solution may not 33 | be realized, at least at long times, if it is unstable.) 34 | """ 35 | function test_sqg_advection(dt, stepper, dev::Device=CPU(), H=Inf; n=128, L=2π, ν=1e-2, nν=1) 36 | n, L = 128, 2π 37 | ν, nν = 1e-2, 1 38 | tf = 0.5 # SQG piles up energy at small scales so running for t ⪆ 0.5 brings instability 39 | nt = round(Int, tf/dt) 40 | 41 | grid = TwoDGrid(dev; nx=n, Lx=L) 42 | x, y = gridpoints(grid) 43 | 44 | ψf = @. sin(2x)*cos(2y) + 2sin(x)*cos(3y) 45 | bf = @. - sqrt(8) * tanh(sqrt(8) * H) * sin(2x)*cos(2y) - sqrt(10) * tanh(sqrt(10) * H) * 2sin(x)*cos(3y) 46 | 47 | Ff = @. ( 48 | - ν*(8*sqrt(8) * tanh(sqrt(8) * H) * sin(2x)*cos(2y) + 10*sqrt(10) * tanh(sqrt(10) * H) * 2sin(x)*cos(3y) ) 49 | - 4*(sqrt(10) * tanh(sqrt(10) * H) - sqrt(8) * tanh(sqrt(8) * H)) * (cos(x)*cos(3y)*sin(2x)*sin(2y) 50 | - 3cos(2x)*cos(2y)*sin(x)*sin(3y)) 51 | ) 52 | 53 | Ffh = rfft(Ff) 54 | 55 | # Forcing 56 | function calcF!(Fh, sol, t, clock, vars, params, grid) 57 | Fh .= Ffh 58 | return nothing 59 | end 60 | 61 | prob = SurfaceQG.Problem(dev; nx=n, Lx=L, ν, nν, H, dt, stepper, calcF=calcF!, stochastic=false) 62 | 63 | SurfaceQG.set_b!(prob, bf) 64 | 65 | stepforward!(prob, nt) 66 | 67 | SurfaceQG.updatevars!(prob) 68 | 69 | return isapprox(prob.vars.b, bf, rtol=rtol_surfaceqg) 70 | end 71 | 72 | function test_sqg_energy_buoyancyvariance(dev::Device=CPU(), H=Inf) 73 | nx, Lx = 128, 2π 74 | ny, Ly = 128, 3π 75 | 76 | grid = TwoDGrid(dev; nx, Lx, ny, Ly) 77 | x, y = gridpoints(grid) 78 | 79 | k₀, l₀ = 2π/grid.Lx, 2π/grid.Ly # fundamental wavenumbers 80 | 81 | K₁, K₂ = sqrt((2k₀)^2 + (2l₀)^2), sqrt((k₀)^2 + (3l₀)^2) 82 | 83 | ψ₀ = @. sin(2k₀*x) * cos(2l₀*y) + 2sin(k₀*x) * cos(3l₀*y) 84 | b₀ = @. - K₁ * tanh(K₁ * H) * sin(2k₀*x) * cos(2l₀*y) - K₂ * tanh(K₂ * H) * 2sin(k₀*x) * cos(3l₀*y) 85 | 86 | kinetic_energy_calc = (K₁^2 + 4 * K₂^2 ) / 8 87 | buoyancy_variance_calc = (K₁^2 * tanh(K₁ * H)^2 + 4 * K₂^2 * tanh(K₂ * H)^2) / 4 88 | total_3D_energy_calc = (K₁ * tanh(K₁ * H) + 4 * K₂ * tanh(K₂ * H) ) / 8 89 | 90 | prob = SurfaceQG.Problem(dev; nx, Lx, ny, Ly, H, stepper="ForwardEuler") 91 | 92 | sol, cl, vr, pr, gr = prob.sol, prob.clock, prob.vars, prob.params, prob.grid; 93 | 94 | SurfaceQG.set_b!(prob, b₀) 95 | SurfaceQG.updatevars!(prob) 96 | 97 | kinetic_energy_b₀ = SurfaceQG.kinetic_energy(prob) 98 | buoyancy_variance_b₀ = SurfaceQG.buoyancy_variance(prob) 99 | total_3D_energy_b₀ = SurfaceQG.total_3D_energy(prob) 100 | 101 | params = SurfaceQG.Params(H, pr.ν, pr.nν, gr) 102 | 103 | return (isapprox(kinetic_energy_b₀, kinetic_energy_calc, rtol=rtol_surfaceqg) && 104 | isapprox(buoyancy_variance_b₀, buoyancy_variance_calc, rtol=rtol_surfaceqg) && 105 | isapprox(total_3D_energy_b₀, total_3D_energy_calc, rtol=rtol_surfaceqg)) 106 | end 107 | 108 | function test_sqg_paramsconstructor(dev::Device=CPU(), H=Inf) 109 | n, L = 128, 2π 110 | ν, nν = 1e-3, 4 111 | 112 | prob = SurfaceQG.Problem(dev; nx=n, Lx=L, ν, nν, H, stepper="ForwardEuler") 113 | 114 | if isinf(H) 115 | params = SurfaceQG.Params(ν, nν, prob.grid) 116 | else 117 | params = SurfaceQG.Params(H, ν, nν, prob.grid) 118 | end 119 | 120 | return (prob.params.H == params.H && 121 | prob.params.ν == params.ν && 122 | prob.params.nν == params.nν && 123 | prob.params.calcF! == params.calcF! && 124 | prob.params.ψhfrombh == params.ψhfrombh) 125 | end 126 | 127 | function test_sqg_noforcing(dev::Device=CPU()) 128 | n, L = 16, 2π 129 | 130 | prob_unforced = SurfaceQG.Problem(dev; nx=n, Lx=L, stepper="ForwardEuler") 131 | 132 | SurfaceQG.addforcing!(prob_unforced.timestepper.N, prob_unforced.sol, prob_unforced.clock.t, prob_unforced.clock, prob_unforced.vars, prob_unforced.params, prob_unforced.grid) 133 | 134 | function calcF!(Fh, sol, t, clock, vars, params, grid) 135 | Fh .= 2 * device_array(dev)(ones(size(sol))) 136 | return nothing 137 | end 138 | 139 | prob_forced = SurfaceQG.Problem(dev; nx=n, Lx=L, stepper="ForwardEuler", calcF=calcF!) 140 | 141 | SurfaceQG.addforcing!(prob_forced.timestepper.N, prob_forced.sol, prob_forced.clock.t, prob_forced.clock, prob_forced.vars, prob_forced.params, prob_forced.grid) 142 | 143 | return prob_unforced.timestepper.N == Complex.(device_array(dev)(zeros(size(prob_unforced.sol)))) && 144 | prob_forced.timestepper.N == Complex.(2*device_array(dev)(ones(size(prob_unforced.sol)))) 145 | end 146 | 147 | function test_sqg_deterministicforcing_buoyancy_variance_budget(dev::Device=CPU(); n=256, dt=0.01, L=2π, ν=1e-7, nν=2, tf=10.0) 148 | n, L = 256, 2π 149 | ν, nν = 1e-7, 2 150 | dt, tf = 0.005, 10 151 | nt = round(Int, tf/dt) 152 | 153 | grid = TwoDGrid(dev; nx=n, Lx=L) 154 | x, y = gridpoints(grid) 155 | 156 | # buoyancy forcing = 0.01cos(4x)cos(5y)cos(2t) 157 | f = @. 0.01cos(4x) * cos(5y) 158 | fh = rfft(f) 159 | function calcF!(Fh, sol, t, clock, vars, params, grid) 160 | @. Fh = fh * cos(2t) 161 | return nothing 162 | end 163 | 164 | prob = SurfaceQG.Problem(dev; nx=n, Lx=L, ν, nν, dt, stepper="RK4", 165 | calcF=calcF!, stochastic=false) 166 | 167 | SurfaceQG.set_b!(prob, 0*x) 168 | 169 | B = Diagnostic(SurfaceQG.buoyancy_variance, prob, nsteps=nt) 170 | D = Diagnostic(SurfaceQG.buoyancy_dissipation, prob, nsteps=nt) 171 | W = Diagnostic(SurfaceQG.buoyancy_work, prob, nsteps=nt) 172 | diags = [B, D, W] 173 | 174 | stepforward!(prob, diags, nt) 175 | 176 | SurfaceQG.updatevars!(prob) 177 | 178 | dBdt_numerical = (B[3:B.i] - B[1:B.i-2]) / (2 * prob.clock.dt) 179 | dBdt_computed = W[2:B.i-1] - D[2:B.i-1] 180 | 181 | return isapprox(dBdt_numerical, dBdt_computed, rtol = 1e-4) 182 | end 183 | 184 | function test_sqg_stochasticforcing_buoyancy_variance_budget(dev::Device=CPU(); n=256, L=2π, dt=0.005, ν=1e-7, nν=2, tf=2.0) 185 | nt = round(Int, tf/dt) 186 | 187 | # Forcing parameters 188 | kf, dkf = 12.0, 2.0 189 | εᵇ = 0.01 190 | 191 | grid = TwoDGrid(dev; nx=n, Lx=L) 192 | x, y = gridpoints(grid) 193 | 194 | Kr = device_array(dev)([CUDA.@allowscalar grid.kr[i] for i=1:grid.nkr, j=1:grid.nl]) 195 | 196 | forcing_spectrum = device_array(dev)(zero(grid.Krsq)) 197 | @. forcing_spectrum = exp(-(sqrt(grid.Krsq) - kf)^2 / (2 * dkf^2)) 198 | @. forcing_spectrum = ifelse(grid.Krsq < 2^2, 0, forcing_spectrum) 199 | @. forcing_spectrum = ifelse(grid.Krsq > 20^2, 0, forcing_spectrum) 200 | @. forcing_spectrum = ifelse(Kr < 2π/L, 0, forcing_spectrum) 201 | εᵇ0 = parsevalsum(forcing_spectrum, grid) / (grid.Lx * grid.Ly) 202 | forcing_spectrum .= εᵇ / εᵇ0 * forcing_spectrum 203 | 204 | Random.seed!(1234) 205 | 206 | function calcF!(Fh, sol, t, clock, vars, params, grid) 207 | eta = device_array(dev)(exp.(2π * im * rand(Float64, size(sol))) / sqrt(clock.dt)) 208 | CUDA.@allowscalar eta[1, 1] = 0.0 209 | @. Fh = eta * sqrt(forcing_spectrum) 210 | return nothing 211 | end 212 | 213 | prob = SurfaceQG.Problem(dev; nx=n, Lx=L, ν, nν, dt, stepper="RK4", 214 | calcF=calcF!, stochastic=true) 215 | 216 | SurfaceQG.set_b!(prob, 0*x) 217 | 218 | B = Diagnostic(SurfaceQG.buoyancy_variance, prob, nsteps=nt) 219 | D = Diagnostic(SurfaceQG.buoyancy_dissipation, prob, nsteps=nt) 220 | W = Diagnostic(SurfaceQG.buoyancy_work, prob, nsteps=nt) 221 | diags = [B, D, W] 222 | 223 | stepforward!(prob, diags, nt) 224 | 225 | SurfaceQG.updatevars!(prob) 226 | 227 | dBdt_numerical = (B[2:B.i] - B[1:B.i-1]) / prob.clock.dt 228 | 229 | dBdt_computed = W[2:B.i] - D[1:B.i-1] 230 | 231 | return isapprox(dBdt_numerical, dBdt_computed, rtol = 1e-4) 232 | end 233 | -------------------------------------------------------------------------------- /test/test_utils.jl: -------------------------------------------------------------------------------- 1 | """ 2 | Test the peakedisotropicspectrum function. 3 | """ 4 | function testpeakedisotropicspectrum(dev::Device=CPU()) 5 | n, L = 128, 2π 6 | grid = TwoDGrid(dev; nx=n, Lx=L) 7 | k0, E0 = 6, 0.5 8 | qi = peakedisotropicspectrum(grid, k0, E0; allones=true) 9 | ρ, qhρ = FourierFlows.radialspectrum(rfft(qi) .* grid.invKrsq, grid) 10 | 11 | ρtest = ρ[ (ρ.>15.0) .& (ρ.<=17.5)] 12 | qhρtest = qhρ[ (ρ.>15.0) .& (ρ.<=17.5)] 13 | 14 | return CUDA.@allowscalar isapprox(abs.(qhρtest)/abs(qhρtest[1]), (ρtest/ρtest[1]).^(-2), rtol=5e-3) 15 | end 16 | 17 | function testpeakedisotropicspectrum_rectangledomain(dev::Device=CPU()) 18 | nx, ny = 32, 34 19 | Lx, Ly = 2π, 3π 20 | grid = TwoDGrid(dev; nx, Lx, ny, Ly) 21 | k0, E0 = 6, 0.5 22 | qi = peakedisotropicspectrum(grid, k0, E0; allones=true) 23 | end 24 | --------------------------------------------------------------------------------