Repository: Cambridge-ICCS/FTorch
Pull request #348: `torch_tensor_from_array` interface with default layout
Author: login: joewallwork
Labels: name: enhancement
description: New feature or request
Created: 2025-04-01T10:07:50Z | Merged: 2025-04-02T13:25:55Z

Closes #337.

It'd be convenient to default the tensor layout to `[1, 2, ..., n]`, as mentioned in #337. However, because this argument to `torch_tensor_from_array` comes before `device_type` (which is not optional), we can't simply make the `layout` argument optional because it would break the current API.

Thanks to the magic of `fypp`, this PR takes an alternative approach of providing more interfaces to `torch_tensor_from_array` such that it can be called without the `layout` argument, in which case it just gets the default value.

[As usual, the vast majority of lines added are due to fypp.]

Linked issue #337: Default tensor layout
`torch_tensor_from_array`'s `tensor_layout` argument does have a use case, but in most cases it could just be defaulted to `[1, 2, ..., n]`, where `n` is the number of dimensions.
Linked issue comments: user: login: joewallwork
created_at: 2025-03-31T08:39:22Z
body: Related to #56 - could instead provide an option for C or Fortran ordering.
author_association: COLLABORATOR

Diff:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6ece4aa9f..7978ee519 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,10 +14,7 @@ For specific details see the [FTorch online documentation](https://cambridge-icc
 - `requires_grad` property hooked up to `torch_tensor` in [#288](https://github.com/Cambridge-ICCS/FTorch/pull/288)
 - MPI example added in [#270](https://github.com/Cambridge-ICCS/FTorch/pull/270)
 - Changelog file and guidance for versioning added in [#313](https://github.com/Cambridge-ICCS/FTorch/pull/313)
-- The unit tests for constructing and destroying tensors were separated out in
-  [#319](https://github.com/Cambridge-ICCS/FTorch/pull/319)
 - A new tensor manipulation demo was introduced in [#291](https://github.com/Cambridge-ICCS/FTorch/pull/291).
-  This became demo 1 and the other demo numbers were bumped to account for this.
 - Backpropagation implemented with `torch_tensor_backward` and
   `torch_tensor_get_gradient` in [#286](https://github.com/Cambridge-ICCS/FTorch/pull/286)
 - Zeroing of gradients associated with a tensor implemented in
@@ -26,12 +23,20 @@ For specific details see the [FTorch online documentation](https://cambridge-icc
   [#342](https://github.com/Cambridge-ICCS/FTorch/pull/342).
 - Implemented `torch_tensor_zero` and class method alias in
   [#338](https://github.com/Cambridge-ICCS/FTorch/pull/338).
+- Provided interface for `torch_tensor_from_array` with default layout in
+  [#348](https://github.com/Cambridge-ICCS/FTorch/pull/348).
 
 ### Changed
 
 - fortitude dependency version increased to 0.7.0
 - Examples reordered to be more logical in [#317](https://github.com/Cambridge-ICCS/FTorch/pull/317)
 - scalar multiplication/division of tensors reworked to require the scalar to first be mapped to a `torch_tensor` in [#289](https://github.com/Cambridge-ICCS/FTorch/pull/289)
+- The unit tests for constructing and destroying tensors were separated out in
+  [#319](https://github.com/Cambridge-ICCS/FTorch/pull/319)
+- Demo numbers were bumped to account for new demo in
+  [#291](https://github.com/Cambridge-ICCS/FTorch/pull/291).
+- Use interface for `torch_tensor_from_array` with default layout in tests and
+  examples in [#348](https://github.com/Cambridge-ICCS/FTorch/pull/348).
 
 ### Removed
 
diff --git a/examples/1_Tensor/tensor_manipulation.f90 b/examples/1_Tensor/tensor_manipulation.f90
index 1c1d45fd0..b877775c3 100644
--- a/examples/1_Tensor/tensor_manipulation.f90
+++ b/examples/1_Tensor/tensor_manipulation.f90
@@ -23,7 +23,6 @@ program tensor_manipulation
   integer(c_int64_t), dimension(2), parameter :: tensor_shape = [2, 3]
 
   ! Variables for constructing tensors with torch_tensor_from_array
-  integer, parameter :: tensor_layout(ndims) = [1, 2]
   real(wp), dimension(2,3), target :: in_data, out_data
 
   ! Create a tensor of ones
@@ -41,10 +40,10 @@ program tensor_manipulation
   ! Create a tensor based off an array
   ! ----------------------------------
   ! Note that the API is slightly different for this subroutine. In particular, the dimension,
-  ! shape and data type of the tensor are automatically inherited from the input array. Further,
-  ! the tensor layout should be specified, which determines the indexing order.
+  ! shape and data type of the tensor are automatically inherited from the input array so do not
+  ! need to be specified.
   in_data(:,:) = reshape([1.0_wp, 2.0_wp, 3.0_wp, 4.0_wp, 5.0_wp, 6.0_wp], [2,3])
-  call torch_tensor_from_array(b, in_data, tensor_layout, torch_kCPU)
+  call torch_tensor_from_array(b, in_data, torch_kCPU)
   ! Another way of viewing the contents of a tensor is to print the array used as its input.
   write(*,*) "Contents of second input tensor:"
   write(*,*) in_data
@@ -53,7 +52,7 @@ program tensor_manipulation
   ! -----------------------------------------------
   ! This requires some setup in advance. Create a tensor based off the Fortran array that you want
   ! to extract data into in the same way as above. There's no need to assign values to the array.
-  call torch_tensor_from_array(c, out_data, tensor_layout, torch_kCPU)
+  call torch_tensor_from_array(c, out_data, torch_kCPU)
 
   ! Perform arithmetic on the tensors using the overloaded addition operator
   ! ------------------------------------------------------------------------
diff --git a/examples/2_SimpleNet/simplenet_infer_fortran.f90 b/examples/2_SimpleNet/simplenet_infer_fortran.f90
index a3dd33cc5..59a73d68f 100644
--- a/examples/2_SimpleNet/simplenet_infer_fortran.f90
+++ b/examples/2_SimpleNet/simplenet_infer_fortran.f90
@@ -22,7 +22,6 @@ program inference
    real(wp), dimension(5), target :: in_data
    real(wp), dimension(5), target :: out_data
    real(wp), dimension(5), target :: expected
-   integer, parameter :: tensor_layout(1) = [1]
 
    ! Set up Torch data structures
    ! The net, a vector of input tensors (in this case we only have one), and the output tensor
@@ -44,8 +43,8 @@ program inference
    in_data = [0.0_wp, 1.0_wp, 2.0_wp, 3.0_wp, 4.0_wp]
 
    ! Create Torch input/output tensors from the above arrays
-   call torch_tensor_from_array(in_tensors(1), in_data, tensor_layout, torch_kCPU)
-   call torch_tensor_from_array(out_tensors(1), out_data, tensor_layout, torch_kCPU)
+   call torch_tensor_from_array(in_tensors(1), in_data, torch_kCPU)
+   call torch_tensor_from_array(out_tensors(1), out_data, torch_kCPU)
 
    ! Load ML model
    call torch_model_load(model, args(1), torch_kCPU)
diff --git a/examples/3_ResNet/resnet_infer_fortran.f90 b/examples/3_ResNet/resnet_infer_fortran.f90
index 87fb68d10..e5cb0fa24 100644
--- a/examples/3_ResNet/resnet_infer_fortran.f90
+++ b/examples/3_ResNet/resnet_infer_fortran.f90
@@ -32,10 +32,8 @@ subroutine main()
 
       integer, parameter :: in_dims = 4
       integer, parameter :: in_shape(in_dims) = [1, 3, 224, 224]
-      integer, parameter :: in_layout(in_dims) = [1, 2, 3, 4]
       integer, parameter :: out_dims = 2
       integer, parameter :: out_shape(out_dims) = [1, 1000]
-      integer, parameter :: out_layout(out_dims) = [1, 2]
 
       ! Path to input data
       character(len=128) :: data_dir
@@ -82,9 +80,9 @@ subroutine main()
       call load_data(filename, tensor_length, in_data)
 
       ! Create input/output tensors from the above arrays
-      call torch_tensor_from_array(in_tensors(1), in_data, in_layout, torch_kCPU)
+      call torch_tensor_from_array(in_tensors(1), in_data, torch_kCPU)
 
-      call torch_tensor_from_array(out_tensors(1), out_data, out_layout, torch_kCPU)
+      call torch_tensor_from_array(out_tensors(1), out_data, torch_kCPU)
 
       ! Load ML model (edit this line to use different models)
       call torch_model_load(model, args(1), torch_kCPU)
diff --git a/examples/4_MultiIO/multiionet_infer_fortran.f90 b/examples/4_MultiIO/multiionet_infer_fortran.f90
index 6d7adc0db..be18926f7 100644
--- a/examples/4_MultiIO/multiionet_infer_fortran.f90
+++ b/examples/4_MultiIO/multiionet_infer_fortran.f90
@@ -25,7 +25,6 @@ program inference
    real(wp), dimension(4), target :: out_data1
    real(wp), dimension(4), target :: out_data2
    real(wp), dimension(4) :: expected
-   integer, parameter :: tensor_layout(1) = [1]
 
    ! Set up Torch data structures
    ! The net, a vector of input tensors (in this case we only have one), and the output tensor
@@ -48,10 +47,10 @@ program inference
    in_data2(:) = [0.0_wp, -1.0_wp, -2.0_wp, -3.0_wp]
 
    ! Create Torch input/output tensors from the above arrays
-   call torch_tensor_from_array(in_tensors(1), in_data1, tensor_layout, torch_kCPU)
-   call torch_tensor_from_array(in_tensors(2), in_data2, tensor_layout, torch_kCPU)
-   call torch_tensor_from_array(out_tensors(1), out_data1, tensor_layout, torch_kCPU)
-   call torch_tensor_from_array(out_tensors(2), out_data2, tensor_layout, torch_kCPU)
+   call torch_tensor_from_array(in_tensors(1), in_data1, torch_kCPU)
+   call torch_tensor_from_array(in_tensors(2), in_data2, torch_kCPU)
+   call torch_tensor_from_array(out_tensors(1), out_data1, torch_kCPU)
+   call torch_tensor_from_array(out_tensors(2), out_data2, torch_kCPU)
 
    ! Load ML model
    call torch_model_load(model, args(1), torch_kCPU)
diff --git a/examples/6_MultiGPU/multigpu_infer_fortran.f90 b/examples/6_MultiGPU/multigpu_infer_fortran.f90
index de1f9e5a0..0ad720332 100644
--- a/examples/6_MultiGPU/multigpu_infer_fortran.f90
+++ b/examples/6_MultiGPU/multigpu_infer_fortran.f90
@@ -24,7 +24,6 @@ program inference
    real(wp), dimension(5), target :: in_data
    real(wp), dimension(5), target :: out_data
    real(wp), dimension(5) :: expected
-   integer, parameter :: tensor_layout(1) = [1]
 
    ! Set up Torch data structures
    type(torch_model) :: model
@@ -67,13 +66,12 @@ program inference
       ! Create Torch input tensor from the above array and assign it to the first (and only)
       ! element in the array of input tensors.
       ! We use the specified GPU device type with the given device index
-      call torch_tensor_from_array(in_tensors(1), in_data, tensor_layout, device_type, &
-                                   device_index=device_index)
+      call torch_tensor_from_array(in_tensors(1), in_data, device_type, device_index=device_index)
 
       ! Create Torch output tensor from the above array.
       ! Here we use the torch_kCPU device type since the tensor is for output only
       ! i.e. to be subsequently used by Fortran on CPU.
-      call torch_tensor_from_array(out_tensors(1), out_data, tensor_layout, torch_kCPU)
+      call torch_tensor_from_array(out_tensors(1), out_data, torch_kCPU)
 
       ! Load ML model. Ensure that the same device type and device index are used
       ! as for the input data.
diff --git a/examples/7_MPI/mpi_infer_fortran.f90 b/examples/7_MPI/mpi_infer_fortran.f90
index 5b1a25d3a..03facbce9 100644
--- a/examples/7_MPI/mpi_infer_fortran.f90
+++ b/examples/7_MPI/mpi_infer_fortran.f90
@@ -26,7 +26,6 @@ program inference
    real(wp), dimension(5), target :: in_data
    real(wp), dimension(5), target :: out_data
    real(wp), dimension(5), target :: expected
-   integer, parameter :: tensor_layout(1) = [1]
 
    ! Set up Torch data structures
    ! The net, a vector of input tensors (in this case we only have one), and the output tensor
@@ -71,8 +70,8 @@ program inference
    100 format('[',4(f5.1,','),f5.1,']')
 
    ! Create Torch input/output tensors from the above arrays
-   call torch_tensor_from_array(in_tensors(1), in_data, tensor_layout, torch_kCPU)
-   call torch_tensor_from_array(out_tensors(1), out_data, tensor_layout, torch_kCPU)
+   call torch_tensor_from_array(in_tensors(1), in_data, torch_kCPU)
+   call torch_tensor_from_array(out_tensors(1), out_data, torch_kCPU)
 
    ! Load ML model
    call torch_model_load(model, args(1), torch_kCPU)
diff --git a/examples/8_Autograd/autograd.f90 b/examples/8_Autograd/autograd.f90
index 707012f6e..1f49f290b 100644
--- a/examples/8_Autograd/autograd.f90
+++ b/examples/8_Autograd/autograd.f90
@@ -20,7 +20,6 @@ program example
   integer, parameter :: n = 2
   real(wp), dimension(n), target :: out_data1, out_data2, out_data3
   real(wp), dimension(n) :: expected
-  integer :: tensor_layout(ndims) = [1]
 
   ! Flag for testing
   logical :: test_pass
@@ -29,16 +28,16 @@ program example
   type(torch_tensor) :: a, b, Q, multiplier, divisor, dQda, dQdb
 
   ! Initialise Torch Tensors from input arrays as in Python example
-  call torch_tensor_from_array(a, [2.0_wp, 3.0_wp], tensor_layout, torch_kCPU, requires_grad=.true.)
-  call torch_tensor_from_array(b, [6.0_wp, 4.0_wp], tensor_layout, torch_kCPU, requires_grad=.true.)
+  call torch_tensor_from_array(a, [2.0_wp, 3.0_wp], torch_kCPU, requires_grad=.true.)
+  call torch_tensor_from_array(b, [6.0_wp, 4.0_wp], torch_kCPU, requires_grad=.true.)
 
   ! Initialise Torch Tensor from array used for output
-  call torch_tensor_from_array(Q, out_data1, tensor_layout, torch_kCPU)
+  call torch_tensor_from_array(Q, out_data1, torch_kCPU)
 
   ! Scalar multiplication and division are not currently implemented in FTorch. However, you can
   ! achieve the same thing by defining a rank-1 tensor with a single entry, as follows:
-  call torch_tensor_from_array(multiplier, [3.0_wp], tensor_layout, torch_kCPU)
-  call torch_tensor_from_array(divisor, [3.0_wp], tensor_layout, torch_kCPU)
+  call torch_tensor_from_array(multiplier, [3.0_wp], torch_kCPU)
+  call torch_tensor_from_array(divisor, [3.0_wp], torch_kCPU)
 
   ! Compute the same mathematical expression as in the Python example
   Q = multiplier * (a**3 - b * b / divisor)
@@ -56,8 +55,8 @@ program example
   call torch_tensor_backward(Q)
 
   ! Create tensors based off output arrays for the gradients and then retrieve them
-  call torch_tensor_from_array(dQda, out_data2, tensor_layout, torch_kCPU)
-  call torch_tensor_from_array(dQdb, out_data3, tensor_layout, torch_kCPU)
+  call torch_tensor_from_array(dQda, out_data2, torch_kCPU)
+  call torch_tensor_from_array(dQdb, out_data3, torch_kCPU)
   call torch_tensor_get_gradient(a, dQda)
   call torch_tensor_get_gradient(b, dQdb)
 
diff --git a/pages/tensor.md b/pages/tensor.md
index 975d5412e..90b80b4b1 100644
--- a/pages/tensor.md
+++ b/pages/tensor.md
@@ -32,7 +32,11 @@ include:
   meaning the array must have been declared with the `target` property. The
   array will continue to be pointed to even when operations are applied to the
   tensor, so this subroutine can be used 'in advance' to set up an array for
-  outputting data.
+  outputting data. `torch_tensor_from_array` may be called with or without the
+  `layout` argument - an array which specifies the order in which indices should
+  be looped over. The default `layout` is `[1,2,...,n]` implies that data will
+  be read into the same indices by Torch. (See the
+  [transposing user guide page](pages/transposing.html) for more details.
 
 It is *compulsory* to call one of the constructors before interacting with it in
 any of the ways described in the following. Each of the constructors sets the
diff --git a/pages/transposing.md b/pages/transposing.md
index 3eef04ba7..5c3108741 100644
--- a/pages/transposing.md
+++ b/pages/transposing.md
@@ -1,6 +1,6 @@
 title: When to transpose data
 
-Transposition of data between Fortran and C can lead to a lot of unneccessary confusion.
+Transposition of data between Fortran and C can lead to a lot of unnecessary confusion.
 The FTorch library looks after this for you with the
 [`torch_tensor_from_array()`](doc/interface/torch_tensor_from_array.html) function which
 allows you to index a tensor in Torch in **exactly the same way** as you would in Fortran.
@@ -82,14 +82,14 @@ to our Torch net is correct as we expect.
 
 ## What can we do?
 
-There are a few approaches we can take to address this.  
+There are a few approaches we can take to address this.
 The first two of these are listed for conceptual purposes, whilst in practice we
-advise handling this using the `torch_tensor_from_array` function described in 
+advise handling this using the `torch_tensor_from_array` function described in
 [3) below](#3-use-the-layout-argument-in-torch_tensor_from_array).
 
 #### 1) Transpose before passing
 As seen from the above example, writing out from Fortran and reading directly in to
-Torch results in us recieving the transpose.
+Torch results in us receiving the transpose.
 
 Therefore we could transpose our Fortran data immediately before passing it to Torch.
 As a result we will read in to Torch indexed the same as in Fortran pre-transposition.
@@ -100,7 +100,7 @@ function.
 
 For larger arrays we are required to use the
 ['reshape()'](https://gcc.gnu.org/onlinedocs/gfortran/RESHAPE.html) intrinsic to swap
-the order of the indices.  
+the order of the indices.
 For example, if we had a 3x4x5 matrix \(A\) we would need to call
 ```
 A_to_torch = reshape(A, shape=[5, 4, 3], order=[3, 2, 1])
@@ -132,20 +132,24 @@ Not doing so could leave us open to introducing bugs.
 
 By far the easiest way to deal with the issue is not to worry about it at all!
 
-As described at the top of this page, by using the
+As described at the top of this page, the
 [torch_tensor_from_array](doc/interface/torch_tensor_from_array.html) function
-we can make use of the `layout` argument.
+provides functionality for handling this through its optional `layout` argument.
 This allows us to take data from Fortran and send it to Torch to be indexed in exactly
 the same way by using strided access based on the shape of the array.
 
 It takes the form of an array specifying which order to read the indices in.
 i.e. `[1, 2]` will read `i` then `j`.
 By passing `layout = [1, 2]` the data will be read into the correct indices by
-Torch.
-
-This is achieved by wrapping the `torch_tensor_from_blob` function to automatically
-generate strides assuming that a straightforward conversion between
-row- and column-major is what should happen.
+Torch. The natural ordering `[1, 2, ..., n]` (where `n` is the dimension of the
+array) is the default used by `torch_tensor_from_array`.
+In cases where your tensors are indexed the same way in both Fortran and Torch, it
+should be sufficient to just use the default value, in which case you don't need
+to pass a `layout` argument at all.
+
+The strided access is achieved by wrapping the `torch_tensor_from_blob` function
+to automatically generate strides assuming that a straightforward conversion
+between row- and column-major is what should happen.
 
 i.e. if the Fortran array `A`
 $$
diff --git a/src/ftorch.F90 b/src/ftorch.F90
index 81013bc06..0d09eaba5 100644
--- a/src/ftorch.F90
+++ b/src/ftorch.F90
@@ -102,6 +102,36 @@ module ftorch
     module procedure torch_tensor_from_array_real64_3d
     module procedure torch_tensor_from_array_real64_4d
     module procedure torch_tensor_from_array_real64_5d
+    module procedure torch_tensor_from_array_int8_1d_default_layout
+    module procedure torch_tensor_from_array_int8_2d_default_layout
+    module procedure torch_tensor_from_array_int8_3d_default_layout
+    module procedure torch_tensor_from_array_int8_4d_default_layout
+    module procedure torch_tensor_from_array_int8_5d_default_layout
+    module procedure torch_tensor_from_array_int16_1d_default_layout
+    module procedure torch_tensor_from_array_int16_2d_default_layout
+    module procedure torch_tensor_from_array_int16_3d_default_layout
+    module procedure torch_tensor_from_array_int16_4d_default_layout
+    module procedure torch_tensor_from_array_int16_5d_default_layout
+    module procedure torch_tensor_from_array_int32_1d_default_layout
+    module procedure torch_tensor_from_array_int32_2d_default_layout
+    module procedure torch_tensor_from_array_int32_3d_default_layout
+    module procedure torch_tensor_from_array_int32_4d_default_layout
+    module procedure torch_tensor_from_array_int32_5d_default_layout
+    module procedure torch_tensor_from_array_int64_1d_default_layout
+    module procedure torch_tensor_from_array_int64_2d_default_layout
+    module procedure torch_tensor_from_array_int64_3d_default_layout
+    module procedure torch_tensor_from_array_int64_4d_default_layout
+    module procedure torch_tensor_from_array_int64_5d_default_layout
+    module procedure torch_tensor_from_array_real32_1d_default_layout
+    module procedure torch_tensor_from_array_real32_2d_default_layout
+    module procedure torch_tensor_from_array_real32_3d_default_layout
+    module procedure torch_tensor_from_array_real32_4d_default_layout
+    module procedure torch_tensor_from_array_real32_5d_default_layout
+    module procedure torch_tensor_from_array_real64_1d_default_layout
+    module procedure torch_tensor_from_array_real64_2d_default_layout
+    module procedure torch_tensor_from_array_real64_3d_default_layout
+    module procedure torch_tensor_from_array_real64_4d_default_layout
+    module procedure torch_tensor_from_array_real64_5d_default_layout
   end interface
 
   !> Interface for deleting generic torch objects
@@ -1298,6 +1328,910 @@ subroutine torch_tensor_from_array_real64_5d(tensor, data_in, layout, &
 
   end subroutine torch_tensor_from_array_real64_5d
 
+  ! TODO: Avoid the following variant of torch_tensor_from_array by making the `layout` argument
+  !       optional. The reason this has not been done already is that it would require either making
+  !       the `device_type` argument optional (which we do not want to do) or break the API.
+
+  !> Return a Torch tensor pointing to data_in array of rank 1 containing data of type `int8` with default layout [1, 2, ..., n].
+  subroutine torch_tensor_from_array_int8_1d_default_layout(tensor, data_in, &
+                                                                       device_type, device_index, &
+                                                                       requires_grad)
+    use, intrinsic :: iso_c_binding, only : c_int
+    use, intrinsic :: iso_fortran_env, only : int8
+
+    ! output tensor
+    type(torch_tensor), intent(out) :: tensor  !! Returned tensor
+
+    ! inputs
+    integer(kind=int8), intent(in), target :: data_in(:)  !! Input data that tensor will point at
+    integer(c_int), intent(in)    :: device_type    !! Device type the tensor will live on (`torch_kCPU` or `torch_kCUDA`)
+    integer, optional, intent(in) :: device_index   !! Device index to use for `torch_kCUDA` case
+    logical, optional, intent(in) :: requires_grad  !! Whether gradients need to be computed for the created tensor
+
+    ! local data
+    integer(ftorch_int)       :: layout(1)  !! Order of indices
+    integer(c_int), parameter :: ndims = 1  !! Number of dimension of input data
+    integer :: i
+
+    ! Set the default tensor layout
+    do i = 1, ndims
+      layout(i) = i
+    end do
+
+    call torch_tensor_from_array(tensor, data_in, layout, device_type, device_index, requires_grad)
+
+  end subroutine torch_tensor_from_array_int8_1d_default_layout
+
+  !> Return a Torch tensor pointing to data_in array of rank 2 containing data of type `int8` with default layout [1, 2, ..., n].
+  subroutine torch_tensor_from_array_int8_2d_default_layout(tensor, data_in, &
+                                                                       device_type, device_index, &
+                                                                       requires_grad)
+    use, intrinsic :: iso_c_binding, only : c_int
+    use, intrinsic :: iso_fortran_env, only : int8
+
+    ! output tensor
+    type(torch_tensor), intent(out) :: tensor  !! Returned tensor
+
+    ! inputs
+    integer(kind=int8), intent(in), target :: data_in(:,:)  !! Input data that tensor will point at
+    integer(c_int), intent(in)    :: device_type    !! Device type the tensor will live on (`torch_kCPU` or `torch_kCUDA`)
+    integer, optional, intent(in) :: device_index   !! Device index to use for `torch_kCUDA` case
+    logical, optional, intent(in) :: requires_grad  !! Whether gradients need to be computed for the created tensor
+
+    ! local data
+    integer(ftorch_int)       :: layout(2)  !! Order of indices
+    integer(c_int), parameter :: ndims = 2  !! Number of dimension of input data
+    integer :: i
+
+    ! Set the default tensor layout
+    do i = 1, ndims
+      layout(i) = i
+    end do
+
+    call torch_tensor_from_array(tensor, data_in, layout, device_type, device_index, requires_grad)
+
+  end subroutine torch_tensor_from_array_int8_2d_default_layout
+
+  !> Return a Torch tensor pointing to data_in array of rank 3 containing data of type `int8` with default layout [1, 2, ..., n].
+  subroutine torch_tensor_from_array_int8_3d_default_layout(tensor, data_in, &
+                                                                       device_type, device_index, &
+                                                                       requires_grad)
+    use, intrinsic :: iso_c_binding, only : c_int
+    use, intrinsic :: iso_fortran_env, only : int8
+
+    ! output tensor
+    type(torch_tensor), intent(out) :: tensor  !! Returned tensor
+
+    ! inputs
+    integer(kind=int8), intent(in), target :: data_in(:,:,:)  !! Input data that tensor will point at
+    integer(c_int), intent(in)    :: device_type    !! Device type the tensor will live on (`torch_kCPU` or `torch_kCUDA`)
+    integer, optional, intent(in) :: device_index   !! Device index to use for `torch_kCUDA` case
+    logical, optional, intent(in) :: requires_grad  !! Whether gradients need to be computed for the created tensor
+
+    ! local data
+    integer(ftorch_int)       :: layout(3)  !! Order of indices
+    integer(c_int), parameter :: ndims = 3  !! Number of dimension of input data
+    integer :: i
+
+    ! Set the default tensor layout
+    do i = 1, ndims
+      layout(i) = i
+    end do
+
+    call torch_tensor_from_array(tensor, data_in, layout, device_type, device_index, requires_grad)
+
+  end subroutine torch_tensor_from_array_int8_3d_default_layout
+
+  !> Return a Torch tensor pointing to data_in array of rank 4 containing data of type `int8` with default layout [1, 2, ..., n].
+  subroutine torch_tensor_from_array_int8_4d_default_layout(tensor, data_in, &
+                                                                       device_type, device_index, &
+                                                                       requires_grad)
+    use, intrinsic :: iso_c_binding, only : c_int
+    use, intrinsic :: iso_fortran_env, only : int8
+
+    ! output tensor
+    type(torch_tensor), intent(out) :: tensor  !! Returned tensor
+
+    ! inputs
+    integer(kind=int8), intent(in), target :: data_in(:,:,:,:)  !! Input data that tensor will point at
+    integer(c_int), intent(in)    :: device_type    !! Device type the tensor will live on (`torch_kCPU` or `torch_kCUDA`)
+    integer, optional, intent(in) :: device_index   !! Device index to use for `torch_kCUDA` case
+    logical, optional, intent(in) :: requires_grad  !! Whether gradients need to be computed for the created tensor
+
+    ! local data
+    integer(ftorch_int)       :: layout(4)  !! Order of indices
+    integer(c_int), parameter :: ndims = 4  !! Number of dimension of input data
+    integer :: i
+
+    ! Set the default tensor layout
+    do i = 1, ndims
+      layout(i) = i
+    end do
+
+    call torch_tensor_from_array(tensor, data_in, layout, device_type, device_index, requires_grad)
+
+  end subroutine torch_tensor_from_array_int8_4d_default_layout
+
+  !> Return a Torch tensor pointing to data_in array of rank 5 containing data of type `int8` with default layout [1, 2, ..., n].
+  subroutine torch_tensor_from_array_int8_5d_default_layout(tensor, data_in, &
+                                                                       device_type, device_index, &
+                                                                       requires_grad)
+    use, intrinsic :: iso_c_binding, only : c_int
+    use, intrinsic :: iso_fortran_env, only : int8
+
+    ! output tensor
+    type(torch_tensor), intent(out) :: tensor  !! Returned tensor
+
+    ! inputs
+    integer(kind=int8), intent(in), target :: data_in(:,:,:,:,:)  !! Input data that tensor will point at
+    integer(c_int), intent(in)    :: device_type    !! Device type the tensor will live on (`torch_kCPU` or `torch_kCUDA`)
+    integer, optional, intent(in) :: device_index   !! Device index to use for `torch_kCUDA` case
+    logical, optional, intent(in) :: requires_grad  !! Whether gradients need to be computed for the created tensor
+
+    ! local data
+    integer(ftorch_int)       :: layout(5)  !! Order of indices
+    integer(c_int), parameter :: ndims = 5  !! Number of dimension of input data
+    integer :: i
+
+    ! Set the default tensor layout
+    do i = 1, ndims
+      layout(i) = i
+    end do
+
+    call torch_tensor_from_array(tensor, data_in, layout, device_type, device_index, requires_grad)
+
+  end subroutine torch_tensor_from_array_int8_5d_default_layout
+
+  !> Return a Torch tensor pointing to data_in array of rank 1 containing data of type `int16` with default layout [1, 2, ..., n].
+  subroutine torch_tensor_from_array_int16_1d_default_layout(tensor, data_in, &
+                                                                       device_type, device_index, &
+                                                                       requires_grad)
+    use, intrinsic :: iso_c_binding, only : c_int
+    use, intrinsic :: iso_fortran_env, only : int16
+
+    ! output tensor
+    type(torch_tensor), intent(out) :: tensor  !! Returned tensor
+
+    ! inputs
+    integer(kind=int16), intent(in), target :: data_in(:)  !! Input data that tensor will point at
+    integer(c_int), intent(in)    :: device_type    !! Device type the tensor will live on (`torch_kCPU` or `torch_kCUDA`)
+    integer, optional, intent(in) :: device_index   !! Device index to use for `torch_kCUDA` case
+    logical, optional, intent(in) :: requires_grad  !! Whether gradients need to be computed for the created tensor
+
+    ! local data
+    integer(ftorch_int)       :: layout(1)  !! Order of indices
+    integer(c_int), parameter :: ndims = 1  !! Number of dimension of input data
+    integer :: i
+
+    ! Set the default tensor layout
+    do i = 1, ndims
+      layout(i) = i
+    end do
+
+    call torch_tensor_from_array(tensor, data_in, layout, device_type, device_index, requires_grad)
+
+  end subroutine torch_tensor_from_array_int16_1d_default_layout
+
+  !> Return a Torch tensor pointing to data_in array of rank 2 containing data of type `int16` with default layout [1, 2, ..., n].
+  subroutine torch_tensor_from_array_int16_2d_default_layout(tensor, data_in, &
+                                                                       device_type, device_index, &
+                                                                       requires_grad)
+    use, intrinsic :: iso_c_binding, only : c_int
+    use, intrinsic :: iso_fortran_env, only : int16
+
+    ! output tensor
+    type(torch_tensor), intent(out) :: tensor  !! Returned tensor
+
+    ! inputs
+    integer(kind=int16), intent(in), target :: data_in(:,:)  !! Input data that tensor will point at
+    integer(c_int), intent(in)    :: device_type    !! Device type the tensor will live on (`torch_kCPU` or `torch_kCUDA`)
+    integer, optional, intent(in) :: device_index   !! Device index to use for `torch_kCUDA` case
+    logical, optional, intent(in) :: requires_grad  !! Whether gradients need to be computed for the created tensor
+
+    ! local data
+    integer(ftorch_int)       :: layout(2)  !! Order of indices
+    integer(c_int), parameter :: ndims = 2  !! Number of dimension of input data
+    integer :: i
+
+    ! Set the default tensor layout
+    do i = 1, ndims
+      layout(i) = i
+    end do
+
+    call torch_tensor_from_array(tensor, data_in, layout, device_type, device_index, requires_grad)
+
+  end subroutine torch_tensor_from_array_int16_2d_default_layout
+
+  !> Return a Torch tensor pointing to data_in array of rank 3 containing data of type `int16` with default layout [1, 2, ..., n].
+  subroutine torch_tensor_from_array_int16_3d_default_layout(tensor, data_in, &
+                                                                       device_type, device_index, &
+                                                                       requires_grad)
+    use, intrinsic :: iso_c_binding, only : c_int
+    use, intrinsic :: iso_fortran_env, only : int16
+
+    ! output tensor
+    type(torch_tensor), intent(out) :: tensor  !! Returned tensor
+
+    ! inputs
+    integer(kind=int16), intent(in), target :: data_in(:,:,:)  !! Input data that tensor will point at
+    integer(c_int), intent(in)    :: device_type    !! Device type the tensor will live on (`torch_kCPU` or `torch_kCUDA`)
+    integer, optional, intent(in) :: device_index   !! Device index to use for `torch_kCUDA` case
+    logical, optional, intent(in) :: requires_grad  !! Whether gradients need to be computed for the created tensor
+
+    ! local data
+    integer(ftorch_int)       :: layout(3)  !! Order of indices
+    integer(c_int), parameter :: ndims = 3  !! Number of dimension of input data
+    integer :: i
+
+    ! Set the default tensor layout
+    do i = 1, ndims
+      layout(i) = i
+    end do
+
+    call torch_tensor_from_array(tensor, data_in, layout, device_type, device_index, requires_grad)
+
+  end subroutine torch_tensor_from_array_int16_3d_default_layout
+
+  !> Return a Torch tensor pointing to data_in array of rank 4 containing data of type `int16` with default layout [1, 2, ..., n].
+  subroutine torch_tensor_from_array_int16_4d_default_layout(tensor, data_in, &
+                                                                       device_type, device_index, &
+                                                                       requires_grad)
+    use, intrinsic :: iso_c_binding, only : c_int
+    use, intrinsic :: iso_fortran_env, only : int16
+
+    ! output tensor
+    type(torch_tensor), intent(out) :: tensor  !! Returned tensor
+
+    ! inputs
+    integer(kind=int16), intent(in), target :: data_in(:,:,:,:)  !! Input data that tensor will point at
+    integer(c_int), intent(in)    :: device_type    !! Device type the tensor will live on (`torch_kCPU` or `torch_kCUDA`)
+    integer, optional, intent(in) :: device_index   !! Device index to use for `torch_kCUDA` case
+    logical, optional, intent(in) :: requires_grad  !! Whether gradients need to be computed for the created tensor
+
+    ! local data
+    integer(ftorch_int)       :: layout(4)  !! Order of indices
+    integer(c_int), parameter :: ndims = 4  !! Number of dimension of input data
+    integer :: i
+
+    ! Set the default tensor layout
+    do i = 1, ndims
+      layout(i) = i
+    end do
+
+    call torch_tensor_from_array(tensor, data_in, layout, device_type, device_index, requires_grad)
+
+  end subroutine torch_tensor_from_array_int16_4d_default_layout
+
+  !> Return a Torch tensor pointing to data_in array of rank 5 containing data of type `int16` with default layout [1, 2, ..., n].
+  subroutine torch_tensor_from_array_int16_5d_default_layout(tensor, data_in, &
+                                                                       device_type, device_index, &
+                                                                       requires_grad)
+    use, intrinsic :: iso_c_binding, only : c_int
+    use, intrinsic :: iso_fortran_env, only : int16
+
+    ! output tensor
+    type(torch_tensor), intent(out) :: tensor  !! Returned tensor
+
+    ! inputs
+    integer(kind=int16), intent(in), target :: data_in(:,:,:,:,:)  !! Input data that tensor will point at
+    integer(c_int), intent(in)    :: device_type    !! Device type the tensor will live on (`torch_kCPU` or `torch_kCUDA`)
+    integer, optional, intent(in) :: device_index   !! Device index to use for `torch_kCUDA` case
+    logical, optional, intent(in) :: requires_grad  !! Whether gradients need to be computed for the created tensor
+
+    ! local data
+    integer(ftorch_int)       :: layout(5)  !! Order of indices
+    integer(c_int), parameter :: ndims = 5  !! Number of dimension of input data
+    integer :: i
+
+    ! Set the default tensor layout
+    do i = 1, ndims
+      layout(i) = i
+    end do
+
+    call torch_tensor_from_array(tensor, data_in, layout, device_type, device_index, requires_grad)
+
+  end subroutine torch_tensor_from_array_int16_5d_default_layout
+
+  !> Return a Torch tensor pointing to data_in array of rank 1 containing data of type `int32` with default layout [1, 2, ..., n].
+  subroutine torch_tensor_from_array_int32_1d_default_layout(tensor, data_in, &
+                                                                       device_type, device_index, &
+                                                                       requires_grad)
+    use, intrinsic :: iso_c_binding, only : c_int
+    use, intrinsic :: iso_fortran_env, only : int32
+
+    ! output tensor
+    type(torch_tensor), intent(out) :: tensor  !! Returned tensor
+
+    ! inputs
+    integer(kind=int32), intent(in), target :: data_in(:)  !! Input data that tensor will point at
+    integer(c_int), intent(in)    :: device_type    !! Device type the tensor will live on (`torch_kCPU` or `torch_kCUDA`)
+    integer, optional, intent(in) :: device_index   !! Device index to use for `torch_kCUDA` case
+    logical, optional, intent(in) :: requires_grad  !! Whether gradients need to be computed for the created tensor
+
+    ! local data
+    integer(ftorch_int)       :: layout(1)  !! Order of indices
+    integer(c_int), parameter :: ndims = 1  !! Number of dimension of input data
+    integer :: i
+
+    ! Set the default tensor layout
+    do i = 1, ndims
+      layout(i) = i
+    end do
+
+    call torch_tensor_from_array(tensor, data_in, layout, device_type, device_index, requires_grad)
+
+  end subroutine torch_tensor_from_array_int32_1d_default_layout
+
+  !> Return a Torch tensor pointing to data_in array of rank 2 containing data of type `int32` with default layout [1, 2, ..., n].
+  subroutine torch_tensor_from_array_int32_2d_default_layout(tensor, data_in, &
+                                                                       device_type, device_index, &
+                                                                       requires_grad)
+    use, intrinsic :: iso_c_binding, only : c_int
+    use, intrinsic :: iso_fortran_env, only : int32
+
+    ! output tensor
+    type(torch_tensor), intent(out) :: tensor  !! Returned tensor
+
+    ! inputs
+    integer(kind=int32), intent(in), target :: data_in(:,:)  !! Input data that tensor will point at
+    integer(c_int), intent(in)    :: device_type    !! Device type the tensor will live on (`torch_kCPU` or `torch_kCUDA`)
+    integer, optional, intent(in) :: device_index   !! Device index to use for `torch_kCUDA` case
+    logical, optional, intent(in) :: requires_grad  !! Whether gradients need to be computed for the created tensor
+
+    ! local data
+    integer(ftorch_int)       :: layout(2)  !! Order of indices
+    integer(c_int), parameter :: ndims = 2  !! Number of dimension of input data
+    integer :: i
+
+    ! Set the default tensor layout
+    do i = 1, ndims
+      layout(i) = i
+    end do
+
+    call torch_tensor_from_array(tensor, data_in, layout, device_type, device_index, requires_grad)
+
+  end subroutine torch_tensor_from_array_int32_2d_default_layout
+
+  !> Return a Torch tensor pointing to data_in array of rank 3 containing data of type `int32` with default layout [1, 2, ..., n].
+  subroutine torch_tensor_from_array_int32_3d_default_layout(tensor, data_in, &
+                                                                       device_type, device_index, &
+                                                                       requires_grad)
+    use, intrinsic :: iso_c_binding, only : c_int
+    use, intrinsic :: iso_fortran_env, only : int32
+
+    ! output tensor
+    type(torch_tensor), intent(out) :: tensor  !! Returned tensor
+
+    ! inputs
+    integer(kind=int32), intent(in), target :: data_in(:,:,:)  !! Input data that tensor will point at
+    integer(c_int), intent(in)    :: device_type    !! Device type the tensor will live on (`torch_kCPU` or `torch_kCUDA`)
+    integer, optional, intent(in) :: device_index   !! Device index to use for `torch_kCUDA` case
+    logical, optional, intent(in) :: requires_grad  !! Whether gradients need to be computed for the created tensor
+
+    ! local data
+    integer(ftorch_int)       :: layout(3)  !! Order of indices
+    integer(c_int), parameter :: ndims = 3  !! Number of dimension of input data
+    integer :: i
+
+    ! Set the default tensor layout
+    do i = 1, ndims
+      layout(i) = i
+    end do
+
+    call torch_tensor_from_array(tensor, data_in, layout, device_type, device_index, requires_grad)
+
+  end subroutine torch_tensor_from_array_int32_3d_default_layout
+
+  !> Return a Torch tensor pointing to data_in array of rank 4 containing data of type `int32` with default layout [1, 2, ..., n].
+  subroutine torch_tensor_from_array_int32_4d_default_layout(tensor, data_in, &
+                                                                       device_type, device_index, &
+                                                                       requires_grad)
+    use, intrinsic :: iso_c_binding, only : c_int
+    use, intrinsic :: iso_fortran_env, only : int32
+
+    ! output tensor
+    type(torch_tensor), intent(out) :: tensor  !! Returned tensor
+
+    ! inputs
+    integer(kind=int32), intent(in), target :: data_in(:,:,:,:)  !! Input data that tensor will point at
+    integer(c_int), intent(in)    :: device_type    !! Device type the tensor will live on (`torch_kCPU` or `torch_kCUDA`)
+    integer, optional, intent(in) :: device_index   !! Device index to use for `torch_kCUDA` case
+    logical, optional, intent(in) :: requires_grad  !! Whether gradients need to be computed for the created tensor
+
+    ! local data
+    integer(ftorch_int)       :: layout(4)  !! Order of indices
+    integer(c_int), parameter :: ndims = 4  !! Number of dimension of input data
+    integer :: i
+
+    ! Set the default tensor layout
+    do i = 1, ndims
+      layout(i) = i
+    end do
+
+    call torch_tensor_from_array(tensor, data_in, layout, device_type, device_index, requires_grad)
+
+  end subroutine torch_tensor_from_array_int32_4d_default_layout
+
+  !> Return a Torch tensor pointing to data_in array of rank 5 containing data of type `int32` with default layout [1, 2, ..., n].
+  subroutine torch_tensor_from_array_int32_5d_default_layout(tensor, data_in, &
+                                                                       device_type, device_index, &
+                                                                       requires_grad)
+    use, intrinsic :: iso_c_binding, only : c_int
+    use, intrinsic :: iso_fortran_env, only : int32
+
+    ! output tensor
+    type(torch_tensor), intent(out) :: tensor  !! Returned tensor
+
+    ! inputs
+    integer(kind=int32), intent(in), target :: data_in(:,:,:,:,:)  !! Input data that tensor will point at
+    integer(c_int), intent(in)    :: device_type    !! Device type the tensor will live on (`torch_kCPU` or `torch_kCUDA`)
+    integer, optional, intent(in) :: device_index   !! Device index to use for `torch_kCUDA` case
+    logical, optional, intent(in) :: requires_grad  !! Whether gradients need to be computed for the created tensor
+
+    ! local data
+    integer(ftorch_int)       :: layout(5)  !! Order of indices
+    integer(c_int), parameter :: ndims = 5  !! Number of dimension of input data
+    integer :: i
+
+    ! Set the default tensor layout
+    do i = 1, ndims
+      layout(i) = i
+    end do
+
+    call torch_tensor_from_array(tensor, data_in, layout, device_type, device_index, requires_grad)
+
+  end subroutine torch_tensor_from_array_int32_5d_default_layout
+
+  !> Return a Torch tensor pointing to data_in array of rank 1 containing data of type `int64` with default layout [1, 2, ..., n].
+  subroutine torch_tensor_from_array_int64_1d_default_layout(tensor, data_in, &
+                                                                       device_type, device_index, &
+                                                                       requires_grad)
+    use, intrinsic :: iso_c_binding, only : c_int
+    use, intrinsic :: iso_fortran_env, only : int64
+
+    ! output tensor
+    type(torch_tensor), intent(out) :: tensor  !! Returned tensor
+
+    ! inputs
+    integer(kind=int64), intent(in), target :: data_in(:)  !! Input data that tensor will point at
+    integer(c_int), intent(in)    :: device_type    !! Device type the tensor will live on (`torch_kCPU` or `torch_kCUDA`)
+    integer, optional, intent(in) :: device_index   !! Device index to use for `torch_kCUDA` case
+    logical, optional, intent(in) :: requires_grad  !! Whether gradients need to be computed for the created tensor
+
+    ! local data
+    integer(ftorch_int)       :: layout(1)  !! Order of indices
+    integer(c_int), parameter :: ndims = 1  !! Number of dimension of input data
+    integer :: i
+
+    ! Set the default tensor layout
+    do i = 1, ndims
+      layout(i) = i
+    end do
+
+    call torch_tensor_from_array(tensor, data_in, layout, device_type, device_index, requires_grad)
+
+  end subroutine torch_tensor_from_array_int64_1d_default_layout
+
+  !> Return a Torch tensor pointing to data_in array of rank 2 containing data of type `int64` with default layout [1, 2, ..., n].
+  subroutine torch_tensor_from_array_int64_2d_default_layout(tensor, data_in, &
+                                                                       device_type, device_index, &
+                                                                       requires_grad)
+    use, intrinsic :: iso_c_binding, only : c_int
+    use, intrinsic :: iso_fortran_env, only : int64
+
+    ! output tensor
+    type(torch_tensor), intent(out) :: tensor  !! Returned tensor
+
+    ! inputs
+    integer(kind=int64), intent(in), target :: data_in(:,:)  !! Input data that tensor will point at
+    integer(c_int), intent(in)    :: device_type    !! Device type the tensor will live on (`torch_kCPU` or `torch_kCUDA`)
+    integer, optional, intent(in) :: device_index   !! Device index to use for `torch_kCUDA` case
+    logical, optional, intent(in) :: requires_grad  !! Whether gradients need to be computed for the created tensor
+
+    ! local data
+    integer(ftorch_int)       :: layout(2)  !! Order of indices
+    integer(c_int), parameter :: ndims = 2  !! Number of dimension of input data
+    integer :: i
+
+    ! Set the default tensor layout
+    do i = 1, ndims
+      layout(i) = i
+    end do
+
+    call torch_tensor_from_array(tensor, data_in, layout, device_type, device_index, requires_grad)
+
+  end subroutine torch_tensor_from_array_int64_2d_default_layout
+
+  !> Return a Torch tensor pointing to data_in array of rank 3 containing data of type `int64` with default layout [1, 2, ..., n].
+  subroutine torch_tensor_from_array_int64_3d_default_layout(tensor, data_in, &
+                                                                       device_type, device_index, &
+                                                                       requires_grad)
+    use, intrinsic :: iso_c_binding, only : c_int
+    use, intrinsic :: iso_fortran_env, only : int64
+
+    ! output tensor
+    type(torch_tensor), intent(out) :: tensor  !! Returned tensor
+
+    ! inputs
+    integer(kind=int64), intent(in), target :: data_in(:,:,:)  !! Input data that tensor will point at
+    integer(c_int), intent(in)    :: device_type    !! Device type the tensor will live on (`torch_kCPU` or `torch_kCUDA`)
+    integer, optional, intent(in) :: device_index   !! Device index to use for `torch_kCUDA` case
+    logical, optional, intent(in) :: requires_grad  !! Whether gradients need to be computed for the created tensor
+
+    ! local data
+    integer(ftorch_int)       :: layout(3)  !! Order of indices
+    integer(c_int), parameter :: ndims = 3  !! Number of dimension of input data
+    integer :: i
+
+    ! Set the default tensor layout
+    do i = 1, ndims
+      layout(i) = i
+    end do
+
+    call torch_tensor_from_array(tensor, data_in, layout, device_type, device_index, requires_grad)
+
+  end subroutine torch_tensor_from_array_int64_3d_default_layout
+
+  !> Return a Torch tensor pointing to data_in array of rank 4 containing data of type `int64` with default layout [1, 2, ..., n].
+  subroutine torch_tensor_from_array_int64_4d_default_layout(tensor, data_in, &
+                                                                       device_type, device_index, &
+                                                                       requires_grad)
+    use, intrinsic :: iso_c_binding, only : c_int
+    use, intrinsic :: iso_fortran_env, only : int64
+
+    ! output tensor
+    type(torch_tensor), intent(out) :: tensor  !! Returned tensor
+
+    ! inputs
+    integer(kind=int64), intent(in), target :: data_in(:,:,:,:)  !! Input data that tensor will point at
+    integer(c_int), intent(in)    :: device_type    !! Device type the tensor will live on (`torch_kCPU` or `torch_kCUDA`)
+    integer, optional, intent(in) :: device_index   !! Device index to use for `torch_kCUDA` case
+    logical, optional, intent(in) :: requires_grad  !! Whether gradients need to be computed for the created tensor
+
+    ! local data
+    integer(ftorch_int)       :: layout(4)  !! Order of indices
+    integer(c_int), parameter :: ndims = 4  !! Number of dimension of input data
+    integer :: i
+
+    ! Set the default tensor layout
+    do i = 1, ndims
+      layout(i) = i
+    end do
+
+    call torch_tensor_from_array(tensor, data_in, layout, device_type, device_index, requires_grad)
+
+  end subroutine torch_tensor_from_array_int64_4d_default_layout
+
+  !> Return a Torch tensor pointing to data_in array of rank 5 containing data of type `int64` with default layout [1, 2, ..., n].
+  subroutine torch_tensor_from_array_int64_5d_default_layout(tensor, data_in, &
+                                                                       device_type, device_index, &
+                                                                       requires_grad)
+    use, intrinsic :: iso_c_binding, only : c_int
+    use, intrinsic :: iso_fortran_env, only : int64
+
+    ! output tensor
+    type(torch_tensor), intent(out) :: tensor  !! Returned tensor
+
+    ! inputs
+    integer(kind=int64), intent(in), target :: data_in(:,:,:,:,:)  !! Input data that tensor will point at
+    integer(c_int), intent(in)    :: device_type    !! Device type the tensor will live on (`torch_kCPU` or `torch_kCUDA`)
+    integer, optional, intent(in) :: device_index   !! Device index to use for `torch_kCUDA` case
+    logical, optional, intent(in) :: requires_grad  !! Whether gradients need to be computed for the created tensor
+
+    ! local data
+    integer(ftorch_int)       :: layout(5)  !! Order of indices
+    integer(c_int), parameter :: ndims = 5  !! Number of dimension of input data
+    integer :: i
+
+    ! Set the default tensor layout
+    do i = 1, ndims
+      layout(i) = i
+    end do
+
+    call torch_tensor_from_array(tensor, data_in, layout, device_type, device_index, requires_grad)
+
+  end subroutine torch_tensor_from_array_int64_5d_default_layout
+
+  !> Return a Torch tensor pointing to data_in array of rank 1 containing data of type `real32` with default layout [1, 2, ..., n].
+  subroutine torch_tensor_from_array_real32_1d_default_layout(tensor, data_in, &
+                                                                       device_type, device_index, &
+                                                                       requires_grad)
+    use, intrinsic :: iso_c_binding, only : c_int
+    use, intrinsic :: iso_fortran_env, only : real32
+
+    ! output tensor
+    type(torch_tensor), intent(out) :: tensor  !! Returned tensor
+
+    ! inputs
+    real(kind=real32), intent(in), target :: data_in(:)  !! Input data that tensor will point at
+    integer(c_int), intent(in)    :: device_type    !! Device type the tensor will live on (`torch_kCPU` or `torch_kCUDA`)
+    integer, optional, intent(in) :: device_index   !! Device index to use for `torch_kCUDA` case
+    logical, optional, intent(in) :: requires_grad  !! Whether gradients need to be computed for the created tensor
+
+    ! local data
+    integer(ftorch_int)       :: layout(1)  !! Order of indices
+    integer(c_int), parameter :: ndims = 1  !! Number of dimension of input data
+    integer :: i
+
+    ! Set the default tensor layout
+    do i = 1, ndims
+      layout(i) = i
+    end do
+
+    call torch_tensor_from_array(tensor, data_in, layout, device_type, device_index, requires_grad)
+
+  end subroutine torch_tensor_from_array_real32_1d_default_layout
+
+  !> Return a Torch tensor pointing to data_in array of rank 2 containing data of type `real32` with default layout [1, 2, ..., n].
+  subroutine torch_tensor_from_array_real32_2d_default_layout(tensor, data_in, &
+                                                                       device_type, device_index, &
+                                                                       requires_grad)
+    use, intrinsic :: iso_c_binding, only : c_int
+    use, intrinsic :: iso_fortran_env, only : real32
+
+    ! output tensor
+    type(torch_tensor), intent(out) :: tensor  !! Returned tensor
+
+    ! inputs
+    real(kind=real32), intent(in), target :: data_in(:,:)  !! Input data that tensor will point at
+    integer(c_int), intent(in)    :: device_type    !! Device type the tensor will live on (`torch_kCPU` or `torch_kCUDA`)
+    integer, optional, intent(in) :: device_index   !! Device index to use for `torch_kCUDA` case
+    logical, optional, intent(in) :: requires_grad  !! Whether gradients need to be computed for the created tensor
+
+    ! local data
+    integer(ftorch_int)       :: layout(2)  !! Order of indices
+    integer(c_int), parameter :: ndims = 2  !! Number of dimension of input data
+    integer :: i
+
+    ! Set the default tensor layout
+    do i = 1, ndims
+      layout(i) = i
+    end do
+
+    call torch_tensor_from_array(tensor, data_in, layout, device_type, device_index, requires_grad)
+
+  end subroutine torch_tensor_from_array_real32_2d_default_layout
+
+  !> Return a Torch tensor pointing to data_in array of rank 3 containing data of type `real32` with default layout [1, 2, ..., n].
+  subroutine torch_tensor_from_array_real32_3d_default_layout(tensor, data_in, &
+                                                                       device_type, device_index, &
+                                                                       requires_grad)
+    use, intrinsic :: iso_c_binding, only : c_int
+    use, intrinsic :: iso_fortran_env, only : real32
+
+    ! output tensor
+    type(torch_tensor), intent(out) :: tensor  !! Returned tensor
+
+    ! inputs
+    real(kind=real32), intent(in), target :: data_in(:,:,:)  !! Input data that tensor will point at
+    integer(c_int), intent(in)    :: device_type    !! Device type the tensor will live on (`torch_kCPU` or `torch_kCUDA`)
+    integer, optional, intent(in) :: device_index   !! Device index to use for `torch_kCUDA` case
+    logical, optional, intent(in) :: requires_grad  !! Whether gradients need to be computed for the created tensor
+
+    ! local data
+    integer(ftorch_int)       :: layout(3)  !! Order of indices
+    integer(c_int), parameter :: ndims = 3  !! Number of dimension of input data
+    integer :: i
+
+    ! Set the default tensor layout
+    do i = 1, ndims
+      layout(i) = i
+    end do
+
+    call torch_tensor_from_array(tensor, data_in, layout, device_type, device_index, requires_grad)
+
+  end subroutine torch_tensor_from_array_real32_3d_default_layout
+
+  !> Return a Torch tensor pointing to data_in array of rank 4 containing data of type `real32` with default layout [1, 2, ..., n].
+  subroutine torch_tensor_from_array_real32_4d_default_layout(tensor, data_in, &
+                                                                       device_type, device_index, &
+                                                                       requires_grad)
+    use, intrinsic :: iso_c_binding, only : c_int
+    use, intrinsic :: iso_fortran_env, only : real32
+
+    ! output tensor
+    type(torch_tensor), intent(out) :: tensor  !! Returned tensor
+
+    ! inputs
+    real(kind=real32), intent(in), target :: data_in(:,:,:,:)  !! Input data that tensor will point at
+    integer(c_int), intent(in)    :: device_type    !! Device type the tensor will live on (`torch_kCPU` or `torch_kCUDA`)
+    integer, optional, intent(in) :: device_index   !! Device index to use for `torch_kCUDA` case
+    logical, optional, intent(in) :: requires_grad  !! Whether gradients need to be computed for the created tensor
+
+    ! local data
+    integer(ftorch_int)       :: layout(4)  !! Order of indices
+    integer(c_int), parameter :: ndims = 4  !! Number of dimension of input data
+    integer :: i
+
+    ! Set the default tensor layout
+    do i = 1, ndims
+      layout(i) = i
+    end do
+
+    call torch_tensor_from_array(tensor, data_in, layout, device_type, device_index, requires_grad)
+
+  end subroutine torch_tensor_from_array_real32_4d_default_layout
+
+  !> Return a Torch tensor pointing to data_in array of rank 5 containing data of type `real32` with default layout [1, 2, ..., n].
+  subroutine torch_tensor_from_array_real32_5d_default_layout(tensor, data_in, &
+                                                                       device_type, device_index, &
+                                                                       requires_grad)
+    use, intrinsic :: iso_c_binding, only : c_int
+    use, intrinsic :: iso_fortran_env, only : real32
+
+    ! output tensor
+    type(torch_tensor), intent(out) :: tensor  !! Returned tensor
+
+    ! inputs
+    real(kind=real32), intent(in), target :: data_in(:,:,:,:,:)  !! Input data that tensor will point at
+    integer(c_int), intent(in)    :: device_type    !! Device type the tensor will live on (`torch_kCPU` or `torch_kCUDA`)
+    integer, optional, intent(in) :: device_index   !! Device index to use for `torch_kCUDA` case
+    logical, optional, intent(in) :: requires_grad  !! Whether gradients need to be computed for the created tensor
+
+    ! local data
+    integer(ftorch_int)       :: layout(5)  !! Order of indices
+    integer(c_int), parameter :: ndims = 5  !! Number of dimension of input data
+    integer :: i
+
+    ! Set the default tensor layout
+    do i = 1, ndims
+      layout(i) = i
+    end do
+
+    call torch_tensor_from_array(tensor, data_in, layout, device_type, device_index, requires_grad)
+
+  end subroutine torch_tensor_from_array_real32_5d_default_layout
+
+  !> Return a Torch tensor pointing to data_in array of rank 1 containing data of type `real64` with default layout [1, 2, ..., n].
+  subroutine torch_tensor_from_array_real64_1d_default_layout(tensor, data_in, &
+                                                                       device_type, device_index, &
+                                                                       requires_grad)
+    use, intrinsic :: iso_c_binding, only : c_int
+    use, intrinsic :: iso_fortran_env, only : real64
+
+    ! output tensor
+    type(torch_tensor), intent(out) :: tensor  !! Returned tensor
+
+    ! inputs
+    real(kind=real64), intent(in), target :: data_in(:)  !! Input data that tensor will point at
+    integer(c_int), intent(in)    :: device_type    !! Device type the tensor will live on (`torch_kCPU` or `torch_kCUDA`)
+    integer, optional, intent(in) :: device_index   !! Device index to use for `torch_kCUDA` case
+    logical, optional, intent(in) :: requires_grad  !! Whether gradients need to be computed for the created tensor
+
+    ! local data
+    integer(ftorch_int)       :: layout(1)  !! Order of indices
+    integer(c_int), parameter :: ndims = 1  !! Number of dimension of input data
+    integer :: i
+
+    ! Set the default tensor layout
+    do i = 1, ndims
+      layout(i) = i
+    end do
+
+    call torch_tensor_from_array(tensor, data_in, layout, device_type, device_index, requires_grad)
+
+  end subroutine torch_tensor_from_array_real64_1d_default_layout
+
+  !> Return a Torch tensor pointing to data_in array of rank 2 containing data of type `real64` with default layout [1, 2, ..., n].
+  subroutine torch_tensor_from_array_real64_2d_default_layout(tensor, data_in, &
+                                                                       device_type, device_index, &
+                                                                       requires_grad)
+    use, intrinsic :: iso_c_binding, only : c_int
+    use, intrinsic :: iso_fortran_env, only : real64
+
+    ! output tensor
+    type(torch_tensor), intent(out) :: tensor  !! Returned tensor
+
+    ! inputs
+    real(kind=real64), intent(in), target :: data_in(:,:)  !! Input data that tensor will point at
+    integer(c_int), intent(in)    :: device_type    !! Device type the tensor will live on (`torch_kCPU` or `torch_kCUDA`)
+    integer, optional, intent(in) :: device_index   !! Device index to use for `torch_kCUDA` case
+    logical, optional, intent(in) :: requires_grad  !! Whether gradients need to be computed for the created tensor
+
+    ! local data
+    integer(ftorch_int)       :: layout(2)  !! Order of indices
+    integer(c_int), parameter :: ndims = 2  !! Number of dimension of input data
+    integer :: i
+
+    ! Set the default tensor layout
+    do i = 1, ndims
+      layout(i) = i
+    end do
+
+    call torch_tensor_from_array(tensor, data_in, layout, device_type, device_index, requires_grad)
+
+  end subroutine torch_tensor_from_array_real64_2d_default_layout
+
+  !> Return a Torch tensor pointing to data_in array of rank 3 containing data of type `real64` with default layout [1, 2, ..., n].
+  subroutine torch_tensor_from_array_real64_3d_default_layout(tensor, data_in, &
+                                                                       device_type, device_index, &
+                                                                       requires_grad)
+    use, intrinsic :: iso_c_binding, only : c_int
+    use, intrinsic :: iso_fortran_env, only : real64
+
+    ! output tensor
+    type(torch_tensor), intent(out) :: tensor  !! Returned tensor
+
+    ! inputs
+    real(kind=real64), intent(in), target :: data_in(:,:,:)  !! Input data that tensor will point at
+    integer(c_int), intent(in)    :: device_type    !! Device type the tensor will live on (`torch_kCPU` or `torch_kCUDA`)
+    integer, optional, intent(in) :: device_index   !! Device index to use for `torch_kCUDA` case
+    logical, optional, intent(in) :: requires_grad  !! Whether gradients need to be computed for the created tensor
+
+    ! local data
+    integer(ftorch_int)       :: layout(3)  !! Order of indices
+    integer(c_int), parameter :: ndims = 3  !! Number of dimension of input data
+    integer :: i
+
+    ! Set the default tensor layout
+    do i = 1, ndims
+      layout(i) = i
+    end do
+
+    call torch_tensor_from_array(tensor, data_in, layout, device_type, device_index, requires_grad)
+
+  end subroutine torch_tensor_from_array_real64_3d_default_layout
+
+  !> Return a Torch tensor pointing to data_in array of rank 4 containing data of type `real64` with default layout [1, 2, ..., n].
+  subroutine torch_tensor_from_array_real64_4d_default_layout(tensor, data_in, &
+                                                                       device_type, device_index, &
+                                                                       requires_grad)
+    use, intrinsic :: iso_c_binding, only : c_int
+    use, intrinsic :: iso_fortran_env, only : real64
+
+    ! output tensor
+    type(torch_tensor), intent(out) :: tensor  !! Returned tensor
+
+    ! inputs
+    real(kind=real64), intent(in), target :: data_in(:,:,:,:)  !! Input data that tensor will point at
+    integer(c_int), intent(in)    :: device_type    !! Device type the tensor will live on (`torch_kCPU` or `torch_kCUDA`)
+    integer, optional, intent(in) :: device_index   !! Device index to use for `torch_kCUDA` case
+    logical, optional, intent(in) :: requires_grad  !! Whether gradients need to be computed for the created tensor
+
+    ! local data
+    integer(ftorch_int)       :: layout(4)  !! Order of indices
+    integer(c_int), parameter :: ndims = 4  !! Number of dimension of input data
+    integer :: i
+
+    ! Set the default tensor layout
+    do i = 1, ndims
+      layout(i) = i
+    end do
+
+    call torch_tensor_from_array(tensor, data_in, layout, device_type, device_index, requires_grad)
+
+  end subroutine torch_tensor_from_array_real64_4d_default_layout
+
+  !> Return a Torch tensor pointing to data_in array of rank 5 containing data of type `real64` with default layout [1, 2, ..., n].
+  subroutine torch_tensor_from_array_real64_5d_default_layout(tensor, data_in, &
+                                                                       device_type, device_index, &
+                                                                       requires_grad)
+    use, intrinsic :: iso_c_binding, only : c_int
+    use, intrinsic :: iso_fortran_env, only : real64
+
+    ! output tensor
+    type(torch_tensor), intent(out) :: tensor  !! Returned tensor
+
+    ! inputs
+    real(kind=real64), intent(in), target :: data_in(:,:,:,:,:)  !! Input data that tensor will point at
+    integer(c_int), intent(in)    :: device_type    !! Device type the tensor will live on (`torch_kCPU` or `torch_kCUDA`)
+    integer, optional, intent(in) :: device_index   !! Device index to use for `torch_kCUDA` case
+    logical, optional, intent(in) :: requires_grad  !! Whether gradients need to be computed for the created tensor
+
+    ! local data
+    integer(ftorch_int)       :: layout(5)  !! Order of indices
+    integer(c_int), parameter :: ndims = 5  !! Number of dimension of input data
+    integer :: i
+
+    ! Set the default tensor layout
+    do i = 1, ndims
+      layout(i) = i
+    end do
+
+    call torch_tensor_from_array(tensor, data_in, layout, device_type, device_index, requires_grad)
+
+  end subroutine torch_tensor_from_array_real64_5d_default_layout
+
 
   ! ============================================================================
   ! --- Procedures for interrogating tensors
diff --git a/src/ftorch.fypp b/src/ftorch.fypp
index 53d570142..153659255 100644
--- a/src/ftorch.fypp
+++ b/src/ftorch.fypp
@@ -96,6 +96,11 @@ module ftorch
     module procedure torch_tensor_from_array_${PREC}$_${RANK}$d
     #:endfor
     #:endfor
+    #:for PREC in PRECISIONS
+    #:for RANK in RANKS
+    module procedure torch_tensor_from_array_${PREC}$_${RANK}$d_default_layout
+    #:endfor
+    #:endfor
   end interface
 
   !> Interface for deleting generic torch objects
@@ -421,6 +426,44 @@ contains
 
   end subroutine torch_tensor_from_array_${PREC}$_${RANK}$d
 
+  #:endfor
+  #:endfor
+  ! TODO: Avoid the following variant of torch_tensor_from_array by making the `layout` argument
+  !       optional. The reason this has not been done already is that it would require either making
+  !       the `device_type` argument optional (which we do not want to do) or break the API.
+
+  #:for PREC in PRECISIONS
+  #:for RANK in RANKS
+  !> Return a Torch tensor pointing to data_in array of rank ${RANK}$ containing data of type `${PREC}$` with default layout [1, 2, ..., n].
+  subroutine torch_tensor_from_array_${PREC}$_${RANK}$d_default_layout(tensor, data_in, &
+                                                                       device_type, device_index, &
+                                                                       requires_grad)
+    use, intrinsic :: iso_c_binding, only : c_int
+    use, intrinsic :: iso_fortran_env, only : ${PREC}$
+
+    ! output tensor
+    type(torch_tensor), intent(out) :: tensor  !! Returned tensor
+
+    ! inputs
+    ${f_type(PREC)}$(kind=${PREC}$), intent(in), target :: data_in${ranksuffix(RANK)}$  !! Input data that tensor will point at
+    integer(c_int), intent(in)    :: device_type    !! Device type the tensor will live on (`torch_kCPU` or `torch_kCUDA`)
+    integer, optional, intent(in) :: device_index   !! Device index to use for `torch_kCUDA` case
+    logical, optional, intent(in) :: requires_grad  !! Whether gradients need to be computed for the created tensor
+
+    ! local data
+    integer(ftorch_int)       :: layout(${RANK}$)  !! Order of indices
+    integer(c_int), parameter :: ndims = ${RANK}$  !! Number of dimension of input data
+    integer :: i
+
+    ! Set the default tensor layout
+    do i = 1, ndims
+      layout(i) = i
+    end do
+
+    call torch_tensor_from_array(tensor, data_in, layout, device_type, device_index, requires_grad)
+
+  end subroutine torch_tensor_from_array_${PREC}$_${RANK}$d_default_layout
+
   #:endfor
   #:endfor
 
diff --git a/test/unit/test_tensor_autograd.pf b/test/unit/test_tensor_autograd.pf
index 9006d88b4..71361fd9e 100644
--- a/test/unit/test_tensor_autograd.pf
+++ b/test/unit/test_tensor_autograd.pf
@@ -25,7 +25,6 @@ module test_tensor_autograd
 
   ! All unit tests in this module use 2D arrays with the default layout and float32 precision
   integer, parameter :: ndims = 2
-  integer(ftorch_int), parameter :: tensor_layout(ndims) = [1, 2]
   integer(c_int64_t), parameter, dimension(ndims) :: tensor_shape = [2, 3]
   integer, parameter :: dtype = torch_kFloat32
 
@@ -44,7 +43,7 @@ contains
     in_data(:,:) = reshape([1.0, 2.0, 3.0, 4.0, 5.0, 6.0], [2, 3])
 
     ! Create a tensor based off an input array
-    call torch_tensor_from_array(a, in_data, tensor_layout, device_type, requires_grad=.true.)
+    call torch_tensor_from_array(a, in_data, device_type, requires_grad=.true.)
 
     ! Create another empty tensor and assign it to the first using the overloaded assignment
     ! operator
@@ -52,7 +51,7 @@ contains
     Q = a
 
     ! Create another tensor based off an output array for the gradient
-    call torch_tensor_from_array(dQda, out_data, tensor_layout, device_type)
+    call torch_tensor_from_array(dQda, out_data, device_type)
 
     ! Apply back-propagation and retrieve the gradient and check it takes the expected value:
     !   Q(a) = a => dQ/da = 1
@@ -89,7 +88,7 @@ contains
     in_data(:,:) = reshape([1.0, 2.0, 3.0, 4.0, 5.0, 6.0], [2, 3])
 
     ! Create a tensor based off an input array
-    call torch_tensor_from_array(a, in_data, tensor_layout, device_type, requires_grad=.true.)
+    call torch_tensor_from_array(a, in_data, device_type, requires_grad=.true.)
 
     ! Create another empty tensor and assign it to the first using the overloaded assignment
     ! operator
@@ -97,7 +96,7 @@ contains
     Q = a
 
     ! Create another tensor based off an output array for the gradient
-    call torch_tensor_from_array(dQda, out_data, tensor_layout, device_type)
+    call torch_tensor_from_array(dQda, out_data, device_type)
 
     ! Apply back-propagation and retrieve the gradient and check it takes the expected value:
     !   Q(a) = a => dQ/da = 1
diff --git a/test/unit/test_tensor_constructors_destructors.pf b/test/unit/test_tensor_constructors_destructors.pf
index d8a4cbee9..c9d576a7a 100644
--- a/test/unit/test_tensor_constructors_destructors.pf
+++ b/test/unit/test_tensor_constructors_destructors.pf
@@ -32,6 +32,7 @@ module test_tensor_constructors_destructors
     logical :: requires_grad  ! Value used for the requires_grad argument
     logical :: auto_delete    ! torch_tensor_delete is called when .false., otherwise the finalizer
                               !   will call it when a tensor goes out of scope
+    logical :: reordered      ! If .true., a reversed tensor layout is used
     integer :: iterations     ! Number of times to construct/destruct a tensor
   contains
     procedure :: toString
@@ -56,32 +57,38 @@ contains
   function get_parameters_destruction() result(params)
     type(TestParametersType), allocatable :: params(:)
     params = [ &
-      TestParametersType(.false.,.false.,1), &
-      TestParametersType(.false.,.false.,2), &
-      TestParametersType(.false.,.true.,1), &
-      TestParametersType(.false.,.true.,2) &
+      TestParametersType(.false.,.false.,.false.,1), &
+      TestParametersType(.false.,.false.,.false.,2), &
+      TestParametersType(.false.,.true.,.false.,1), &
+      TestParametersType(.false.,.true.,.false.,2) &
     ]
   end function get_parameters_destruction
 
-  ! A fixture comprised of a short list of parameter sets
+  ! A fixture comprised of parameter sets for varying the requires_grad argument
   function get_parameters_requires_grad() result(params)
     type(TestParametersType), allocatable :: params(:)
-    params = [TestParametersType(.false.,.false.,1)]
-    params = [TestParametersType(.true.,.false.,1)]
+    params = [ &
+      TestParametersType(.false.,.false.,.false.,1), &
+      TestParametersType(.true.,.false.,.false.,1) &
+    ]
   end function get_parameters_requires_grad
 
-  ! A fixture comprised of a short list of parameter sets
-  function get_parameters_short() result(params)
+  ! A fixture comprised of parameter sets for varying the tensor layout
+  function get_parameters_reordered() result(params)
     type(TestParametersType), allocatable :: params(:)
-    params = [TestParametersType(.false.,.false.,1)]
-  end function get_parameters_short
+    params = [ &
+      TestParametersType(.false.,.false.,.false.,1), &
+      TestParametersType(.false.,.false.,.true.,1) &
+    ]
+  end function get_parameters_reordered
 
   ! Function for representing a parameter set as a string
   function toString(this) result(string)
     class(TestParametersType), intent(in) :: this
     character(:), allocatable :: string
-    character(len=5) :: str
-    write(str,"(l1,',',l1,',',i1)") this%requires_grad, this%auto_delete, this%iterations
+    character(len=7) :: str
+    write(str,"(l1,',',l1,',',l1,',',i1)") this%requires_grad, this%auto_delete, this%reordered, &
+                                           this%iterations
     string = str
   end function toString
 
@@ -120,7 +127,6 @@ contains
     type(torch_tensor) :: tensor1, tensor2
     integer, parameter :: ndims = 2
     integer(c_int64_t), parameter :: tensor_shape(ndims) = [2, 3]
-    integer, parameter :: tensor_layout(ndims) = [1, 2]
     real(wp), dimension(2,3), target :: out_data
     real(wp), dimension(2,3) :: expected
 
@@ -135,7 +141,7 @@ contains
     @assertTrue(c_associated(tensor1%p))
 
     ! Create a tensor based off an output array
-    call torch_tensor_from_array(tensor2, out_data, tensor_layout, device_type, device_index)
+    call torch_tensor_from_array(tensor2, out_data, device_type, device_index)
 
     ! Set the values of the second tensor to match those of the first using the overloaded
     ! assignment operator
@@ -161,7 +167,6 @@ contains
     type(torch_tensor) :: tensor1, tensor2
     integer, parameter :: ndims = 2
     integer(c_int64_t), parameter :: tensor_shape(ndims) = [2, 3]
-    integer, parameter :: tensor_layout(ndims) = [1, 2]
     real(wp), dimension(2,3), target :: out_data
     real(wp), dimension(2,3) :: expected
 
@@ -176,7 +181,7 @@ contains
     @assertTrue(c_associated(tensor1%p))
 
     ! Create a tensor based off an output array
-    call torch_tensor_from_array(tensor2, out_data, tensor_layout, device_type, device_index)
+    call torch_tensor_from_array(tensor2, out_data, device_type, device_index)
 
     ! Set the values of the second tensor to match those of the first using the overloaded
     ! assignment operator
@@ -200,7 +205,6 @@ contains
     class(TestCaseType), intent(inout) :: this
     type(torch_tensor) :: tensor1, tensor2
     integer, parameter :: ndims = 1
-    integer, parameter :: tensor_layout(ndims) = [1]
     real(wp), dimension(6), target :: in_data
     real(wp), dimension(6), target :: out_data
 
@@ -211,14 +215,14 @@ contains
     @assertFalse(c_associated(tensor1%p))
 
     ! Create a tensor based off an input array
-    call torch_tensor_from_array(tensor1, in_data, tensor_layout, device_type, device_index, &
+    call torch_tensor_from_array(tensor1, in_data, device_type, device_index, &
                                  this%param%requires_grad)
 
     ! Check the tensor pointer is associated
     @assertTrue(c_associated(tensor1%p))
 
     ! Create a tensor based off an output array
-    call torch_tensor_from_array(tensor2, out_data, tensor_layout, device_type, device_index)
+    call torch_tensor_from_array(tensor2, out_data, device_type, device_index)
 
     ! Set the values of the second tensor to match those of the first using the overloaded
     ! assignment operator
@@ -233,7 +237,7 @@ contains
   end subroutine test_torch_from_array_1d
 
   ! Unit test for the torch_tensor_from_array subroutine in the 2D case
-  @test(testParameters={get_parameters_short()})
+  @test(testParameters={get_parameters_reordered()})
   subroutine test_torch_from_array_2d(this)
 
     implicit none
@@ -241,9 +245,10 @@ contains
     class(TestCaseType), intent(inout) :: this
     type(torch_tensor) :: tensor1, tensor2
     integer, parameter :: ndims = 2
-    integer, parameter :: tensor_layout(ndims) = [1, 2]
+    integer :: tensor_layout(ndims)
     real(wp), dimension(2,3), target :: in_data
     real(wp), dimension(2,3), target :: out_data
+    real(wp), dimension(2,3) :: expected
 
     ! Create an arbitrary input array
     in_data(:,:) = reshape([1.0, 2.0, 3.0, 4.0, 5.0, 6.0], [2,3])
@@ -252,6 +257,11 @@ contains
     @assertFalse(c_associated(tensor1%p))
 
     ! Create a tensor based off an input array
+    if (this%param%reordered) then
+      tensor_layout(:) = [2, 1]
+    else
+      tensor_layout(:) = [1, 2]
+    end if
     call torch_tensor_from_array(tensor1, in_data, tensor_layout, device_type, device_index, &
                                  this%param%requires_grad)
 
@@ -259,14 +269,15 @@ contains
     @assertTrue(c_associated(tensor1%p))
 
     ! Create a tensor based off an output array
-    call torch_tensor_from_array(tensor2, out_data, tensor_layout, device_type, device_index)
+    call torch_tensor_from_array(tensor2, out_data, device_type, device_index)
 
     ! Set the values of the second tensor to match those of the first using the overloaded
     ! assignment operator
     tensor2 = tensor1
 
-    ! Compare the data in the tensor to the input data
-    if (.not. assert_allclose(out_data, in_data, test_name="test_torch_tensor_from_array")) then
+    ! Compare the data in the tensor to the (reordered) input data
+    expected(:,:) = reshape(in_data, [2,3], order=tensor_layout)
+    if (.not. assert_allclose(out_data, expected, test_name="test_torch_tensor_from_array")) then
       print *, "Error :: incorrect output from torch_tensor_from_array subroutine"
       stop 999
     end if
@@ -274,7 +285,7 @@ contains
   end subroutine test_torch_from_array_2d
 
   ! Unit test for the torch_tensor_from_array subroutine in the 3D case
-  @test(testParameters={get_parameters_short()})
+  @test(testParameters={get_parameters_reordered()})
   subroutine test_torch_from_array_3d(this)
 
     implicit none
@@ -282,9 +293,10 @@ contains
     class(TestCaseType), intent(inout) :: this
     type(torch_tensor) :: tensor1, tensor2
     integer, parameter :: ndims = 3
-    integer, parameter :: tensor_layout(ndims) = [1, 2, 3]
+    integer :: tensor_layout(ndims)
     real(wp), dimension(1,2,3), target :: in_data
     real(wp), dimension(1,2,3), target :: out_data
+    real(wp), dimension(1,2,3) :: expected
 
     ! Create an arbitrary input array
     in_data(:,:,:) = reshape([1.0, 2.0, 3.0, 4.0, 5.0, 6.0], [1,2,3])
@@ -293,6 +305,11 @@ contains
     @assertFalse(c_associated(tensor1%p))
 
     ! Create a tensor based off an input array
+    if (this%param%reordered) then
+      tensor_layout(:) = [3, 2, 1]
+    else
+      tensor_layout(:) = [1, 2, 3]
+    end if
     call torch_tensor_from_array(tensor1, in_data, tensor_layout, device_type, device_index, &
                                  this%param%requires_grad)
 
@@ -300,14 +317,15 @@ contains
     @assertTrue(c_associated(tensor1%p))
 
     ! Create a tensor based off an output array
-    call torch_tensor_from_array(tensor2, out_data, tensor_layout, device_type, device_index)
+    call torch_tensor_from_array(tensor2, out_data, device_type, device_index)
 
     ! Set the values of the second tensor to match those of the first using the overloaded
     ! assignment operator
     tensor2 = tensor1
 
-    ! Compare the data in the tensor to the input data
-    if (.not. assert_allclose(out_data, in_data, test_name="test_torch_tensor_from_array")) then
+    ! Compare the data in the tensor to the (reordered) input data
+    expected(:,:,:) = reshape(in_data, [1,2,3], order=tensor_layout)
+    if (.not. assert_allclose(out_data, expected, test_name="test_torch_tensor_from_array")) then
       print *, "Error :: incorrect output from torch_tensor_from_array subroutine"
       stop 999
     end if
diff --git a/test/unit/test_tensor_manipulation.pf b/test/unit/test_tensor_manipulation.pf
index 73f7dd6d4..0c65a1621 100644
--- a/test/unit/test_tensor_manipulation.pf
+++ b/test/unit/test_tensor_manipulation.pf
@@ -27,7 +27,6 @@ contains
 
     type(torch_tensor) :: tensor
     integer, parameter :: ndims = 2
-    integer, parameter :: tensor_layout(ndims) = [1, 2]
     integer, parameter :: dtype = torch_kFloat32
     real(wp), dimension(2,3), target :: in_data
     real(wp), dimension(2,3) :: expected
@@ -37,7 +36,7 @@ contains
     in_data(:,:) = reshape([1.0, 2.0, 3.0, 4.0, 5.0, 6.0], [2, 3])
 
     ! Create a tensor based off an input array
-    call torch_tensor_from_array(tensor, in_data, tensor_layout, device_type)
+    call torch_tensor_from_array(tensor, in_data, device_type)
 
     ! Call the torch_tensor_zero subroutine using its class method alias
     call tensor%zero()
diff --git a/test/unit/test_tensor_operator_overloads.pf b/test/unit/test_tensor_operator_overloads.pf
index 1b1632e0c..1d0a87ad5 100644
--- a/test/unit/test_tensor_operator_overloads.pf
+++ b/test/unit/test_tensor_operator_overloads.pf
@@ -21,9 +21,8 @@ module test_tensor_operator_overloads
   ! All unit tests in this module run on CPU
   integer, parameter :: device_type = torch_kCPU
 
-  ! All unit tests in this module use 2D arrays with the default layout and float32 precision
+  ! All unit tests in this module use 2D arrays with float32 precision
   integer, parameter :: ndims = 2
-  integer, parameter :: tensor_layout(ndims) = [1, 2]
   integer, parameter :: dtype = torch_kFloat32
 
   ! Typedef holding a set of parameter values
@@ -87,10 +86,10 @@ contains
     in_data(:,:) = reshape([1.0, 2.0, 3.0, 4.0, 5.0, 6.0], [2, 3])
 
     ! Create a tensor based off an input array
-    call torch_tensor_from_array(tensor1, in_data, tensor_layout, device_type)
+    call torch_tensor_from_array(tensor1, in_data, device_type)
 
     ! Create another tensor based off an output array
-    call torch_tensor_from_array(tensor2, out_data, tensor_layout, device_type)
+    call torch_tensor_from_array(tensor2, out_data, device_type)
 
     ! Create another tensor by copying the first using the overloaded assignment operator
     tensor2 = tensor1
@@ -126,11 +125,11 @@ contains
     in_data2(:,:) = reshape([7.0, 8.0, 9.0, 10.0, 11.0, 12.0], [2, 3])
 
     ! Create tensors based off the two input arrays
-    call torch_tensor_from_array(tensor1, in_data1, tensor_layout, device_type)
-    call torch_tensor_from_array(tensor2, in_data2, tensor_layout, device_type)
+    call torch_tensor_from_array(tensor1, in_data1, device_type)
+    call torch_tensor_from_array(tensor2, in_data2, device_type)
 
     ! Create another tensor based off an output array
-    call torch_tensor_from_array(tensor3, out_data, tensor_layout, device_type)
+    call torch_tensor_from_array(tensor3, out_data, device_type)
 
     ! Create another tensor as the sum of the first two using the overloaded addition operator
     tensor3 = tensor1 + tensor2
@@ -172,11 +171,11 @@ contains
     in_data2(:,:) = reshape([7.0, 8.0, 9.0, 10.0, 11.0, 12.0], [2, 3])
 
     ! Create tensors based off the two input arrays
-    call torch_tensor_from_array(tensor1, in_data1, tensor_layout, device_type)
-    call torch_tensor_from_array(tensor2, in_data2, tensor_layout, device_type)
+    call torch_tensor_from_array(tensor1, in_data1, device_type)
+    call torch_tensor_from_array(tensor2, in_data2, device_type)
 
     ! Create another tensor based off an output array
-    call torch_tensor_from_array(tensor3, out_data, tensor_layout, device_type)
+    call torch_tensor_from_array(tensor3, out_data, device_type)
 
     ! Create another tensor as the difference of the first two using the overloaded subtraction
     ! operator
@@ -218,10 +217,10 @@ contains
     in_data(:,:) = reshape([1.0, 2.0, 3.0, 4.0, 5.0, 6.0], [2, 3])
 
     ! Create tensors based off the input array
-    call torch_tensor_from_array(tensor1, in_data, tensor_layout, device_type)
+    call torch_tensor_from_array(tensor1, in_data, device_type)
 
     ! Create another tensor based off an output array
-    call torch_tensor_from_array(tensor2, out_data, tensor_layout, device_type)
+    call torch_tensor_from_array(tensor2, out_data, device_type)
 
     ! Create another tensor as the negative of the first using the overloaded negative operator
     tensor2 = -tensor1
@@ -258,11 +257,11 @@ contains
     in_data2(:,:) = reshape([7.0, 8.0, 9.0, 10.0, 11.0, 12.0], [2, 3])
 
     ! Create tensors based off the two input arrays
-    call torch_tensor_from_array(tensor1, in_data1, tensor_layout, device_type)
-    call torch_tensor_from_array(tensor2, in_data2, tensor_layout, device_type)
+    call torch_tensor_from_array(tensor1, in_data1, device_type)
+    call torch_tensor_from_array(tensor2, in_data2, device_type)
 
     ! Create another tensor based off an output array
-    call torch_tensor_from_array(tensor3, out_data, tensor_layout, device_type)
+    call torch_tensor_from_array(tensor3, out_data, device_type)
 
     ! Create another tensor as the product of the first two using the overloaded multiplication
     ! operator
@@ -305,13 +304,13 @@ contains
     in_data(:,:) = reshape([1.0, 2.0, 3.0, 4.0, 5.0, 6.0], [2, 3])
 
     ! Create a tensor based off the input array
-    call torch_tensor_from_array(tensor1, in_data, tensor_layout, device_type)
+    call torch_tensor_from_array(tensor1, in_data, device_type)
 
     ! Create a rank-1 tensor based off the scalar multiplier
     call torch_tensor_from_array(multiplier, [scalar], [1], device_type)
 
     ! Create another tensor based off an output array
-    call torch_tensor_from_array(tensor2, out_data, tensor_layout, device_type)
+    call torch_tensor_from_array(tensor2, out_data, device_type)
     if (this%param%switch) then
       tensor2 = multiplier * tensor1
     else
@@ -351,11 +350,11 @@ contains
     in_data2(:,:) = reshape([7.0, 8.0, 9.0, 10.0, 11.0, 12.0], [2, 3])
 
     ! Create tensors based off the two input arrays
-    call torch_tensor_from_array(tensor1, in_data1, tensor_layout, device_type)
-    call torch_tensor_from_array(tensor2, in_data2, tensor_layout, device_type)
+    call torch_tensor_from_array(tensor1, in_data1, device_type)
+    call torch_tensor_from_array(tensor2, in_data2, device_type)
 
     ! Create another tensor based off an output array
-    call torch_tensor_from_array(tensor3, out_data, tensor_layout, device_type)
+    call torch_tensor_from_array(tensor3, out_data, device_type)
 
     ! Create another tensor as the quotient of the first two using the overloaded division operator
     tensor3 = tensor1 / tensor2
@@ -400,13 +399,13 @@ contains
     call torch_tensor_from_array(divisor, [scalar], [1], device_type)
 
     ! Create a tensor based off the input array
-    call torch_tensor_from_array(tensor1, in_data, tensor_layout, device_type)
+    call torch_tensor_from_array(tensor1, in_data, device_type)
 
     ! Create a rank-1 tensor based off the scalar divisor
     call torch_tensor_from_array(divisor, [scalar], [1], device_type)
 
     ! Create another tensor based off an output array
-    call torch_tensor_from_array(tensor2, out_data, tensor_layout, device_type)
+    call torch_tensor_from_array(tensor2, out_data, device_type)
 
     ! Create another tensor as the quotient of the first tensor and a scalar constant using the
     ! overloaded division operator
@@ -444,10 +443,10 @@ contains
     in_data(:,:) = reshape([1.0, 2.0, 3.0, 4.0, 5.0, 6.0], [2, 3])
 
     ! Create a tensor based off the input array
-    call torch_tensor_from_array(tensor1, in_data, tensor_layout, device_type)
+    call torch_tensor_from_array(tensor1, in_data, device_type)
 
     ! Create another tensor based off an output array
-    call torch_tensor_from_array(tensor2, out_data, tensor_layout, device_type)
+    call torch_tensor_from_array(tensor2, out_data, device_type)
 
     ! Create another tensor as the first tensor to the power of an exponent using the
     ! overloaded exponentiation operator
@@ -488,10 +487,10 @@ contains
     in_data(:,:) = reshape([1.0, 2.0, 3.0, 4.0, 5.0, 6.0], [2, 3])
 
     ! Create a tensor based off the input array
-    call torch_tensor_from_array(tensor1, in_data, tensor_layout, device_type)
+    call torch_tensor_from_array(tensor1, in_data, device_type)
 
     ! Create another tensor based off an output array
-    call torch_tensor_from_array(tensor2, out_data, tensor_layout, device_type)
+    call torch_tensor_from_array(tensor2, out_data, device_type)
 
     ! Create another tensors as the tensor to the power of 0.5 using the overloaded exponentiation
     ! operator
diff --git a/test/unit/test_tensor_operator_overloads_autograd.pf b/test/unit/test_tensor_operator_overloads_autograd.pf
index 59b1838c8..542df3f7d 100644
--- a/test/unit/test_tensor_operator_overloads_autograd.pf
+++ b/test/unit/test_tensor_operator_overloads_autograd.pf
@@ -24,9 +24,9 @@ module test_tensor_operator_overloads_autograd
   ! All unit tests in this module run on CPU
   integer, parameter :: device_type = torch_kCPU
 
-  ! All unit tests in this module use 2x3 arrays with the default layout and float32 precision
+  ! All unit tests in this module use 2x3 arrays with float32 precision
   integer, parameter :: ndims = 2
-  integer, parameter :: tensor_layout(ndims) = [1, 2]
+  integer(c_int64_t), parameter, dimension(1) :: scalar_shape = [1]
   integer(c_int64_t), parameter, dimension(ndims) :: tensor_shape = [2, 3]
   integer, parameter :: dtype = torch_kFloat32
 
@@ -91,7 +91,7 @@ contains
     in_data(:,:) = reshape([1.0, 2.0, 3.0, 4.0, 5.0, 6.0], [2, 3])
 
     ! Create a tensor based off an input array
-    call torch_tensor_from_array(a, in_data, tensor_layout, device_type, requires_grad=.true.)
+    call torch_tensor_from_array(a, in_data, device_type, requires_grad=.true.)
 
     ! Create another empty tensor and assign it to the first using the overloaded assignment
     ! operator
@@ -102,7 +102,7 @@ contains
     call torch_tensor_backward(Q)
 
     ! Create another tensor based off an output array for the gradient and then retrieve it
-    call torch_tensor_from_array(dQda, out_data, tensor_layout, device_type)
+    call torch_tensor_from_array(dQda, out_data, device_type)
     call torch_tensor_get_gradient(a, dQda)
 
     ! Check the computed gradient takes the expected value:
@@ -131,8 +131,8 @@ contains
     in_data2(:,:) = reshape([7.0, 8.0, 9.0, 10.0, 11.0, 12.0], [2, 3])
 
     ! Create tensor based off input arrays
-    call torch_tensor_from_array(a, in_data1, tensor_layout, device_type, requires_grad=.true.)
-    call torch_tensor_from_array(b, in_data2, tensor_layout, device_type, requires_grad=.true.)
+    call torch_tensor_from_array(a, in_data1, device_type, requires_grad=.true.)
+    call torch_tensor_from_array(b, in_data2, device_type, requires_grad=.true.)
 
     ! Create another empty tensor and assign it to the sum of the first two using the overloaded
     ! addition operator
@@ -143,8 +143,8 @@ contains
     call torch_tensor_backward(Q)
 
     ! Create tensors based off output arrays for the gradient and then retrieve them
-    call torch_tensor_from_array(dQda, out_data1, tensor_layout, device_type)
-    call torch_tensor_from_array(dQdb, out_data2, tensor_layout, device_type)
+    call torch_tensor_from_array(dQda, out_data1, device_type)
+    call torch_tensor_from_array(dQdb, out_data2, device_type)
     call torch_tensor_get_gradient(a, dQda)
     call torch_tensor_get_gradient(b, dQdb)
 
@@ -181,7 +181,7 @@ contains
     in_data(:,:) = reshape([1.0, 2.0, 3.0, 4.0, 5.0, 6.0], [2, 3])
 
     ! Create a tensor based off the input array
-    call torch_tensor_from_array(a, in_data, tensor_layout, device_type, requires_grad=.true.)
+    call torch_tensor_from_array(a, in_data, device_type, requires_grad=.true.)
 
     ! Create another empty tensor and assign it to the negative of the first one using the
     ! overloaded negation operator
@@ -192,7 +192,7 @@ contains
     call torch_tensor_backward(Q)
 
     ! Create another tensor based off an output array for the gradient and then retrieve it
-    call torch_tensor_from_array(dQda, out_data, tensor_layout, device_type)
+    call torch_tensor_from_array(dQda, out_data, device_type)
     call torch_tensor_get_gradient(a, dQda)
 
     ! Extract Fortran array from the computed gradient and its data with the expected value:
@@ -221,8 +221,8 @@ contains
     in_data2(:,:) = reshape([7.0, 8.0, 9.0, 10.0, 11.0, 12.0], [2, 3])
 
     ! Create tensor based off input arrays
-    call torch_tensor_from_array(a, in_data1, tensor_layout, device_type, requires_grad=.true.)
-    call torch_tensor_from_array(b, in_data2, tensor_layout, device_type, requires_grad=.true.)
+    call torch_tensor_from_array(a, in_data1, device_type, requires_grad=.true.)
+    call torch_tensor_from_array(b, in_data2, device_type, requires_grad=.true.)
 
     ! Create another empty tensor and assign it to the difference of the first two using the
     ! overloaded subtraction operator
@@ -233,8 +233,8 @@ contains
     call torch_tensor_backward(Q)
 
     ! Create tensors based off output arrays for the gradient and then retrieve them
-    call torch_tensor_from_array(dQda, out_data1, tensor_layout, device_type)
-    call torch_tensor_from_array(dQdb, out_data2, tensor_layout, device_type)
+    call torch_tensor_from_array(dQda, out_data1, device_type)
+    call torch_tensor_from_array(dQdb, out_data2, device_type)
     call torch_tensor_get_gradient(a, dQda)
     call torch_tensor_get_gradient(b, dQdb)
 
@@ -272,8 +272,8 @@ contains
     in_data2(:,:) = reshape([7.0, 8.0, 9.0, 10.0, 11.0, 12.0], [2, 3])
 
     ! Create tensor based off input arrays
-    call torch_tensor_from_array(a, in_data1, tensor_layout, device_type, requires_grad=.true.)
-    call torch_tensor_from_array(b, in_data2, tensor_layout, device_type, requires_grad=.true.)
+    call torch_tensor_from_array(a, in_data1, device_type, requires_grad=.true.)
+    call torch_tensor_from_array(b, in_data2, device_type, requires_grad=.true.)
 
     ! Create another empty tensor and assign it to the product of the first two using the
     ! overloaded multiplication operator
@@ -284,8 +284,8 @@ contains
     call torch_tensor_backward(Q)
 
     ! Create tensors based off output arrays for the gradient and then retrieve them
-    call torch_tensor_from_array(dQda, out_data1, tensor_layout, device_type)
-    call torch_tensor_from_array(dQdb, out_data2, tensor_layout, device_type)
+    call torch_tensor_from_array(dQda, out_data1, device_type)
+    call torch_tensor_from_array(dQdb, out_data2, device_type)
     call torch_tensor_get_gradient(a, dQda)
     call torch_tensor_get_gradient(b, dQdb)
 
@@ -324,10 +324,10 @@ contains
     in_data(:,:) = reshape([1.0, 2.0, 3.0, 4.0, 5.0, 6.0], [2, 3])
 
     ! Create tensor based off input array
-    call torch_tensor_from_array(a, in_data, tensor_layout, device_type, requires_grad=.true.)
+    call torch_tensor_from_array(a, in_data, device_type, requires_grad=.true.)
 
     ! Create rank-1 tensor based off scalar
-    call torch_tensor_from_array(multiplier, [scalar], tensor_layout, device_type)
+    call torch_tensor_from_array(multiplier, [scalar], device_type)
 
     ! Create another empty tensors and assign it to the product of a scalar constant and the first
     ! tensor using the overloaded multiplication operator
@@ -342,7 +342,7 @@ contains
     call torch_tensor_backward(Q)
 
     ! Create another tensor based off an output array for the gradient and then retrieve it
-    call torch_tensor_from_array(dQda, out_data, tensor_layout, device_type)
+    call torch_tensor_from_array(dQda, out_data, device_type)
     call torch_tensor_get_gradient(a, dQda)
 
     ! Extract Fortran array from the first computed gradient and its data with the expected value:
@@ -372,8 +372,8 @@ contains
     in_data2(:,:) = reshape([7.0, 8.0, 9.0, 10.0, 11.0, 12.0], [2, 3])
 
     ! Create tensor based off input arrays
-    call torch_tensor_from_array(a, in_data1, tensor_layout, device_type, requires_grad=.true.)
-    call torch_tensor_from_array(b, in_data2, tensor_layout, device_type, requires_grad=.true.)
+    call torch_tensor_from_array(a, in_data1, device_type, requires_grad=.true.)
+    call torch_tensor_from_array(b, in_data2, device_type, requires_grad=.true.)
 
     ! Create another empty tensor and assign it to the quotient of the first two using the
     ! overloaded division operator
@@ -384,8 +384,8 @@ contains
     call torch_tensor_backward(Q)
 
     ! Create tensors based off output arrays for the gradient and then retrieve them
-    call torch_tensor_from_array(dQda, out_data1, tensor_layout, device_type)
-    call torch_tensor_from_array(dQdb, out_data2, tensor_layout, device_type)
+    call torch_tensor_from_array(dQda, out_data1, device_type)
+    call torch_tensor_from_array(dQdb, out_data2, device_type)
     call torch_tensor_get_gradient(a, dQda)
     call torch_tensor_get_gradient(b, dQdb)
 
@@ -424,10 +424,10 @@ contains
     in_data(:,:) = reshape([1.0, 2.0, 3.0, 4.0, 5.0, 6.0], [2, 3])
 
     ! Create tensor based off input array
-    call torch_tensor_from_array(a, in_data, tensor_layout, device_type, requires_grad=.true.)
+    call torch_tensor_from_array(a, in_data, device_type, requires_grad=.true.)
 
     ! Create rank-1 tensor based off scalar
-    call torch_tensor_from_array(divisor, [scalar], tensor_layout, device_type)
+    call torch_tensor_from_array(divisor, [scalar], device_type)
 
     ! Create another empty tensors and assign it to the product of a scalar constant and the first
     ! tensor using the overloaded multiplication operator
@@ -438,7 +438,7 @@ contains
     call torch_tensor_backward(Q)
 
     ! Create another tensor based off an output array for the gradient and then retrieve it
-    call torch_tensor_from_array(dQda, out_data, tensor_layout, device_type)
+    call torch_tensor_from_array(dQda, out_data, device_type)
     call torch_tensor_get_gradient(a, dQda)
 
     ! Extract Fortran array from the first computed gradient and its data with the expected value:
@@ -467,7 +467,7 @@ contains
     in_data(:,:) = reshape([1.0, 2.0, 3.0, 4.0, 5.0, 6.0], [2, 3])
 
     ! Create a tensor based off the input array
-    call torch_tensor_from_array(a, in_data, tensor_layout, device_type, requires_grad=.true.)
+    call torch_tensor_from_array(a, in_data, device_type, requires_grad=.true.)
 
     ! Create another empty tensor and assign it to the square of the first one using the
     ! overloaded power operator
@@ -482,7 +482,7 @@ contains
     call torch_tensor_backward(Q)
 
     ! Create another tensor based off an output array for the gradient and then retrieve it
-    call torch_tensor_from_array(dQda, out_data, tensor_layout, device_type)
+    call torch_tensor_from_array(dQda, out_data, device_type)
     call torch_tensor_get_gradient(a, dQda)
 
     ! Extract Fortran array from the computed gradient and its data with the expected value:
@@ -510,7 +510,7 @@ contains
     in_data(:,:) = reshape([1.0, 2.0, 3.0, 4.0, 5.0, 6.0], [2, 3])
 
     ! Create a tensor based off the input array
-    call torch_tensor_from_array(a, in_data, tensor_layout, device_type, requires_grad=.true.)
+    call torch_tensor_from_array(a, in_data, device_type, requires_grad=.true.)
 
     ! Create another empty tensor and assign it to the square root of the first one using the
     ! overloaded power operator
@@ -521,7 +521,7 @@ contains
     call torch_tensor_backward(Q)
 
     ! Create another tensor based off an output array for the gradient and then retrieve it
-    call torch_tensor_from_array(dQda, out_data, tensor_layout, device_type)
+    call torch_tensor_from_array(dQda, out_data, device_type)
     call torch_tensor_get_gradient(a, dQda)
 
     ! Extract Fortran array from the computed gradient and its data with the expected value:


Reviews:
user: login: jatkinson1000
body: After some reflection I think this is the least objectionable approach 😝 
Looking at the examples after the change the interface for tensor creation is much cleaner.

Just a couple of docs/phrasing suggestions.
In particular amending the "when to transpose" to say you do not need to worry as long as your python and Fortran use the same layout at code level (not memory level).
I always find this hard to explain clearly.

To summarise other things from discussion:

- We should perhaps add a list of things to change in v2.0, with tensor_layout becoming an optional argument being one. I see you opened #349 
- This, or something very close, warrants a minor release.
- The growth of tensor creation routines is another nudge that we should look at breaking apart into smaller modules, as in #320 for optimizers, but this is for a different PR.
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-04-01T15:21:29Z
user: login: joewallwork
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-01T15:35:58Z
user: login: jatkinson1000
body: Thanks @jwallwork23 this is great.

2 Small phrasing suggestions up to you.
And a rebase should resolve the workflow problems.
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-02T10:38:41Z

Review comments:
diff_hunk: @@ -32,7 +32,10 @@ include:
   meaning the array must have been declared with the `target` property. The
   array will continue to be pointed to even when operations are applied to the
   tensor, so this subroutine can be used 'in advance' to set up an array for
-  outputting data.
+  outputting data. `torch_tensor_from_array` may be called with or without the
+  `layout` argument - an array which specifies the order in which indices should
+  be looped over. The default `layout` is `[1,2,...,n]`, i.e., the natural
+  ordering in Fortran.
path: pages/tensor.md
user: login: jatkinson1000
body: I think this needs a little more, as it relates to how the tensor is used by a row-major language.

An mxn tensor in Fortran will also be read as an mxn tensor in Lib/PyTorch, assuming default layout. The sentence here reads to me that the choice is somehow Fortran dependent and isn't super clear.
created_at: 2025-04-01T13:22:24Z
author_association: MEMBER
diff_hunk: @@ -32,7 +32,10 @@ include:
   meaning the array must have been declared with the `target` property. The
   array will continue to be pointed to even when operations are applied to the
   tensor, so this subroutine can be used 'in advance' to set up an array for
-  outputting data.
+  outputting data. `torch_tensor_from_array` may be called with or without the
+  `layout` argument - an array which specifies the order in which indices should
+  be looped over. The default `layout` is `[1,2,...,n]`, i.e., the natural
+  ordering in Fortran.
path: pages/tensor.md
user: login: jatkinson1000
body: Consider also adding to `pages/transposing.md` and/or referencing from here.
created_at: 2025-04-01T13:25:57Z
author_association: MEMBER
diff_hunk: @@ -32,7 +32,10 @@ include:
   meaning the array must have been declared with the `target` property. The
   array will continue to be pointed to even when operations are applied to the
   tensor, so this subroutine can be used 'in advance' to set up an array for
-  outputting data.
+  outputting data. `torch_tensor_from_array` may be called with or without the
+  `layout` argument - an array which specifies the order in which indices should
+  be looped over. The default `layout` is `[1,2,...,n]`, i.e., the natural
+  ordering in Fortran.
path: pages/tensor.md
user: login: joewallwork
body: How about what I put in 15524d69fff8dbad448423db8054579314ea48ff?
created_at: 2025-04-01T15:35:58Z
author_association: COLLABORATOR
diff_hunk: @@ -32,7 +32,11 @@ include:
   meaning the array must have been declared with the `target` property. The
   array will continue to be pointed to even when operations are applied to the
   tensor, so this subroutine can be used 'in advance' to set up an array for
-  outputting data.
+  outputting data. `torch_tensor_from_array` may be called with or without the
+  `layout` argument - an array which specifies the order in which indices should
+  be looped over. The default `layout` is `[1,2,...,n]` implies that data will
+  be read into the correct indices by Torch. (See the
path: pages/tensor.md
user: login: jatkinson1000
body: Similarly, I feel "correct" could be ambiguous depending on how the reader feel's this should be implemented, perhaps "same"?
```suggestion
  be read into the same indices by Torch. (See the
```
created_at: 2025-04-02T10:32:41Z
author_association: MEMBER
diff_hunk: @@ -132,20 +132,23 @@ Not doing so could leave us open to introducing bugs.
 
 By far the easiest way to deal with the issue is not to worry about it at all!
 
-As described at the top of this page, by using the
+As described at the top of this page, the
 [torch_tensor_from_array](doc/interface/torch_tensor_from_array.html) function
-we can make use of the `layout` argument.
+provides functionality for handling this through its optional `layout` argument.
 This allows us to take data from Fortran and send it to Torch to be indexed in exactly
 the same way by using strided access based on the shape of the array.
 
 It takes the form of an array specifying which order to read the indices in.
 i.e. `[1, 2]` will read `i` then `j`.
 By passing `layout = [1, 2]` the data will be read into the correct indices by
-Torch.
-
-This is achieved by wrapping the `torch_tensor_from_blob` function to automatically
-generate strides assuming that a straightforward conversion between
-row- and column-major is what should happen.
+Torch. The natural ordering `[1, 2, ..., n]` (where `n` is the dimension of the
+array) is the default used by `torch_tensor_from_array`. In most cases, it
path: pages/transposing.md
user: login: jatkinson1000
body: ```suggestion
array) is the default used by `torch_tensor_from_array`.
In cases where your tensors are indexed the same way in both Fortran and Torch, it
```
created_at: 2025-04-02T10:37:28Z
author_association: MEMBER

PR comments:
user: login: joewallwork
created_at: 2025-04-01T15:36:39Z
body: Thanks very much for the review!

>     * This, or something very close, warrants a minor release.

Agreed. Would that be v1.1?
 
>     * The growth of tensor creation routines is another nudge that we should look at breaking apart into smaller modules, as in [Add optimisers #320](https://github.com/Cambridge-ICCS/FTorch/pull/320) for optimizers, but this is for a different PR.

Yeah, I agree with this, too.

author_association: COLLABORATOR
user: login: joewallwork
created_at: 2025-04-01T15:45:16Z
body: Hm, it's odd that this pFUnit error sometimes happens and sometimes doesn't. Given that it passed before the last commit, which was docs-only, this PR does pass the test suite.
author_association: COLLABORATOR
user: login: joewallwork
created_at: 2025-04-02T13:20:36Z
body: > 2 Small phrasing suggestions up to you.

Thanks, both good suggestions.
author_association: COLLABORATOR