Coding Style#
When contributing to the Einsums code base, we want to make sure our code fits to certain guidelines. The main purpose of these guidelines is to get contributors to think more deeply about their code, and to provide a single style to make our code more consistent to read. All files should begin with the following statement.
----------------------------------------------------------------------------------------------
Copyright (c) The Einsums Developers. All rights reserved.
Licensed under the MIT License. See LICENSE.txt in the project root for license information.
----------------------------------------------------------------------------------------------
C++ Style#
To make code formatting easier, we have provided two files, .clang-tidy and .clang-format.
These files can be used with the clang-tidy package to format C++ code, as well as to hint at
possible errors in your code. One thing that may come up when using these is a strange warning about
certain C++20 features. If you are getting this error, you may need to add a new file called .clangd
which contains the following.
CompileFlags:
#C++20
Add: [-std=c++20]
Coding Conventions#
Names#
Names should follow these conventions.
Types, including classes, structs, enums, and typedefs should be in
PascalCase. This means that every word begins with a capital letter, and words are concatenated without any extra symbols. Some examples.TensorGPUViewDeviceTensorViewTensor::ValueType
Concepts, requirements, and compile-time boolean statements underlying those concepts and requirements should also be in
PascalCase. Also, requirements and boolean statements should begin withIsand end inV, standing for “value”. Some examples.TensorConceptis a concept.IsTensorVis a requirement or compile-time boolean statement.
Namespaces should be in
snake_case, where every word is lower case, and each word is separated by underscores. Some examples.einsums::tensor_algebraeinsums::tensor_props
Functions and methods should be in
snake_caseas well. Some examples.einsum(...)A.full_view_of_underlying()
Private and protected properties in a class should begin in a single underscore. However, some older code may have them end in a single underscore instead. As we work on the code, we will bring these outliers into agreement with this style. Some examples.
this->dims_: Discouraged for consistency.this->_dims: Preferred.
Variables and parameters should usually be in
snake_case. However, to be consistent with mathematical notation, tensor variables and parameters are usually capitalized. This includes when the name of a tensor is referenced in a variable name. Some examples.std::string const &name:namedoes not refer to a tensor, so it is in snake case.TensorType const &A:Ais a tensor, so it is capitalized.std::tuple<AIndices...> const &A_indices:Ais a tensor, but the variable is not. This means that the “A” is capitalized, but the rest of the variable is in snake case.
Macros and enum members should be in
ALL_CAPS.
Order of Things#
cv keywords should never come before the pointer or reference type they modify. As an example,
std::string const &name, notconst std::string &name.However,
const size_t sizeis allowed, butsize_t const sizeis preferred.constexprgoes before the type, soconstexpr size_t rankis preferred.
When writing a class, the preferred order of blocks is public, then protected, then private.
Place simple return types in front of function names. Place complicated return types after function names. Using the
autokeyword can sometimes mess with Doxygen, so this is the compromise we have to make. Some examples.size_t size()Tensor<double, 2> &create_tensor()orauto create_tensor() -> Tensor<double, 2> &are both alright. Use your best judgement here.auto common_initialization(TensorType<T, OtherRank> const &other, Args &&...args) -> std::enable_if_t<std::is_base_of_v<::einsums::tensor_base::Tensor<T, OtherRank>, TensorType<T, OtherRank>>>This is a complicated return type. Use theautokeyword for readability.
Miscellaneous#
Indents are four spaces. Do not use tabs.
Lines should be at most 140 columns long.
Avoid using the function call operator on a tensor inside a large loop. This is slow. Here are some options, in order of what should be tried.
Using
tensor.data()[index]to directly index a tensor. This is fast, but can only be used on certain kinds of tensors, so be careful. It can be used when iterating over a tensor’s elements in order. If you are skipping around, it is better to use something else for better readability.Using
sentinel_to_sentinelsandtensor.data()[index]. This should be faster than subscripting tensors, but be careful, since it can only be used on certain kinds of tensors. As with the previous method, it can be used when iterating over a tensor’s elements in order. It is still readable even when skipping around, but it will only work on :cpp:class:`Tensor`s and :cpp:class:`TensorView`s.Using
sentinel_to_indicesandsubscript_tensor. This will make the choice between thesubscriptmethod and the function call syntax, using thesubscriptmethod as the primary and the function call syntax as a fallback. This is the most general way, and it should be preferred unless you can ensure specificity in the kinds of tensors you are being passed.
Some constructions need to have serious thought before they are used. Before any code with these constructions is accepted, their use will need to be justified.
gotostatements.do { } while(false);blocks outside of macros. They are fine within macros, since their use is considered idiomatic to C/C++ for making a macro require a semicolon after the closing parenthesis.Inline assembly will be outright banned. One of the goals of Einsums is portability. This goes against this goal.
Anything considered to be undefined behavior. Different compilers and systems may have different behavior, so it is best to not use this. Some examples of undefined behavior includes the following.
Anything that uses the binary representation of floating point numbers. IEEE 754 states that this is only an exchange format. Modifying the underlying binary representation is considered to be undefined behavior.
Assuming the size of variables. For instance, the presence and size of
long doubleis highly system dependent.
Python Style#
The approach to Python style is to generally follow the standard Python style guidelines. Some things to keep in mind.
Try to use type annotations when writing Python code. Some examples.
def set_name(name): Bad.def set_name(name: str): Good.def iterate_elements(param): Fine.paramcan be pretty much any type.def iterate_elements(param: Any)would be preferred, but brevity is sometimes better than verbosity.
Prefer
PascalCasefor type names.Prefer
snake_casefor functions, methods, and variables.However, the same considerations for tensor variables apply as in C++. Tensor varaibles are in
UPPER_CASE, and any reference to a tensor variable in a non-tensor variable should match the case of the tensor.A: Tensor variable.A_indices: References a tensor variable, but is not a tensor variable.
CMake Style#
A .cmake-format.py file is provided to ensure consist formatting of CMake files. You may need to
install the package cmake-format to use it.
Using pre-commit with Einsums#
Einsums uses pre-commit to enforce consistent code style, license headers, and formatting standards for C++, CMake, and Python files. This helps maintain code quality and reduces stylistic differences across contributors.
Installation#
First, install the pre-commit Python package:
pip install pre-commit
Alternatively, you may use conda or pipx:
conda install -c conda-forge pre-commit
# or
pipx install pre-commit
Enabling pre-commit Hooks#
Once installed, enable the hooks in your local clone of the Einsums repository:
cd /path/to/Einsums
pre-commit install
This installs Git hook scripts so that pre-commit runs automatically when you commit.
To manually run checks on all files (useful before a PR):
pre-commit run --all-files
Hook Summary#
The following hooks are configured in .pre-commit-config.yaml:
C++ formatting
Hook:
clang-format(v16.0.6)Files checked:
.cpp,.hppConfig: Uses project’s
.clang-formatstylePurpose: Ensures consistent formatting of C++ source and header files.
CMake formatting
Hook:
cmake-format(v0.6.10)Files checked:
CMakeLists.txt,*.cmakePurpose: Applies consistent formatting to all CMake files.
License header checks
Hook:
insert-license(v1.5.4)Files checked: - C++:
.cpp,.hpp(C++-style comments) - CMake:CMakeLists.txt,*.cmake(hash comments) - Python:.py(hash comments)License template:
devtools/LicenseHeader.txtPurpose: Verifies that all relevant files begin with a consistent license header.
All hooks are configured to exclude build directories such as build/ and cmake-build-* automatically.
Updating Hooks#
To update all hooks to their latest compatible versions:
pre-commit autoupdate
To update a specific hook:
pre-commit autoupdate --repo <repo-url>
If you encounter caching or hook resolution issues, you can clear the cache:
pre-commit clean
Troubleshooting#
Common tasks:
Run a specific hook manually:
pre-commit run clang-format --all-files
Temporarily skip hooks (not recommended unless necessary):
git commit --no-verify
Reinstall hooks:
pre-commit uninstall pre-commit install
Contributing#
All contributors to Einsums are expected to run pre-commit before pushing code or submitting a pull request.
The typical workflow is:
pre-commit run --all-files
git add .
git commit -m "Apply pre-commit fixes"
This minimizes unnecessary review cycles and helps maintain code consistency.