Repository: stfc/PSyclone
Pull request #2959 (2025-04-17 11:16:06)

Update spack and enable NEMO multi-gpu
This PR updates the integration test spack to bring a more recent version of nvfortran (24.11) which is the same as in the MetOffice Azure. The new compiler has the advantage that:
- LFRic does not need patching anymore
- Nemo works with gpu and multi-gpu (ORCA1 is now multigpu to allow multiple mpi ranks to do IO)

however, it has some negatives:
- Nemo scripts have to exclude more files (particularly for BENCH) - but at least we are now tracking them




Patch:
diff --git a/doc/developer_guide/index.rst b/doc/developer_guide/index.rst
index a8cf03c9a..3e68696ed 100644
--- a/doc/developer_guide/index.rst
+++ b/doc/developer_guide/index.rst
@@ -3,18 +3,25 @@ Developer Guide
 ===============
 
 .. toctree::
-   :maxdepth: 3
+    :maxdepth: 2
+    :caption: Working Practices
 
-   working_practises
-   psyir
-   psyir_symbols
-   psyir_backends
-   psykal
-   module_manager
-   APIs
-   dependency
-   sympy
-   transformations
-   psy_data
-   system_specific_setup
-   coding-style
+    working_practises
+    system_specific_setup
+    coding-style
+    integration-test
+
+.. toctree::
+    :maxdepth: 2
+    :caption: Implementation details
+
+    psyir
+    psyir_symbols
+    psyir_backends
+    psykal
+    module_manager
+    APIs
+    dependency
+    sympy
+    transformations
+    psy_data
diff --git a/examples/gocean/eg2/acc_transform.py b/examples/gocean/eg2/acc_transform.py
index 4c18086e3..f42d0d5c3 100644
--- a/examples/gocean/eg2/acc_transform.py
+++ b/examples/gocean/eg2/acc_transform.py
@@ -41,7 +41,8 @@ to have them compiled for an OpenACC accelerator. '''
 from psyclone.domain.common.transformations import KernelModuleInlineTrans
 from psyclone.psyir.nodes import Loop
 from psyclone.transformations import (
-    ACCParallelTrans, ACCEnterDataTrans, ACCLoopTrans, ACCRoutineTrans)
+    ACCParallelTrans, ACCEnterDataTrans, ACCLoopTrans, ACCRoutineTrans,
+    TransformationError)
 
 
 def trans(psyir):
@@ -60,6 +61,16 @@ def trans(psyir):
     for schedule in psyir.children[0].children:
         if schedule.name == 'invoke_0_inc_field':
 
+            # Put an 'acc routine' directive inside each kernel
+            try:
+                for kern in schedule.coded_kernels():
+                    ktrans.apply(kern)
+                    itrans.apply(kern)
+            except TransformationError:
+                # TODO #2856: Currenly we refuse to offload code containing
+                # the REAL intrinsic
+                continue
+
             # Apply the OpenACC Loop transformation to *every* loop
             # nest in the schedule
             for child in schedule.children:
@@ -71,8 +82,3 @@ def trans(psyir):
 
             # Add an enter-data directive
             dtrans.apply(schedule)
-
-            # Put an 'acc routine' directive inside each kernel
-            for kern in schedule.coded_kernels():
-                ktrans.apply(kern)
-                itrans.apply(kern)
diff --git a/examples/lfric/scripts/KGOs/lfric_nvidia.patch b/examples/lfric/scripts/KGOs/lfric_nvidia.patch
deleted file mode 100644
index 4cfb299e9..000000000
--- a/examples/lfric/scripts/KGOs/lfric_nvidia.patch
+++ /dev/null
@@ -1,661 +0,0 @@
-diff --git a/lfric/components/driver/source/driver_mesh_mod.f90 b/lfric/components/driver/source/driver_mesh_mod.f90
-index 2237d181..f075436d 100644
---- a/lfric/components/driver/source/driver_mesh_mod.f90
-+++ b/lfric/components/driver/source/driver_mesh_mod.f90
-@@ -168,7 +168,8 @@ subroutine init_mesh( configuration,           &
-   !============================================================================
-   if (present(alt_names)) then
-     if (size(alt_names) == size(mesh_names)) then
--      allocate(names, source=alt_names)
-+      !allocate(names, source=alt_names)
-+      names=alt_names
-     else
-       write(log_scratch_space, '(A)')                   &
-           'Specified alternative mesh names to does '// &
-@@ -176,7 +177,8 @@ subroutine init_mesh( configuration,           &
-       call log_event(log_scratch_space, LOG_LEVEL_ERROR)
-     end if
-   else
--    allocate(names, source=mesh_names)
-+    !allocate(names, source=mesh_names)
-+    names=mesh_names
-   end if
- 
- 
-diff --git a/lfric/infrastructure/build/tools/configurator/templates/loader.f90.jinja b/lfric/infrastructure/build/tools/configurator/templates/loader.f90.jinja
-index fbd03f17..ba9f035f 100644
---- a/lfric/infrastructure/build/tools/configurator/templates/loader.f90.jinja
-+++ b/lfric/infrastructure/build/tools/configurator/templates/loader.f90.jinja
-@@ -27,7 +27,8 @@ module {{moduleName}}
- {{' '*indent}}{{listname}}_is_loaded, &
- {{' '*indent}}{{listname}}_reset_load_status, &
- {{' '*indent}}{{listname}}_final, &
--{{' '*indent}}get_{{listname}}_nml
-+{{' '*indent}}get_{{listname}}_nml, &
-+{{' '*indent}}get_{{listname}}_nml2
- {%-   endfor %}
- {%- endif %}
- 
-@@ -192,7 +193,10 @@ contains
- 
-     type(namelist_collection_type), intent(inout) :: nml_bank
- 
--    type(namelist_type) :: nml_obj
-+    ! FS#35515
-+{%- for listname in namelists %}
-+    type(namelist_type), save :: nml_obj_{{listname}}
-+{%- endfor %}
- 
-     integer(i_def) :: i, j
- 
-@@ -225,8 +229,9 @@ contains
-             call read_{{listname}}_namelist( unit, local_rank, scan )
-             if (.not. scan) then
-               call postprocess_{{listname}}_namelist()
--              nml_obj = get_{{listname}}_nml()
--              call nml_bank%add_namelist(nml_obj)
-+              !nml_obj = get_{{listname}}_nml()
-+              !call nml_bank%add_namelist(nml_obj)
-+              call get_{{listname}}_nml2(nml_obj_{{listname}},nml_bank)
-             end if
-           else
-             write( log_scratch_space, '(A)' )      &
-diff --git a/lfric/infrastructure/build/tools/configurator/templates/namelist.f90.jinja b/lfric/infrastructure/build/tools/configurator/templates/namelist.f90.jinja
-index d95e764d..a9d2b9ec 100644
---- a/lfric/infrastructure/build/tools/configurator/templates/namelist.f90.jinja
-+++ b/lfric/infrastructure/build/tools/configurator/templates/namelist.f90.jinja
-@@ -17,6 +17,7 @@ module {{listname}}_config_mod
- 
-   use namelist_mod,      only: namelist_type
-   use namelist_item_mod, only: namelist_item_type
-+  use namelist_collection_mod, only: namelist_collection_type
- 
- {%- for module, symbols in use_from | dictsort %}
- {%-   if loop.first %}{{'\n'}}{% endif %}
-@@ -34,7 +35,7 @@ module {{listname}}_config_mod
- {{' '*12}}{{listname}}_is_loadable, {{listname}}_is_loaded, &
- {{' '*12}}{{listname}}_reset_load_status, &
- {{' '*12}}{{listname}}_multiples_allowed, {{listname}}_final, &
--{{' '*12}}get_{{listname}}_nml
-+{{' '*12}}get_{{listname}}_nml, get_{{listname}}_nml2
- 
- {%- for name in enumerations | sort %}
- {%-   if loop.first %}{{'\n'}}{%- endif %}
-@@ -401,12 +402,21 @@ contains
-     type(namelist_type)      :: namelist_obj
-     type(namelist_item_type) :: members({{parameters|length}})
- 
-+    ! FS#35515
-+    allocate(namelist_obj%members({{parameters|length}}))
-+{%- for name, parameter in parameters | dictsort %}
-+{%-   if loop.first %}{{'\n'}}{% endif %}
-+      call namelist_obj%members({{loop.index}})%initialise( &
-+                  '{{parameter.name}}', {{parameter.name}} )
-+{{- '\n'}}
-+{%- endfor %}
- {%- for name, parameter in parameters | dictsort %}
- {%-   if loop.first %}{{'\n'}}{% endif %}
-       call members({{loop.index}})%initialise( &
-                   '{{parameter.name}}', {{parameter.name}} )
- {{- '\n'}}
- {%- endfor %}
-+
-     if (trim(profile_name) /= trim(cmdi) ) then
-       call namelist_obj%initialise( trim(listname), &
-                                     members, &
-@@ -418,6 +428,44 @@ contains
- 
-   end function get_{{listname}}_nml
- 
-+  !> @brief Returns a <<namelist_type>> object populated with the
-+  !>        current contents of this configuration module.
-+  !> @return namelist_obj <<namelist_type>> with current namelist contents.
-+  subroutine get_{{listname}}_nml2(namelist_obj,nml_bank)
-+
-+    implicit none
-+
-+    type(namelist_type)      :: namelist_obj
-+    type(namelist_collection_type) :: nml_bank
-+    type(namelist_item_type) :: members({{parameters|length}})
-+
-+    ! FS#35515
-+    allocate(namelist_obj%members({{parameters|length}}))
-+{%- for name, parameter in parameters | dictsort %}
-+{%-   if loop.first %}{{'\n'}}{% endif %}
-+      call namelist_obj%members({{loop.index}})%initialise( &
-+                  '{{parameter.name}}', {{parameter.name}} )
-+{{- '\n'}}
-+{%- endfor %}
-+{%- for name, parameter in parameters | dictsort %}
-+{%-   if loop.first %}{{'\n'}}{% endif %}
-+      call members({{loop.index}})%initialise( &
-+                  '{{parameter.name}}', {{parameter.name}} )
-+{{- '\n'}}
-+{%- endfor %}
-+
-+    if (trim(profile_name) /= trim(cmdi) ) then
-+      call namelist_obj%initialise( trim(listname), &
-+                                    members, &
-+                                    profile_name = profile_name )
-+    else
-+      call namelist_obj%initialise( trim(listname), &
-+                                    members )
-+    end if
-+    call nml_bank%add_namelist(namelist_obj)
-+
-+  end subroutine get_{{listname}}_nml2
-+
- 
-   !> Performs any processing to be done once all namelists are loaded
-   !>
-diff --git a/lfric/infrastructure/build/update_mod.sh b/lfric/infrastructure/build/update_mod.sh
-new file mode 100644
-index 00000000..4ed8f129
---- /dev/null
-+++ b/lfric/infrastructure/build/update_mod.sh
-@@ -0,0 +1,9 @@
-+#!/bin/bash
-+
-+for f in $(find $1 -type f); do
-+    bname=`basename $f`
-+    if [ ! -f $2/$bname ] || [[ `diff <(sed '3d' $1/$bname) <(sed '3d' $2/$bname)` ]]; then
-+        echo "Updating $bname"
-+        cp $1/$bname $2/$bname
-+    fi
-+done
-diff --git a/lfric/infrastructure/source/configuration/namelist_collection_mod.f90 b/lfric/infrastructure/source/configuration/namelist_collection_mod.f90
-index c52f67fc..885922fc 100644
---- a/lfric/infrastructure/source/configuration/namelist_collection_mod.f90
-+++ b/lfric/infrastructure/source/configuration/namelist_collection_mod.f90
-@@ -60,6 +60,7 @@ module namelist_collection_mod
-     procedure, public :: get_name
-     procedure, public :: namelist_exists
-     procedure, public :: get_namelist_names
-+    procedure, public :: print_namelists
-     procedure, public :: get_namelist_profiles
-     procedure, public :: get_n_namelists
-     procedure, public :: get_table_len
-@@ -69,7 +70,8 @@ module namelist_collection_mod
- 
-     procedure, public :: clear
- 
--    final :: namelist_collection_destructor
-+    ! FS#35515
-+    ! final :: namelist_collection_destructor
- 
-   end type namelist_collection_type
- 
-@@ -402,6 +404,62 @@ function get_name( self ) result( name )
-   name = self%name
- 
- end function get_name
-+subroutine print_namelists( self)
-+
-+  implicit none
-+
-+  class(namelist_collection_type), intent(in) :: self
-+
-+  character(str_def) :: name_to_add
-+
-+  type(namelist_type), pointer :: namelist_obj => null()
-+
-+  ! Pointer to linked list - used for looping through the list
-+  type(linked_list_item_type), pointer :: loop => null()
-+
-+  integer(i_def) :: i
-+  integer(i_def) :: n_lists
-+
-+  n_lists = self%get_n_namelists()
-+
-+  if (n_lists > 1) then
-+
-+    do i=0, self%table_len-1
-+
-+      loop => self%namelist_list(i)%get_head()
-+
-+      do while ( associated(loop) )
-+
-+        ! Loop over all items in this list
-+        if ( associated(loop) ) then
-+
-+          select type( payload => loop%payload )
-+          type is ( namelist_type )
-+            namelist_obj => payload
-+
-+            name_to_add = trim(namelist_obj%get_full_name())
-+            print *, name_to_add
-+            call namelist_obj%print()
-+          end select
-+
-+          loop => loop%next
-+        else
-+          loop => loop%next
-+        end if
-+      end do
-+    end do
-+
-+  else
-+    write( log_scratch_space, '(A)')              &
-+        'Namelist collection '//trim(self%name)// &
-+        ' is empty.'
-+    call log_event( log_scratch_space, LOG_LEVEL_ERROR )
-+  end if
-+
-+  nullify(namelist_obj)
-+
-+  return
-+end subroutine print_namelists
- 
- 
- !> @brief Queries the unique names of the namelists in the collection.
-diff --git a/lfric/infrastructure/source/configuration/namelist_item_mod.f90 b/lfric/infrastructure/source/configuration/namelist_item_mod.f90
-index f1c607e9..03b76ff1 100644
---- a/lfric/infrastructure/source/configuration/namelist_item_mod.f90
-+++ b/lfric/infrastructure/source/configuration/namelist_item_mod.f90
-@@ -62,8 +62,6 @@ module namelist_item_mod
-   !=========================================
-   type :: namelist_item_type
- 
--    private
--
-     class(key_value_type), allocatable :: key_value_pair
- 
-   contains
-diff --git a/lfric/infrastructure/source/configuration/namelist_mod.F90 b/lfric/infrastructure/source/configuration/namelist_mod.F90
-index 5ddacf53..bad1d81a 100644
---- a/lfric/infrastructure/source/configuration/namelist_mod.F90
-+++ b/lfric/infrastructure/source/configuration/namelist_mod.F90
-@@ -23,6 +23,13 @@ module namelist_mod
-                                   LOG_LEVEL_INFO
-   use constants_mod,        only: imdi, rmdi, cmdi, str_def, i_def
-   use namelist_item_mod,    only: namelist_item_type
-+  use key_value_mod,   only: key_value_type,                                   &
-+                            int32_key_value_type,     int64_key_value_type,        &
-+                            int32_arr_key_value_type, int64_arr_key_value_type,    &
-+                            real32_key_value_type,    real64_key_value_type,        &
-+                            real32_arr_key_value_type,real64_arr_key_value_type,    &
-+                            logical_key_value_type, logical_arr_key_value_type,&
-+                            str_key_value_type,     str_arr_key_value_type
- 
-   implicit none
- 
-@@ -35,7 +42,8 @@ module namelist_mod
-   !=========================================
-   type, extends(linked_list_data_type) :: namelist_type
- 
--    private
-+    ! FS#35515
-+    ! private
- 
-     character(:), allocatable :: listname
-     character(str_def) :: profile_name = trim(cmdi)
-@@ -65,6 +73,7 @@ module namelist_mod
-     procedure :: get_listname
-     procedure :: get_profile_name
-     procedure :: get_full_name
-+    procedure :: print => namelist_print
- 
-     generic   :: get_value => get_int32_value,   get_int32_arr_value,   &
-                               get_int64_value,   get_int64_arr_value,   &
-@@ -90,6 +99,52 @@ contains
-   ! 1.0 Constructors
-   !=========================================
- 
-+  subroutine namelist_print(self)
-+
-+    implicit none
-+
-+    class(namelist_type),     intent(inout) :: self
-+
-+    integer(i_def) :: i, j
-+    character(:), allocatable :: key
-+
-+    if ( allocated(self%members) ) then
-+      print *, "  ", "number of members", size(self%members)
-+      do j=1, size(self%members)
-+        key = self%members(j)%get_key()
-+        select type( clay => self%members(j)%key_value_pair )
-+        class is (int32_key_value_type)
-+          print *, trim(key), clay%value
-+        class is (int64_key_value_type)
-+          print *, trim(key), clay%value
-+        class is (real32_key_value_type)
-+          print *, trim(key), clay%value
-+        class is (real64_key_value_type)
-+          print *, trim(key), clay%value
-+        class is (logical_key_value_type)
-+          print *, trim(key), clay%value
-+        class is (str_key_value_type)
-+          print *, trim(key), trim(clay%value)
-+        class is (int32_arr_key_value_type)
-+          print *, trim(key), clay%value
-+        class is (int64_arr_key_value_type)
-+          print *, trim(key), clay%value
-+        class is (real32_arr_key_value_type)
-+          print *, trim(key), clay%value
-+        class is (real64_arr_key_value_type)
-+          print *, trim(key), clay%value
-+        class is (logical_arr_key_value_type)
-+          print *, trim(key), clay%value
-+        class is (str_arr_key_value_type)
-+          print *, trim(key), clay%value
-+        end select
-+
-+      end do
-+
-+    else
-+      print *, "  ", "<empty>"
-+    end if
-+  end subroutine
-   !------------------------------------------
-   ! 1.4 GENERIC namelist constructor
-   !============================================================================
-diff --git a/lfric/infrastructure/source/field/field_mod.t90 b/lfric/infrastructure/source/field/field_mod.t90
-index 777ec479..0001fc8e 100644
---- a/lfric/infrastructure/source/field/field_mod.t90
-+++ b/lfric/infrastructure/source/field/field_mod.t90
-@@ -86,6 +86,7 @@ module field_{{kind}}_mod
-     !> Function to get a proxy with public pointers to the data in a
-     !! field_type.
-     procedure, public :: get_proxy
-+    procedure, private :: get_proxy2
- 
-     !> Setter for the field write method
-     procedure, public :: set_write_behaviour
-@@ -226,18 +227,39 @@ contains
-   !>
-   !> @return The proxy type with public pointers to the elements of
-   !> field_type
-+  subroutine get_proxy2(self, proxy)
-+    implicit none
-+    class(field_{{kind}}_type), target, intent(in)  :: self
-+    type(field_{{kind}}_proxy_type), intent(inout) :: proxy
-+    type(field_{{kind}}_type), pointer    :: sself
-+
-+    ! Call the routine that initialises the proxy for data held in the parent
-+    sself => self
-+    call sself%field_parent_proxy_initialiser(proxy)
-+
-+    if (allocated(sself%data))then
-+      proxy%data => sself%data
-+    else
-+      ! Fields can alternatively point to bespoke data
-+      proxy%data => sself%override_data
-+    end if
-+
-+  end subroutine get_proxy2
-+
-   type(field_{{kind}}_proxy_type) function get_proxy(self)
-     implicit none
-     class(field_{{kind}}_type), target, intent(in)  :: self
-+    type(field_{{kind}}_type), pointer    :: sself
- 
-     ! Call the routine that initialises the proxy for data held in the parent
--    call self%field_parent_proxy_initialiser(get_proxy)
-+    sself => self
-+    call sself%field_parent_proxy_initialiser(get_proxy)
- 
--    if (allocated(self%data))then
--      get_proxy%data => self%data
-+    if (allocated(sself%data))then
-+      get_proxy%data => sself%data
-     else
-       ! Fields can alternatively point to bespoke data
--      get_proxy%data => self%override_data
-+      get_proxy%data => sself%override_data
-     end if
- 
-   end function get_proxy
-@@ -260,7 +282,7 @@ contains
- 
-     implicit none
- 
--    class(field_{{kind}}_type), intent(inout)        :: self
-+    class(field_{{kind}}_type), intent(inout), target        :: self
-     type(function_space_type), pointer, intent(in) :: vector_space
-     character(*), optional, intent(in)             :: name
-     {{type}}({{kind}}), target, optional, intent(in)     :: override_data( : )
-@@ -275,6 +297,8 @@ contains
-     ! Depth of halo to allocate data array
-     integer(i_def) :: field_halo_depth
- 
-+    type(field_{{kind}}_type), pointer    :: sself
-+
-     if ( present(name) ) then
-       local_name = name
-     else
-@@ -295,7 +319,8 @@ contains
-     ! re-initialisation
-     call field_{{kind}}_destructor_scalar(self)
- 
--    call self%field_parent_initialiser(vector_space, &
-+    sself => self
-+    call sself%field_parent_initialiser(vector_space, &
-                                        name=local_name, &
-                                        fortran_type={{type}}_type, &
-                                        fortran_kind={{kind}}, &
-@@ -622,18 +647,21 @@ contains
- 
-     implicit none
- 
--    class(field_{{kind}}_type),   intent(in) :: this
-+    class(field_{{kind}}_type),   intent(in), target :: this
-     character(len=*), optional, intent(in) :: field_name
-+    type(field_{{kind}}_type), pointer :: tthis
- 
-     character(str_def) :: name_used
-+    type(field_{{kind}}_proxy_type) :: proxy
- 
-+    tthis => this
-     if (present(field_name)) then
- 
-       name_used = field_name
- 
-     else
- 
--      name_used = this%get_name()
-+      name_used = tthis%get_name()
-       if (name_used == name_none) &
-         call log_event( 'Attempt to write field with undefined name', LOG_LEVEL_ERROR )
- 
-@@ -641,7 +669,8 @@ contains
- 
-     if (associated(this%write_method)) then
- 
--      call this%write_method(trim(name_used), this%get_proxy())
-+      call tthis%get_proxy2(proxy)
-+      call tthis%write_method(name_used, proxy)
- 
-     else
- 
-diff --git a/lfric/infrastructure/source/key_value/key_value_collection_mod.f90 b/lfric/infrastructure/source/key_value/key_value_collection_mod.f90
-index 324c466c..c8eec92b 100644
---- a/lfric/infrastructure/source/key_value/key_value_collection_mod.f90
-+++ b/lfric/infrastructure/source/key_value/key_value_collection_mod.f90
-@@ -178,7 +178,7 @@ subroutine create_key_value_object( self, key, value )
- 
-   instance => create_key_value( key, value )
-   call self%add_key_value_object( instance )
--  deallocate( instance )
-+  !deallocate( instance )
- 
- end subroutine create_key_value_object
- 
-@@ -800,7 +800,7 @@ subroutine get_abstract_value(self, key, value)
-     ! 'cast' to the data type
-     select type(listitem => loop%payload)
-       class is (abstract_key_value_type)
--      if ( trim(key) == trim(listitem%get_key()) ) then
-+      if ( key == listitem%get_key() ) then
-           value => listitem%value
-           exit
-       end if
-@@ -867,7 +867,9 @@ function get_hash(self, key) result(hash)
-   class(key_value_collection_type), intent(in) :: self
-   character(*), intent(in) :: key
-   integer(i_def) :: hash
--  hash = mod(sum_string(trim(key)),self%get_table_len())
-+  character(len=100) :: key2
-+  key2 = key
-+  hash = mod(sum_string(trim(key2)),self%get_table_len())
- end function get_hash
- 
- !> Clears all items from the collection
-diff --git a/lfric/infrastructure/source/key_value/key_value_mod.f90 b/lfric/infrastructure/source/key_value/key_value_mod.f90
-index 4abfbb8f..0a693548 100644
---- a/lfric/infrastructure/source/key_value/key_value_mod.f90
-+++ b/lfric/infrastructure/source/key_value/key_value_mod.f90
-@@ -48,7 +48,6 @@ module key_value_mod
-   !=========================================
-   type, abstract, extends(linked_list_data_type) :: key_value_type
- 
--    private
-     character(:), allocatable :: key
- 
-   contains
-@@ -271,7 +270,8 @@ subroutine key_value_initialise( self, key )
-         'Type already initialised as ' // trim(self%key)
-     call log_event( log_scratch_space, LOG_LEVEL_ERROR )
-   else
--    allocate( self%key, source=trim(key) )
-+    !allocate( self%key, source=trim(key) )
-+    self%key = key
-   end if
- 
-   return
-@@ -283,11 +283,12 @@ function get_key_value_key( self ) result( key )
- 
-   implicit none
- 
--  class(key_value_type), intent(in) :: self
-+  class(key_value_type), target, intent(in) :: self
- 
--  character(:), allocatable :: key
-+  character(:), pointer :: key
- 
--  allocate( key, source=trim(self%key) )
-+  !allocate( key, source=trim(self%key) )
-+  key => self%key
- 
-   return
- end function get_key_value_key
-diff --git a/lfric_apps/applications/gravity_wave/source/driver/gravity_wave_infrastructure_mod.f90 b/lfric_apps/applications/gravity_wave/source/driver/gravity_wave_infrastructure_mod.f90
-index 672a462f..10ccfa13 100644
---- a/lfric_apps/applications/gravity_wave/source/driver/gravity_wave_infrastructure_mod.f90
-+++ b/lfric_apps/applications/gravity_wave/source/driver/gravity_wave_infrastructure_mod.f90
-@@ -204,7 +204,8 @@ contains
-                     extrusion, stencil_depth,     &
-                     apply_partition_check )
- 
--    allocate( twod_names, source=base_mesh_names )
-+    !allocate( twod_names, source=base_mesh_names )
-+    twod_names = base_mesh_names
-     do i=1, size(twod_names)
-       twod_names(i) = trim(twod_names(i))//'_2d'
-     end do
-diff --git a/lfric_apps/applications/shallow_water/source/driver/shallow_water_model_mod.F90 b/lfric_apps/applications/shallow_water/source/driver/shallow_water_model_mod.F90
-index 670e2225..5c5aaaeb 100644
---- a/lfric_apps/applications/shallow_water/source/driver/shallow_water_model_mod.F90
-+++ b/lfric_apps/applications/shallow_water/source/driver/shallow_water_model_mod.F90
-@@ -189,7 +189,7 @@ module shallow_water_model_mod
-                     stencil_depth, check_partitions )
- 
- 
--    allocate( twod_names, source=base_mesh_names )
-+    twod_names = base_mesh_names
-     do i=1, size(twod_names)
-       twod_names(i) = trim(twod_names(i))//'_2d'
-     end do
-diff --git a/lfric_apps/applications/transport/source/driver/transport_driver_mod.f90 b/lfric_apps/applications/transport/source/driver/transport_driver_mod.f90
-index 54507eec..4b3859c6 100644
---- a/lfric_apps/applications/transport/source/driver/transport_driver_mod.f90
-+++ b/lfric_apps/applications/transport/source/driver/transport_driver_mod.f90
-@@ -228,11 +228,12 @@ contains
- 
-     ! 1.1b Meshes the require a shifted extrusion
-     ! ---------------------------------------------------------
--    allocate(meshes_to_shift,  source=base_mesh_names)
-+    !allocate(meshes_to_shift,  source=base_mesh_names)
-+    meshes_to_shift = base_mesh_names
- 
-     ! 1.1c Meshes that require a double-level extrusion
-     ! ---------------------------------------------------------
--    allocate(meshes_to_double, source=base_mesh_names)
-+    meshes_to_double = base_mesh_names
- 
- 
-     !=======================================================================
-@@ -255,7 +256,7 @@ contains
-                                                   number_of_layers, &
-                                                   PRIME_EXTRUSION ) )
- 
--    allocate( twod_names, source=base_mesh_names )
-+    twod_names = base_mesh_names
-     do i=1, size(twod_names)
-       twod_names(i) = trim(twod_names(i))//'_2d'
-     end do
-@@ -309,7 +310,7 @@ contains
-     if (allocated(meshes_to_shift)) then
-       if (size(meshes_to_shift) > 0) then
- 
--        allocate( shifted_names, source=meshes_to_shift )
-+        shifted_names = meshes_to_shift
-         do i=1, size(shifted_names)
-           shifted_names(i) = trim(shifted_names(i))//'_shifted'
-         end do
-@@ -326,7 +327,7 @@ contains
-     if (allocated(meshes_to_double)) then
-       if (size(meshes_to_double) > 0) then
- 
--        allocate( double_names, source=meshes_to_double )
-+        double_names = meshes_to_double
-         do i=1, size(double_names)
-           double_names(i) = trim(double_names(i))//'_double'
-         end do
-diff --git a/lfric_apps/science/gungho/source/driver/gungho_model_mod.F90 b/lfric_apps/science/gungho/source/driver/gungho_model_mod.F90
-index 0ffb6dde..f136b599 100644
---- a/lfric_apps/science/gungho/source/driver/gungho_model_mod.F90
-+++ b/lfric_apps/science/gungho/source/driver/gungho_model_mod.F90
-@@ -603,7 +603,8 @@ contains
-                     apply_partition_check )
- 
- 
--    allocate( twod_names, source=base_mesh_names )
-+    !allocate( twod_names, source=base_mesh_names )
-+    twod_names = base_mesh_names
-     do i=1, size(twod_names)
-       twod_names(i) = trim(twod_names(i))//'_2d'
-     end do
-@@ -617,7 +618,8 @@ contains
-     if (allocated(meshes_to_shift)) then
-       if (size(meshes_to_shift) > 0) then
- 
--        allocate( shifted_names, source=meshes_to_shift )
-+        !allocate( shifted_names, source=meshes_to_shift )
-+        shifted_names = meshes_to_shift
-         do i=1, size(shifted_names)
-           shifted_names(i) = trim(shifted_names(i))//'_shifted'
-         end do
-@@ -634,7 +636,8 @@ contains
-     if (allocated(meshes_to_double)) then
-       if (size(meshes_to_double) > 0) then
- 
--        allocate( double_names, source=meshes_to_double )
-+        !allocate( double_names, source=meshes_to_double )
-+        double_names = meshes_to_double
-         do i=1, size(double_names)
-           double_names(i) = trim(double_names(i))//'_double'
-         end do
-diff --git a/lfric_apps/science/um_physics_interface/source/psy/psykal_lite_phys_mod.F90 b/lfric_apps/science/um_physics_interface/source/psy/psykal_lite_phys_mod.F90
-index ef6d8f4b..a8167ff2 100644
---- a/lfric_apps/science/um_physics_interface/source/psy/psykal_lite_phys_mod.F90
-+++ b/lfric_apps/science/um_physics_interface/source/psy/psykal_lite_phys_mod.F90
-@@ -216,7 +216,7 @@ contains
-   ! Add arrays my_coeff_rad & my_phi_stph by hand
-   real(kind=r_def), intent(in), dimension(:,:) :: coeffc_phase
-   real(kind=r_def), intent(in), dimension(:,:) :: coeffs_phase
--  integer(kind=i_def), parameter :: nranks_array = rank(coeffc_phase)
-+  integer(kind=i_def), parameter :: nranks_array = 2
-   integer(kind=i_def), dimension(nranks_array) :: dims_array
- 
-   ! Get the upper bound for each rank of each scalar array
diff --git a/examples/lfric/scripts/KGOs/nvidia_lfric_core.patch b/examples/lfric/scripts/KGOs/nvidia_lfric_core.patch
new file mode 100644
index 000000000..fe4b61231
--- /dev/null
+++ b/examples/lfric/scripts/KGOs/nvidia_lfric_core.patch
@@ -0,0 +1,24 @@
+Index: components/driver/source/io_context_collection_mod.f90
+===================================================================
+--- components/driver/source/io_context_collection_mod.f90	(revision 52772)
++++ components/driver/source/io_context_collection_mod.f90	(working copy)
+@@ -44,7 +44,6 @@
+     procedure, public :: context_exists
+     procedure, public :: get_table_len
+     procedure, public :: clear
+-    final :: destructor
+ 
+   end type io_context_collection_type
+ 
+Index: infrastructure/build/fortran/nvfortran.mk
+===================================================================
+--- infrastructure/build/fortran/nvfortran.mk	(revision 52772)
++++ infrastructure/build/fortran/nvfortran.mk	(working copy)
+@@ -42,5 +42,4 @@
+ 	LDFLAGS_OPENMP = -mp
+ endif
+ 
+-FPP = nvfortran -F
+-FPPFLAGS = -P -D__NVCOMPILER
+\ No newline at end of file
++FPPFLAGS = -P -D__NVCOMPILER
diff --git a/examples/nemo/scripts/KGOs/run.stat.orca1.nvhpc.10steps b/examples/nemo/scripts/KGOs/run.stat.orca1.nvhpc.10steps
index 0e5b47364..8fb6f86f3 100644
--- a/examples/nemo/scripts/KGOs/run.stat.orca1.nvhpc.10steps
+++ b/examples/nemo/scripts/KGOs/run.stat.orca1.nvhpc.10steps
@@ -1,10 +1,10 @@
  it :       1    |ssh|_max:  0.2916211875218087D+01 |U|_max:  0.9981163300069769D+00 |V|_max:  0.1307725327848167D+01 S_min:  0.4662006718575353D+01 S_max:  0.4113683127178150D+02
- it :       2    |ssh|_max:  0.2993687776643657D+01 |U|_max:  0.2222546780479133D+01 |V|_max:  0.2356596334062006D+01 S_min:  0.4676370204123349D+01 S_max:  0.4113676679086335D+02
- it :       3    |ssh|_max:  0.2980914460894080D+01 |U|_max:  0.3209483567663414D+01 |V|_max:  0.2754499501399275D+01 S_min:  0.4689947532502443D+01 S_max:  0.4113668748287516D+02
- it :       4    |ssh|_max:  0.2931506876273262D+01 |U|_max:  0.4144909991453730D+01 |V|_max:  0.2975812579592136D+01 S_min:  0.4699047798840489D+01 S_max:  0.4113662256133599D+02
- it :       5    |ssh|_max:  0.2722453056197456D+01 |U|_max:  0.4767435607496263D+01 |V|_max:  0.3687030466427947D+01 S_min:  0.4706627021626255D+01 S_max:  0.4113654818608597D+02
- it :       6    |ssh|_max:  0.2646796570389673D+01 |U|_max:  0.5207612464375176D+01 |V|_max:  0.3717499845111119D+01 S_min:  0.4713059832415338D+01 S_max:  0.4113648388708570D+02
- it :       7    |ssh|_max:  0.2567591158968733D+01 |U|_max:  0.5134450014154115D+01 |V|_max:  0.3696139950895641D+01 S_min:  0.4718670148787920D+01 S_max:  0.4113641268478865D+02
- it :       8    |ssh|_max:  0.2405462273409237D+01 |U|_max:  0.5097657179971666D+01 |V|_max:  0.3337026635793594D+01 S_min:  0.4723834038866872D+01 S_max:  0.4113635084308526D+02
- it :       9    |ssh|_max:  0.2786981129335129D+01 |U|_max:  0.4730664948687216D+01 |V|_max:  0.3157690707425841D+01 S_min:  0.4727376010231853D+01 S_max:  0.4113628337011738D+02
- it :      10    |ssh|_max:  0.2788274028393792D+01 |U|_max:  0.4406279755214312D+01 |V|_max:  0.3292925965364787D+01 S_min:  0.4730630269756457D+01 S_max:  0.4113622442765696D+02
+ it :       2    |ssh|_max:  0.2993687784161748D+01 |U|_max:  0.2222546780505595D+01 |V|_max:  0.2356596334082419D+01 S_min:  0.4676370208981596D+01 S_max:  0.4113676679086334D+02
+ it :       3    |ssh|_max:  0.2980914470480548D+01 |U|_max:  0.3209483574116028D+01 |V|_max:  0.2754499501649306D+01 S_min:  0.4689947538986199D+01 S_max:  0.4113668748285953D+02
+ it :       4    |ssh|_max:  0.2931506882470331D+01 |U|_max:  0.4144909990365217D+01 |V|_max:  0.2975812579508291D+01 S_min:  0.4699047801599650D+01 S_max:  0.4113662256134601D+02
+ it :       5    |ssh|_max:  0.2722453058758575D+01 |U|_max:  0.4767435612169635D+01 |V|_max:  0.3687030466409389D+01 S_min:  0.4706627021589049D+01 S_max:  0.4113654818608338D+02
+ it :       6    |ssh|_max:  0.2646796565438226D+01 |U|_max:  0.5207612464182856D+01 |V|_max:  0.3717499844921252D+01 S_min:  0.4713059827493960D+01 S_max:  0.4113648388709610D+02
+ it :       7    |ssh|_max:  0.2567591150969884D+01 |U|_max:  0.5134450017254206D+01 |V|_max:  0.3696139950768055D+01 S_min:  0.4718670142570327D+01 S_max:  0.4113641268479495D+02
+ it :       8    |ssh|_max:  0.2405462262543169D+01 |U|_max:  0.5097657181162150D+01 |V|_max:  0.3337026635416233D+01 S_min:  0.4723834032268113D+01 S_max:  0.4113635084308534D+02
+ it :       9    |ssh|_max:  0.2786981137643356D+01 |U|_max:  0.4730664946038251D+01 |V|_max:  0.3157690707102010D+01 S_min:  0.4727376007437497D+01 S_max:  0.4113628337012292D+02
+ it :      10    |ssh|_max:  0.2788274374684698D+01 |U|_max:  0.4406279753802181D+01 |V|_max:  0.3292925966047464D+01 S_min:  0.4730630270851409D+01 S_max:  0.4113622442764144D+02
diff --git a/examples/nemo/scripts/omp_gpu_trans.py b/examples/nemo/scripts/omp_gpu_trans.py
index aa8adfe0a..807725550 100755
--- a/examples/nemo/scripts/omp_gpu_trans.py
+++ b/examples/nemo/scripts/omp_gpu_trans.py
@@ -41,8 +41,8 @@ import os
 from utils import (
     add_profiling, inline_calls, insert_explicit_loop_parallelism,
     normalise_loops, enhance_tree_information, PARALLELISATION_ISSUES,
-    PRIVATISATION_ISSUES, NEMO_MODULES_TO_IMPORT)
-from psyclone.psyir.nodes import Loop, Routine
+    NEMO_MODULES_TO_IMPORT, PRIVATISATION_ISSUES)
+from psyclone.psyir.nodes import Routine
 from psyclone.psyir.transformations import OMPTargetTrans
 from psyclone.transformations import (
     OMPLoopTrans, OMPDeclareTargetTrans, TransformationError)
@@ -68,10 +68,7 @@ RESOLVE_IMPORTS = NEMO_MODULES_TO_IMPORT
 # List of all files that psyclone will skip processing
 FILES_TO_SKIP = []
 
-NEMOV5_EXCLUSIONS = [
-    # Produces different output results
-    "fldread.f90",
-]
+NEMOV5_EXCLUSIONS = []
 
 NEMOV4_EXCLUSIONS = [
     "dynspg_ts.f90",
@@ -86,10 +83,20 @@ SKIP_FOR_PERFORMANCE = [
 ]
 
 OFFLOADING_ISSUES = [
-    # Runtime Error on BENCH: Illegal address during kernel execution
-    "trcrad.f90",
     # Produces different output results
     "zdftke.f90",
+    # The following issues only affect BENCH (because ice is enabled?)
+    # Runtime Error: Illegal address during kernel execution
+    "trcrad.f90",
+    # Signal 11 issues
+    "trcbbl.f90",
+    "bdyice.f90",
+    "sedfunc.f90",
+    "stpmlf.f90",
+    "trddyn.f90",
+    "trczdf.f90",
+    "trcice_pisces.f90",
+    "dtatsd.f90",
 ]
 
 
@@ -155,7 +162,7 @@ def trans(psyir):
         enhance_tree_information(subroutine)
         normalise_loops(
                 subroutine,
-                hoist_local_arrays=True,
+                hoist_local_arrays=False,
                 convert_array_notation=True,
                 loopify_array_intrinsics=True,
                 convert_range_loops=True,
@@ -168,9 +175,10 @@ def trans(psyir):
         # These are functions that are called from inside parallel regions,
         # annotate them with 'omp declare target'
         if (
-            subroutine.name.lower().startswith("sign_") or
-            subroutine.name.lower() == "solfrac" or
-            (psyir.name == "sbc_phy.f90" and not subroutine.walk(Loop))
+            subroutine.name.lower().startswith("sign_")
+            or subroutine.name.lower() == "solfrac"
+            # Important for performance but causes SIGNAL 11 in some cases
+            # or (psyir.name == "sbc_phy.f90" and not subroutine.walk(Loop))
         ):
             try:
                 OMPDeclareTargetTrans().apply(subroutine)
diff --git a/examples/nemo/scripts/utils.py b/examples/nemo/scripts/utils.py
index 4249355b9..8084e2e63 100755
--- a/examples/nemo/scripts/utils.py
+++ b/examples/nemo/scripts/utils.py
@@ -38,7 +38,7 @@
 from psyclone.domain.common.transformations import KernelModuleInlineTrans
 from psyclone.psyir.nodes import (
     Assignment, Loop, Directive, Reference, CodeBlock, ArrayReference,
-    Call, Return, IfBlock, Routine, IntrinsicCall)
+    Call, Return, IfBlock, Routine, IntrinsicCall, StructureReference)
 from psyclone.psyir.symbols import (
     DataSymbol, INTEGER_TYPE, ScalarType, RoutineSymbol)
 from psyclone.psyir.transformations import (
@@ -142,6 +142,25 @@ NEMO_FUNCTIONS = [
     'z0tq_LKB', 'zdf_gls_alloc', 'zdf_iwm_alloc', 'zdf_mfc_alloc',
     'zdf_mxl_alloc', 'zdf_oce_alloc', 'zdf_osm_alloc', 'zdf_phy_alloc',
     'zdf_tke_alloc', 'zdf_tmx_alloc',
+    # grep -rh "INTERFACE" src | grep -v "END" | awk '{print $2}' | uniq | sort
+    'alpha_sw', 'bulk_formula', 'cp_air', 'debug', 'DECAL_FEEDBACK',
+    'DECAL_FEEDBACK_2D', 'depth_to_e3', 'de_sat_dt_ice', 'dia_ar5_hst',
+    'dia_ptr_hst', 'div_hor', 'dom_tile_copyin', 'dom_tile_copyout',
+    'dq_sat_dt_ice', 'dyn_vor', 'e3_to_depth', 'eos', 'eos_fzp',
+    'eos_rab', 'e_sat', 'e_sat_ice', 'f_h_louis', 'f_m_louis',
+    'gamma_moist', 'glob_2Dmax', 'glob_2Dmin', 'glob_2Dsum', 'glob_3Dmax',
+    'glob_3Dmin', 'glob_3Dsum', 'halo_mng_resize', 'icb_utl_bilin_h',
+    'ice_var_itd', 'ice_var_snwblow', 'ice_var_snwfra', 'iom_get',
+    'iom_getatt', 'iom_nf90_get', 'iom_put', 'iom_putatt',
+    'iom_rstput', 'lbc_lnk', 'lbc_lnk_neicoll', 'lbc_lnk_pt2pt',
+    'lbc_nfd', 'lbnd_ij', 'ldf_eiv_trp', 'local_2Dmax', 'local_2Dmin',
+    'local_2Dsum', 'local_3Dmax', 'local_3Dmin', 'local_3Dsum',
+    'L_vap', 'mpp_max', 'mpp_maxloc', 'mpp_min', 'mpp_minloc',
+    'mpp_nfd', 'mpp_sum', 'pres_temp', 'prt_ctl_sum', 'ptr_mpp_sum',
+    'ptr_sj', 'ptr_sum', 'qlw_net', 'q_sat', 'rho_air', 'Ri_bulk',
+    'SIGN', 'sum3x3', 'theta_exner', 'tra_mle_trp', 'trd_vor_zint',
+    'virt_temp', 'visc_air', 'wAimp', 'wzv', 'zdf_osm_iomput',
+    'zdf_osm_velocity_rotation',
 ]
 
 # Currently fparser has no way of distinguishing array accesses from statement
@@ -153,7 +172,6 @@ CONTAINS_STMT_FUNCTIONS = ["sbc_dcy"]
 PARALLELISATION_ISSUES = [
     "ldfc1d_c2d.f90",
     "tramle.f90",
-    "dynspg_ts.f90",
 ]
 
 PRIVATISATION_ISSUES = [
@@ -348,6 +366,8 @@ def normalise_loops(
         # Convert all array implicit loops to explicit loops
         explicit_loops = ArrayAssignment2LoopsTrans()
         for assignment in schedule.walk(Assignment):
+            if assignment.walk(StructureReference):
+                continue  # TODO #2951 Fix issues with structure_refs
             try:
                 explicit_loops.apply(assignment)
             except TransformationError:
@@ -411,6 +431,8 @@ def insert_explicit_loop_parallelism(
         write-write race conditions.
 
     '''
