Skip to content

Commit

Permalink
Provide document of numeric types and type stability (#1938)
Browse files Browse the repository at this point in the history
* start with docs/src/conventions

* check doc style

* initial docs version

* check and add more

* add more check

* apply suggestions from review

* apply suggestions from code review

Co-authored-by: Michael Schlottke-Lakemper <michael@sloede.com>

* revise again

* apply suggestions from code review

Co-authored-by: Michael Schlottke-Lakemper <michael@sloede.com>

---------

Co-authored-by: Michael Schlottke-Lakemper <michael@sloede.com>
  • Loading branch information
huiyuxie and sloede committed May 15, 2024
1 parent fadfb3a commit 3d0cc8d
Showing 1 changed file with 67 additions and 4 deletions.
71 changes: 67 additions & 4 deletions docs/src/conventions.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ following naming conventions:
use these indices.


# Keywords in elixirs
## Keywords in elixirs

Trixi.jl is distributed with several examples in the form of elixirs, small
Julia scripts containing everything to set up and run a simulation. Working
Expand All @@ -61,9 +61,9 @@ can only perform simple replacements. Some standard variables names are
Moreover, [`convergence_test`](@ref) requires that the spatial resolution is
set via the keywords
- `initial_refinement_level`
(an integer, e.g. for the [`TreeMesh`](@ref) and the [`P4estMesh`](@ref)) or
(an integer, e.g., for the [`TreeMesh`](@ref) and the [`P4estMesh`](@ref)) or
- `cells_per_dimension`
(a tuple of integers, one per spatial dimension, e.g. for the [`StructuredMesh`](@ref)
(a tuple of integers, one per spatial dimension, e.g., for the [`StructuredMesh`](@ref)
and the [`DGMultiMesh`](@ref)).


Expand Down Expand Up @@ -101,8 +101,71 @@ based on the following rules.
(or `wrap_array_native(u_ode, semi)`) for further processing.
- When some solution is passed together with the `mesh, equations, solver, cache, ...`,
it is already wrapped via `wrap_array` (or `wrap_array_native`).
- Exceptions of this rule are possible, e.g. for AMR, but must be documented in
- Exceptions of this rule are possible, e.g., for AMR, but must be documented in
the code.
- `wrap_array` should be used as default option. `wrap_array_native` should only
be used when necessary, e.g., to avoid additional overhead when interfacing
with external C libraries such as HDF5, MPI, or visualization.

## Numeric types and type stability

In Trixi.jl, we use generic programming to support custom data types to store the numerical simulation data, including standard floating point types and automatic differentiation types.
Specifically, `Float32` and `Float64` types are fully supported, including the ability to run Trixi.jl on hardware that only supports `Float32` types.
We ensure the type stability of these numeric types throughout the development process.
Below are some guidelines to apply in various scenarios.

### Exact floating-point numbers

Some real numbers can be represented exactly as both `Float64` and `Float32` values (e.g., `0.25`, `0.5`, `1/2`). We prefer to use `Float32` type for these numbers to achieve a concise way of possible type promotion. For example,
```julia
# Assume we have `0.25`, `0.5`, `1/2` in function
0.25f0, 0.5f0, 0.5f0 # corresponding numbers
```
Generally, this equivalence is true for integer multiples of powers of two. That is, numbers that can be written as ``m 2^n``, where ``m, n \in \mathbb{Z}``, and where ``m`` and ``n`` are such that the result is representable as a [single precision floating point](https://en.wikipedia.org/wiki/Single-precision_floating-point_format) value. If a decimal value `v` is exactly representable in `Float32`, the expression
```julia
Float32(v) == v
```
will evaluate to `true`.

### Non-exact floating-point numbers

For real numbers that cannot be exactly represented in machine precision (e.g., `0.1`, `1/3`, `pi`), use the `convert` function to make them consistent with the type of the function input. For example,
```julia
# Assume we are handling `pi` in function
function foo(..., input, ...)
RealT = eltype(input) # see **notes** below
# ...
c1 = convert(RealT, pi) * c2 # sample operation
# ...
end
```

### Integer numbers

Integers need special consideration. Using functions like `convert` (as mentioned above), `zero`, and `one` to change integers to a specific type or keeping them in integer format is feasible in most cases. We aim to balance code readability and consistency while maintaining type stability. Here are some examples to guide our developers.
```julia
# The first example - `SVector`, keep code consistency within `SVector`
SVector(0, 0, 0)
SVector(zero(RealT), zero(RealT), zero(RealT))
Svector(one(RealT), one(RealT), one(RealT))

# The second example - inner functions, keep them type-stable as well
function foo(..., input, ...)
RealT = eltype(input) # see **notes** below
# ...
c1 = c2 > 0.5f0 ? one(RealT) : convert(RealT, 0.1) # make type-stable
# ...
end

# The third example - some operations (e.g., `/`, `sqrt`, `inv`), convert them definitely
c1 = convert(RealT, 4) # suppose we get RealT before
c2 = 1 / c1
c3 = sqrt(c1)
c4 = inv(c1)
```
In general, in the case of integer numbers, our developers should apply a case-by-case strategy to maintain type stability.

### Notes
1. If the function gets a local pointwise vector of the solution variables `u` such as `flux(u, equations)`, use `u` to determine the real type `eltype(u)`.
2. If `u` is not passed as an argument but a vector of coordinates `x` such as `initial_condition(x, t, equations)`, use `eltype(x)` instead.
3. Choose an appropriate argument to determine the real type otherwise.

0 comments on commit 3d0cc8d

Please sign in to comment.