Skip to content

Developer Guide

Ying-Jer Kao edited this page Dec 13, 2024 · 1 revision

Cytnx Developer's Guide (2024-07-16)

Prepare the environment

1. Dependencies:

  • Please install all dependencies according to the Advanced Install of Cytnx
  • If you use conda environment, please activate it.
  • Please install the gtest and doxygen
    pip install gtest
    conda install doxygen

2. Create the your own branch from the branch dev-master:

  1. Create your own branch from the branch dev-master:

your_branch

  1. Clone the Cytnx repository and switch to that brach you created.
    git clone https://github.com/Cytnx-dev/Cytnx.git
    cd Cytnx
    git checkout <YOUR_BRANCH>
  2. Modify the 'Install.sh' and build the Cytnx library:
    sh Install.sh

3. Try to run ctest to check if the installation is successful:

cd build && ctest

Start to develop

1. The structure of the Cytnx library:

Suppose <Cytnx_PATH> is the path you clone the Cytnx repository. You will see the following structure:

  • include/: All of the header files of the user API will be here.
  • src/: The implementation code will be here.
  • tests/: The test code based on gtest (C++) will be here.
  • pytest/: The test code based on pytest (python) will be here. But it is under maintenance currently.
  • pybind/: Use the pybind11 to wrap the Cytnx library to Python. The code will be here.
  • bm_test/: The benchmark test code will be here. But it is under maintenance currently.
  • docs.doxygen: The configuration file of doxygen.

2. Create the corresponding header file:

We take Svd for the Block type UniTensor as an example. We note that the corresponding API in python code:

S, U, Vt = cytnx.linalg.Svd(T)

And in the C++ code:

svds = cytnx::linalg::Svd(T);

Which means that this API is in the namespace cytnx::linalg. Then we write the API in the header file include/linalg.hpp:

699  std::vector<cytnx::UniTensor> Svd(const cytnx::UniTensor &Tin, const bool &is_UvT = true);

Note that the default argument needs to be set here, and the API is in the namespace cytnx::linalg.

3. Write the implementation code:

  1. We write the implementation code in the file src/linalg/Svd.cpp. The entry point of the API is:

    411     std::vector<cytnx::UniTensor> Svd(const cytnx::UniTensor &Tin, const bool &is_UvT) {
    412       // using rowrank to split the bond to form a matrix.
    413       cytnx_error_msg(Tin.rowrank() < 1 || Tin.rank() == 1,
    414           "[Svd][ERROR] Svd for UniTensor should have rank>1 and rowrank>0%s", "\n");
    415 
    416       cytnTin.is_diag(),
    417           "[Svd][ERROR] Svd for diagonal UniTensor is trivial and currently not "
    418           "support. Use other manipulation.%s",
    419           "\n");
    420 
    421       std::vector<UniTensor> outCyT;
    422       if (Tin.uten_type() == UTenType.Dense) {
    423         _svd_Dense_UT(outCyT, Tin, is_UvT);
    424 
    425       } else if (Tin.uten_type() == UTenType.Block) {
    426         _svd_Block_UT(outCyT, Tin, is_UvT);
    427 
    428       } else {
    429         cytnx_error_msg(true, "[ERROR] only support svd for Dense and Block UniTensor.%s", "\n");
    430 
    431       }  // is block form ?
    432 
    433       return outCyT;
    434 
    435     };  // Svd

    After entering the API, we need to veriry the input date. If the input data is not correct, we will throw an error message via the cytnx_error_msg. For example in line 413:

    413       cytnx_error_msg(Tin.rowrank() < 1 || Tin.rank() == 1,
    414           "[Svd][ERROR] Svd for UniTensor should have rank>1 and rowrank>0%s", "\n");

    Please take care if the GPU part still not support the API, you need to add the corresponding error message.

  2. To Compile the code, we need to add the file src/linalg/Svd.cpp to the src/linalg/CMakeLists.txt:

    46     Svd.cpp

4. Write the test code:

We use the framework gtest to write the test code. The test code is in the directory tests. We take the Bond as an example. The test code is in the file tests/Bond_test.cpp:

3 TEST(Bond, EmptyBond) {
4   Bond bd;
5 
6   EXPECT_EQ(bd.type(), BD_REG);
7 
8   EXPECT_EQ(bd.dim(), 0);
9 }
  1. You may need to use the EXPECT_EQ or EXPECT_TRUE to verify the result, or use the EXPECT_ANY_THROW to test if the input data is wrong but the code can catch it. Please refer to the gtest assertions for more information.

  2. In order to avoid the contradiction of the declaration of the object of the function, we recommend that you can use another namespace. For example, we use the namespace SvdTest to test the Svd API. See the file tests/linalg/Svd_test.cpp.

  3. To compile the test code, we also need to add the file in the tests/CMakeLists.txt:

     10   Bond_test.cpp
  4. To run the test code, you need to build the project first. You have two ways to build the project. One is that you can just enter the build directory and use the command:

    make

    The other way is that you can use the Install.sh to build the project:

    sh Install.sh

    To accelerate the compilation, you can comment the line in the Install.sh before you run the sh Install.sh:

    194 #rm -rf build
    195 #mkdir build
    .
    .
    .
    199 #make install
    200 # if DRUN_TESTS=ON, run tests
    201 #ctest

    After the build, you can run the test code in the build directory. There are two way. For example, if you want to test all Svd test case, you can use the command:

    cd biuld
    ctest -R Svd

    Or you can execute the test code in the build/tests/test_main.e:

    cd build/tests
    ./test_main --gtest_filter=*Svd*
  5. [optional] If you also want to test the python code, you can write the test code in the directory pytest. Becareful the nameing convention of the pytest. For more information, please refer to the pytest. Currently, the pytest is under maintenance. This step need to be done after you use pybind11 to wrap the Cytnx library to Python.(See step 6)

  6. [optional] If you also want to test the benchmark code, you can write the test code in the directory bm_test. Currently, the bm_test is under maintenance. You may need to uncomment the corresponding code in the <Cytnx_PATH>/CMakeLists.txt about gtest and bm_test.

