GithubHelp home page GithubHelp logo

fourierflows / fourierflows.jl Goto Github PK

View Code? Open in Web Editor NEW
200.0 200.0 29.0 5.14 MB

Tools for building fast, hackable, pseudospectral partial differential equation solvers on periodic domains

Home Page: https://bit.ly/FourierFlows

License: MIT License

Julia 100.00%
cfd fluid-dy fourier julia partial-differential-equations periodic-domains pseudospectral-methods spectral-methods

fourierflows.jl's People

Contributors

apaloczy avatar cnrrobertson avatar doraemonho avatar github-actions[bot] avatar glwagner avatar jbisits avatar juliatagbot avatar maleadt avatar mortenpi avatar navidcy avatar parfenyev avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

fourierflows.jl's Issues

Sandbox

Let's remove the sandbox. I don't think anybody out there benefits from that. To the least, I think, it only adds confusion.

Diagnostics only work with functions that output scalars

In its current form, the ProblemDiagnostic only supports function that output scalars.
What if we want to save, e.g., the kx=0 component of the solution every 10 time-steps as a diagnostic?

We need to generalize:

mutable struct Diagnostic{T} <: AbstractDiagnostic
calc::Function
prob::Problem
num::Int
data::Array{T,1}
time::Array{Float64,1}
step::Array{Int,1}
value::T
count::Int
freq::Int
end

and its constructor so that they can accept functions that output arrays.

Timestepper overhaul

We need to restart / finish the time-stepper overhaul. These are the issues that should be addressed:

  1. The function get_etd_coeffs is not as general or well-written as it could be.

  2. I believe that all "helper" functions (like get_etd_coeffs) should be moved to the top of the file. The time-stepper definitions can then go below. This may improve code readability.

  3. The time-stepper definitions / constructors are too restrictive. Many of them explicitly require 2- or 3-dimensional solution arrays, which is unnecessary. We should use type parameterization to generalize these definitions.

  4. The syntax used in the step-forward functions is inconsistent (regarding the use of @., mostly), and sometimes not as efficient as it could be. For example, I believe that the algorithm

a .= b .+ c
a .*= d

is less efficient than

a .= d .* (b .+ c)

The reason is because in the first example, the compiler does not know that it can pass the values of d, b, c to the processor simultaneously. I think in the second example, the compiler may be able to produce faster code that sends the values of all 3 array to the processors for an operation, thus consolidating and perhaps reducing memory-transfer operations. I'm not 100% sure I'm right about this, but we can test it.

  1. In order to make these changes, we need to construct a convenient test (like a forward run of the McWilliams problem that extracts values of the solution at particular points). It would be even better if the test could compare some 'original' version of FourierFlows with an updated version...

ForwardEulerTimeStepper aliases eq.LC with ts.N

The way ForwardEulerTimeStepper is constructed it aliases ts.N with eq.LC. This implies that eq.LC changes every time the time stepper calls calcN!.

The reason for that is that the outer constructor written after the ForwardEulerTimeStepper definition is never being used because its arguments are of exactly the same type as the ForwardEulerTimeStepper properties. That is, lines 138-141 below are never used:

struct ForwardEulerTimeStepper{dim} <: AbstractTimeStepper
dt::Float64
N::Array{Complex{Float64},dim} # Explicit linear and nonlinear terms
end
function ForwardEulerTimeStepper(dt::Float64, sol::AbstractArray)
N = zeros(sol)
ForwardEulerTimeStepper{ndims(LC)}(dt, N)
end

To fix that we need to implement a so-called inner constructor.

Refactor Problem, State, and Timestepper types

We'd like to refactor the internal type system of FourierFlows.

Consider the current form of Problem and State:

mutable struct Problem <: AbstractProblem
  grid::AbstractGrid
  vars::AbstractVars
  params::AbstractParams
  eqn::AbstractEquation
  ts::AbstractTimeStepper
  state::AbstractState
  t::Float64
  step::Int
end
mutable struct State{T,dim} <: AbstractState
  t::Float64
  step::Int
  dt::Float64
  sol::Array{T,dim}
end

Both are mutable. Also, both redundantly store t and step, and State mysteriously includes dt among it's fields. This is not currently used, though it might be if one of the time-steppers were adaptive.

