3.3. Accessing elements¶
Next, let’s take a look on how we can access elements of a Tensor.
3.3.1. Get elements¶
On the Python side, we can simply use slice to get the elements, just as common with list/numpy.array/torch.tensor in Python. See This page for more details. In C++, Cytnx ports this approach from Python to the C++ API. You can simply use a slice string to access elements.
For example:
In Python:
1A = cytnx.arange(24).reshape(2,3,4)
2print(A)
3
4B = A[0,:,1:4:2]
5print(B)
6
7C = A[:,1]
8print(C)
In C++:
1auto A = cytnx::arange(24).reshape(2, 3, 4);
2cout << A << endl;
3
4auto B = A(0, ":", "1:4:2");
5cout << B << endl;
6
7auto C = A(":", 1);
8cout << C << endl;
Output >>
Total elem: 24
type : Double (Float64)
cytnx device: CPU
Shape : (2,3,4)
[[[0.00000e+00 1.00000e+00 2.00000e+00 3.00000e+00 ]
[4.00000e+00 5.00000e+00 6.00000e+00 7.00000e+00 ]
[8.00000e+00 9.00000e+00 1.00000e+01 1.10000e+01 ]]
[[1.20000e+01 1.30000e+01 1.40000e+01 1.50000e+01 ]
[1.60000e+01 1.70000e+01 1.80000e+01 1.90000e+01 ]
[2.00000e+01 2.10000e+01 2.20000e+01 2.30000e+01 ]]]
Total elem: 6
type : Double (Float64)
cytnx device: CPU
Shape : (3,2)
[[1.00000e+00 3.00000e+00 ]
[5.00000e+00 7.00000e+00 ]
[9.00000e+00 1.10000e+01 ]]
Total elem: 8
type : Double (Float64)
cytnx device: CPU
Shape : (2,4)
[[4.00000e+00 5.00000e+00 6.00000e+00 7.00000e+00 ]
[1.60000e+01 1.70000e+01 1.80000e+01 1.90000e+01 ]]
Note
To convert between Python and C++ APIs, notice that in C++ you need to use operator() instead of operator[] if you are using slice strings to access elements.
The return value will always be a Tensor object, even it only contains one element.
In the case where you have only one element in a Tensor, you can use item() to get the element as a standard Python/C++ type.
In Python:
1A = cytnx.arange(24).reshape(2,3,4)
2B = A[0,0,1]
3C = B.item()
4print(B)
5print(C)
In C++:
1auto A = cytnx::arange(24).reshape(2, 3, 4);
2auto B = A(0, 0, 1);
3Scalar C = B.item();
4double Ct = B.item<double>();
5
6cout << B << endl;
7cout << C << endl;
8cout << Ct << endl;
Output >>
Total elem: 1
type : Double (Float64)
cytnx device: CPU
Shape : (1)
[1.00000e+00 ]
1.0
Note
In C++, using item<>() to get the element requires to explicitly specify the type that matches the dtype of the Tensor. If the type specifier does not match, an error will be prompted.
Starting from v0.7+, users can use item() in C++ without explicitly specifying the type with a template.
3.3.2. Set elements¶
Setting elements is pretty much the same as in numpy.array/torch.tensor. You can assign a Tensor to a specific slice, or set all the elements in that slice to be the same value.
For example:
In Python:
1A = cytnx.arange(24).reshape(2,3,4)
2B = cytnx.zeros([3,2])
3print(A)
4print(B)
5
6A[1,:,::2] = B
7print(A)
8
9A[0,::2,2] = 4
10print(A)
In C++:
1auto A = cytnx::arange(24).reshape(2, 3, 4);
2auto B = cytnx::zeros({3, 2});
3cout << A << endl;
4cout << B << endl;
5
6A(1, ":", "::2") = B;
7cout << A << endl;
8
9A(0, "::2", 2) = 4;
10cout << A << endl;
Output >>
Total elem: 24
type : Double (Float64)
cytnx device: CPU
Shape : (2,3,4)
[[[0.00000e+00 1.00000e+00 2.00000e+00 3.00000e+00 ]
[4.00000e+00 5.00000e+00 6.00000e+00 7.00000e+00 ]
[8.00000e+00 9.00000e+00 1.00000e+01 1.10000e+01 ]]
[[1.20000e+01 1.30000e+01 1.40000e+01 1.50000e+01 ]
[1.60000e+01 1.70000e+01 1.80000e+01 1.90000e+01 ]
[2.00000e+01 2.10000e+01 2.20000e+01 2.30000e+01 ]]]
Total elem: 6
type : Double (Float64)
cytnx device: CPU
Shape : (3,2)
[[0.00000e+00 0.00000e+00 ]
[0.00000e+00 0.00000e+00 ]
[0.00000e+00 0.00000e+00 ]]
Total elem: 24
type : Double (Float64)
cytnx device: CPU
Shape : (2,3,4)
[[[0.00000e+00 1.00000e+00 2.00000e+00 3.00000e+00 ]
[4.00000e+00 5.00000e+00 6.00000e+00 7.00000e+00 ]
[8.00000e+00 9.00000e+00 1.00000e+01 1.10000e+01 ]]
[[0.00000e+00 1.30000e+01 0.00000e+00 1.50000e+01 ]
[0.00000e+00 1.70000e+01 0.00000e+00 1.90000e+01 ]
[0.00000e+00 2.10000e+01 0.00000e+00 2.30000e+01 ]]]
Total elem: 24
type : Double (Float64)
cytnx device: CPU
Shape : (2,3,4)
[[[0.00000e+00 1.00000e+00 4.00000e+00 3.00000e+00 ]
[4.00000e+00 5.00000e+00 6.00000e+00 7.00000e+00 ]
[8.00000e+00 9.00000e+00 4.00000e+00 1.10000e+01 ]]
[[0.00000e+00 1.30000e+01 0.00000e+00 1.50000e+01 ]
[0.00000e+00 1.70000e+01 0.00000e+00 1.90000e+01 ]
[0.00000e+00 2.10000e+01 0.00000e+00 2.30000e+01 ]]]
3.3.3. Low-level API (C++ only)¶
On the C++ side, Cytnx provides lower-level APIs with slightly smaller overhead for getting elements. These low-level APIs require using an Accessor object.
- Accessor:
Accessor object is equivalent to Python slice. It is sometimes convenient to use aliases to simplify the expression when using it.
1using ac = cytnx::Accessor;
2
3ac(4); // this is equal to index '4' in Python
4ac::all(); // this is equal to ':' in Python
5ac::range(0, 4, 2); // this is equal to '0:4:2' in Python
In the following, let’s see how it can be used to get/set the elements from/in a Tensor.
operator[] (middle level API) :
1using ac = cytnx::Accessor;
2auto A = cytnx::arange(24).reshape(2, 3, 4);
3auto B = cytnx::zeros({3, 2});
4
5// [get] this is equal to A[0,:,1:4:2] in Python:
6auto C = A[{ac(0), ac::all(), ac::range(1, 4, 2)}];
7
8// [set] this is equal to A[1,:,0:4:2] = B in Python:
9A[{ac(1), ac::all(), ac::range(0, 4, 2)}] = B;
Note
Remember to write a braket{} around the elements to be accessed. This is needed because the C++ operator[] can only accept one argument.
- get/set (low level API) :
get() and set() are part of the low-level API. Operator() and Operator[] are all built based on these.
1using ac = cytnx::Accessor;
2auto A = cytnx::arange(24).reshape(2, 3, 4);
3auto B = cytnx::zeros({3, 2});
4
5// [get] this is equal to A[0,:,1:4:2] in Python:
6auto C = A.get({ac(0), ac::all(), ac::range(1, 4, 2)});
7
8// [set] this is equal to A[1,:,0:4:2] = B in Python:
9A.set({ac(1), ac::all(), ac::range(0, 4, 2)}, B);
Hint
Similarly, you can also pass a C++ vector<cytnx_int64> as an argument.
Tip
If your code makes frequent use of get/set elements, using the low-level API can reduce the overhead.
3.3.4. Raw pointer access (C++ only)¶
Tensor::ptr_as<T>() and Tensor::gpu_ptr_as<T>() expose the raw storage
address of a Tensor. They are intended for low-level library code and should be
used with care.
Use ptr_as<T>() when T is the logical Cytnx element type of the Tensor.
This is the correct accessor for ordinary C++ code and for raw memory-copy
interfaces such as memcpy or cudaMemcpy. For complex tensors, the logical
type is std::complex<double> or std::complex<float>.
Use gpu_ptr_as<T>() only when passing Tensor storage to CUDA APIs that require
CUDA ABI element types. For complex tensors, this means cuDoubleComplex or
cuComplex.
Both accessors return the same underlying storage address. The difference is the dtype mapping they validate before returning it:
ptr_as<T>()checksTagainst the normal Cytnx type list.gpu_ptr_as<T>()checksTagainst the CUDA type list.
If a function only needs an address for a byte copy, keep the typed pointer from
ptr_as<T>() and pass it directly to the copy routine.