Thank you for investing your precious time to contribute to <T>LAPACK! Please read the following guidelines before proposing a pull request.
In this section we describe the code style used in <T>LAPACK. We also describe the naming conventions used in the library. We suggest following the same style and conventions when contributing to <T>LAPACK.
<T>LAPACK uses ClangFormat (version 10) to enforce a consistent code style. The configuration file is located at .clang-format
. The code style is based on the Google C++ Style Guide and the differences are marked in the configuration file. You should never modify the .clang-format
file in <T>LAPACK, unless this is the reason behind your pull request.
To format code you are adding or modifying, we suggest using the git-clang-format script that is distributed together with ClangFormat. Use git-clang-format
to format the code that was modified or added before a commit. You can use git-clang-format --help
to access all options. Mind that one of the tests in the Continuous Integration checks that the code is properly formatted.
- Avoid any identifiers starting with underscores even if not followed by Uppercase. This is aligned to 17.4.3.1.2 C++ global names.
Note
You may use the following regular expression to find relevant identifiers that start with underscore in VS Code:
__(.*)__ # Identifiers limited by double underscore
([^\w|])_([A-Z]) # Start with underscore then uppercase letter
([^\w])_(\w)([, ;/\)\(.\[\]]) # Other identifiers that start with underscore
Template parameters and type aliases should be named using the conventions:
- Use
T
possibly followed by a short identifier (preferrably one uppercase character) to represent a scalar type likefloat
,double
,int
andstd::complex<float>
. For instance,TA
andTB
could represent the types of variablesa
andb
, or the types of the entries of matricesA
andB
. - Use
matrixA_t
andvectorA_t
to represent the types of matrices and vectors, respectively. Use a short identifier (preferrably one uppercase character) likeA
andB
between the wordsmatrix
(porvector
) and_t
. - Use
foo_bar_t
to represent the type of the variablefooBar
. This convention should be used whenever the first and second rules are not clear enough. For instance,alpha_t
andbeta_t
are used in gemm to represent the types of scalarsalpha
andbeta
in the BLAS functiongemm
, whileTA
andTB
represent the types of the entries of matricesA
andB
. - Use
foo_bar_f
to represent the type of the functorfooBar
. For instance,abs_f
is the type of the functorabs
in lassq.hpp.
Special cases:
- Use
real_t
to represent the default real type to be used inside a given scope. - Use
scalar_t
orT
to represent the default scalar type to be used inside a given scope. The identifierT
is preferrable. - Use
idx_t
to represent the default index type to be used inside a given scope.
Function arguments should be named using either:
- a single capital letter if it is a matrix, or
- lower camel case style.
For instance, fooBar
is a good name for a function argument, while foo_bar
is not. A
and matrixA
are good names for a matrix, while a
is not.
There is no strict rule for naming local variables. However, we suggest:
- Declare real-valued constants using real types. For instance, use
const real_type<T> zero(0)
instead ofconst T zero(0)
. This is because the typeT
may be a complex type, and the constantzero
is real. Avoid constants likeconst real_type<T> rzero(0)
andconst T czero(0)
in the same function, because it is confusing.
- Use upper camel case (Pascal case) style to name:
- concepts, e.g.,
tlapack::concepts::Scalar
andtlapack::concepts::LegacyArray
. - enumeration classes, e.g.,
tlapack::GetrfVariant
andtlapack::Layout
. - classes with non-static members, e.g.,
tlapack::ErrorCheck
andtlapack::StrongZero
.
- concepts, e.g.,
- Use snake case style to name:
- namespaces, e.g.,
tlapack
andtlapack::legacy
. - functions, e.g.,
tlapack::multishift_qr
andtlapack::gemm
. - aliases to fundamental types (see https://en.cppreference.com/w/cpp/language/types), e.g.,
tlapack::byte
is an alias tounsigned char
. - template aliases to fundamental types or classes with only static members, e.g.,
tlapack::enable_if_allow_optblas_t
,tlapack::type_t
andtlapack::real_type
. - namespace template variables, e.g.,
tlapack::layout
andtlapack::is_real
.
- namespaces, e.g.,
- Namespace non-template variables are named using all-caps snake case, e.g.,
tlapack::LOWER_HESSENBERG
andtlapack::LOWER_HESSENBERG
. - Preprocessor constants are named using all-caps snake case starting with
TLAPACK_
, e.g.,TLAPACK_NDEBUG
andTLAPACK_MATRIX
. - Preprocessor function macros are named using lower snake case starting with
tlapack_
, e.g.,tlapack_check
andtlapack_warning
.
Special cases:
- Traits are classes with only static members and, thus, they are named using snake case. Moreover, they have the suffix
_trait
or_traits
, e.g.,tlapack::traits::entry_type_trait
andtlapack::traits::matrix_type_traits
. - Classes for optional arguments of mathematical routines usually have non-static members and, therefore, they are named using upper camel case (Pascal case) style. Moreover, they have the suffix
Opts
, e.g.,tlapack::GeqrfOpts
andtlapack::BlockedCholeskyOpts
.
This section describes the guidelines for writing code in the include and src directories.
<T>LAPACK routines have three types of return:
-
Routines that return
void
. The void return is reserved for BLAS routines that do not return a value, e.g.,tlapack::scal()
andtlapack::gemm()
, and for some auxiliary routines liketlapack::lassq()
andtlapack::larf()
. Those routines are not supposed to fail and do not need to signal invalid outputs, so they do not return an error code. They could still throw an exception if the input is invalid. See flagTLAPACK_CHECK_INPUT
in README.md for more details. -
Routine that return a value. Those routines are supposed to return a value, e.g.,
tlapack::iamax()
andtlapack::lange()
. The return value is the result of the routine as explained in its documentation. -
Routines that return an integer. Those routines are supposed to return an error code, e.g.,
tlapack::getrf()
andtlapack::potrf()
. A zero return means that the routine was successful. A non-zero return means that the routine failed to execute and that the output parameters are not valid. The documentation of each routine should specify the significance of each error code.
A routine that fits in the categories 2 and 3, i.e., returns a value and may signal invalid outputs, should have those invalid outputs explicitly in its documentation. For instance, a return 0 in tlapack::iamax()
will be used to both (1) signal that the input vector is empty and (2) signal that there is a NAN
at the first position of the input vector.
In C++ all types are evaluated at compile time. The auto
keyword is used to enable the compiler to deduce the type of a variable automatically. For instance, the instructions
int m = 1;
auto n = m + m;
and
int m = 1;
int n = m + m;
are equivalent, and deduce the type int
for n
. We suggest avoiding the usage of auto
in the following cases:
-
When the type is known, like in lassq:
const real_t tsml = blue_min<real_t>();
In this case, the reader does not need to go to the definition of
blue_min
to know the type oftsml
. -
When the type can be deduced using
tlapack::real_type<>
,tlapack::complex_type<>
,tlapack::scalar_type<>
,tlapack::type_t<>
,tlapack::size_type<>
or any combination of them. For instance, in gemm:using TB = type_t<matrixB_t>; using scalar_t = scalar_type<alpha_t,TB>; const scalar_t alphaTimesblj = alpha*B(l,j);
This design makes it clear the type we obtain after operating with the types. Moreover, it avoids a problem with lazy evaluation. Indeed, consider the following (bad) example:
using T = mpf_class; T w = 1.0; T x = 2.0; auto y = w + x; T z = y * y;
where
mpf_class
is the GNU multiprecision floating-point type. The type ofy
is notmpf_class
, becausempf_class
resort to lazy evaluation before evaluating mathematical expressions. Thus,w + x
will be actually computed twice in the last instruction, which is not ideal. That is why we suggest to avoid the usage ofauto
in those situations.
We recommend the usage of auto
in the following cases:
-
To get the output of
a.
tlapack::legacy_matrix(...)
andtlapack::legacy_vector(...)
when writing wrappers to optimized BLAS and LAPACK.b. slicing matrices and vectors using
tlapack::slice(...)
,tlapack::rows(...)
,tlapack::col(...)
, etc. See concepts for more details.c. the functor
tlapack::Create< >(...)(...)
. See arrayTraits.hpp for more details. -
In the return type of functions like
tlapack::asum
,tlapack::dot
,tlapack::nrm2
andtlapack::lange
. By defining the output asauto
, we enable overloading of those functions using mixed precision. For instance, one may write a overloading oftlapack::lange
for matricesEigen::MatrixXf
that returnsdouble
.
assert()
: Used on checks related to the logic of an algorithm. The assertion is supposed to check that the logic is correct, and so they are active only on debug mode. The functionassert()
does nothing if theNDEBUG
flag is defined. Some examples of usage in <T>LAPACK are:- check the range of (i,j) before access A(i,j) in the legacy classes for matrices and vectors.
- check if
x
is zero when calling the constructorStrongZero(x)
.
tlapack_check()
: Used on checks related to the validity of an input of a function. It is enabled if TLAPACK_CHECK_INPUT is defined and TLAPACK_NDEBUG is not. Also used to test the input parameters when creating new legacy matrices and vectors, seeLegacyMatrix.hpp
. THe reason to usetlapack_check()
instead of assert for matrix and vector creation is to be in the same page as LAPACK. LAPACK routines check the dimensionsm
,n
andldim
the same way it checks other input parameters.tlapack_check_false(cond)
is the same astlapack_check(!cond)
.
Use the constexpr
specifier for:
- Utility functions like
safe_max()
,abs1()
andWorkInfo::minMax()
. - Non-recursive workspace queries.
- Constructors and destructors with simple implementation.
Use the inline
specifier on non-template functions implemented on header files. Mind the compilers make the final decision about inlining or not a function, so use it carefully when the objective is performance gain. See https://en.cppreference.com/w/cpp/language/inline. The use of the inline specifier may change the priority for inlining a function by the compiler. It is worth noticing that forcing the inline may lead to large executables, which is specially bad when the library is already based on templates.
-
In internal calls, use compile-time flags instead of runtime flags. For instance, use
tlapack::LEFT_SIDE
instead oftlapack::Side::Left
andtlapack::NO_TRANS
instead oftlapack::Op::NoTrans
. This practice usually leads to faster code. -
Avoid writing code that depends explicitly on
std::complex<T>
by usingtlapack::real_type<T>
,tlapack::complex_type<T>
andtlapack::scalar_type<T>
. Any scalar typeT
supported by <T>LAPACK should implement those 3 classes.
Note
std::complex
is one way to define complex numbers. It has undesired behavior such as:
std::abs(std::complex<T>)
may not propagate NaNs. See tlapack#134 (comment).- Operations with
std::complex<T>
, forT=float,double,long double
are wrappers to operations in C. Other types have their implementation in C++. Because of that, the logic of complex multiplication, division and other operations may change from type to type. See advanpix/mpreal#11.
Examples show how to use <T>LAPACK. They are located in the examples directory. We use CMake and GNU Make to build the examples. The examples are built by default when you build <T>LAPACK. You can disable the examples by setting the CMake variable BUILD_EXAMPLES
to OFF
.
Before adding an example, please take a look at the current examples in examples. You may find an example that is similar to the one you want to add. If that is the case, you can copy the example and modify it.
To add a new example, you need to:
-
Create a new directory in examples with the name of the example,
examples/<new_example>
. -
Create a file
examples/<new_example>/CMakeLists.txt
that contains a new project. Each example must be prepared to be compiled together with <T>LAPACK or separately. Because of that, we suggest this file contains the following:# Load <T>LAPACK if( NOT TARGET tlapack ) find_package( tlapack REQUIRED ) endif()
-
Add a
README.md
file to the directory of the example. This file should contain a description of the example and instructions on how to compile and run it. -
In
examples/CMakeLists.txt
, add the following:add_subdirectory( my_fl_type )
This will make sure that the example is compiled when you build <T>LAPACK with the option
BUILD_EXAMPLES
set toON
. -
In
examples/README.md
, add a link to theREADME.md
file of the example. -
Additionally, it is recommended that you create a
Makefile
in the directory of the example. This way, your example could be built by a broader audience, including people that are used to use GNU Make and are not familiarized with CMake.
<T>LAPACK uses a couple of different test suites for unit tests, all located in the test directory. See github.com/tlapack/tlapack/wiki/Test-Suite for more details on what each test set cover. Here we focus on how to write tests in test/src for <T>LAPACK.
We use Catch2 as the testing framework. This means that you need to use a few macros like TEST_CASE
, REQUIRE
, CHECK
, etc. It also means that you may use other macros provided by Catch2. For instance, you may use REQUIRE_THROWS_AS
to check that a routine throws an exception of a certain type. See github.com/catchorg/Catch2/docs.
Consider following the steps below before writing a test for a new routine:
-
Take a look at the current tests in test/src to see if there is already a test for the routine you want to add. If there is, you can add your test to the existing file. If there is not, you can create a new test file.
-
If you need to create a new test file, you can copy one of the existing tests. The naming convention is
test_<routine_name>.cpp
. For instance, the tests fortlapack::getrf
are located in test/src/test_getrf.cpp. -
Add an entry
add_executable( test_<routine_name> test_<routine_name>.cpp )
to test/src/CMakeLists.txt. This will make sure that the test is compiled and run when CTest is called. -
Configure your CMake build to build the target
test_<routine_name>
so that you only need to compile and run the test you are writing.
-
Only include the headers you need. This also means:
- You should never include
tlapack.hpp
in a test. Instead, include the headers for the routines you are testing. Compilation times may increase significantly if you includetlapack.hpp
in a test. - If needed, include
testutils.hpp
before including other headers from <T>LAPACK. This will make sure that the macros are defined before they are used. Also, it makes sure that the plugins for matrix types are loaded before other headers are included.
- You should never include
-
You should use
using namespace tlapack;
in a test. This will make the code more readable. -
If using the macro
TEMPLATE_TEST_CASE()
from Catch2, please set the last argument to eitherTLAPACK_TYPES_TO_TEST
,TLAPACK_REAL_TYPES_TO_TEST
orTLAPACK_COMPLEX_TYPES_TO_TEST
. This will make sure that the test is run for all the types. -
Try using tags following the directions in Tags for tests.
-
For creating objects inside the tests:
-
If you need to create a matrix, use
tlapack::Create<TestType>(...)
. See arrayTraits.hpp for more details. -
If you need to create a vector there are two options. The first option is to use
tlapack::Create< vector_type<TestType> >(...)
. See arrayTraits.hpp for more details. The second option is to usestd::vector< type_t<TestType> >
orstd::vector< real_type<type_t<TestType>> >
.
-
-
Use the macro
GENERATE()
to create a range of values. For instance, you may useGENERATE(1,2,3)
to create a range of values{1,2,3}
. This way you can avoid writing a loop to test a routine for different values of a parameter. -
Whenever possible, create a
DYNAMIC_SECTION(...)
after all commandsGENERATE()
. Example:const idx_t m = GENERATE(1,5,9); const idx_t n = GENERATE(1,5,9); const Uplo = GENERATE(Uplo::Lower, Uplo::Upper); DYNAMIC_SECTION("m = " << m << " n = " << n << " Uplo = " << Uplo) { // test code }
-
Use the macros
INFO()
andUNSCOPED_INFO()
to print other information about the test. -
Use the macros
REQUIRE()
,CHECK()
,REQUIRE_THROWS_AS()
andCHECK_THROWS_AS()
to check the results of the test.CHECK()
is preferred overREQUIRE()
because it allows the test to continue even if one of the assertions is false. UseREQUIRE()
only when it does not make sense to continue the test if the assertion is false. -
Do not allocate workspaces inside the test. Instead, let each routine allocate the workspace it needs. This has at least two benefits: (1) tests are simpler; (2) routines will run at minimum workspace, which means we will be testing that the minimum workspace size is well defined.
<T>LAPACK uses the following TAGs for tests:
[bidiagonal][svd][concept][plugins][lqf][getrf][getri][auxiliary][hessenberg][eigenvalues][generalized eigenvalues][hessenbergtriangular][qr][qrf][aux][larfg][larf][sylvester][lauum][lu check][lu][qrt][norm][optBLAS][potrf][doubleshift_qr][multishift_qr][generalizedeigenvalues][rscl][qr-svd][util][trtri][ul_mul][unm2l][unm2r][unml2][unmlq][unmql][unmqr][unmr2][unmrq][utils]
<T>LAPACK uses Doxygen to generate documentation. The documentation is generated automatically by Github Actions and is available at tlapack.github.io/tlapack. The documentation can be also generated locally by running cmake --target doxygen
in the build directory and, in this case, you can access the documentation at <build-dir>/doc/html/index.html
. For directions on how to write documentation using Doxygen, see www.doxygen.nl/manual/index.html.
For documentation in C/C++:
- Use
///
for documentation of files and brief documentation of functions, classes, etc. - Use
///<
for inline documentation after members of classes, structs, etc. - Use
/** ... */
for detailed documentation of functions, classes, etc. - Use
@param
,@return
,@note
, etc., for special comments, instead of\param
,\return
,\note
, etc.
Each file should have the same Copyright information, and this should not be used with Doxygen comments. The text in C/C++ files should be:
// Copyright (c) 2021-2023, University of Colorado Denver. All rights reserved.
//
// This file is part of <T>LAPACK.
// <T>LAPACK is free software: you can redistribute it and/or modify it under
// the terms of the BSD 3-Clause license. See the accompanying LICENSE file.
Every CMake and Python file should contain the same text at the top, with #
instead of //
.
Every Fortran file should contain the same text at the top, with !
instead of //
.
In addition, the header of all C, C++ and Fortran files should contain the following information as Doxygen comments:
- The name of the file using
@file
. - The author(s) of the file and contact information using
@author
. - Any other information that is relevant to the file.
The documentation of functions should contain the following information:
- A brief description of the function using
@brief
. The@brief
tag is optional if this description is the first line of the documentation. - A detailed description of the function.
- A list of parameters using
@param
. Each parameter should be documented using@param[in]
,@param[out]
or@param[in,out]
. Workspaces should be documented using@param
. - A description of the return value using
@return
. In case the function returnsvoid
or if the return value is an error code that should always be zero, the@return
tag is optional. - The group the function belongs to using
@ingroup
. The group should be one of the groups defined in groups.dox.
The doxygen documentation is prepared to translate LaTeX formulas. For instance, you can write $\alpha$
to get the Greek letter alpha or
/**
* \[
* C := \alpha op(A) \times op(B) + \beta C,
* \]
*/
to get the formula for the matrix-matrix multiplication. We strongly suggest using LaTeX formulas in the documentation of <T>LAPACK.
The documentation of classes and structs follows the same rules as the documentation of functions with respect to the brief description and detailed description. In addition,
- The documentation should contain a list of template parameters using
@tparam
. - Each member of the class or struct should be documented. Functions should follow the rules for the documentation of functions, although, in most cases, there will be no need to associate a group to the function.
Concepts are documented as Doxygen interfaces, i.e., using the tag @interface
. They should contain a brief and, possibly, a detailed description. They should also contain a list of template parameters using @tparam
. All concepts should belong to the Dpoxygen group concepts
.
We currently do not document namespaces.
Function-like macros should be documented as functions. Object-like macros should be documented with a brief description and a detailed description when it makes sense. Macros should belong to a Doxygen group so that they can be easily found in the documentation. See all groups defined in groups.dox.
You can use @todo
to mark incomplete code. For instance, some code may work for all precisions but not for complex numbers. Then, you could warn in the code: Implement the complex version of this code
. It is good to do it in a proper manner, so that it is easy to find all the TO-DO's in the code. We suggest using the following:
/// @todo: Implement the complex version of this code
(Some code goes here)
Using the triple slash ///
with tag @todo
will make sure that the reminder is visible in the Doxygen documentation.
Updating tests/blaspp and tests/lapackpp
There are two situations in which you may need to update tests/blaspp and tests/lapackpp:
- When you want to enable a test.
- When you want to use a new version of those libraries for tests.
You can run the Github Actions locally using act. This is useful for debugging the Github Actions workflow.