We feel that the Grid, Vars, Params, Equation, and TimeStepper types are effective. However, State should change.

One proposal is to eliminate State and create a new type called Clock, defined thusly:

mutable struct Clock{T}
  t::T
  dt::T
  step::Int
end

Then Problem becomes

struct Problem{Tsol,d} 
  grid::AbstractGrid
  vars::AbstractVars
  params::AbstractParams
  eqn::AbstractEquation
  ts::AbstractTimeStepper
  clock::Clock
  sol::Array{Tsol,d}
end

or something of the sort. But not mutable!

This will require us to retool all of the timesteppers, the physical modules, and the docs. It's not a minor change. For example, sol must be passed explicitly to stepforward! rather than s::State, and a new variable cl::Clock must be passed as well.

It may make our lives easier down the road, however.

Julia 0.7 compatibility

At some point we should make sure the code is compatible with Julia 0.7. For the moment it does not pass the julia 0.7 tests.

BarotropicQG module

With topography present, viscosity diffuses topography. This needs to be resolved. Viscosity should act only on relative vorticity, not on topography.

Questions about ETDRK4

Hey, I am the developer of DifferentialEquations.jl and am looking to add an ETDRK4 method to our suite. Thank you for building this code since it is a great resource! I am looking at the portion:

https://github.com/FourierFlows/FourierFlows.jl/blob/master/src/timesteppers.jl#L298-L355

Could you explain a little bit about what's going on there? Is there a non-complex version or does it require a complex linear operator (LC is the linear operator?) for the contour integral (does it make sense for non-complex?)? Does that trivially extend to arbitrary dimensions as well?

And what are the dual and filtered versions?

README badge is broken

The README claims that "Julia v0.7 v0.1.0 Tests fail". This is not what it should say.

How was this badge generated and how can we fix it?

type of wavenumber arrays

Do we really need our wavenumber arrays to be defined as Complex128?
(see e.g. domain.jl, ... k::Array{Complex128, 1})

It creates confusion. Let's see if it results in any speedup otherwise we go back to Float64 and sanity.

Documentation

We need to add more detailed documentation. Specifically, we need to add documentation for:

  • traceradvdiff module
  • verticallyfourierboussinesq module
  • verticallycosineboussinesq module
    At its current state the documentation only includes the equation(s) each module solve.

We should also include some documentation explaining how the code is structures. For example, explaining the hierarchy among AbstractGrid, AbstractParams, AbstractVars, AbstractEquation, AbstractTimeStepper, and AbstractProblem types.

Also, we need to document the cuda version of the code.

Last, we need to add somewhere in the documentation the discussion on stochastic forcing implementation and proper calculation of energy budgets.

test_twodturb fails with FilteredTimeSteppers

There is a problem with test/test_twodturb.jl when it uses the Filtered version of the timesteppers. That's why the lines:

# @test teststepforward(128, 2π, 1e-2, 2; stepper="FiltrForwardEuler")

# @test teststepforward(128, 2π, 1e-2, 2; stepper="FiltrETDRK4")

are commented out. I spend an afternoon but couldn't figure it out... We need to dig into this.

Add time-stepper tests for when `sol` is `AbstractArray`

We should extend the diffusion module to add a case in which sol is an abstract array. This way we'd be able to test timesteppers.jl for AbstractArrays, e.g.,

function stepforward!(sol::AbstractArray{T}, cl, ts::ForwardEulerTimeStepper, eq, v, p, g) where T<:AbstractArray
eq.calcN!(ts.N, sol, cl.t, cl, v, p, g)
for i = 1:length(sol)
@views @. sol[i] += cl.dt*(eq.L[i]*sol[i] + ts.N[i])
end
cl.t += cl.dt
cl.step += 1
nothing
end

Perhaps something along the lines of:

L1, L2 = [kappa1], [kappa2]
L = [L1, L2]

QL mode in BarotropicQG

@glwagner:

So I coded up the QL equations in BarotropicQG. Do you think I should include them inside BarotropicQG or add a new module BarotropicQGQL?

Including them in BarotropicQG module would make the module more cumbersome.
Also should we really add a new module for QL in the code? It's not the most widely used equations. Is there a way we have a separate repo with modules we use and add the BarotropicQGQL there? That way only people who want to use QL would load it.

norm(matrix) -> opnorm(matrix) in Julia 0.7