+    if schedule.name == "ts_wgt":
+        return  # TODO #2937 WaW dependency incorrectly considered private
     # Add the parallel directives in each loop
     for loop in schedule.walk(Loop):
         if loop.ancestor(Directive):
diff --git a/lib/extract/standalone/lfric/Makefile b/lib/extract/standalone/lfric/Makefile
index 5280ac246..6ab64a63f 100644
--- a/lib/extract/standalone/lfric/Makefile
+++ b/lib/extract/standalone/lfric/Makefile
@@ -115,8 +115,7 @@ compare_variables_mod.F90: $(PSYDATA_LIB_DIR)/extract/compare_variables_mod.jinj
 	$(F90) $(F90FLAGS) $(LFRIC_INCLUDE_FLAGS) -c $<
 
 clean:
-	rm -f *.o *.mod $(PSYDATA_LIB) psy_data_base.f90 extract_standalone_base.F90 \
-	read_kernel_data_mod.f90
+	rm -f *.o *.mod $(PSYDATA_LIB) psy_data_base.f90 extract_standalone_base.F90
 
 allclean: clean
 	$(MAKE) -C $(LFRIC_PATH) allclean
diff --git a/src/psyclone/generator.py b/src/psyclone/generator.py
index f26d17298..9a7b3b835 100644
--- a/src/psyclone/generator.py
+++ b/src/psyclone/generator.py
@@ -540,7 +540,7 @@ def main(arguments):
                   "specify the output destination of each psykal layer.")
             sys.exit(1)
 
