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

  1. 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.

  2. 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

  1. 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.

  2. 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.

  1. 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.

  1. 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

  1. 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>() checks T against the normal Cytnx type list.

  • gpu_ptr_as<T>() checks T against 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.