It looks like may be using norm(matrix). In Julia 0.7, this will compute the Frobenius norm (vecnorm in Julia 0.6), due to JuliaLang/julia#27401. If you want the induced/operator norm as in Julia 0.6, use opnorm(matrix) instead, or Compat.opnorm(matrix) to work in 0.6 and 0.7 (JuliaLang/Compat.jl#577).

Note that, for testing purposes, rather than @test norm(A - B) ≤ tol, it is usually preferred to do @test A ≈ B or @test A ≈ B rtol=... (which uses isapprox).

An instance of Output() function can never be called

@glwagner ,

I think that as the code stands now this instance can never be called (because the code calls simply the Output constructor with these arguments).

function Output(prob, path, fields::Dict{Symbol,Function})
truepath = uniquepath(withoutjld2(path)*".jld2") # ensure path ends in ".jld2"
saveproblem(prob, truepath)
Output(prob, filename, fields)
end

Am I wrong? None of the tests in test_output.jl manages to call these lines...

Fix or delete?

Proposed test for twodturb (or any module really)

@oliasselin mention to me a way to test twodturb (and in principle any physics)

Say we want to write a test for some physics that we don't really know an analytical solution. Take twodturb as an example:
∂ζ/∂t + J(ψ,ζ) = ν Δζ.

The procedure goes as follows: Assume there exist an analytical solution in closed form ζ_guess(x,y,t)=... (you can take any guess that satisfies the boundary conditions). Plug then this guess back into the equation and compute the residual R(x,y,t) analytically. Then add this residual as a forcing on the r.h.s.:
∂ζ/∂t + J(ψ,ζ) = ν Δζ + R.
Now your guessed solution is actually a solution of the forced problem. If, for simplicity, you guess a steady solution ζ_guess(x,y) then time-stepping ∂ζ/∂t + J(ψ,ζ) = ν Δζ + R starting from rest should lead you to ζ(x,y,t)=ζ_guess.

Given that you have computed R analytically if indeed the forced problem converges to your guessed solution it means that all other terms in the equation were coded correctly.

dealias! for TwoDGrid is wrong!

It's amazing how anything that slipped out of the tests potentially is wrong. Even very simple things.
I'll push to test coverage increase!

Clear up utils.jl

These moments seems that they don't really belong here anymore:

# Moments and cumulants
"Compute the average of `c` on the grid `g`."
domainaverage(c, g) = g.dx*g.dy*sum(c)/(g.Lx*g.Ly)
"Compute the `n`th x-moment of `c` on the grid `g`."
xmoment(c, g, n=1) = sum(g.X.^n.*c)/sum(c)
"Compute the `n`th y-moment of `c` on the grid `g`."
ymoment(c, g, n=1) = sum(g.Y.^n.*c)/sum(c)

Possibly other things should/could be cleared out from utils.jl.

change notation in TwoDTurb module

I propose changing the name of the variables:
q --> zeta
U --> u
V --> v

This smoothes out differences between TwoDTurb and BarotropicQG modules.

use Parameters.jl

We could use Parameters.jl for constructing Vars and Params. It's claimed that Parameters.jl will be included in julia base v.1.x. Perhaps we should wait until that happens?

Let's clear up tests folder

I know that:

test_grid.jl
test_fft.jl
test_baroQG_timestep.jl
are OK. Also, test_twodturb.jl is OK but somehow broken now. Perhaps due to the new grid? Or probably due to various updates of the code that the .jl file hasn't caught up.

But what about all other tests?
(twomodeutils.jl, test_twomodeboussinesq.jl, test_niwqg.jl, test_niwqg_rochawagneryoung.jl, test_twomodeboussinesq.jl)
Are they good or drafts/tests/leftovers?
I'm sure some are good ones but I don't know which.

Filter refactor

After the ClockReplacesState #96 refactor we are now able to construct a module whose sol is an array of arrays of different types (see, for example, https://github.com/FourierFlows/GeophysicalFlows.jl/blob/96fbb1b7844da0aa35764769e487125ef4f4de5b/src/barotropicqg.jl#L167). This brings up the necessity to generalize the makefilter since at its current form it won't work with the BarotropicQG with U(t) as implemented in the branch above.

