Neko 0.9.99
A portable framework for high-order spectral element flow simulations
|
This section aims to summarize the programming conventions in Neko to guide all developers into writing code with good style and facilitate the reuse of the same programming patterns throughout the code base. It is a good idea to check your code against the guidelines here before opening a PR.
It should be noted that these are note followed universally throughout the code base as of now, but we should at least make all new code as clean as possible.
_
when needed. Some exceptions take place, e.g. Re
for the Reynolds number and Xh
for the function space, but these should be kept to a minimum.Xh
for the function space. Use easy-to-remember unambiguous variable names. Avoid abbreviations unless they are universally understandable (e.g. RANS).
The basic rationale is that code is read much more often than it is written, so clarity is very important. Moreover, it reduces the cognitive load of the developers since one does not have to remember the abbreviations, which often follow no logic. Longer variable names are also no longer a practical issue due to assistance from IDEs. Moreover, there is always the option to use associate
, if contracting a variable name is really necessary.
Antipattern: usr
, msh
, intrp
, tnsr
.
Pattern: user
, mesh
, interpolate
, tensor
.
This rule is
public
, particularly interfaces._t
in the name.mytype_t
, the module should reside in mytype.f90
and the name of the module should be mytype
.Prepend the implementations of type-bound procedures (TBPs) with the name of the type, sans the _t
.
This may lead to long names when both the name of the type and of the procedure by themselves have long names. But since this name is fully internal, it is not a major concern. By following the pattern, we never have to think how to choose the name.
Antipattern:
Pattern:
private
or just generally intended as internal to the type, add an underscore to the end of name. Whe you wish to add a getter for the component, it can be called the same name as the component, sans the underscore.init
for constructor TBPs.free
for destructor TBPs.pass
ed to TBPs should be called this
.Always use only
when using
something from another module. The neko
module is an exception and imports everything. Additionally, current compiler limitations make it impossible to use only
with comm
and device
.
This latter is done so that the user .f90
files need only use
the neko
module to get access to everything.
implicit none
at module level.private
at module level.Apply own taste and judgement regarding which procedures or components should be private
.
We are generally quite permissive in terms of access and rely on the developers' good judgement to not mess with the types in the wrong way. That being said, using private
is not discouraged in any way.
final
attribute for destructors, the compilers don't support it well.init_base
. Call the destructor free_base
.For types initializing from JSON, let init
be the constructor from the json_file
type and init_from_components
a constructor directly for component types. The init
constructor should parse the JSON and the call init_from_components
.
This way we always have constructors for concrete types that are independent of JSON.
init
should be the name of the constructor, which is introduced as a deferred procedure in the base abstract class. Same for free
for the destructor.@param
s describing all the dummy arguments.@details
or other Doxygen decorators. It is highly encouraged.All components of the type should be documented. Put the docstring above the component.
The alternative is to put it on the same line as the component, but it only works for short docstrings and cannot be multiline. Since a future component may require a long docstring, and mixing the styles does not look nice, it is better to default to putting the string above.
The points here are mostly things to consider rather than strict rules. Hopefully, they can guide towards better implementation.
For types doing have computations, consider creating separate subroutines for each compute backend, and putting them in separate files in the bcknd
.
This way nitty-gritty optimisation can happen in bcknd
, whereas the main code for the type is kept clean.
Try to add support for as many backends as possible. CPU-only will be merged, but should throw a clear error message when run on an accelerator.
Wrap legacy code with wrappers following correct naming conventions.
We will surely leverage the fact that Neko is written in Fortran, and lift some old Nek5000 code to quickly add some functionalities. This old code should be in an internal subroutine and called from a wrapper.
optional
dummy arguments, consider whether it makes more sense to split the code into separate procedures instead. However, optional
is not as such disapproved of.Follow the single responsibility principle for types.
For example, don't mix I\O and other functionality into one type. It is very common and completely fine to create helper types encapsulating some necessary functionality. Don't try to map types to "real-world concepts", that doesn't work.
This point represents the S in SOLID, which is a set of design principles for types. Highly recommended to get acquainted with all of them.
When encountering code repetition, consider whether these two pieces of code will likely change at different rates and different reasons in the future. If so, code repetition is fine, it will eventually cease to exist naturally.
True code duplication is when a change in one place will also necessary a change in the other.