Proposed Style Guide WIP #292
rafmudaf
started this conversation in
v3 Design Discussion
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
Style Guide for FLORIS v3
Here are some ideas for establishing our own internal style guide.
Numpy usage
Numpy is quite flexible and this leave many ways to do the same thing with comparable performance.
How to operate on arrays
Do we use np.array methods on the arrays or from the numpy library:
versus
np.func(array)
array.func()
Support Python List?
I vote no. I'd rather only support np.array for input arguments instead of both np.array and List. This is for type hinting in the function signature, and should only apply to the simulation package.
np.array
support only unless of a specific use case, or potentially externally facing functionality. It is typically faster for much of the work that will be done with FLORIS and ensures consistent vectorization across the software.Standard method for adding axes to an array for broadcasting
We've seen many ways to handle resizing and reshaping arrays prior to an array arithmetic operation that will involve broadcasting. Here, we should establish our standard method for accomplishing this task since it done in many places.
Possible methods:
np.resize(array, (new_dimension_size, *array.shape)
: adds new dimensions of any sizenp.stack([array] * new_dimension_size, axis=0)
: adds new dimensions of any size, but needs to be done more carefullynp.expand_dims(array, axis=0)
: adds a sized-1 new dimension and easily controlled viaaxis=
array[np.newaxis, :]
: same as above, but more concisearray[None, :]
: same as above sincenp.newaxis
is same as None.Operation chaining
It can be simpler to chain operations so that when they are expanded the full line results in one operation. For example, the
fCt
functions in FLORIS are handles to callable functions, so they can be accessed and used like this:The last line above gets the callable from the fCt numpy array and also calls the function. Due to the chained operations, this is not very clear to read. For anyone not familiar with this area of the code, it might be unexpected to have an array of functions in the first place, so this line is even more opaque. A more clear approach is to first pull out the function and name it appropriately. Then, call the function directly.
Array masking
We often write the construction of a boolean mask like this:
However, this causes the creation of an extra array due to the unnecessary type cast. The result of a comparison operation involving a np.Array is an array with type np.Array, anyway. This should instead be written like this including the parenthesis to denote that its an operation in itself rather than a chain of arithmetic:
Explicit vs implicit array sizing
For the sake of being explicit about our data, the FLORIS code should resize arrays with the explicit dimension size where possible. For example, it is valid to do this:
and it would result in an array of size (1,4,1,1). However, in this case we know the size of the second dimension so it would be equivalent in operation and better in readability to do this:
or even better
Scalar values in right-most dimensions
We often have a scalar value in the right-most dimensions of an array. For example, the wake models are a function of the current turbine's rotor diameter, so when we are finding the wake profile on the TurbineGrid we have something like
This could also be handled by expanding the scalar values to 5x5 grids in
rotor_diameter
andCt
. The first method is more specially efficient (memory-wise), but the second method is less complex since we could say blanket-statement all arrays have the same shape.Import statements
Absolute imports are recommended over relative imports as described in pep8: https://www.python.org/dev/peps/pep-0008/#imports
Module directories should contain a file
__init__.py
that imports all modules with the directory. For example, the file at/Users/rmudafor/Development/floris/src/simulation/wake_velocity/__init__.py
should contain the following line:So that the imported object can be used in other modules as
Errors, warnings, and other feedback to users
We currently either raise an exception or use the
logging_manager
to display information to the user via an error file or dump to the console. Exceptions should be raised when the code enters a state that is invalid. For example, a NaN value in a calculation may be a valid reason to raise an exception. On the other hand, a message should be sent through the logging manager in the following situations:Syntax
These should be configured in a Python formatter where possible.
Multiline statements with arguments
Function definitions and function calls (including creating container classes) may use a multiline syntax when appropriate. For instances with a single or two arguments, it may be more readable to use a one-line statement. However, multiline statements provide a few benefits:
Arrays and other containers
There should be no space at the beginning and end of the container:
Lines exceeding max line length
When an equation exceeds the line length configured in the linter, it is preferred to wrap the equation in parenthesis and express it vertically term by term. For example, the equation below is too long on one line, but it can easily be split at the mathematical operators so that it can be read and comprehended vertically. Notice how
cosd(yaw_angle) ** pW
are kept on a single line retaining the context that the cos(yaw_angle) is cubed. This is consistent with how we would communicate this mathematically or in conversation.There are two main benefits to this style:
average_velocity
and away from the rest of the terms:Similarly, f-strings that exceed the max line length can be wrapped in parentheses and split by line. No comma is needed between lines as Python will automatically concatenate each line.
Beta Was this translation helpful? Give feedback.
All reactions