Actually, a better suggestion is to remove FilteredTimeSteppers all together and instead add the optional post-timestep application of a function poststep! to sol. This way the user can prescribe filters in the form of the solution within any module and then apply them, e.g., through

function poststep!(sol, params)
  @. sol *= params.filter
end

Development of a "Field" abstraction for solutions in 1, 2, and 3 dimensions

It'd be nice to have a (lightweight) abstraction for a "Field" in FourierFlows. The FourierFlows field could store either data in physical/grid space, or data in transform/coefficient space, or both (we should decide on semantics as well).

Type information can also be used to indicate whether the coefficient-space data is in "conjugate symmetric" form (currently we hack a fragile query into functions like parsevalsum that attempts to diagnose this fact from the size of the array with an if-statement, rather than using multiple dispatch on types).

Syntax could get a little nicer because we can define a function compute_grid_space!(field) and compute_coefficient_space!(field) functions that do fourier transforms (rather than requiring users / external packages like GeophysicalFlows to manually write FFTs).

Food for thought.

__precompile__() ?

Should we remove the __precompile__() headers from most of the .jl files?
Our initial intention was to have them so as to speed-up tests in the development.

TracerAdvDiff module not working

@glwagner: Have a look at the FixTracerAdvDiff branch.
Commit 60307b fixed some omissions from the traceradvdiff.jl. But still I can't get a simple example to work..
I tried putting a fixed flow from ψ=sinx siny and start with c(x, y, t=0) = 1. Have a look at example.jl.

Writing CuArrays as .jld2 files - is it possible?

@glwagner

within the gpu branch try running this on a machine with GPU:

using
  FourierFlows,
  FourierFlows.Diffusion,
  FFTW,
  LinearAlgebra,
  Printf,
  JLD2,
  Test

using FourierFlows: parsevalsum2

using LinearAlgebra: mul!, ldiv!, norm

@hascuda using CuArrays

dev = GPU()

prob = Problem(nx=32, Lx=2π, kappa=1e-2, dt=1e-7, stepper="ForwardEuler", dev=dev)
filename = joinpath(".", "testoutput.jld2")
if isfile(filename); rm(filename); end

ctest = devzeros(dev, Float64, (prob.grid.nx, ))
ctest[3] = π
prob.vars.c .= ctest

get_c(prob) = prob.vars.c
out = Output(prob, filename, (:c, get_c))

saveproblem(out)

saveoutput(out)

file = jldopen(filename)

return isfile(filename) && isapprox(file["snapshots"]["c"]["0"], ctest, rtol=rtol_output) && isapprox(file["grid"]["Lx"], prob.grid.Lx, rtol=rtol_output) && isapprox(file["eqn"]["L"], prob.eqn.L, rtol=rtol_output)

You'll get an error at the saveproblem(out). Is it because JLD2 cannot write CuArrays to file?

Adding a "State" Composite Type

I think we need a new Composite Type called, for example, State

Right now, the 'problem agnostic' fields t and sol are contained in the Vars type. I think we should move these to a new type, defined perhaps in FourierFlows.jl or somewhere equivalently appropriate. Something like

mutable struct State{dim} <: AbstractState
    t::Float64
    sol::Array{Complex{Float64},dim}
    step::Int
end

and

mutable struct DualState{dimc,dimr} <: AbstractState
    t::Float64
    solc::Array{Complex{Float64},dimc}
    solr::Array{Complex{Float64},dimr}
    step::Int
end

Then the call signature for calcN! would be calcN!(N, sol, t, v, p, g, s) where s is a variable of type State.

We would also add State to the Problem type.

Or maybe ProblemState and DualProblemState. Something like that. This would require extensive changes to all the modules as well as timesteppers.jl, but I bet I could complete it all in an hour or too. Thoughts?

Testing for physics

We need to figure out how to test physics. For example, we need to have a test for twodturb.jl which does something like:

  1. loads an initial condition from file
  2. runs a simulation
  3. compares with some saved output
  4. determines that the simulation is accurate within a specified tolerance

Generating the initial condition and output is not difficult, but determining what tolerance is acceptable is hard. The tolerance may have to do with the number of timesteps taken.

Having such a test would speed up testing new features substantially, since we would then be free to make changes to time steppers (or even introduce new ones!)

Moments in utils.jl

Should we remove these

