Tensor Construction ==================== This section describes the syntax and semantics for the tensor notation in TAMM. This section also describes how sparse tensor construction and usage is done in TAMM through dependent ``TiledIndexSpace`` objects. Tensor is the main computation and storage data structure in TAMM. The main constructs for creating a ``Tensor`` object is using ``TiledIndexSpace``\ s or ``TiledIndexLabel``\ s for each dimension. For dense case, the construction uses independent ``TiledIndexSpace``\ s or labels related to these spaces. Using Labels ------------ ``TiledIndexLabel`` pairs a TiledIndexSpace with an integer label. These labels can be created using TiledIndexSpace methods: ``labels(...)`` and ``label(...)``. (Note that ``labels(...)`` method starts label value ``0`` by default if no value is provided,( this might end up problematic label creation if used on the same tiled index space multiple times.). These objects are the main components for describing a Tensor storage and computation over these tensors. Labeling comes handy in the use of dependent ``TiledIndexSpace`` for constructing sparse tensors or describing sparse computation over the tensors. A label for a dependent ``TiledIndexLabel`` uses secondary labels within parenthesis to describe the dependency in the tensor description. For dense cases, ``TiledIndexSpace``\ s can used for constructing the tensors but in case of sparse tensor construction the only option is to use labeling for describing the dependencies between dimensions. .. code:: cpp using tensor_type = Tensor; // Create labels assuming MO and depAO are defined auto [i, j] = MO.labels<2>("all"); auto mu = depAO.label("all"); // Dense tensor construction tensor_type T1{i, j}; // MO x MO tensor_type T2{MO, MO}; // MO x MO // Sparse tensor construction // mu(i) will construct a dependent TiledIndexLabel which is validated internally. tensor_type T3{i, mu(i)} // Perhaps don't use dependancies in the first example? Using TiledIndexSpace --------------------- The main construction for ``Tensor`` objects are based on a list of ``TiledIndexSpace`` objects for each mode (dimension) of the tensor. Users can use operator overloads to get a specific portion of ``TiledIndexSpace`` (any named sub-space specified in index space is tiled as well e.g. \ ``occ``, ``virt`` etc.) while constructing Tensor objects. Note that the generated ``TiledIndexSpace`` will have the same tiling size as the parent index space. .. code:: cpp // Create TiledIndexSpace objects assuming an index space // for AO and MO is constructed TiledIndexSpace MO{MO_is, 10}, AO{AO_is, 10}; Tensor A{MO, AO}; // 2-mode tensor (MO by AO index space) with double elements Tensor B{AO, AO, AO}; // 3-mode tensor (AO index space for all three modes) with double elements Tensor C{MO("occ"), AO}; // 2-mode tensor (occupied MO by AO) with double elements Using TiledIndexLabel --------------------- Users can also construct a ``Tensor`` object using ``TiledIndexLabel`` object related to a ``TiledIndexSpace``. This is just a convenience constructor for independent index spaces, internally ``Tensor`` object will use ``TiledIndexSpace`` objects. .. code:: cpp // Create TiledIndexSpace objects TiledIndexSpace MO{MO_is, 10}, AO{AO_is, 10}; // Create TiledIndexLabel objects TiledIndexLabel i, j, k, l, a, b; // Relate labels with TiledIndexSpace objects // Multiple labels at once std::tie(i, j) = MO.labels<2>("all"); // Single label for different portions k = MO.label("occ"); l = MO.label("virt"); std::tie(a, b) = AO.labels<2>("all"); Tensor A{i, a}; // 2-mode tensor (MO by AO index space) with double elements Tensor B{a, a, a}; // 3-mode tensor (AO index space for all three modes) with double elements Tensor C{k, a}; // 2-mode tensor (occupied MO by AO) with double elements For ``Tensor`` objects over **dependent** index spaces can only be done using ``TiledIndexLabel``\ s as the construction will dependent on the relation between ``TiledIndexSpace``\ s. .. code:: cpp // Creating index spaces MO, AO, and Atom IndexSpace MO_is{range(0, 100), {{"occ", range(0, 50)}, {"virt", range(50, 100)}}}; IndexSpace Atom_is{range(0, 5)}; // Tile Atom space with tiling size of 3 TiledIndexSpace T_Atom{Atom_is, 3}; // Construct dependency relation for Atom indices std::map dep_relation{ {IndexVector{0}, MO_is("occ")}, {IndexVector{1}, MO_is("virt")} }; // IndexSpace(const std::vector& dep_spaces, // const std::map dep_relation) IndexSpace subMO_Atom_is{{T_Atom}, dep_relation}; TiledIndexSpace T_subMO_Atom{subMO_Atom_is, 3} TiledIndexLabel a = T_subMO_Atom.label("all"); TiledIndexLabel i = T_Atom.label("all"); // 2-mode tensor (subMO_Atom by Atom index space) with double elements Tensor T{i, a(i)}; Specialized constructors ------------------------ For now only specialization for ``Tensor`` object construction is having a lambda expression for on-the-fly calculated ``Tensor``\ s. **Note that** these tensors are not stored in memory, they are only read-only objects that can only by on the right hand side of a computation. .. code:: cpp // Create TiledIndexSpace objects TiledIndexSpace MO{MO_is, 10}, AO{AO_is, 10}; // 2-mode tensor (MO by AO index space) with // double elements and specialized lambda expression Tensor A{{MO, AO}, [] (const IndexVector& block_id, span buf){ /* lambda body*/ }}; // Lambda expression definition auto one_body_overlap_integral_lambda = [] (const IndexVector& block_id, span buf) { /* lambda body*/ }; // 2-mode tensor (AO by MO index space) with // double elements and specialized lambda expression Tensor B{{AO, MO}, one_body_overlap_integral_lambda}; Tensor Allocation and Deallocation ---------------------------------- For allocating and deallocating a ``Tensor`` object is explicitly done using an ``ExecutionContext`` constructed by TAMM memory manager and distribution: .. code:: cpp // Constructing process group, memory manager, distribution to construct // an execution context for allocation ProcGroup pg = ProcGroup::create_world_coll(); ExecutionContext ec{pg, DistributionKind::nw, MemoryManagerKind::ga}; // We also provide a utility function that constructs // an ExecutionContext object with default process group, // memory manager and distribution auto ec_default = tamm::make_execution_context(); TiledIndexSpace MO{/*...*/}; auto O = MO("occ"); auto V = MO("virt"); auto N = MO("all"); Tensor d_f1{N, N, N, N}; Tensor d_r1{O, O, O, O}; // Tensor allocation using static methods Tensor::allocate(&ec, d_r1, d_f1); /* Do work on tensors */ // Deallocation for tensors d_r1 and d_f1 Tensor::deallocate(d_r1, d_f1); // Tensor allocation using Tensor object member functions d_r1.allocate(&ec); d_f1.allocate(&ec); /* Do work on tensors */ // Deallocation for tensors d_r1 and d_f1 d_r1.deallocate(); d_f1.deallocate(); // Tensor allocation using Scheduler member functions Scheduler{&ec} // Allocate tensors .allocate(d_r1, d_f1) (/*Do work on tensors*/) // Deallocate the tensors (unless will be used afterwards) .deallocate(d_r1, d_f1) .execute(); **Note:** The tensors are has to be explicitly allocated using the specified execution context before being used and they should be deallocated once their use is finished. Furthermore, allocating a tensor that is either allocated or has been deallocated is an error. A tensor can be allocated and then deallocated only once. Tensors that are not explicitly deallocated are registered for deallocation in the execution context that was used to deallocate them. The member function ``flush_and_sync`` of an execution context can be used to deallocate tensors that cannot be referenced anymore. Finally, if any tensors were allocated but not deallocated, ``flush_and_sync`` should be called to avoid memory and resource leaks. When calling library functions that can create tensors, ``flush_and_sync`` should be called unless it is known that the called functions did not postpone deallocation of any tensors. Tensor Accessors ----------------- TAMM provides tensor accessors based on the ``TiledIndexSpace``\ s used for construction, as a result the block IDs provided to any accessor will correspond to the tile ID for each mode of ``Tensor`` object. .. code:: cpp TiledIndexSpace MO{/*...*/}; TiledIndexSpace O = MO("occ"); TiledIndexSpace V = MO("virt"); TiledIndexSpace N = MO("all"); Tensor d_f1{N, N, N, N}; Tensor d_r1{O, O, O, O}; // Allocation for the tensors d_r1 and d_f1 Tensor::allocate(&ec, d_r1, d_f1); // Construct a block ID using the tile indices for each mode IndexVector blockId{0, 0, 0, 0}; // Get the size of the corresponding block size_t size = d_r1.block_size(blockId); // Construct the data to put std::vector buff{size}; // Read data from a source ReadData(buff, size); // Put a value to a block of tensor d_r1 d_r1.put(blockId, buff); // internally buff will be converted to a span // Similarly, users can read from the tensor std::vector readBuff{size}; d_r1.get(blockId, readBuff); // Or can do an accumulate on the tensor d_r1.add(blockId, buff); // Deallocation for tensors d_r1 and d_f1 Tensor::deallocate(d_r1, d_f1); .. raw:: html Local Tensor Construction ------------------------------ TAMM also provides a rank local tensor implementation called ``LocalTensor`` that allows to construct a tensor that resides in each rank. While the constructors for this specialized tensor is very similar to default distributed tensors, users can have element-wise operaitons over these tensors as they are locally allocated. Different than the default tensor constructors, users can choose to use size values to construct correspond tensors. .. code:: cpp // Tensor B{tis1, tis1}; // Local tensor construction using TiledIndexSpaces LocalTensor local_A{tis1, tis1, tis1}; LocalTensor local_B{B.tiled_index_spaces()}; // Local tensor construction using TiledIndexLabels LocalTensor local_C{i, j, l}; size_t N = 20; // Local tensor construction using a size LocalTensor local_D{N, N, N}; LocalTensor local_E{10, 10, 10}; Similar to general tensor objects in TAMM, ``LocalTensor`` objects have to be allocated. While allocation/deallocation calls are the same with general Tensor constructs, users have to use an ``ExecutionContext`` object with ``LocalMemoryManager``. Below is an example of how the allocation for these tensors looks like .. code:: cpp // Execution context with LocalMemoryManager ExecutionContext local_ec{sch.ec().pg(), DistributionKind::nw, MemoryManagerKind::local}; // Scheduler constructed with the new local_ec Scheduler sch_local{local_ec}; // Allocate call using the local scheduler sch_local.allocate(local_A, local_B, local_C, local_D, local_E).execute(); Local Tensor Operations ----------------------- The `LocalTensor` object provides various functionalities, such as retrieving blocks of data, resizing tensors, and element-wise access. A `LocalTensor` object allows you to retrieve a block of data using the `block` method. This method has two variants: one for general multi-dimensional tensors and another specifically for 2-dimensional tensors. .. code-block:: cpp // Extract block from a 3-D Tensor auto local_E = local_A.block({0, 0, 0}, {4, 4, 4}); // Extract block from a 2-D Tensor auto local_F = local_B.block(0, 0, 4, 4); In the example above, the first call to `block` extracts a `4x4x4` block starting at the offset `{0, 0, 0}`, while the second call directly specifies the start offset for the x and y axes, followed by the block dimensions. Another special feature of `LocalTensor` objects is the ability to resize the tensor to a new size, while maintaining the same number of dimensions. Depending on the new size, values from the original tensor are automatically carried over. The examples below demonstrate resizing a local tensor to a smaller and then to a larger size. Note that resizing causes a new tensor to be allocated, and the corresponding data is copied over. .. code-block:: cpp // Resize tensor to a smaller size local_A.resize(5, 5, 5); // Resize tensor to a larger size local_A.resize(N, N, N); `LocalTensor` objects also support element-wise accessor methods, `get` and `set`. Unlike default TAMM tensors, all data in a `LocalTensor` resides in local memory, enabling element access via index location. .. code-block:: cpp // Set values for the entire tensor using the local scheduler sch_local.allocate(local_A, local_B) (local_A() = 42.0) (local_B() = 21.0) .execute(); // Set a specific value in the tensor local_A.set({0, 0, 0}, 1.0); // Retrieve a value from the tensor auto val = local_B.get(0, 0, 0); // Looping through tensor elements for (size_t i = 0; i < N; i++) { for (size_t j = 0; j < N; j++) { for (size_t k = 0; k < N; k++) { local_A.set({i, j, k}, local_B.get(i, j)); } } } The examples above illustrate element-wise operations. Users can perform scheduler-based operations with the local scheduler or define element-wise updates using loops. `LocalTensor` object also allows copying from or to a distributed tensor object. This is particularly useful in situations where users need a local copy of distributed tensors to apply element-wise updates. Below is an example usage of this scenario: .. code-block:: cpp // Distributed tensor constructor Tensor dist_A{tN, tN, tN}; // ... // Local tensor construction LocalTensor local_A{dist_A.tiled_index_spaces()}; sch_local.allocate(local_A) .execute(); // Copy from distributed tensor local_A.from_distributed_tensor(dist_A); // Apply updates sch_local (local_A() = 21.0) .execute(); // Copy back to distributed tensor local_A.to_distributed_tensor(dist_A); Block Sparse Tensor Construction -------------------------------- TAMM supports the construction of general block sparse tensors using underlying `TiledIndexSpace` constructs. Users can specify non-zero blocks by providing a lambda function that replaces the block-wise `is_non_zero` check, which is internally called for each block operation (e.g., allocation, element-wise operations, tensor operations). This approach allows for efficient allocation of only non-zero blocks and optimized tensor operations on these portions. The following code demonstrates how to define a custom lambda function to check for block sparsity and construct a block sparse tensor: .. code-block:: cpp // List of index spaces for the tensor construction TiledIndexSpaceVec t_spaces{SpinTIS, SpinTIS}; // Spin mask for the dimensions std::vector spin_mask_2D{SpinPosition::lower, SpinPosition::upper}; // Custom lambda function for the is_non_zero check auto is_non_zero_2D = [t_spaces, spin_mask_2D](const IndexVector& blockid) -> bool { Spin upper_total = 0, lower_total = 0, other_total = 0; for (size_t i = 0; i < 2; i++) { const auto& tis = t_spaces[i]; if (spin_mask_2D[i] == SpinPosition::upper) { upper_total += tis.spin(blockid[i]); } else if (spin_mask_2D[i] == SpinPosition::lower) { lower_total += tis.spin(blockid[i]); } else { other_total += tis.spin(blockid[i]); } } return (upper_total == lower_total); }; // TensorInfo construction TensorInfo tensor_info{t_spaces, is_non_zero_2D}; // Tensor constructor Tensor tensor{t_spaces, tensor_info}; TAMM offers a more convenient `TensorInfo` struct to describe non-zero blocks using stringed sub-space constructs in `TiledIndexSpace`s. This simplifies the process of constructing block sparse tensors. Here's an example of using `TensorInfo`: .. code-block:: cpp // Map labels to corresponding sub-space strings Char2TISMap char2MOstr = {{'i', "occ"}, {'j', "occ"}, {'k', "occ"}, {'l', "occ"}, {'a', "virt"}, {'b', "virt"}, {'c', "virt"}, {'d', "virt"}}; // Construct TensorInfo TensorInfo tensor_info{ {MO, MO, MO, MO}, // Tensor dimensions {"ijab", "iajb", "ijka", "ijkl", "iabc", "abcd"}, // Allowed blocks char2MOstr // Character to sub-space string mapping // ,{"abij", "aibj"} // Disallowed blocks (optional) }; // Block Sparse Tensor construction Tensor tensor{{MO, MO, MO, MO}, tensor_info}; TAMM also provides a simplified constructor that only requires a list of allowed blocks and the character-to-sub-space string map: .. code-block:: cpp // Block Sparse Tensor construction using allowed blocks Tensor tensor{{MO, MO, MO, MO}, {"ijab", "ijka", "iajb"}, char2MOstr}; Block Sparse `Tensor` inherits from general TAMM tensor constructs, enabling the application of standard tensor operations to block sparse tensors. Users can employ labels over the entire `TiledIndexSpace` for general computations or use sub-space labels to access specific blocks. The following code illustrates how to allocate, set values, and perform operations on different blocks of block sparse tensors: .. code-block:: cpp // Construct Block Sparse Tensors with different allowed blocks Tensor tensorA{{MO, MO, MO, MO}, {"ijab", "ijkl"}, char2MOstr}; Tensor tensorB{{MO, MO, MO, MO}, {"ijka", "iajb"}, char2MOstr}; Tensor tensorC{{MO, MO, MO, MO}, {"iabc", "abcd"}, char2MOstr}; // Allocate and set values sch.allocate(tensorA, tensorB, tensorC) (tensorA() = 2.0) (tensorB() = 4.0) (tensorC() = 0.0) .execute(); // Use different blocks to update output tensor // a, b, c, d: MO virtual space labels // i, j, k, l: MO virtual space labels sch (tensorC(a, b, c, d) += tensorA(i, j, a, b) * tensorB(j, c, i, d)) (tensorC(i, a, b, c) += 0.5 * tensorA(j, k, a, b) * tensorB(i, j, k, c)) .execute(); // De-allocate tensors tensorA.deallocate(); tensorB.deallocate(); tensorC.deallocate(); TAMM also provides block sparse constructors similar to the general tensor construction by allowing use of TiledIndexLabels, TiledIndexSpaces, and strings corresponding to the sub-space names in TiledIndexSpaces for representing only the allowed blocks. With these constructors users don't have to provide a mapping from char to corresponding sub-space names as they are provided explicitly. Below code shows the use of this constructions, similar to previous case block sparse tensors constructed using these methods can be directly used in any tensor operations for general tensors: .. code-block:: cpp // Construct Block Sparse Tensors with different allowed blocks // Using TiledIndexLabels for allowed blocks Tensor tensorA{{MO, MO, MO, MO}, {{i, j, a, b}, {i, j, k, l}}}; // Using TiledIndexSpaces for allowed blocks TiledIndexSpace Occ = MO("occ"); TiledIndexSpace Virt = MO("virt"); Tensor tensorB{{MO, MO, MO, MO}, {TiledIndexSpaceVec{Occ, Occ, Occ, Occ}, TiledIndexSpaceVec{Occ, Virt, Occ, Virt}}}; // Using list of comma seperated strings representing sub-space names Tensor tensorC{{MO, MO, MO, MO}, {{"occ, virt, virt, virt"}, {"virt, virt, virt, virt"}}}; // ... Example Tensor Constructions ---------------------------- Basic examples ~~~~~~~~~~~~~~ 1. scalar .. code:: cpp // Construct a scalar value Tensor T_1{}; .. 2. vector of length 10 .. code:: cpp // Create an index space of length 10 IndexSpace is_2{range(10)}; // Apply default tiling TiledIndexSpace tis_2{is_2}; // Create a vector with index space is_2 Tensor T_2{tis_2}; .. 3. matrix that is 10 by 20 .. code:: cpp // Create an index space of length 10 and 20 IndexSpace is1_3{range(10)}; IndexSpace is2_3{range(20)}; // Apply default tiling TiledIndexSpace tis1_3{is1_3}, tis2_3{is2_3}; // Create a matrix on tiled index spaces tis1_3, tis2_3 Tensor T_3{tis1_3, tis2_3}; .. 4. order 3 tensor that is 10 by 20 by 30 .. code:: cpp // Create an index space of length 10, 20 and 30 IndexSpace is1_4{range(10)}; IndexSpace is2_4{range(20)}; IndexSpace is3_4{range(30)}; // Apply default tiling TiledIndexSpace tis1_4{is1_4}, tis2_4{is2_4}, tis3_4{is3_4}; // Construct order 3 tensor in tiled index spaces tis1_4, tis2_4 and tis3_4 Tensor T_4{tis1_4, tis2_4, tis3_4}; .. 5. vector from 2 with subspaces of length 4 and 6 .. code:: cpp // Spliting is_2 into two sub-spaces with 4 and 6 elements IndexSpace is1_5{is_2, range(0, 4)}; IndexSpace is2_5{is_2, range(4, is_2.size())}; // Create index space combining sub-spaces IndexSpace is3_5{{is1_5, is2_5}}; // Apply default tiling TiledIndexSpace tis_5{is3_5}; // Create a vector over combined index space Tensor T_5{tis1_5}; .. 6. matrix from 3 whose rows are split into two subspaces of length 4 and 6 .. code:: cpp // Spliting is1_3 from 3 into two sub-spaces with 4 and 6 elements IndexSpace is1_6{is1_3, range(0, 4)}; IndexSpace is2_6{is1_3, range(4, is1_3.size()}; // Create index space combining sub-spaces IndexSpace is3_6{{is1_6, is2_6}}; // Apply default tiling TiledIndexSpace tis_6{is3_6}; // Create a matrix with rows on combined tiled index space // columns on tis2_3 from 3 Tensor T_6{tis_6, tis2_3}; .. 7. matrix from 3 whose columns are split into two subspaces of lengths 12 and 8 .. code:: cpp // Spliting is2_3 from 3 into two sub-spaces with 12 and 8 elements IndexSpace is1_7{is2_3, range(0, 12)}; IndexSpace is2_7{is2_3, range(12, is2_3.size())}; // Create index space combining sub-spaces IndexSpace is3_7{{is1_7, is2_7}}; // Apply default tiling TiledIndexSpace tis_7{is3_7}; // Create a matrix with rows on tis1_3 from 3 // columns on combined tiled index space Tensor T_7{tis1_3, tis_7}; .. 8. matrix from 3 having subspaces of both 6 and 7 .. code:: cpp // Create matrix on tis_6 from 6 and tis_7 from 7 Tensor T_8{tis_6, tis_7}; .. 9. tensor with mode 0 split into subspaces of 4 and 6 .. code:: cpp // Create order 3 tensor using split version from 5 // and full spaces from 4 Tensor T_9{tis_5, tis2_4, tis3_4}; .. 10. tensor with mode 1 split into subspaces of 12 and 8 .. code:: cpp // Create order 3 tensor using split version from 7 // and full spaces from 4 Tensor T_10{tis1_4, tis_7, tis3_4}; .. 11. tensor with mode 2 split into subspaces of 13 and 17 .. code:: cpp // Split the index space form 4 into sub-spaces of length 13 and 17 IndexSpace is1_11{is3_4, range(0, 13)}; IndexSpace is2_11{is3_4, range(13, is3_4.size())}; // Combine the sub-spaces into another index space IndexSpace is3_11{{is1_11, is2_11}}; // Apply default tiling TiledIndexSpace tis_11{is3_11}; // Create order 3 tensor using new split version // and full spaces from 4 Tensor T_11{tis1_4, tis2_4, tis_11}; .. 12. Combine 9 and 10 .. code:: cpp // Create order 3 tensor using splits from 9 and 10 // tis_5 --> split length 4 and 6 // tis_7 --> split length 12 and 8 // tis3_4 --> length 30 index space Tensor T12{tis_5,tis_7,tis3_4}; .. 13. Combine 9 and 11 .. code:: cpp // Create order 3 tensor using splits from 9 and 11 // tis_5 --> split length 4 and 6 // tis2_4 --> length 20 index space // tis_11 --> split length 13 and 17 Tensor T13{tis_5,tis2_4,tis_11}; .. 14. Combine 10 and 11 .. code:: cpp // Create order 3 tensor using splits from 9 and 11 // tis1_4 --> length 10 index space // tis_7 --> split length 12 and 8 // tis_11 --> split length 13 and 17 Tensor T14{tis1_4,tis_7,tis_11}; .. 15. Combine 9, 10, and 11 .. code:: cpp // Create order 3 tensor using splits from 9 and 11 // tis_5 --> split length 4 and 6 // tis_7 --> split length 12 and 8 // tis_11 --> split length 13 and 17 Tensor T15{tis_5,tis_7,tis_11}; .. 16. Vector from 2 with the first subspace split again into a subspaces of size 1 and 3 .. code:: cpp // Split the sub-space from 5 into another with size 1 and 3 // is1_5 --> split of size 4 // is2_5 --> split of size 6 IndexSpace is1_16{is1_5, range(0,1)}; IndexSpace is2_16{is1_5, range(1,3)}; // Combine all into a full space IndexSpace is3_16{{is1_16, is2_16, is2_5}}; // Apply default tiling TiledIndexSpace tis_16{is3_16}; // Create a vector over new tiled index space Tensor T16{tis_16}; .. 17. matrix from 8 with the 4 by 12 subspace split further into a 1 by 12 and a 3 by 12 .. code:: cpp // Create a matrix from splits from 16 and 7 // tis_16 --> split of size 1, 3 and 6 // tis_7 --> split of size 12 and 8 Tensor T17{tis_16, tis_7}; .. 18. vector from 1 where odd numbered elements are in one space and even numbered elements are in another .. code:: cpp // Odd numbered elements from 1 to 9 IndexSpace is1_18{range(1,10,2)}; // Even numbered elements from 0 to 8 IndexSpace is2_18{range(0,10,2)}; // Aggregate odd and even numbered index spaces IndexSpace is3_18{{is1_18, is2_18}}; // Apply default tiling TiledIndexSpace tis3_18{is3_18}; // Create a vector with tiled index space Tensor T18{tis3_18}; .. 19. matrix from 2 where odd rows are in one space even in another .. code:: cpp // Odd numbered elements from 1 to 9 IndexSpace is1_19{range(1,10,2)}; // Even numbered elements from 0 to 8 IndexSpace is2_19{range(0,10,2)}; // Aggregate odd and even numbered index spaces IndexSpace is3_19{{is1_19, is2_19}}; // Apply default tiling TiledIndexSpace tis1_19{is3_19}; // Create a matrix using tiled index space with odd and even numbered // elements as the row and tiled index space from 3 a columns Tensor T19{tis1_19, tis2_3}; .. 20. matrix from 6 that also has the odd rows in one space and the even in another .. code:: cpp // Odd numbered elements from 1 to 9 IndexSpace is1_20{range(1,10,2)}; // Even numbered elements from 0 to 8 IndexSpace is2_20{range(0,10,2)}; // Aggregate odd and even numbered index spaces IndexSpace is3_20{is1_20, is2_20}; // Spliting is3_20 into two sub-spaces with 4 and 6 elements IndexSpace is4_20{is3_20, range(0, 4)}; IndexSpace is5_20{is3_20, range(4, is3_20.size())}; // Aggregate split indexes IndexSpace is6_20{is4_20, is5_20}; // Apply default tiling TiledIndexSpace tis1_20{is6_20}; // Create a matrix using tiled index space with odd and even numbered // elements then splitted as the row and tiled index space // from 3 a columns Tensor T20{tis1_20, tis2_3}; Dependent Index Spaces ~~~~~~~~~~~~~~~~~~~~~~ For ease of use, if the user provides a dependent label without secondary labels the tensor will be constructed over the reference ``TiledIndexSpace`` of the given dependent ``TiledIndexSpace``. .. code:: cpp using tensor_type = Tensor; auto [i, j] = MO.labels<2>("all"); auto [A, B] = AO.labels<2>("all"); auto [mu, nu] = depAO.labels<2>("all"); // Dense tensor construction tensor_type T1{i, j}; // MO x MO Tensor tensor_type T2{i, A}; // MO x AO Tensor tensor_type T3{mu, nu}; // AO x AO Tensor tensor_type T4{mu, i}; // AO x MO Tensor // Sparse tensor construction // mu(i) will construct a dependent TiledIndexLabel which is validated internally. tensor_type T5{i, mu(i)}; // MO x depAO Tensor .. raw:: html Loop Nest Order and Construction ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The default loop nest ordering is from left-hand side (LHS) to right-hand side (RHS) labels. For example the ordering for a simple assignment with sum over operation on the ``(T1(i, j) = T6(j, i, k)`` will end up ordering of “:math:`i \to j \to k`” where k is the summed over index. This ordering becomes more important when the operations are over dependent index spaces, as there will be conflicting orders with the dependency order described in the operation and the storage of the tensors. In case of the dependencies are not satisfied with the default ordering the ``TiledIndexSpace`` transformations will be used to eliminate the dependencies. .. code:: cpp // Given an order std::vector