-    # This has be be before the Config.get, because otherwise that creates a
+    # This has to be before the Config.get, because otherwise that creates a
     # ModuleManager Singleton without caching
     _ = ModuleManager.get(cache_active=args.enable_cache)
 
diff --git a/src/psyclone/psyir/frontend/fparser2.py b/src/psyclone/psyir/frontend/fparser2.py
index af4257669..465232898 100644
--- a/src/psyclone/psyir/frontend/fparser2.py
+++ b/src/psyclone/psyir/frontend/fparser2.py
@@ -380,14 +380,18 @@ def _find_or_create_psyclone_internal_cmp(node):
         if container and not isinstance(container, FileContainer):
             # pylint: disable=import-outside-toplevel
             from psyclone.psyir.frontend.fortran import FortranReader
+            # Giving them the same name in different modules causes issues with
+            # the nvidia compiler when offloading, so we use the module name as
+            # prefix
+            root = container.name + "_psyclone_internal_cmp"
             name_interface = node.scope.symbol_table.next_available_name(
-                "psyclone_internal_cmp")
+                root)
             name_f_int = node.scope.symbol_table.next_available_name(
-                "psyclone_cmp_int")
+                root + "_int")
             name_f_logical = node.scope.symbol_table.next_available_name(
-                "psyclone_cmp_logical")
+                root + "_logical")
             name_f_char = node.scope.symbol_table.next_available_name(
-                "psyclone_cmp_char")
+                root + "_char")
             fortran_reader = FortranReader()
             dummymod = fortran_reader.psyir_from_source(f'''
             module dummy
@@ -424,14 +428,17 @@ def _find_or_create_psyclone_internal_cmp(node):
             # manually.
             sym = dummymod.symbol_table.lookup(name_interface)
             routine_symbol1 = container.symbol_table.lookup(name_f_int)
+            routine_symbol1.visibitity = Symbol.Visibility.PRIVATE
             routine_symbol2 = container.symbol_table.lookup(name_f_logical)
+            routine_symbol2.visibitity = Symbol.Visibility.PRIVATE
             routine_symbol3 = container.symbol_table.lookup(name_f_char)
+            routine_symbol3.visibitity = Symbol.Visibility.PRIVATE
             symbol = GenericInterfaceSymbol(
                     sym.name,
                     [(routine_symbol1, sym.routines[0].from_container),
                      (routine_symbol2, sym.routines[1].from_container),
                      (routine_symbol3, sym.routines[2].from_container)],
-                    visibility=sym.visibility
+                    visibility=Symbol.Visibility.PRIVATE
                     )
             container.symbol_table.add(symbol)
             symbol = container.symbol_table.lookup(name_interface)
diff --git a/src/psyclone/psyir/nodes/intrinsic_call.py b/src/psyclone/psyir/nodes/intrinsic_call.py
index bf925c829..970fffe26 100644
--- a/src/psyclone/psyir/nodes/intrinsic_call.py
+++ b/src/psyclone/psyir/nodes/intrinsic_call.py
@@ -794,7 +794,7 @@ class IntrinsicCall(Call):
             IntrinsicCall.Intrinsic.LOG,
             IntrinsicCall.Intrinsic.MAX, IntrinsicCall.Intrinsic.MIN,
             IntrinsicCall.Intrinsic.MOD, IntrinsicCall.Intrinsic.NINT,
-            IntrinsicCall.Intrinsic.NOT, IntrinsicCall.Intrinsic.REAL,
+            IntrinsicCall.Intrinsic.NOT,
             IntrinsicCall.Intrinsic.SIGN, IntrinsicCall.Intrinsic.SIN,
             IntrinsicCall.Intrinsic.SINH, IntrinsicCall.Intrinsic.SQRT,
             IntrinsicCall.Intrinsic.TAN, IntrinsicCall.Intrinsic.TANH,
@@ -802,7 +802,7 @@ class IntrinsicCall(Call):
             # The ones below can be offloaded but provide numerical differences
             # even with the -gpu=uniform_math flag, ideally it should be
             # configurable if these are allowed or not.
-            # IntrinsicCall.Intrinsic.LOG10,
+            # IntrinsicCall.Intrinsic.LOG10, IntrinsicCall.Intrinsic.REAL,
             # The one below are not documented on nvidia compiler
             IntrinsicCall.Intrinsic.PRODUCT, IntrinsicCall.Intrinsic.SIZE,
             IntrinsicCall.Intrinsic.SUM, IntrinsicCall.Intrinsic.LBOUND,


Test patch:
diff --git a/.github/workflows/lfric_test.yml b/.github/workflows/lfric_test.yml
index a0e098c0b..3be186a6e 100644
--- a/.github/workflows/lfric_test.yml
+++ b/.github/workflows/lfric_test.yml
@@ -46,8 +46,8 @@ jobs:
     if: ${{ github.repository == 'stfc/PSyclone-mirror' }}
     runs-on: self-hosted
     env:
-      LFRIC_APPS_REV: 7536
-      LFRIC_CORE_REV: 52054
+      LFRIC_APPS_REV: 10798
+      LFRIC_CORE_REV: 52772
       PYTHON_VERSION: 3.13.3
 
     steps:
@@ -74,6 +74,17 @@ jobs:
         # than the latest release from pypi.
         # pip install external/fparser
         pip install .[test]
+        # Fetch the specified version of LFRic apps
+        source /apps/spack/psyclone-spack-25.3/spack-repo/share/spack/setup-env.sh
+        spack load fcm%gcc
+        fcm co -r ${LFRIC_APPS_REV} https://code.metoffice.gov.uk/svn/lfric_apps/main/trunk/ lfric_apps
+        # We need to patch lfric_core to resolve issues with the nvfortran compiler
+        # see https://code.metoffice.gov.uk/trac/lfric/ticket/4621
+        fcm co -r ${LFRIC_CORE_REV} https://code.metoffice.gov.uk/svn/lfric/LFRic/trunk/ lfric_core
+        sed -i "s|lfric_core_rev=$LFRIC_CORE_REV|lfric_core_rev=|g" lfric_apps/dependencies.sh
+        sed -i "s|lfric_core_sources=|lfric_core_sources=$PWD/lfric_core|g" lfric_apps/dependencies.sh
+        cd lfric_core
+        svn patch ${GITHUB_WORKSPACE}/examples/lfric/scripts/KGOs/nvidia_lfric_core.patch
 
     # PSyclone, compile and run MetOffice LFRic with 6 MPI ranks.
     # COMPUTE_ANNEXED_DOFS is set to false for this test by using the
@@ -81,16 +92,17 @@ jobs:
     - name: LFRic passthrough (with DistributedMemory)
       run: |
         # Set up environment
-        source /apps/spack/psyclone-spack/spack-repo/share/spack/setup-env.sh
-        spack load lfric-build-environment%gcc@14
+        source /apps/spack/psyclone-spack-25.3/spack-repo/share/spack/setup-env.sh
+        spack load lfric-build-environment%gcc
+        spack load gcc
         source .runner_venv/bin/activate
         export PSYCLONE_LFRIC_DIR=${GITHUB_WORKSPACE}/examples/lfric/scripts
         export PSYCLONE_CONFIG_FILE=${PSYCLONE_LFRIC_DIR}/KGOs/lfric_psyclone_no_annexed.cfg
-        export LFRIC_DIR=${HOME}/LFRic/lfric_apps_${LFRIC_APPS_REV}
+        export LFRIC_DIR=${GITHUB_WORKSPACE}/lfric_apps
         cd ${LFRIC_DIR}
         # Ensure no transformations are applied by supplying an empty
         # transformation script.
-        export OPT_DIR=${LFRIC_DIR}/applications/gungho_model/optimisation/psyclone-test
+        export OPT_DIR=${LFRIC_DIR}/applications/gungho_model/optimisation/psyclone-test/psykal
         mkdir -p ${OPT_DIR}
         cp ${PSYCLONE_LFRIC_DIR}/does_nothing.py ${OPT_DIR}/global.py
         # Clean previous version and compile again
@@ -100,6 +112,9 @@ jobs:
         cd applications/gungho_model/example
         rm -f timer.txt gungho_model-checksums.txt  # In case there were from a previous run
         cp ${PSYCLONE_LFRIC_DIR}/KGOs/configuration_c16_10it.nml .
+        # Number of OMP threads have to be set even when psyclone does not inject OpenMP
+        # because some psykal-lite routines have it.
+        export OMP_NUM_THREADS=1
         mpirun -n 6 ../bin/gungho_model configuration_c16_10it.nml
         python ${PSYCLONE_LFRIC_DIR}/compare_ouput.py ${PSYCLONE_LFRIC_DIR}/KGOs/gungho_model_c16_10it-checksums.txt gungho_model-checksums.txt
         cat timer.txt
@@ -117,28 +132,16 @@ jobs:
     - name: LFRic GungHo with OpenMP offload
       run: |
         # Set up environment
-        source /apps/spack/psyclone-spack/spack-repo/share/spack/setup-env.sh
+        source /apps/spack/psyclone-spack-25.3/spack-repo/share/spack/setup-env.sh
         spack load lfric-build-environment%nvhpc
+        spack load nvhpc
         source .runner_venv/bin/activate
         export PSYCLONE_LFRIC_DIR=${GITHUB_WORKSPACE}/examples/lfric/scripts
         export PSYCLONE_CONFIG_FILE=${PSYCLONE_LFRIC_DIR}/KGOs/lfric_psyclone.cfg
-        # The LFRic source must be patched to workaround bugs in the NVIDIA
-        # compiler's namelist handling.
-        rm -rf ${HOME}/LFRic/gpu_build
-        mkdir -p ${HOME}/LFRic/gpu_build
-        cp -r ${HOME}/LFRic/lfric_apps_${LFRIC_APPS_REV} ${HOME}/LFRic/gpu_build/lfric_apps
-        cp -r ${HOME}/LFRic/lfric_core_${LFRIC_CORE_REV} ${HOME}/LFRic/gpu_build/lfric
-        cd ${HOME}/LFRic/gpu_build
-        patch -p1 < ${PSYCLONE_LFRIC_DIR}/KGOs/lfric_nvidia.patch
-        # Update dependencies.sh to point to our patched lfric core.
-        sed -i -e 's/export lfric_core_sources=.*$/export lfric_core_sources\=\/home\/gh_runner\/LFRic\/gpu_build\/lfric/' lfric_apps/dependencies.sh
-        export LFRIC_DIR=${HOME}/LFRic/gpu_build/lfric_apps
+        export LFRIC_DIR=${GITHUB_WORKSPACE}/lfric_apps
         cd ${LFRIC_DIR}
-        # Update the PSyclone commands to ensure transformed kernels are written
-        # to working directory.
-        cp ${PSYCLONE_LFRIC_DIR}/KGOs/psyclone.mk ../lfric/infrastructure/build/psyclone/.
-        # PSyclone scripts must now be under 'optimisation' and be called 'global.py'
-        export OPT_DIR=${LFRIC_DIR}/applications/gungho_model/optimisation/psyclone-test
+        # PSyclone scripts must now be under 'optimisation/psykal' and be called 'global.py'
+        export OPT_DIR=${LFRIC_DIR}/applications/gungho_model/optimisation/psyclone-test/psykal
         mkdir -p ${OPT_DIR}
         cp ${PSYCLONE_LFRIC_DIR}/gpu_offloading.py ${OPT_DIR}/global.py
         # Clean previous version and compile again
@@ -148,7 +151,7 @@ jobs:
         rm -f timer.txt gungho_model-checksums.txt  # In case there were from a previous run
         cp ${PSYCLONE_LFRIC_DIR}/KGOs/configuration_c16_10it.nml .
         export OMP_NUM_THREADS=1
-        mpirun -n 6 ../bin/gungho_model configuration_c16_10it.nml
+        mpirun -n 1 ../bin/gungho_model configuration_c16_10it.nml
         python ${PSYCLONE_LFRIC_DIR}/compare_ouput.py ${PSYCLONE_LFRIC_DIR}/KGOs/gungho_model_c16_10it-checksums.txt gungho_model-checksums.txt
         cat timer.txt
         export VAR_TIME=$(grep "gungho_model" timer.txt | cut -d'|' -f5)
@@ -159,34 +162,22 @@ jobs:
           --eval 'db.GitHub_CI.insertOne({branch_name: "'"$GITHUB_REF_NAME"'", commit: "'"$GITHUB_SHA"'",
           github_job: "'"$GITHUB_RUN_ID"'"-"'"$GITHUB_RUN_ATTEMPT"'",
           ci_test: "LFRic OpenMP offloading", lfric_apps_version: '"$LFRIC_APPS_REV"', system: "GlaDos",
-          compiler:"spack-nvhpc-24.5", date: new Date(), elapsed_time: '"$VAR_TIME"'})'
+          compiler:"spack-nvhpc-24.11", date: new Date(), elapsed_time: '"$VAR_TIME"'})'
 
     # PSyclone, compile and run MetOffice gungho_model on GPU
     - name: LFRic GungHo with OpenACC offload
       run: |
         # Set up environment
-        source /apps/spack/psyclone-spack/spack-repo/share/spack/setup-env.sh
+        source /apps/spack/psyclone-spack-25.3/spack-repo/share/spack/setup-env.sh
         spack load lfric-build-environment%nvhpc
+        spack load nvhpc
         source .runner_venv/bin/activate
         export PSYCLONE_LFRIC_DIR=${GITHUB_WORKSPACE}/examples/lfric/scripts
         export PSYCLONE_CONFIG_FILE=${PSYCLONE_LFRIC_DIR}/KGOs/lfric_psyclone.cfg
-        # The LFRic source must be patched to workaround bugs in the NVIDIA
-        # compiler's namelist handling.
-        rm -rf ${HOME}/LFRic/gpu_build
-        mkdir -p ${HOME}/LFRic/gpu_build
-        cp -r ${HOME}/LFRic/lfric_apps_${LFRIC_APPS_REV} ${HOME}/LFRic/gpu_build/lfric_apps
-        cp -r ${HOME}/LFRic/lfric_core_${LFRIC_CORE_REV} ${HOME}/LFRic/gpu_build/lfric
-        cd ${HOME}/LFRic/gpu_build
-        patch -p1 < ${PSYCLONE_LFRIC_DIR}/KGOs/lfric_nvidia.patch
-        # Update dependencies.sh to point to our patched lfric core.
-        sed -i -e 's/export lfric_core_sources=.*$/export lfric_core_sources\=\/home\/gh_runner\/LFRic\/gpu_build\/lfric/' lfric_apps/dependencies.sh
-        export LFRIC_DIR=${HOME}/LFRic/gpu_build/lfric_apps
+        export LFRIC_DIR=${GITHUB_WORKSPACE}/lfric_apps
         cd ${LFRIC_DIR}
-        # Update the PSyclone commands to ensure transformed kernels are written
-        # to working directory.
-        cp ${PSYCLONE_LFRIC_DIR}/KGOs/psyclone.mk ../lfric/infrastructure/build/psyclone/.
-        # PSyclone scripts must now be under 'optimisation' and be called 'global.py'
-        export OPT_DIR=${LFRIC_DIR}/applications/gungho_model/optimisation/psyclone-test
+        # PSyclone scripts must now be under 'optimisation/psykal' and be called 'global.py'
+        export OPT_DIR=${LFRIC_DIR}/applications/gungho_model/optimisation/psyclone-test/psykal
         mkdir -p ${OPT_DIR}
         cp ${PSYCLONE_LFRIC_DIR}/gpu_offloading.py ${OPT_DIR}/global.py
         # Clean previous version and compile again
@@ -196,7 +187,7 @@ jobs:
         rm -f timer.txt gungho_model-checksums.txt  # In case there were from a previous run
         cp ${PSYCLONE_LFRIC_DIR}/KGOs/configuration_c16_10it.nml .
         export OMP_NUM_THREADS=1
-        mpirun -n 6 ../bin/gungho_model configuration_c16_10it.nml
+        mpirun -n 1 ../bin/gungho_model configuration_c16_10it.nml
         python ${PSYCLONE_LFRIC_DIR}/compare_ouput.py ${PSYCLONE_LFRIC_DIR}/KGOs/gungho_model_c16_10it-checksums.txt gungho_model-checksums.txt
         cat timer.txt
         export VAR_TIME=$(grep "gungho_model" timer.txt | cut -d'|' -f5)
@@ -207,22 +198,23 @@ jobs:
           --eval 'db.GitHub_CI.insertOne({branch_name: "'"$GITHUB_REF_NAME"'", commit: "'"$GITHUB_SHA"'",
           github_job: "'"$GITHUB_RUN_ID"'"-"'"$GITHUB_RUN_ATTEMPT"'",
           ci_test: "LFRic OpenACC", lfric_apps_version: '"$LFRIC_APPS_REV"', system: "GlaDos",
-          compiler:"spack-nvhpc-24.5", date: new Date(), elapsed_time: '"$VAR_TIME"'})'
+          compiler:"spack-nvhpc-24.11", date: new Date(), elapsed_time: '"$VAR_TIME"'})'
 
     # PSyclone, compile and run MetOffice LFRic with all optimisations and 6 mpi ranks each with 2 OpenMP threads
     - name: LFRic with all transformations
       run: |
         # Set up environment
-        source /apps/spack/psyclone-spack/spack-repo/share/spack/setup-env.sh
-        spack load lfric-build-environment%gcc@14
+        source /apps/spack/psyclone-spack-25.3/spack-repo/share/spack/setup-env.sh
+        spack load lfric-build-environment%gcc
+        spack load gcc
         source .runner_venv/bin/activate
         export PSYCLONE_LFRIC_DIR=${GITHUB_WORKSPACE}/examples/lfric/scripts
         export PSYCLONE_CONFIG_FILE=${PSYCLONE_LFRIC_DIR}/KGOs/lfric_psyclone.cfg
-        export LFRIC_DIR=${HOME}/LFRic/lfric_apps_${LFRIC_APPS_REV}
+        export LFRIC_DIR=${GITHUB_WORKSPACE}/lfric_apps
         cd ${LFRIC_DIR}
-        # PSyclone scripts must now be under 'optimisation' and be called 'global.py'
-        export OPT_DIR=${LFRIC_DIR}/applications/gungho_model/optimisation/psyclone-test
-        mkdir -p applications/gungho_model/optimisation/psyclone-test
+        # PSyclone scripts must now be under 'optimisation/psykal' and be called 'global.py'
+        export OPT_DIR=${LFRIC_DIR}/applications/gungho_model/optimisation/psyclone-test/psykal
+        mkdir -p ${OPT_DIR}
         cp ${PSYCLONE_LFRIC_DIR}/everything_everywhere_all_at_once.py ${OPT_DIR}/global.py
         # Clean previous version and compile again
         rm -rf applications/gungho_model/working
@@ -250,15 +242,16 @@ jobs:
       run: |
         # Set up environment
         source /apps/spack/psyclone-spack/spack-repo/share/spack/setup-env.sh
-        spack load lfric-build-environment%gcc@14
+        spack load lfric-build-environment%gcc
+        spack load gcc
         source .runner_venv/bin/activate
         export PSYCLONE_LFRIC_DIR=${GITHUB_WORKSPACE}/examples/lfric/scripts
         export PSYCLONE_CONFIG_FILE=${PSYCLONE_LFRIC_DIR}/KGOs/lfric_psyclone.cfg
-        export LFRIC_DIR=${HOME}/LFRic/lfric_apps_${LFRIC_APPS_REV}
+        export LFRIC_DIR=${GITHUB_WORKSPACE}/lfric_apps
         cd ${LFRIC_DIR}
-        # PSyclone scripts must now be under 'optimisation' and be called 'global.py'
-        export OPT_DIR=${LFRIC_DIR}/applications/gungho_model/optimisation/psyclone-test
-        mkdir -p applications/gungho_model/optimisation/psyclone-test
+        # PSyclone scripts must now be under 'optimisation/psykal' and be called 'global.py'
+        export OPT_DIR=${LFRIC_DIR}/applications/gungho_model/optimisation/psyclone-test/psykal
+        mkdir -p ${OPT_DIR}
         cp ${PSYCLONE_LFRIC_DIR}/tiledcolouring_and_omp.py ${OPT_DIR}/global.py
         # Clean previous version and compile again
         rm -rf applications/gungho_model/working
diff --git a/.github/workflows/nemo_v5_tests.yml b/.github/workflows/nemo_v5_tests.yml
index bd426cb6c..41e759604 100644
--- a/.github/workflows/nemo_v5_tests.yml
+++ b/.github/workflows/nemo_v5_tests.yml
@@ -78,7 +78,7 @@ jobs:
       if: ${{ github.run_attempt != '1' }}
       run: |
         # Set up environment
-        source /apps/spack/psyclone-spack/spack-repo/share/spack/setup-env.sh
+        source /apps/spack/psyclone-spack-25.3/spack-repo/share/spack/setup-env.sh
         spack unload && spack load nemo-build-environment%gcc@14
         source .runner_venv/bin/activate
         export PSYCLONE_NEMO_DIR=${GITHUB_WORKSPACE}/examples/nemo/scripts
@@ -109,8 +109,8 @@ jobs:
       if: ${{ github.run_attempt != '1' }}
       run: |
         # Set up environment
-        source /apps/spack/psyclone-spack/spack-repo/share/spack/setup-env.sh
-        spack unload && spack load nemo-build-environment%nvhpc@24.5
+        source /apps/spack/psyclone-spack-25.3/spack-repo/share/spack/setup-env.sh
+        spack unload && spack load nemo-build-environment%nvhpc@24.11
         source .runner_venv/bin/activate
         export PSYCLONE_NEMO_DIR=${GITHUB_WORKSPACE}/examples/nemo/scripts
         export PSYCLONE_HOME=${PWD}/.runner_venv
@@ -123,10 +123,9 @@ jobs:
         export FCFLAGS="-i4 -Mr8 -O2 -nofma -Mnovect -g"
         
         # Clean up and compile
-        # Without key_mpi_off it fails to compile (even without psyclone)
         rm -rf tests/${TEST_DIR}
         ./makenemo -r BENCH -m linux_spack -n ${TEST_DIR} -p ${PSYCLONE_NEMO_DIR}/passthrough.py \
-          add_key "key_mpi_off key_nosignedzero" -j 4 -v 1
+          -j 4 -v 1
 
         # Run test
         cd $NEMO_DIR/tests/${TEST_DIR}/EXP00
@@ -142,7 +141,7 @@ jobs:
       if: ${{ github.run_attempt != '1' }}
       run: |
         # Set up environment
-        source /apps/spack/psyclone-spack/spack-repo/share/spack/setup-env.sh
+        source /apps/spack/psyclone-spack-25.3/spack-repo/share/spack/setup-env.sh
         spack unload && spack load nemo-build-environment%oneapi
         source .runner_venv/bin/activate
         export PSYCLONE_NEMO_DIR=${GITHUB_WORKSPACE}/examples/nemo/scripts
@@ -170,7 +169,7 @@ jobs:
     - name: NEMO 5.0 gfortran OpenMP for CPUs
       run: |
         # Set up environment
-        source /apps/spack/psyclone-spack/spack-repo/share/spack/setup-env.sh
+        source /apps/spack/psyclone-spack-25.3/spack-repo/share/spack/setup-env.sh
         spack unload && spack load nemo-build-environment%gcc@14
         source .runner_venv/bin/activate
         export PSYCLONE_NEMO_DIR=${GITHUB_WORKSPACE}/examples/nemo/scripts
@@ -207,8 +206,8 @@ jobs:
     - name: NEMO 5.0 nvidia OpenMP for GPUs (BENCH - managed memory)
       run: |
         # Set up environment
-        source /apps/spack/psyclone-spack/spack-repo/share/spack/setup-env.sh
-        spack unload && spack load nemo-build-environment%nvhpc@24.5
+        source /apps/spack/psyclone-spack-25.3/spack-repo/share/spack/setup-env.sh
+        spack unload && spack load nemo-build-environment%nvhpc@24.11
         source .runner_venv/bin/activate
         export PSYCLONE_NEMO_DIR=${GITHUB_WORKSPACE}/examples/nemo/scripts
         export PSYCLONE_HOME=${PWD}/.runner_venv
@@ -222,16 +221,14 @@ jobs:
         export FCFLAGS="-i4 -Mr8 -O2 -Mnofma -Mnovect -g -mp=gpu -gpu=mem:managed,math_uniform"
 
         # Clean up and compile
-        # Without key_mpi_off it fails to compile (even without psyclone)
         rm -rf tests/${TEST_DIR}
         ./makenemo -r BENCH -m linux_spack -n ${TEST_DIR} -p ${PSYCLONE_NEMO_DIR}/omp_gpu_trans.py \
-          add_key "key_mpi_off key_nosignedzero" -j 4 -v 1
+          -j 4 -v 1
 
         # Run test
         cd $NEMO_DIR/tests/${TEST_DIR}/EXP00
         cp $PSYCLONE_NEMO_DIR/KGOs/namelist_cfg_bench_small namelist_cfg
-        ./nemo
-        # tail run.stat
+        OMP_NUM_THREADS=4 mpirun -np 1 ./nemo
         diff $PSYCLONE_NEMO_DIR/KGOs/run.stat.bench.nvhpc.small.10steps run.stat
         export TIME_sec=$(grep "local proces" timing.output | head -n 1 | awk '{print $4}' | tr -d s)
         ${HOME}/mongosh-2.1.1-linux-x64/bin/mongosh \
@@ -241,13 +238,13 @@ jobs:
           --eval 'db.GitHub_CI.insertOne({branch_name: "'"$GITHUB_REF_NAME"'", commit: "'"$GITHUB_SHA"'",
           github_job: "'"$GITHUB_RUN_ID"'"-"'"$GITHUB_RUN_ATTEMPT"'",
           ci_test: "NEMOv5 OpenMP for GPU (BENCH)", nemo_version: "NEMOv5", system: "GlaDos",
-          compiler:"nvhpc-24.5" , date: new Date(), elapsed_time: '"${TIME_sec}"'})'
+          compiler:"nvhpc-24.11" , date: new Date(), elapsed_time: '"${TIME_sec}"'})'
 
     - name: NEMO 5.0 nvidia OpenMP for GPUs (UKMO ORCA1 - managed memory)
       run: |
         # Set up environment
-        source /apps/spack/psyclone-spack/spack-repo/share/spack/setup-env.sh
-        spack unload && spack load nemo-build-environment%nvhpc@24.5
+        source /apps/spack/psyclone-spack-25.3/spack-repo/share/spack/setup-env.sh
+        spack unload && spack load nemo-build-environment%nvhpc@24.11
         source .runner_venv/bin/activate
         export PSYCLONE_NEMO_DIR=${GITHUB_WORKSPACE}/examples/nemo/scripts
         export PSYCLONE_HOME=${PWD}/.runner_venv
@@ -261,17 +258,19 @@ jobs:
         export FCFLAGS="-i4 -Mr8 -O2 -Mnofma -Mnovect -g -mp=gpu -gpu=mem:managed,math_uniform"
 
         # Clean up and compile
-        # Without key_mpi_off it fails to compile (even without psyclone)
         rm -rf cfgs/${TEST_DIR}
         ./makenemo -r GOSI10p0.0_like_eORCA1 -m linux_spack -n ${TEST_DIR} -p ${PSYCLONE_NEMO_DIR}/omp_gpu_trans.py \
-          add_key "key_mpi_off key_nosignedzero" -j 4 -v 1
+          -j 4 -v 1
 
         # Run test
         cd $NEMO_DIR/cfgs/${TEST_DIR}/EXP00
         ln -sf /archive/NEMO_INPUTS/NEMOv5_MO_original_files/ancil/eORCA1/link/* .
-        ./nemo
+        # Make sure mpi has been built with cuda support
+        ompi_info --parsable --all | grep mpi_built_with_cuda_support:value
+        # Run with round robin allocations of GPUs to MPI ranks
+        mpirun -n 2 sh -c 'CUDA_VISIBLE_DEVICES=$OMPI_COMM_WORLD_LOCAL_RANK ./nemo'
         diff $PSYCLONE_NEMO_DIR/KGOs/run.stat.orca1.nvhpc.10steps run.stat
-        export TIME_sec=$(grep "local proces" timing.output | head -n 1 | awk '{print $4}' | tr -d s)
+        export TIME_sec=$(grep "local MPI proces" timing.output | head -n 1 | awk '{print $5}' | tr -d s)
         ${HOME}/mongosh-2.1.1-linux-x64/bin/mongosh \
           "mongodb+srv://cluster0.x8ncpxi.mongodb.net/PerformanceMonitoring" \
           --quiet --apiVersion 1 --username ${{ secrets.MONGODB_USERNAME }} \
@@ -279,13 +278,13 @@ jobs:
           --eval 'db.GitHub_CI.insertOne({branch_name: "'"$GITHUB_REF_NAME"'", commit: "'"$GITHUB_SHA"'",
           github_job: "'"$GITHUB_RUN_ID"'"-"'"$GITHUB_RUN_ATTEMPT"'",
           ci_test: "NEMOv5 OpenMP for GPU (ORCA1)", nemo_version: "NEMOv5", system: "GlaDos",
-          compiler:"nvhpc-24.5" , date: new Date(), elapsed_time: '"${TIME_sec}"'})'
+          compiler:"nvhpc-24.11" , date: new Date(), elapsed_time: '"${TIME_sec}"'})'
 
     - name: NEMO 5.0 nvidia OpenMP for GPUs (UKMO ORCA2 - managed memory)
       run: |
         # Set up environment
-        source /apps/spack/psyclone-spack/spack-repo/share/spack/setup-env.sh
-        spack unload && spack load nemo-build-environment%nvhpc@24.5
+        source /apps/spack/psyclone-spack-25.3/spack-repo/share/spack/setup-env.sh
+        spack unload && spack load nemo-build-environment%nvhpc@24.11
         source .runner_venv/bin/activate
         export PSYCLONE_NEMO_DIR=${GITHUB_WORKSPACE}/examples/nemo/scripts
         export PSYCLONE_HOME=${PWD}/.runner_venv
@@ -299,7 +298,6 @@ jobs:
         export FCFLAGS="-i4 -Mr8 -O2 -Mnofma -Mnovect -g -mp=gpu -gpu=mem:managed,math_uniform"
 
         # Clean up and compile
-        # Without key_mpi_off it fails to compile (even without psyclone)
         rm -rf cfgs/${TEST_DIR}
         ./makenemo -r ORCA2_ICE_PISCES -m linux_spack -n ${TEST_DIR} -p ${PSYCLONE_NEMO_DIR}/omp_gpu_trans.py \
           add_key "key_mpi_off key_nosignedzero" -j 4 -v 1
@@ -307,7 +305,8 @@ jobs:
         # Run test
         cd $NEMO_DIR/cfgs/${TEST_DIR}/EXP00
         ln -sf ../../../ORCA2_ICE_v4.2.0/* .
-        ./nemo
+        # Uses both, threading and offloading
+        OMP_NUM_THREADS=4 ./nemo
         diff $PSYCLONE_NEMO_DIR/KGOs/run.stat.orca2.nvhpc.10steps run.stat
         export TIME_sec=$(grep "local proces" timing.output | head -n 1 | awk '{print $4}' | tr -d s)
         ${HOME}/mongosh-2.1.1-linux-x64/bin/mongosh \
@@ -317,4 +316,4 @@ jobs:
           --eval 'db.GitHub_CI.insertOne({branch_name: "'"$GITHUB_REF_NAME"'", commit: "'"$GITHUB_SHA"'",
           github_job: "'"$GITHUB_RUN_ID"'"-"'"$GITHUB_RUN_ATTEMPT"'",
           ci_test: "NEMOv5 OpenMP for GPU (ORCA2)", nemo_version: "NEMOv5", system: "GlaDos",
-          compiler:"nvhpc-24.5" , date: new Date(), elapsed_time: '"${TIME_sec}"'})'
+          compiler:"nvhpc-24.11" , date: new Date(), elapsed_time: '"${TIME_sec}"'})'
diff --git a/doc/developer_guide/integration-test.rst b/doc/developer_guide/integration-test.rst
new file mode 100644
index 000000000..29714e1d5
--- /dev/null
+++ b/doc/developer_guide/integration-test.rst
@@ -0,0 +1,35 @@
+
+Integration Tests: Timing Results
+#################################
+
+
+Comparison against baseline (last 10 entries)
+=============================================
+
+
+`View chart 1 <https://charts.mongodb.com/charts-project-0-gvldi/embed/charts?id=706f9e2a-90f6-42b5-8974-dc957c3171ed>`_
+
+.. raw:: html
+
+	<iframe style="background: #FFFFFF;border: none;border-radius: 2px;box-shadow: 0 2px 10px 0 rgba(70, 76, 79, .2);" width="100%" height="480" src="https://charts.mongodb.com/charts-project-0-gvldi/embed/charts?id=706f9e2a-90f6-42b5-8974-dc957c3171ed&maxDataAge=3600&theme=light&autoRefresh=true"></iframe>
+
+Elapsed time (last 10 entries)
+==============================
+
+
+`View chart 2 <https://charts.mongodb.com/charts-project-0-gvldi/embed/charts?id=6644da54-276c-4aa7-8eb7-ef1a23a5f07d>`_
+
+.. raw:: html
+
+	<iframe style="background: #FFFFFF;border: none;border-radius: 2px;box-shadow: 0 2px 10px 0 rgba(70, 76, 79, .2);" width="100%" height="480" src="https://charts.mongodb.com/charts-project-0-gvldi/embed/charts?id=6644da54-276c-4aa7-8eb7-ef1a23a5f07d&maxDataAge=3600&theme=light&autoRefresh=true"></iframe>
+
+Historical results
+==================
+
+
+`View chart 3  <https://charts.mongodb.com/charts-project-0-gvldi/embed/charts?id=bba0cbbd-6171-4bcf-8c31-5fda3c7bec89>`_
+
+.. raw:: html
+
+	<iframe style="background: #FFFFFF;border: none;border-radius: 2px;box-shadow: 0 2px 10px 0 rgba(70, 76, 79, .2);" width="100%" height="480" src="https://charts.mongodb.com/charts-project-0-gvldi/embed/charts?id=bba0cbbd-6171-4bcf-8c31-5fda3c7bec89&maxDataAge=3600&theme=light&autoRefresh=true"></iframe>
+
diff --git a/src/psyclone/tests/psyir/frontend/fparser2_select_case_test.py b/src/psyclone/tests/psyir/frontend/fparser2_select_case_test.py
index 9f804e552..e6847f78d 100644
--- a/src/psyclone/tests/psyir/frontend/fparser2_select_case_test.py
+++ b/src/psyclone/tests/psyir/frontend/fparser2_select_case_test.py
@@ -492,41 +492,43 @@ def has_cmp_interface(code):
     generic interface and its 3 implementations are part of a given code.
     '''
 
-    # Check that the generic interface in in the code
-    assert '''interface psyclone_internal_cmp
-    procedure :: psyclone_cmp_int, psyclone_cmp_logical, psyclone_cmp_char
-  end interface psyclone_internal_cmp
+    # Check that the generic interface is in the code
+    assert '''interface test_psyclone_internal_cmp
+    procedure :: test_psyclone_internal_cmp_int, \
+test_psyclone_internal_cmp_logical, \
+test_psyclone_internal_cmp_char
+  end interface test_psyclone_internal_cmp
 ''' in code
 
     # Check that the integer implementation is in the code
-    assert '''function psyclone_cmp_int(op1, op2)
+    assert '''function test_psyclone_internal_cmp_int(op1, op2)
     integer, intent(in) :: op1
     integer, intent(in) :: op2
-    logical :: psyclone_cmp_int
+    logical :: test_psyclone_internal_cmp_int
 
-    psyclone_cmp_int = op1 == op2
+    test_psyclone_internal_cmp_int = op1 == op2
 
-  end function psyclone_cmp_int''' in code
+  end function test_psyclone_internal_cmp_int''' in code
 
     # Check that the char implementation is in the code
-    assert '''function psyclone_cmp_char(op1, op2)
+    assert '''function test_psyclone_internal_cmp_char(op1, op2)
     CHARACTER(LEN = *), INTENT(IN) :: op1
     CHARACTER(LEN = *), INTENT(IN) :: op2
-    logical :: psyclone_cmp_char
+    logical :: test_psyclone_internal_cmp_char
 
-    psyclone_cmp_char = op1 == op2
+    test_psyclone_internal_cmp_char = op1 == op2
 
-  end function psyclone_cmp_char''' in code
+  end function test_psyclone_internal_cmp_char''' in code
 
     # Check that the logical implementation is in the code
-    assert '''function psyclone_cmp_logical(op1, op2)
+    assert '''function test_psyclone_internal_cmp_logical(op1, op2)
     logical, intent(in) :: op1
     logical, intent(in) :: op2
-    logical :: psyclone_cmp_logical
+    logical :: test_psyclone_internal_cmp_logical
 
-    psyclone_cmp_logical = op1 .EQV. op2
+    test_psyclone_internal_cmp_logical = op1 .EQV. op2
 
-  end function psyclone_cmp_logical''' in code
+  end function test_psyclone_internal_cmp_logical''' in code
 
 
 def test_find_or_create_psyclone_internal_cmp(fortran_writer):
@@ -547,16 +549,16 @@ def test_find_or_create_psyclone_internal_cmp(fortran_writer):
 
     # Check that the interface and 3 additional functions have been added to
     # the container
-    assert "psyclone_internal_cmp" in container.symbol_table
-    assert symbol is container.symbol_table.lookup_with_tag(
-            "psyclone_internal_cmp")
+    symtab = container.symbol_table
+    assert "test_psyclone_internal_cmp" in symtab
+    assert symbol is symtab.lookup_with_tag("psyclone_internal_cmp")
     assert symbol.visibility == Symbol.Visibility.PRIVATE
     assert len(container.children) == 4
-    assert (container.symbol_table.lookup("psyclone_cmp_int").visibility
+    assert (symtab.lookup("test_psyclone_internal_cmp_int").visibility
             == Symbol.Visibility.PRIVATE)
-    assert (container.symbol_table.lookup("psyclone_cmp_logical").visibility
+    assert (symtab.lookup("test_psyclone_internal_cmp_logical").visibility
             == Symbol.Visibility.PRIVATE)
-    assert (container.symbol_table.lookup("psyclone_cmp_char").visibility
+    assert (symtab.lookup("test_psyclone_internal_cmp_char").visibility
             == Symbol.Visibility.PRIVATE)
 
     # Check the generated code matches the expected code
@@ -574,19 +576,20 @@ def test_find_or_create_psyclone_internal_cmp(fortran_writer):
     del container.symbol_table.tags_dict['psyclone_internal_cmp']
     another_symbol = _find_or_create_psyclone_internal_cmp(node_in_subroutine)
     assert another_symbol is not symbol
-    assert another_symbol.name == "psyclone_internal_cmp_1"
+    assert another_symbol.name == "test_psyclone_internal_cmp_1"
     assert len(container.children) == 7  # 3 more functions added
-    assert "psyclone_internal_cmp_1" in container.symbol_table
+    assert "test_psyclone_internal_cmp_1" in container.symbol_table
 
     # Check that the interface new names are internally consistent
-    assert '''interface psyclone_internal_cmp_1
-    procedure :: psyclone_cmp_int_1, psyclone_cmp_logical_1, \
-psyclone_cmp_char_1
-  end interface psyclone_internal_cmp_1''' in fortran_writer(container)
+    assert '''interface test_psyclone_internal_cmp_1
+    procedure :: test_psyclone_internal_cmp_int_1, \
+test_psyclone_internal_cmp_logical_1, \
+test_psyclone_internal_cmp_char_1
+  end interface test_psyclone_internal_cmp_1''' in fortran_writer(container)
 
     # And that from now on the tag refers to the new symbol
     assert container.symbol_table.lookup_with_tag(
-            "psyclone_internal_cmp").name == "psyclone_internal_cmp_1"
+            "psyclone_internal_cmp").name == "test_psyclone_internal_cmp_1"
 
 
 def test_expression_case(fortran_reader, fortran_writer):
@@ -646,8 +649,8 @@ def test_unresolved_types_case(fortran_reader, fortran_writer):
     has_cmp_interface(output)
 
     # Check that the canonicalised comparisons use the interface method
-    assert "if (psyclone_internal_cmp(a, b)) then" in output
-    assert "if (psyclone_internal_cmp(a, c)) then" in output
+    assert "if (test_psyclone_internal_cmp(a, b)) then" in output
+    assert "if (test_psyclone_internal_cmp(a, c)) then" in output
 
 
 def test_unresolved_types_case_without_module(fortran_reader):
@@ -720,5 +723,5 @@ def test_derived_types_case(fortran_reader, fortran_writer):
     has_cmp_interface(output)
 
     # Check that the canonicalised comparisons use the interface method
-    assert "if (psyclone_internal_cmp(a%b(i)%c, b%d)) then" in output
-    assert "if (psyclone_internal_cmp(a%b(i)%c, c%a)) then" in output
+    assert "if (test_psyclone_internal_cmp(a%b(i)%c, b%d)) then" in output
+    assert "if (test_psyclone_internal_cmp(a%b(i)%c, c%a)) then" in output
diff --git a/src/psyclone/tests/psyir/nodes/intrinsic_call_test.py b/src/psyclone/tests/psyir/nodes/intrinsic_call_test.py
index 15b8130ce..6eec6ca32 100644
--- a/src/psyclone/tests/psyir/nodes/intrinsic_call_test.py
+++ b/src/psyclone/tests/psyir/nodes/intrinsic_call_test.py
@@ -141,7 +141,7 @@ def test_intrinsiccall_is_inquiry():
                 (IntrinsicCall.Intrinsic.MOD, True),
                 (IntrinsicCall.Intrinsic.NINT, True),
                 (IntrinsicCall.Intrinsic.NOT, True),
-                (IntrinsicCall.Intrinsic.REAL, True),
+                (IntrinsicCall.Intrinsic.REAL, False),
                 (IntrinsicCall.Intrinsic.SIGN, True),
                 (IntrinsicCall.Intrinsic.SIN, True),
                 (IntrinsicCall.Intrinsic.SINH, True),