"Compute the `n`th x-moment of `c` on the grid `g`."
xmoment(c, g, n=1) = sum(g.X.^n.*c)/sum(c)
"Compute the `n`th y-moment of `c` on the grid `g`."
ymoment(c, g, n=1) = sum(g.Y.^n.*c)/sum(c)

from utils.jl?

Or if we are to keep them, we should generalize them to work for grids where dxdy. (From what I understand the moment definition as it stands it assumes dx=dy.)

Diagnostics definition is awkward

I think we should consider changing how the "diagnostics" work. This change will break old code (it is possible for us to define a new Diagnostics type, but I feel we should make these dramatic changes and clean up the code sooner rather than later.) I think this is probably a good time to do it.

I am envisioning something similar to how the "Output" type is currently defined:

https://github.com/navidcy/FourierFlows.jl/blob/master/src/output.jl#L17

In Output, the fields are given in a dictionary. The dictionary keys give the name of the variable, and the item corresponding to each key is a function that calculates the field. The result is then stored in the file specified by "filename".

Right now, Diagnostics are defined individually, which can be collected into an array and passed to stepforward!.

I think though that it will be simpler to have a single object called "Diagnostics", which collects all the diagnostics into a dictionary-like object. We can then define a getindex function for the object, so that diagnostic values can be accessed with the syntax diags[:energy] (to retrieve the current value of energy) and diags.timeseries[:energy] (to retrieve a time-series of energy), for example. I think this will make the code clearer. Also, because fields are then required to be defined with names, the saving of diagnostics fields will be simplified.

Grids with T other than the default Float64 are not constructed in a consistent manner

There is a problem here:

function TwoDGrid(nx, Lx, ny=nx, Ly=Lx; x0=-Lx/2, y0=-Ly/2, nthreads=Sys.CPU_THREADS, effort=FFTW.MEASURE, T=Float64,
dealias=1/3)
dx = Lx/nx
dy = Ly/ny

If somebody prescribes

Lx=2π

as Float64 and then calls gr=TwoDGrid(nx, Lx, T=Float32), the resulting grid has

julia> typeof(gr.Lx)
Float64

while, e.g,

julia> typeof(gr.x)
Array{Float32,2}

I feel that the solution is to make sure Lx is typed correctly by adding

Lx = T(Lx)
Ly = T(Ly)

in the beginning of TwoDGrid function.

(Similarly in OneDGrid...)

Viscosity/Hyper-viscosity

I suggest using nu instead of ν for the viscosity/hyper-viscosity coefficient.

I also suggest defining the hyper-viscosity order nun so that the operators is

-(-1)^nun*\nabla^{2*nun}

This way the term actually acts as dissipation for any positive integer value for nun. For nun=1 is plain-old viscosity, while nun>=2 is hyper-viscosity.

`parsevalsum` and `parsevalsum2` probably trigger scalar operations on GPU

The utility functions parsevalsum and parsevalsum2 likely trigger scalar operations. These functions are defined here:

function parsevalsum2(uh, g::TwoDGrid)

Some diagnostics in GeophysicalFlows.TwoDTurb depend on parsevalsum. I think this probably slows them down.

It should be possible to define a slightly different algorithm for computing the sum. The main issue is that sum cannot be called on "non-contiguous" data (in memory), which I think uh[2:end, :] is, indeed (the first index is the fast index, so [2:end, :] requires "skipping" the first element of each column).

Thus we should rewrite these functions to compute sum on contiguous data only. I think this is possible by rewriting parsevalsum, for example, to:

"""
    parsevalsum(uh, g)
Returns `real(Σ uh)` on the grid `g`.
"""
function parsevalsum(uh, g::TwoDGrid)
  if size(uh, 1) == g.nkr                # uh is conjugate symmetric
    U = 2 * sum(uh)                      # sum all modes twice
    U -= @views sum(uh[1, :])            # subtract redundant k=1 mode
  else # count every mode once
    U = sum(uh)
  end

  norm = g.Lx*g.Ly/(g.nx^2*g.ny^2) # weird normalization for dft
  return norm*real(U)
end

As an aside, we should adopt the practice of always writing explicit return statements within functions. It makes them easier to interpret.

Rectangular grid

I defined the rectangular grid in a very stupid repetitive way. Can you have a look at domain.jl and make it smarter?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.