5. Write the document:

  1. Write the cocumentation for your API based on the doxygen. For example, we write the documentation for the Svd API in the file include/linalg.hpp:

    /**
    @brief Perform Singular-Value decomposition on a UniTensor using divide-and-conquer method.
    @details This function performs the Singular-Value decomposition on a UniTensor \p Tin.
    The result will depend on the rowrank of the UniTensor \p Tin. For more details, please
    refer to the documentation of the function Svd(const Tensor &Tin, const bool &is_UvT).
    */
    std::vector<cytnx::UniTensor> Svd(const cytnx::UniTensor &Tin, const bool &is_UvT = true);

    for UniTensor version. And for the Tensor version:

    // Svd:
        //==================================================
        /**
        @brief Perform Singular-Value decomposition on a rank-2 Tensor (a @em matrix).
        @details This function will perform Singular-Value decomposition on a matrix (a rank-2
        Tensor). That means givent a matrix \p Tin as \f$ M \f$, then the result will be:
        \f[
        M = U S V^\dagger,
        \f]
        where \f$ U \f$ is a left uniform matrix, \f$ S \f$ is a diagonal matrix with singular
        values, and \f$ V^\dagger \f$ is the conjugate transpose of the right uniform matrix \f$ V
        \f$. Furthermore, \f$ U \f$ and \f$ V \f$ are unitary matrices, and \f$ S \f$ is a
        non-negative diagonal matrix.
    
        @param[in] Tin a Tensor, it should be a rank-2 tensor (matrix)
        @param[in] is_UvT whether need to return a left unitary matrix.
        @return
        @parblock
        [std::vector<Tensors>]
    
        1. The first tensor is a 1-d tensor contanin the singular values
        2. If \p is_UvT is true, then the tensors \f$ U,V^\dagger \f$ will be pushed back to the vector.
        @endparblock
        @pre The input tensor should be a rank-2 tensor (matrix).
        @see \ref Svd_truncate(const Tensor &Tin, const cytnx_uint64 &keepdim, const double &err, const
        bool &is_UvT, const unsigned int &return_err) "Svd_truncate"
        */
        std::vector<Tensor> Svd(const Tensor &Tin, const bool &is_UvT = true);

    Then you can generate the document by the command:

    doxygen docs.doxygen

    The entry point of the document is the file docs/html/index.html. And you can open it by the browser to check. Svd_doc

  2. If this API is important, you can also add the document in the repo.

6. Use pybind11 to wrap the Cytnx library to Python:

Take the Svd API as an example. We need to write the corresponding code in the file pybind/linalg_py.cpp:

m_linalg.def(
  "Svd",
  [](const cytnx::UniTensor &Tin, const bool &is_UvT) { return cytnx::linalg::Svd(Tin, is_UvT); },
  py::arg("Tin"), py::arg("is_UvT") = true);

The default argument needs to consistent with the C++ API declared in the include/linalg.hpp.

std::vector<cytnx::UniTensor> Svd(const cytnx::UniTensor &Tin, const bool &is_UvT = true);

Sometimes we need to modify the file pybind/cytnx_py.cpp. You can use pytest to test your code after you build the project again.

7. Submit the code to the repo:

  1. Bore you submit the code, you need to check the code can build successfully and pass all the test cases. Uncomment the line in the Install.sh:
194 rm -rf build
195 mkdir build
.
.
.
199 make install
200  if DRUN_TESTS=ON, run tests
201 ctest

and build the project again:

sh Install.sh

Check all of the test cases pass.

  1. Use the pre-commit tool to check the coding style:

    (1). If you have not installed the pre-commit tool, you can install it by the command:

    conda install pre-commit

    (2). Then you can run the following command to fix the coding style:

    pre-commit run --all-files

    You can run twice to make sure all of them are passed.

  2. Submit the code to the repo and create a Pull Request:

    (1). You can use the following command to submit the code to the repo:

    git add .
    git commit -m "Your commit message"
    git push origin \<Your created branch\>

    (2). Then you can create a Pull Request. After the PR is created, the CI will check the code and run the test cases. action (3). If the CI passes, please select the reviewer to review your code then wait to approve it to merge.

reviewer Then merge the code to the dev-master branch.


Consider the creation of new type of UniTensor

Here, just list something about when you want to create the new UniTensor type, say 'FermionUniTensor'.

You may need to add a type in the include/UniTensor.hpp:

34   class UniTensorType_class {
35    public:
36     enum : int {
37       Void = -99,
38       Dense = 0,
39       Sparse = 1,
40       Block = 2,
         //Maybe some new type here, Ferimion = 3?
41     };
42     std::string getname(const int &ut_type);
43   };

and

95     friend class UniTensor;  // allow wrapper to access the private elems
96     friend class DenseUniTensor;
97     // friend class SparseUniTensor;
98     friend class BlockUniTensor;
99     // friend class FermionUniTensor; <-- maybe like this

and another implementation in this file.

Also you may need to add the file src/FermionUniTensor.cpp to do the implementation.