submodule_sph_particles_apm.f90 Source File


Copyright (C) 2020-2023 Francesco Torsello * * This file is part of SPHINCS_ID * * SPHINCS_ID is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * SPHINCS_ID is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with SPHINCS_ID. If not, see https://www.gnu.org/licenses/. * The copy of the GNU General Public License should be in the file * 'COPYING'. *



This file depends on

sourcefile~~submodule_sph_particles_apm.f90~~EfferentGraph sourcefile~submodule_sph_particles_apm.f90 submodule_sph_particles_apm.f90 sourcefile~module_sph_particles.f90 module_sph_particles.f90 sourcefile~submodule_sph_particles_apm.f90->sourcefile~module_sph_particles.f90 sourcefile~module_utility.f90 module_utility.f90 sourcefile~submodule_sph_particles_apm.f90->sourcefile~module_utility.f90 sourcefile~module_sph_particles.f90->sourcefile~module_utility.f90 sourcefile~module_id_base.f90 module_id_base.f90 sourcefile~module_sph_particles.f90->sourcefile~module_id_base.f90 sourcefile~module_id_base.f90->sourcefile~module_utility.f90

Contents


Source Code

!# File:         submodule_sph_particles_apm.f90
!  Authors:      Francesco Torsello (FT)
!************************************************************************
! Copyright (C) 2020-2023 Francesco Torsello                            *
!                                                                       *
! This file is part of SPHINCS_ID                                       *
!                                                                       *
! SPHINCS_ID is free software: you can redistribute it and/or modify    *
! it under the terms of the GNU General Public License as published by  *
! the Free Software Foundation, either version 3 of the License, or     *
! (at your option) any later version.                                   *
!                                                                       *
! SPHINCS_ID is distributed in the hope that it will be useful,         *
! but WITHOUT ANY WARRANTY; without even the implied warranty of        *
! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the          *
! GNU General Public License for more details.                          *
!                                                                       *
! You should have received a copy of the GNU General Public License     *
! along with SPHINCS_ID. If not, see <https://www.gnu.org/licenses/>.   *
! The copy of the GNU General Public License should be in the file      *
! 'COPYING'.                                                            *
!************************************************************************

SUBMODULE (sph_particles) apm

  !***********************************
  !
  !# This SUBMODULE contains the
  !  implementation of the method
  !  perform_apm of TYPE [[particles]].
  !
  !  FT 04.06.2021
  !
  !***********************************

  USE constants,  ONLY: quarter
  USE utility,    ONLY: zero, one, two, three, four, five, ten, sph_path, &
                        Msun_geo


  IMPLICIT NONE


  CONTAINS


  MODULE PROCEDURE perform_apm

    !*****************************************************
    !
    !# Compute the particle positions as follows:
    !
    !   1. Take initial particle distribution as input
    !   2. Assume that the particles have the same mass
    !   3. Do the APM iteration so that the final
    !      SPH kernel estimate of the baryon mass
    !      density matches the baryon density in the
    !      given |id|
    !   4. Correct the particle masses ONCE, in order
    !      to match the density even better. Since we
    !      don't want a large mass ratio, we impose a
    !      maximum mass ratio when performing this
    !      correction.
    !
    !  After this procedure, the resulting particle
    !  distribution has positions and baryon numbers
    !  that kernel-estimate very well the mass density
    !  of the star, and has a low mass ratio.
    !
    !  This procedure assigns positions, smoothing
    !  lengths \(h\), and \(\nu\).
    !
    !  FT 04.06.2021
    !
    !*****************************************************

    USE utility,             ONLY: cnt, spherical_from_cartesian, &
                                   cartesian_from_spherical
    USE constants,           ONLY: half, third, Msun, amu, pi

    USE sph_variables,       ONLY: allocate_sph_memory, deallocate_sph_memory, &
                                   npart, h, nu
    USE metric_on_particles, ONLY: allocate_metric_on_particles, &
                                   deallocate_metric_on_particles
    USE gradient,            ONLY: allocate_gradient, deallocate_gradient
    USE set_h,               ONLY: exact_nei_tree_update, posmash
    USE units,               ONLY: umass, m0c2_cu

    USE APM,                 ONLY: density_loop, position_correction, assign_h
    USE analyze,             ONLY: COM
    USE matrix,              ONLY: determinant_4x4_matrix

    USE sphincs_sph,         ONLY: density, ncand, all_clists
    USE matrix,              ONLY: invert_3x3_matrix
    USE kernel_table,        ONLY: &!dWdv_no_norm, &
                                   dv_table, dv_table_1,&
                                   W_no_norm, n_tab_entry, &
                                   interp_gradW_table,interp_W_gradW_table
    USE RCB_tree_3D,         ONLY: iorig, &
                                   !nic, nfinal, nprev, lpart, rpart, &
                                   allocate_RCB_tree_memory_3D, &
                                   deallocate_RCB_tree_memory_3D

    IMPLICIT NONE

    INTEGER,          PARAMETER:: max_npart        = 20D+6
    INTEGER,          PARAMETER:: nn_des           = 301
    INTEGER,          PARAMETER:: m_max_it         = 50
    INTEGER,          PARAMETER:: search_pos       = 10
    !INTEGER,          PARAMETER:: print_step       = 15
    INTEGER,          PARAMETER:: nuratio_max_steps= 300
    INTEGER,          PARAMETER:: nuratio_min_it   = 100

    DOUBLE PRECISION, PARAMETER:: tol              = 1.0D-3
    !DOUBLE PRECISION, PARAMETER:: iter_tol         = 2.0D-2
    !DOUBLE PRECISION, PARAMETER:: backup_h        = 0.25D0
    DOUBLE PRECISION, PARAMETER:: max_art_pr_ghost = 1.0D+10
    DOUBLE PRECISION, PARAMETER:: tiny_real        = 1.0D-10
    DOUBLE PRECISION, PARAMETER:: nuratio_tol      = 2.5D-3

    INTEGER:: a, itr, itr2, i_shell, n_inc, cnt1, b, inde, index1   ! iterators
    INTEGER:: npart_real, npart_real_half, npart_ghost, npart_all
    INTEGER:: nx, ny, nz, i, j, k
    INTEGER:: a_numin, a_numin2, a_numax, a_numax2
    INTEGER:: nuratio_cnt
    INTEGER:: dim_seed, rel_sign
    INTEGER:: n_problematic_h, ill, l, itot, cnt_push_ghost, max_push_ghost
    INTEGER, DIMENSION(:), ALLOCATABLE:: cnt_move

    DOUBLE PRECISION:: ghost_displacement, min_radius, max_radius, radius_part
    DOUBLE PRECISION:: ellipse_thickness
    DOUBLE PRECISION:: radius_x, radius_y, radius_z
    DOUBLE PRECISION:: h_max, h_av, tmp, dens_min, atmosphere_density!, delta
    DOUBLE PRECISION:: xmin, xmax, ymin, ymax, zmin, zmax, dx, dy, dz, &
                       rad_x, rad_y, rad_z, com_x, com_y, com_z, com_d
    DOUBLE PRECISION:: max_z_real
    DOUBLE PRECISION:: xtemp, ytemp, ztemp, x_ell, y_ell, z_ell
    DOUBLE PRECISION:: min_nu, max_nu, min_nu2, max_nu2
    ! The value of nu equal for all the particles, used during the APM iteration
    DOUBLE PRECISION:: nu_all, nu_ghost
    DOUBLE PRECISION:: err_N_mean_min, err_N_mean_min_old, err_N_mean, &
                       err_mean_old, err_n_min, err_N_max, dN, &!dNstar, &
                       nstar_id_err, nstar_sph_err, dN_max, dN_av
    DOUBLE PRECISION:: art_pr_max
    DOUBLE PRECISION:: nu_tot, nu_ratio, nu_tmp2, nuratio_tmp, nuratio_tmp_prev
    DOUBLE PRECISION:: variance_nu, stddev_nu, mean_nu
    DOUBLE PRECISION:: variance_dN, stddev_dN
    DOUBLE PRECISION:: rand_num, rand_num2
    DOUBLE PRECISION:: r, theta, phi, frac
    DOUBLE PRECISION:: r_ell, theta_ell, phi_ell
    DOUBLE PRECISION:: dS_norm_av
    DOUBLE PRECISION:: nstar_id_av, nstar_sph_av, nlrf_id_av, pressure_id_av
    DOUBLE PRECISION:: l2norm_displacement, average_displacement

    INTEGER, DIMENSION(:), ALLOCATABLE:: neighbors_lists
    INTEGER, DIMENSION(:), ALLOCATABLE:: n_neighbors
    INTEGER, DIMENSION(:), ALLOCATABLE:: seed

    DOUBLE PRECISION, DIMENSION(3):: pos_corr_tmp
    DOUBLE PRECISION, DIMENSION(3):: pos_maxerr
    DOUBLE PRECISION, DIMENSION(:,:), ALLOCATABLE:: pos
    DOUBLE PRECISION, DIMENSION(:,:), ALLOCATABLE:: pos_tmp
    DOUBLE PRECISION, DIMENSION(:,:), ALLOCATABLE:: ghost_pos
    DOUBLE PRECISION, DIMENSION(:,:), ALLOCATABLE:: all_pos
    DOUBLE PRECISION, DIMENSION(:,:), ALLOCATABLE:: all_pos_prev
    DOUBLE PRECISION, DIMENSION(:,:), ALLOCATABLE:: correction_pos

    DOUBLE PRECISION, DIMENSION(:),   ALLOCATABLE:: h_guess
    DOUBLE PRECISION, DIMENSION(:),   ALLOCATABLE:: h_tmp
    DOUBLE PRECISION, DIMENSION(:),   ALLOCATABLE:: h_guess_tmp
    DOUBLE PRECISION, DIMENSION(:),   ALLOCATABLE:: cnt_array

    DOUBLE PRECISION, DIMENSION(:),   ALLOCATABLE:: rho_tmp
    DOUBLE PRECISION, DIMENSION(:),   ALLOCATABLE:: nstar_id
    DOUBLE PRECISION, DIMENSION(:),   ALLOCATABLE:: nlrf_id
    DOUBLE PRECISION, DIMENSION(:),   ALLOCATABLE:: pressure_id
    !DOUBLE PRECISION, DIMENSION(:),   ALLOCATABLE:: nstar_eul_id
    !DOUBLE PRECISION, DIMENSION(:),   ALLOCATABLE:: nu_eul
    DOUBLE PRECISION, DIMENSION(:),   ALLOCATABLE:: nstar_sph
    DOUBLE PRECISION, DIMENSION(:),   ALLOCATABLE:: nlrf_sph
    DOUBLE PRECISION, DIMENSION(:),   ALLOCATABLE:: pressure_sph
    DOUBLE PRECISION, DIMENSION(:),   ALLOCATABLE:: sqg
    DOUBLE PRECISION, DIMENSION(:,:), ALLOCATABLE:: dS
    DOUBLE PRECISION, DIMENSION(:),   ALLOCATABLE:: dNstar
    DOUBLE PRECISION, DIMENSION(:),   ALLOCATABLE:: art_pr

    DOUBLE PRECISION, DIMENSION(:),   ALLOCATABLE:: nu_tmp
    DOUBLE PRECISION, DIMENSION(:),   ALLOCATABLE:: pvol_tmp
    DOUBLE PRECISION, DIMENSION(:),   ALLOCATABLE:: nu_one

    DOUBLE PRECISION, DIMENSION(:),   ALLOCATABLE:: nstar_int
    DOUBLE PRECISION, DIMENSION(:),   ALLOCATABLE:: particle_density_final

    DOUBLE PRECISION, DIMENSION(:,:), ALLOCATABLE:: nearest_neighbors

    LOGICAL:: exist

    CHARACTER(LEN=:), ALLOCATABLE:: finalnamefile

    LOGICAL, PARAMETER:: debug= .FALSE.
    !LOGICAL:: few_ncand!, invertible_matrix
    LOGICAL:: push_away_ghosts

    TYPE(timer):: find_h_bruteforce_timer

    find_h_bruteforce_timer= timer( "find_h_bruteforce_timer" )

    CALL RANDOM_SEED( SIZE= dim_seed )
    ALLOCATE( seed( dim_seed ) )
    seed( 1 )= 2
    seed( 2 )= 1
    DO itr= 3, dim_seed
      seed( itr )= seed( itr - 1 ) + seed( itr - 2 )
    ENDDO
    CALL RANDOM_SEED( PUT= seed )

    IF( debug ) PRINT *, "0"

    npart_real= SIZE( pos_input(1,:) )

    IF( debug ) PRINT *, "npart_real= ", npart_real

    PRINT *, "** Steering parameters for the APM iteration:"
    PRINT *
    PRINT *, "   mass_it= ",           mass_it
    PRINT *, "   use_pressure= ",      use_pressure
    PRINT *, "   adapt_ghosts= ",      adapt_ghosts
    PRINT *, "   move_away_ghosts= ",  move_away_ghosts
    PRINT *, "   use_atmosphere= ",    use_atmosphere
    PRINT *, "   remove_atmosphere= ", remove_atmosphere
    PRINT *

    IF(use_pressure)THEN
      PRINT *, "** Using the physical pressure rather than the density to ", &
               "compute the artificial pressure."
      PRINT *
    ENDIF

    !---------------------------------------------------------------!
    !-- Tune the displacement the ghosts depending on the desired --!
    !-- EXIT condition for the APM iteration                      --!
    !---------------------------------------------------------------!

    IF( nuratio_des > zero )THEN

      ghost_displacement= third/DBLE(nuratio_max_steps)/ten
      max_push_ghost    = two*nuratio_max_steps

    ELSE

      ghost_displacement= third/DBLE(max_inc)/ten
      max_push_ghost    = max_inc

    ENDIF

    !------------------------------------------------!
    !-- If desired, compute the atmosphere density --!
    !------------------------------------------------!

    IF( use_atmosphere )THEN

      dens_min= HUGE(one)
      DO a= 1, npart_real, 1

        tmp= get_density( pos_input(1,a), pos_input(2,a), pos_input(3,a) )

        IF( tmp < dens_min )THEN

          dens_min= tmp

        ENDIF

      ENDDO
      atmosphere_density= zero*dens_min*1.0D-30

    ENDIF

    !---------------------------------------!
    !-- Allocate, assign and test h_guess --!
    !---------------------------------------!

    IF(.NOT.ALLOCATED( h_guess ))THEN
      ALLOCATE( h_guess( max_npart ), STAT= ios, &
          ERRMSG= err_msg )
      IF( ios > 0 )THEN
         PRINT *, "...allocation error for array h_guess in SUBROUTINE ", &
                  "perform_apm. The error message is",&
                  err_msg
         STOP
      ENDIF
    ENDIF

    h_guess= zero
    !$OMP PARALLEL DO DEFAULT( NONE ) &
    !$OMP             SHARED( h_guess, pvol, npart_real ) &
    !$OMP             PRIVATE( a )
    DO a= 1, npart_real, 1
      h_guess(a)= three*(pvol(a)**third)
    ENDDO
    !$OMP END PARALLEL DO

    find_h_bruteforce_timer= timer( "find_h_bruteforce_timer" )
    CALL find_h_bruteforce_timer% start_timer()
    n_problematic_h= 0
    check_h_guess: DO a= 1, npart_real, 1
    ! find_h_backup, called below, is OMP parallelized, so this loop
    ! should not be parallelized as well

      IF( .NOT.is_finite_number( h_guess(a) ) .OR. h_guess(a) <= zero )THEN

        n_problematic_h= n_problematic_h + 1
        h_guess(a)= find_h_backup( a, npart_real, pos_input, nn_des )
        IF( .NOT.is_finite_number( h_guess(a) ) .OR. h_guess(a) <= zero )THEN
          PRINT *, "** ERROR! h=0 on particle ", a, "even with the brute", &
                   " force method."
          PRINT *, "   Particle position: ", pos_input(:,a)
          STOP
        ENDIF

      ENDIF

    ENDDO check_h_guess
    CALL find_h_bruteforce_timer% stop_timer()
    CALL find_h_bruteforce_timer% print_timer( 2 )

    PRINT *, " * The smoothing length was found brute-force for ", &
             n_problematic_h, " particles."
    PRINT *

    IF( debug ) PRINT *, "0.5"

    !--------------------------------------------------------------------!
    !-- Store particles above xy plane as the first half of the array, --!
    !-- and mirror them to the second half                             --!
    !--------------------------------------------------------------------!

    pos_tmp= pos_input
    h_tmp= h_guess
    itr= 0

    DO a= 1, npart_real, 1
      IF( pos_tmp(3,a) > zero )THEN
        itr= itr + 1
        pos_input(1,itr)= pos_tmp(1,a)
        pos_input(2,itr)= pos_tmp(2,a)
        pos_input(3,itr)= pos_tmp(3,a)
        h_guess( itr )     = h_tmp( itr )
      ENDIF
    ENDDO
    npart_real_half= itr

    DO a= 1, npart_real_half, 1
      pos_input(1, npart_real_half + a)=   pos_input(1,a)
      pos_input(2, npart_real_half + a)=   pos_input(2,a)
      pos_input(3, npart_real_half + a)= - pos_input(3,a)
      h_guess( npart_real_half + a )   =   h_guess( a )
    ENDDO
    npart_real= 2*npart_real_half

    IF( debug ) PRINT *, "1"
    IF( debug ) PRINT *, "npart_real= ", npart_real

    !--------------------------------------------------------------------!
    !-- Find the maximum and the average smoothing length of the       --!
    !-- particles whose distance from the center is higher than        --!
    !-- radius_z, and use them to place ghost particles a little more  --!
    !-- outside than the surface of the particles.                     --!
    !--------------------------------------------------------------------!

    radius_x= MAX( sizes(1), sizes(2) )
    radius_y= MAX( sizes(3), sizes(4) )
    radius_z= MAX( sizes(5), sizes(6) )
    min_radius = MINVAL([radius_x, radius_y, radius_z])
    max_radius = MAXVAL([radius_x, radius_y, radius_z])

    h_max= zero
    h_av = zero
    itr  = 0
    max_z_real= ABS( MAXVAL( ABS(pos_input(3,:) - center(3)), DIM= 1 ) )
    ALLOCATE(cnt_array(npart_real))
    cnt_array= zero
    !$OMP PARALLEL DO DEFAULT( NONE ) &
    !$OMP             SHARED( pos_input, npart_real, center, h_guess, &
    !$OMP                     cnt_array, max_z_real ) &
    !$OMP             PRIVATE( a ) &
    !$OMP             REDUCTION( MAX: h_max ) &
    !$OMP             REDUCTION( +: h_av )
    DO a= 1, npart_real, 1

      IF( SQRT( ( pos_input(1,a) - center(1) )**two &
              + ( pos_input(2,a) - center(2) )**two &
              + ( pos_input(3,a) - center(3) )**two ) &
                > (one - one/(ten*ten))*max_z_real )THEN

        cnt_array(a)= one
        !IF( h_guess(a) > h_max )THEN
        !  h_max= h_guess(a)
        !ENDIF
        h_max= MAX(h_max, h_guess(a))
        h_av = h_av + h_guess(a)

      ENDIF

    ENDDO
    !$OMP END PARALLEL DO
    h_av= h_av/SUM(cnt_array, DIM=1)
    IF( debug ) PRINT *, "h_av=", h_av
    IF( debug ) PRINT *

    IF( debug ) PRINT *, "2"

    !-------------------------------!
    !--  Placing ghost particles  --!
    !-------------------------------!

    CALL place_and_print_ghost_particles()

    ! Assign values to all_pos
    IF(.NOT.ALLOCATED( all_pos ))THEN
      ALLOCATE( all_pos( 3, npart_all ), STAT= ios, &
          ERRMSG= err_msg )
      IF( ios > 0 )THEN
         PRINT *, "...allocation error for array ghost_pos in SUBROUTINE ", &
                  "perform_apm. The error message is",&
                  err_msg
         STOP
      ENDIF
    ENDIF
    all_pos(:, 1:npart_real)          = pos_input
    all_pos(:, npart_real+1:npart_all)= ghost_pos

    CALL allocate_apm_fields( npart_real, npart_ghost )

    IF(debug)THEN
      !
      !-- Estimate ghost density considering also the real particles
      !

      npart= npart_all

      CALL allocate_SPH_memory

      CALL allocate_RCB_tree_memory_3D(npart)
      iorig(1:npart)= (/ (a,a=1,npart) /)

      IF( debug ) PRINT *, "10"

      CALL allocate_gradient( npart )
      CALL allocate_metric_on_particles( npart )

      ! Determine smoothing length so that each particle has exactly
      ! 300 neighbours inside 2h
      CALL assign_h( nn_des, &
                     npart_all, &
                     all_pos, &
                     h_guess, &
                     h )

      find_h_bruteforce_timer= timer( "find_h_bruteforce_timer" )
      CALL find_h_bruteforce_timer% start_timer()
      n_problematic_h= 0
      DO a= 1, npart_all, 1
      ! find_h_backup, called below, is OMP parallelized, so this loop
      ! should not be parallelized as well

        IF( .NOT.is_finite_number( h(a) ) .OR. h(a) <= zero )THEN

          n_problematic_h= n_problematic_h + 1
          h(a)= find_h_backup( a, npart_all, all_pos(:,:), nn_des)
          IF( .NOT.is_finite_number( h(a) ) .OR. h(a) <= zero )THEN
            PRINT *, "** ERROR! h=0 on particle ", a, "even with the brute", &
                     " force method."
            PRINT *, "   Particle position: ", all_pos(:,a)
            STOP
          ENDIF

        ENDIF

      ENDDO
      CALL find_h_bruteforce_timer% stop_timer()
      CALL find_h_bruteforce_timer% print_timer( 2 )

      PRINT *, " * The smoothing length was found brute-force for ", &
               n_problematic_h, " particles."
      PRINT *

      PRINT *, " * Measure SPH particle number density..."
      PRINT *

      nu(1:npart_real)          = (mass/DBLE(npart_real))*umass/amu
      nu(npart_real+1:npart_all)= nu_ghost
      PRINT *, "npart_all         =", npart_all
      PRINT *, "SIZE(all_pos(1,:))=", SIZE(all_pos(1,:))
      PRINT *, "SIZE(nu)          =", SIZE(nu)
      PRINT *, "SIZE(h)           =", SIZE(h)
      PRINT *, "SIZE(nstar_sph)   =", SIZE(nstar_sph)
      PRINT *
      CALL density_loop( npart_all, all_pos, &
                         nu, h, nstar_sph )

      !nstar_sph_ghost_av= SUM(nstar_sph_ghost)/npart_ghost
      PRINT *, "nstar_sph_ghost_max=", &
               MAXVAL(nstar_sph(npart_real+1:npart_all), DIM=1)
      PRINT *, "nstar_sph_ghost_av=", &
               SUM(nstar_sph(npart_real+1:npart_all))/npart_ghost
      PRINT *, "nstar_sph_ghost_min=", &
               MINVAL(nstar_sph(npart_real+1:npart_all), DIM=1)

      CALL deallocate_metric_on_particles()
      CALL deallocate_gradient()
      CALL deallocate_RCB_tree_memory_3D()
      CALL deallocate_SPH_memory()

    ENDIF

    !STOP

    !----------------------------!
    !-- Allocate needed memory --!
    !----------------------------!

    npart= npart_all

    CALL allocate_SPH_memory

    CALL allocate_RCB_tree_memory_3D(npart)
    iorig(1:npart)= (/ (a,a=1,npart) /)

    IF( debug ) PRINT *, "10"

    CALL allocate_gradient( npart )
    CALL allocate_metric_on_particles( npart )

    !-------------------------------------!
    !-- Setting up ID for APM iteration --!
    !-------------------------------------!

    PRINT *, "** Setting up ID for APM iteration..."
    PRINT *

    PRINT *, " * Assigning h..."
    PRINT *

    ! Determine smoothing length so that each particle has exactly
    ! 300 neighbours inside 2h
    CALL assign_h( nn_des, &
                   npart_all, &
                   all_pos, h_guess, &
                   h )

    find_h_bruteforce_timer= timer( "find_h_bruteforce_timer" )
    CALL find_h_bruteforce_timer% start_timer()
    n_problematic_h= 0
    check_h1: DO a= 1, npart_real, 1
    ! find_h_backup, called below, is OMP parallelized, so this loop
    ! should not be parallelized as well

      IF( .NOT.is_finite_number( h(a) ) .OR. h(a) <= zero )THEN

        n_problematic_h= n_problematic_h + 1
        h(a)= find_h_backup( a, npart_real, all_pos(:,1:npart_real), nn_des )
        IF( .NOT.is_finite_number( h(a) ) .OR. h(a) <= zero )THEN
          PRINT *, "** ERROR! h=0 on particle ", a, "even with the brute", &
                   " force method."
          PRINT *, "   Particle position: ", all_pos(:,a)
          STOP
        ENDIF

      ENDIF

    ENDDO check_h1
    CALL find_h_bruteforce_timer% stop_timer()
    CALL find_h_bruteforce_timer% print_timer( 2 )

    PRINT *, " * The smoothing length was found brute-force for ", &
             n_problematic_h, " particles."
    PRINT *

    PRINT *, " * Measure SPH particle number density..."
    PRINT *

    nu= one
    CALL density_loop( npart_all, all_pos, &    ! input
                       nu, h, nstar_sph )      ! output

    !$OMP PARALLEL DO DEFAULT( NONE ) &
    !$OMP             SHARED( all_pos, npart_all, nstar_sph, h, nu, &
    !$OMP                     center ) &
    !$OMP             PRIVATE( a )
    check_nstar_sph: DO a= 1, npart_all, 1

      IF( .NOT.is_finite_number( nstar_sph( a ) ) )THEN

        PRINT *, "** WARNING! nstar_sph(", a, ") is a not a finite number ",&
                 "in SUBROUTINE perform_apm!"
        IF( debug ) PRINT *, " * h(", a, ")=", h(a)
        IF( debug ) PRINT *, " * nu(", a, ")=", nu(a)
        IF( debug ) PRINT *, " * all_pos(", a, ")=", all_pos(:,a)
        IF( debug ) PRINT *, " * r(", a, ")=", &
                              SQRT( ( all_pos(1,a) - center )**two &
                              + all_pos(2,a)**two + all_pos(3,a)**two )
        PRINT *
        STOP

      ENDIF

    ENDDO check_nstar_sph
    !$OMP END PARALLEL DO

    IF( debug ) PRINT *, "4"

    max_nu= zero
    min_nu= HUGE(one)

    IF( debug ) PRINT *, "7"

    CALL get_nstar_id_atm( npart_real, all_pos(1,1:npart_real), &
                           all_pos(2,1:npart_real), &
                           all_pos(3,1:npart_real), &
                           nstar_sph, nstar_id, nlrf_sph, sqg, &
                           use_atmosphere )

  ! The following test is done inside get_nstar_id_atm. Kept here for paranoia
  !  !$OMP PARALLEL DO DEFAULT( NONE ) &
  !  !$OMP             SHARED( all_pos, npart_all, nstar_id, h, nu, &
  !  !$OMP                     center, dNstar ) &
  !  !$OMP             PRIVATE( a )
  !  check_nstar_id: DO a= 1, npart_all, 1
  !
  !    IF( .NOT.is_finite_number( nstar_id( a ) ) )THEN
  !
  !      PRINT *, "** WARNING! nstar_id(", a, ") is a not a finite number ", &
  !               "in SUBROUTINE perform_apm!"
  !      PRINT *, "   nstar_id(", a, ")=", nstar_id(a)
  !      PRINT *, "   dNstar(", a, ")=", dNstar(a)
  !      PRINT *, "   rho(", a, ")=", get_density( all_pos(1,a), &
  !                                                all_pos(2,a), &
  !                                                all_pos(3,a) )
  !      IF( debug ) PRINT *, " * h(", a, ")=", h(a)
  !      IF( debug ) PRINT *, " * nu(", a, ")=", nu(a)
  !      IF( debug ) PRINT *, " * all_pos(", a, ")=", all_pos(:,a)
  !      IF( debug ) PRINT *, " * r(", a, ")=", &
  !                            SQRT( ( all_pos(1,a) - center )**two &
  !                            + all_pos(2,a)**two + all_pos(3,a)**two )
  !      PRINT *
  !      STOP
  !
  !    ENDIF
  !
  !  ENDDO check_nstar_id
  !  !$OMP END PARALLEL DO

    IF( debug ) PRINT *, "8"

    !----------------------------------------------------!
    !-- enforce centre of mass after having changed nu --!
    !----------------------------------------------------!

    IF( com_star(1) == zero &
        .AND. com_star(2) == zero .AND. com_star(3) == zero )THEN

      CALL COM( npart_real, all_pos(:,1:npart_real), nu(1:npart_real), & ! input
                com_star(1), com_star(2), com_star(3), com_d ) ! output

    ENDIF

    CALL correct_center_of_mass( npart_real, all_pos(:,1:npart_real), &
                                 nu(1:npart_real), get_density, &
                                 validate_position_final, com_star, &
                                 verbose= .TRUE. )

    !-----------------------------------------------------------------------!
    !-- Mirror the positions after having repositioned the center of mass --!
    !-----------------------------------------------------------------------!

    CALL impose_equatorial_plane_symmetry( npart_real, &
                                           all_pos(:,1:npart_real), &
                                           nu(1:npart_real) )

    PRINT *, " * ID set up for the APM iteration."
    PRINT *

    !-----------------------------------------------------------------------!
    !-----------------------------------------------------------------------!
    !--                           APM iteration                           --!
    !-- Assume equal mass particles, and move them so that the SPH kernel --!
    !-- estimate of the mass density matches the star mass density as     --!
    !-- well as reasonably possible.                                      --!
    !-----------------------------------------------------------------------!
    !-----------------------------------------------------------------------!



    PRINT *, " * Performing APM iteration..."
    PRINT *

    cnt_move= 0

    ! Set the particles to be equal-mass
    nu_all= (mass/DBLE(npart_real))*umass/amu
    nu(1:npart_real)          = nu_all
    nu(npart_real+1:npart_all)= nu_ghost
    ! nu_ghost is set in place_and_print_ghost_particles

    CALL correct_center_of_mass( npart_real, all_pos(:,1:npart_real), &
                                 nu(1:npart_real), get_density, &
                                 validate_position_final, com_star, &
                                 verbose= .TRUE. )

    all_pos_prev= -one
    PRINT *, " * The APM iteration starts here."
    PRINT *

    n_inc           = 0
    err_N_mean_min  = HUGE(one)
    nuratio_cnt     = 0
    nuratio_tmp_prev= 1.D-8
    cnt_push_ghost  = 0
    apm_iteration: DO itr= 1, apm_max_it, 1

      PRINT *, "------------------------------------------"
      PRINT *, " * Starting with APM step #: ", itr
      PRINT *

      IF( print_step /= 0 &
          .AND. &
          MOD( itr, print_step ) == 0 )THEN

     ! DEBUGGING
     !
     !   DO a= 1, npart_real, 1
     !     IF( check_particle_position( a - 1, &
     !                                  all_pos(:,1:a-1), &
     !                                  all_pos(:,a) ) > 0 &
     !         .AND. &
     !         check_particle_position( npart_real - a, &
     !                                  all_pos(:,a+1:npart_real), &
     !                                  all_pos(:,a) ) > 0 &
     !     )THEN
     !
     !       CALL RANDOM_NUMBER( rand_num )
     !       CALL RANDOM_NUMBER( rand_num2 )
     !
     !       IF( rand_num2 < half )  rel_sign= - 1
     !       IF( rand_num2 >= half ) rel_sign=   1
     !
     !       all_pos(:,a)= all_pos(:,a)*( one + &
     !                                    DBLE(rel_sign)*rand_num*half*third )
     !
     !     ENDIF
     !   ENDDO
     !
     ! END DEBUGGING

        PRINT *, " * Printing temporary APM data to file..."
        CALL dump_apm_pos()
        PRINT *, "   ...done."
        PRINT *

      ENDIF

      IF( debug ) PRINT *, "enforcing center of mass..."

      CALL correct_center_of_mass( npart_real, all_pos(:,1:npart_real), &
                                   nu(1:npart_real), get_density, &
                                   validate_position_final, com_star )

      IF( debug ) PRINT *, "mirroring particles..."

      CALL impose_equatorial_plane_symmetry( npart_real, &
                                             all_pos(:,1:npart_real), &
                                             nu(1:npart_real) )

      IF( debug )THEN

        CALL COM( npart_real, all_pos(:,1:npart_real), &  !
                  nu(1:npart_real), &                     ! input
                  com_x, com_y, com_z, com_d )            ! output

        PRINT *, "** After center of mass correction:"
        PRINT *, " * x coordinate of the center of mass of the star, ", &
                 "from ID: com_star= ", com_star, "Msun_geo"
        PRINT *, " * x coordinate of the center of mass of the particle ", &
                 "distribution: com_x= ", com_x, "Msun_geo"
        PRINT *, " * y coordinate of the center of mass of the particle ", &
                 "distribution: com_y= ", com_y, "Msun_geo"
        PRINT *, " * z coordinate of the center of mass of the particle ", &
                 "distribution: com_z= ", com_z, "Msun_geo"
        PRINT *, " * Distance of the center of mass of the particle ", &
                 "distribution from the  origin: com_d= ", com_d
        PRINT *, " * |com_x-com_star(1)|/|com_star(1)+1|=", &
                 ABS( com_x-com_star(1) )/ABS( com_star(1) + 1 )
        PRINT *, " * |com_y-com_star(2)|/|com_star(2)+1|=", &
                 ABS( com_y-com_star(2) )/ABS( com_star(2) + 1 )
        PRINT *, " * |com_z-com_star(3)|/|com_star(3)+1|=", &
                 ABS( com_z-com_star(3) )/ABS( com_star(3) + 1 )
        PRINT *

        finalnamefile= TRIM(sph_path)//"dbg-pos.dat"

        INQUIRE( FILE= TRIM(finalnamefile), EXIST= exist )

        IF( exist )THEN
            OPEN( UNIT= 2, FILE= TRIM(finalnamefile), STATUS= "REPLACE", &
                  FORM= "FORMATTED", &
                  POSITION= "REWIND", ACTION= "WRITE", IOSTAT= ios, &
                  IOMSG= err_msg )
        ELSE
            OPEN( UNIT= 2, FILE= TRIM(finalnamefile), STATUS= "NEW", &
                  FORM= "FORMATTED", &
                  ACTION= "WRITE", IOSTAT= ios, IOMSG= err_msg )
        ENDIF
        IF( ios > 0 )THEN
          PRINT *, "...error when opening " // TRIM(finalnamefile), &
                   ". The error message is", err_msg
          STOP
        ENDIF

        DO a= 1, npart_real, 1
          WRITE( UNIT = 2, IOSTAT = ios, IOMSG = err_msg, FMT = * ) &
            1, a, &
            all_pos(1,a), &
            all_pos(2,a), &
            all_pos(3,a)
        ENDDO

        DO a= npart_real+1, npart_all, 1
          WRITE( UNIT = 2, IOSTAT = ios, IOMSG = err_msg, FMT = * ) &
            2, a, &
            all_pos(1,a), &
            all_pos(2,a), &
            all_pos(3,a)
        ENDDO

        CLOSE( UNIT= 2 )

      ENDIF

      IF( debug ) PRINT *, "assign h..."

      h_guess(1:npart_real)= h(1:npart_real)
      !h_guess(npart_real+1:npart_all)= dx*dy*dz

      CALL assign_h( nn_des, &
                     npart_all, &
                     all_pos, h_guess, h )

      find_h_bruteforce_timer= timer( "find_h_bruteforce_timer" )
      CALL find_h_bruteforce_timer% start_timer()
      n_problematic_h= 0
      check_h2: DO a= 1, npart_real, 1
      ! find_h_backup, called below, is OMP parallelized, so this loop
      ! should not be parallelized as well

        IF( .NOT.is_finite_number( h(a) ) .OR. h(a) <= zero )THEN

          n_problematic_h= n_problematic_h + 1
          h(a)= find_h_backup( a, npart_real, all_pos(:,1:npart_real), nn_des )
          IF( .NOT.is_finite_number( h(a) ) .OR. h(a) <= zero )THEN
            PRINT *, "** ERROR! h=0 on particle ", a, "even with the brute", &
                     " force method."
            PRINT *, "   Particle position: ", all_pos(:,a)
            STOP
          ENDIF

        ENDIF

      ENDDO check_h2
      CALL find_h_bruteforce_timer% stop_timer()
      CALL find_h_bruteforce_timer% print_timer( 2 )

      PRINT *, " * The smoothing length was found brute-force for ", &
               n_problematic_h, " particles."
      PRINT *

      IF( debug ) PRINT *, "density_loop..."

      CALL density_loop( npart_all, all_pos, &    ! input
                         nu, h, nstar_sph )      ! output

      IF( debug ) PRINT *, "npart_real= ", npart_real
      IF( debug ) PRINT *, "npart_all= ", npart_all
      IF( debug ) PRINT *

      !$OMP PARALLEL DO DEFAULT( NONE ) &
      !$OMP             SHARED( all_pos, npart_all, nstar_sph, h, nu, &
      !$OMP                     center ) &
      !$OMP             PRIVATE( a )
      check_nstar_sph2: DO a= 1, npart_all, 1

        IF( .NOT.is_finite_number( nstar_sph( a ) ) )THEN

          PRINT *, "** WARNING! nstar_sph(", a, ") is a not a finite number ",&
                   "in SUBROUTINE perform_apm!"
          IF( debug ) PRINT *, " * h(", a, ")=", h(a)
          IF( debug ) PRINT *, " * nu(", a, ")=", nu(a)
          IF( debug ) PRINT *, " * all_pos(", a, ")=", all_pos(:,a)
          IF( debug ) PRINT *, " * r(", a, ")=", &
                                SQRT( ( all_pos(1,a) - center )**two &
                                + all_pos(2,a)**two + all_pos(3,a)**two )
          PRINT *
          STOP

        ENDIF

      ENDDO check_nstar_sph2
      !$OMP END PARALLEL DO

      CALL get_nstar_id_atm( npart_real, all_pos(1,1:npart_real), &
                             all_pos(2,1:npart_real), &
                             all_pos(3,1:npart_real), &
                             nstar_sph, nstar_id, nlrf_sph, sqg, &
                             use_atmosphere )

! The following test is done inside get_nstar_id_atm. Kept here for paranoia
!      !$OMP PARALLEL DO DEFAULT( NONE ) &
!      !$OMP             SHARED( all_pos, npart_all, nstar_id, h, nu, &
!      !$OMP                     center, dNstar ) &
!      !$OMP             PRIVATE( a )
!      check_nstar_id2: DO a= 1, npart_all, 1
!
!        IF( .NOT.is_finite_number( nstar_id( a ) ) )THEN
!
!          PRINT *, "** WARNING! nstar_id(", a, ") is a not a finite number ", &
!                   "in SUBROUTINE perform_apm!"
!          PRINT *, "   nstar_id(", a, ")=", nstar_id(a)
!          PRINT *, "   dNstar(", a, ")=", dNstar(a)
!          PRINT *, "   rho(", a, ")=", get_density( all_pos(1,a), &
!                                                    all_pos(2,a), &
!                                                    all_pos(3,a) )
!          IF( debug ) PRINT *, " * h(", a, ")=", h(a)
!          IF( debug ) PRINT *, " * nu(", a, ")=", nu(a)
!          IF( debug ) PRINT *, " * all_pos(", a, ")=", all_pos(:,a)
!          IF( debug ) PRINT *, " * r(", a, ")=", &
!                                SQRT( ( all_pos(1,a) - center )**two &
!                                + all_pos(2,a)**two + all_pos(3,a)**two )
!          PRINT *
!          STOP
!
!        ENDIF
!
!      ENDDO check_nstar_id2
!      !$OMP END PARALLEL DO

      IF(use_pressure)THEN

        CALL read_pressure_id( npart_real, &
                               all_pos(1,1:npart_real), &
                               all_pos(2,1:npart_real), &
                               all_pos(3,1:npart_real), &
                               pressure_id(1:npart_real) )

        CALL compute_pressure( npart_all, &
                               all_pos(1,:), all_pos(2,:), all_pos(3,:), &
                               nlrf_sph, eqos, pressure_sph, .FALSE. )

      ENDIF

      !PRINT *, pressure_sph(10)
      !PRINT *, pressure_id(10)
      !STOP

      !
      !-- Assign artificial pressure to the real particles
      !
      art_pr_max= zero
      err_N_max=  zero
      err_N_min=  HUGE(one)
      err_N_mean= zero

      !$OMP PARALLEL DO DEFAULT( NONE ) &
      !$OMP             SHARED( npart_real, nstar_sph, nstar_id, &
      !$OMP                     pressure_sph, pressure_id, &
      !$OMP                     dNstar, art_pr, use_pressure ) &
      !$OMP             PRIVATE( a )
      assign_artificial_pressure_on_real_particles: DO a= 1, npart_real, 1

        IF( nstar_id(a) <= zero )THEN

          dNstar(a)= zero

        ELSEIF(use_pressure)THEN

          dNstar(a)= (pressure_sph(a) - pressure_id(a))/pressure_id(a)

        ELSE

          dNstar(a)= (nstar_sph(a) - nstar_id(a))/nstar_id(a)

        ENDIF
        art_pr(a) = MAX( one + dNstar(a), one/ten )

      ENDDO assign_artificial_pressure_on_real_particles
      !$OMP END PARALLEL DO

      !$OMP PARALLEL DO DEFAULT( NONE ) &
      !$OMP             SHARED( npart_all, npart_real, art_pr, nstar_sph, &
      !$OMP                     nstar_id, dNstar, all_pos ) &
      !$OMP             PRIVATE( a )
      find_nan_in_art_pr: DO a= 1, npart_real, 1

        IF( .NOT.is_finite_number(art_pr(a)) )THEN
          PRINT *, "** ERROR! art_pr(", a, ")= ", art_pr(a), &
                   " is not a finite number on a real particle!"
          PRINT *, "   nstar_sph(", a, ")=", nstar_sph(a)
          PRINT *, "   nstar_id(", a, ")=", nstar_id(a)
          PRINT *, "   dNstar(", a, ")=", dNstar(a)
          PRINT *, "   rho(", a, ")=", get_density( all_pos(1,a), &
                                                    all_pos(2,a), &
                                                    all_pos(3,a) )
          PRINT *, " * Stopping..."
          PRINT *
          STOP
        ENDIF

      ENDDO find_nan_in_art_pr
      !$OMP END PARALLEL DO

      art_pr_max= - HUGE(one)
      err_N_max = - HUGE(one)
      err_N_min =   HUGE(one)
      !$OMP PARALLEL DO DEFAULT( NONE ) &
      !$OMP             SHARED( npart_real, art_pr, dNstar ) &
      !$OMP             PRIVATE( a ) &
      !$OMP             REDUCTION( MAX: art_pr_max, err_N_max ) &
      !$OMP             REDUCTION( MIN: err_N_min )
      DO a= 1, npart_real, 1
        art_pr_max= MAX( art_pr_max, art_pr(a) )
        err_N_max = MAX( err_N_max, ABS(dNstar(a)) )
        err_N_min = MIN( err_N_min, ABS(dNstar(a)) )
      ENDDO
      !$OMP END PARALLEL DO
      IF( .NOT.is_finite_number( art_pr_max ) )THEN
        PRINT *, "** ERROR! art_pr_max is not a finite number!", &
                 " Stopping.."
        PRINT *
        STOP
      ENDIF

      !$OMP PARALLEL DO DEFAULT( NONE ) &
      !$OMP             SHARED( npart_real, dNstar, nstar_id ) &
      !$OMP             PRIVATE( a ) &
      !$OMP             REDUCTION( +: err_N_mean )
      DO a= 1, npart_real, 1
        err_N_mean= err_N_mean + ABS(dNstar(a))
      ENDDO
      !$OMP END PARALLEL DO
      err_N_mean= err_N_mean/npart_real
      err_N_mean_min= MIN( err_N_mean, err_N_mean_min )

      !$OMP PARALLEL DO DEFAULT( NONE ) &
      !$OMP             SHARED( all_pos, npart_real, nstar_sph, nstar_id, &
      !$OMP                     dNstar, err_N_max, &
      !$OMP                     pos_maxerr, nstar_sph_err, nstar_id_err ) &
      !$OMP             PRIVATE( a )
      DO a= 1, npart_real, 1

        IF( dNstar(a) == err_N_max )THEN
          pos_maxerr   = all_pos(:,a)
          nstar_sph_err= nstar_sph(a)
          nstar_id_err = nstar_id(a)
        ENDIF

        IF( .NOT.is_finite_number(dNstar(a)) )THEN
          PRINT *, "** ERROR! dNstar(", a, ")= ", dNstar(a), &
                   " is not a finite number on a real particle!"
          PRINT *, "   nstar_sph= ", nstar_sph(a)
          PRINT *, "   nstar_id= ", nstar_id(a)
          STOP
        ENDIF

      ENDDO
      !$OMP END PARALLEL DO

      IF( .NOT.is_finite_number( nu_all ) )THEN
        PRINT *, "** ERROR! nu_all is not a finite number!", &
                 " * Stopping.."
        PRINT *
        STOP
      ENDIF

      !
      !-- Assign artificial pressure to the ghost particles
      !
      IF(adapt_ghosts)THEN

        IF(debug) PRINT *, "adapt_ghosts=", adapt_ghosts
        IF(debug) PRINT *

        nstar_id( npart_real+1:npart_all )= nstar_id_av
        IF(debug) PRINT*, "nstar_id_av=", nstar_id_av

        !$OMP PARALLEL DO DEFAULT( NONE ) &
        !$OMP             SHARED( npart_real, npart_all, nstar_sph, nstar_id, &
        !$OMP                     dNstar, art_pr, nstar_id_av, pressure_id_av, &
        !$OMP                     pressure_sph, use_pressure ) &
        !$OMP             PRIVATE( a )
        assign_artificial_pressure_on_ghost_particles_adapt: &
        DO a= npart_real + 1, npart_all, 1

          IF(use_pressure)THEN

            art_pr(a)= MAX(one + (pressure_sph(a) - pressure_id_av) &
                                  /pressure_id_av, zero)

          ELSE

            art_pr(a)= MAX(one + (nstar_sph(a) - nstar_id_av)/nstar_id_av, zero)

          ENDIF

        ENDDO assign_artificial_pressure_on_ghost_particles_adapt
        !$OMP END PARALLEL DO

      ELSE

        nstar_id( npart_real+1:npart_all )= zero
        !art_pr ( npart_real+1:npart_all )= 6.0D0*art_pr_max

        !$OMP PARALLEL DO DEFAULT( NONE ) &
        !$OMP             SHARED( all_pos, npart_all, npart_real, center, &
        !$OMP                     dNstar, art_pr, rad_x, rad_y, rad_z, &
        !$OMP                     art_pr_max, itr, ellipse_thickness ) &
        !$OMP             PRIVATE( a, r, theta, phi, x_ell, y_ell, z_ell, r_ell, &
        !$OMP                      i_shell )
        assign_artificial_pressure_on_ghost_particles: &
        !
        !-- Assign a pressure to the ghosts, that grows linearly with the
        !-- distance from the center of the matter object
        !
        DO a= npart_real + 1, npart_all, 1

          CALL spherical_from_cartesian( &
            all_pos(1,a), all_pos(2,a), all_pos(3,a), &
            center(1), center(2), center(3), &
            r, theta, phi )

          CALL cartesian_from_spherical( &
            rad_x, theta, phi, &
            center(1), center(2), center(3), &
            x_ell, y_ell, z_ell, rad_y/rad_x, rad_z/rad_x )

          r_ell= SQRT( ( x_ell - center(1) )**two &
                     + ( y_ell - center(2) )**two &
                     + ( z_ell - center(3) )**two )

          shell_loop: DO i_shell= 1, 10, 1

            IF( r <= (one + (ellipse_thickness - one)*DBLE(i_shell)/ten)*r_ell &
                .AND. &
            r >= (one + (ellipse_thickness - one)*DBLE(i_shell - 1)/ten)*r_ell &
            )THEN

              art_pr(a)= DBLE(3*i_shell)*art_pr_max
              IF( .NOT.is_finite_number(art_pr(a)) &
                  .OR. &
                  art_pr(a) > max_art_pr_ghost &
              )THEN
                art_pr(a)= max_art_pr_ghost
              ENDIF
              EXIT

            ENDIF

          ENDDO shell_loop

        ENDDO assign_artificial_pressure_on_ghost_particles
        !$OMP END PARALLEL DO

      ENDIF

      !STOP

      IF( debug ) PRINT *, "Before calling position_correction"

      IF( debug ) PRINT *, npart_all
      IF( debug ) PRINT *, SIZE(all_pos(1,:))
      IF( debug ) PRINT *, SIZE(h)
      IF( debug ) PRINT *, SIZE(art_pr)
      IF( debug ) PRINT *, SIZE(nstar_sph)
      IF( debug ) PRINT *, SIZE(correction_pos(1,:))

      !
      !-- The following loop shouldn't be needed, but apparently
      !-- the test in the previous loop is not enough to remove
      !-- random NaNs from the artificial pressure on the ghost particles.
      !-- Which, btw, shouldn't be there at all since all the quantities are
      !-- tested, and no errors are detected...
      !-- Also, this loop is needed only when compiling with gfortran.
      !
      !$OMP PARALLEL DO DEFAULT( NONE ) &
      !$OMP             SHARED( npart_all, npart_real, art_pr, nstar_sph, &
      !$OMP                     nstar_id, all_pos ) &
      !$OMP             PRIVATE( a )
      fix_nan_in_art_pr_ghost: DO a= npart_real + 1, npart_all, 1

        IF( .NOT.is_finite_number(art_pr(a)) )THEN
          art_pr(a)= max_art_pr_ghost
          !PRINT *, "** ERROR! art_pr(", a, ")= ", art_pr(a), &
          !         " is not a finite number on a ghost particle!"
          !PRINT *, "   nstar_sph(", a, ")=", nstar_sph(a)
          !PRINT *, "   nstar_id(", a, ")=", nstar_id(a)
          !PRINT *, "   rho(", a, ")=", get_density( all_pos(1,a), &
          !                                          all_pos(2,a), &
          !                                          all_pos(3,a) )
          !PRINT *, " * Stopping.."
          !PRINT *
          !STOP
        ENDIF

      ENDDO fix_nan_in_art_pr_ghost
      !$OMP END PARALLEL DO

      PRINT *, " * Maximum relative error between the star density profile", &
               " and its SPH estimate: err_N_max= ", err_N_max
      PRINT *, "     at position: x=", pos_maxerr(1), ", y=", pos_maxerr(2), &
               ", z=", pos_maxerr(3)
      PRINT *, "     with r/(system size)= ", SQRT( &
                              ( ABS(pos_maxerr(1)) - ABS(center(1)) )**two &
                            + ( ABS(pos_maxerr(2)) - ABS(center(2)) )**two &
                            + ( ABS(pos_maxerr(3)) - ABS(center(3)) )**two ) &
                            /sizes(1)
      PRINT *, "   The ID density is   = ", nstar_id_err
      PRINT *, "   The SPH estimate is= ", nstar_sph_err
      PRINT *
      PRINT *, " * Minimum relative error between the star density profile", &
               " and its SPH estimate: ", err_N_min
      PRINT *, " * Average relative error between the star density profile", &
               " and its SPH estimate: ", err_N_mean
      PRINT *, " * Minimum of the average relative error between the star", &
               " density profile and its SPH estimate: ", err_N_mean_min
      PRINT *

      !
      !-- Compute what would be the baryon number at this step
      !

      ! Compute particle number density
      nu= one
      CALL density_loop( npart_all, all_pos, &    ! input
                         nu, h, nstar_sph )      ! output
      ! Reset nu to its values immediately
      nu(1:npart_real)          = nu_all
      nu(npart_real+1:npart_all)= nu_ghost

      !$OMP PARALLEL DO DEFAULT( NONE ) &
      !$OMP             SHARED( nu_tmp, nu, nstar_id, nstar_sph, &
      !$OMP                     nuratio_thres, npart_real ) &
      !$OMP             PRIVATE( nu_tmp2, a )
      cap_nu: DO a= 1, npart_real, 1

        nu_tmp2= nu(a)
        nu_tmp(a)= nstar_id(a)/nstar_sph(a)

          IF( nu_tmp(a) > nu_tmp2*SQRT(nuratio_thres) ) nu_tmp(a)= &
                                            nu_tmp2*SQRT(nuratio_thres)
          IF( nu_tmp(a) < nu_tmp2/SQRT(nuratio_thres) ) nu_tmp(a)= &
                                            nu_tmp2/SQRT(nuratio_thres)

      ENDDO cap_nu
      !$OMP END PARALLEL DO
      nuratio_tmp= MAXVAL( nu_tmp(1:npart_real), DIM= 1 )&
                  /MINVAL( nu_tmp(1:npart_real), DIM= 1 )

      PRINT *, " * Stopping the APM iteration at this step, with a threshold", &
               " for the baryon number ratio equal to "
      PRINT *, "   nu_thres=", nuratio_thres, ","
      PRINT *, "   the baryon number ratio would be equal to the following."
      PRINT *, " * Temporary CORRECTED maximum baryon number at this step=", &
               MAXVAL( nu_tmp(1:npart_real), DIM= 1 )
      PRINT *, " * Temporary CORRECTED minimum baryon number at this step=", &
               MINVAL( nu_tmp(1:npart_real), DIM= 1 )
      PRINT *, " * Temporary CORRECTED baryon number ratio at this step=", &
               nuratio_tmp
      PRINT *

      !
      !-- Setting variables to steer the APM iteration
      !
      push_away_ghosts= .FALSE.
      IF( err_N_mean > err_mean_old )THEN
        n_inc= n_inc + 1
        !IF( n_inc >= max_inc*half )THEN
        !  push_away_ghosts= .TRUE.
        !ENDIF
      ENDIF
      IF( itr > nuratio_min_it .AND. nuratio_tmp /= nuratio_thres .AND. &
          ABS(nuratio_tmp - nuratio_tmp_prev)/nuratio_tmp_prev &
          <= nuratio_tol )THEN
        nuratio_cnt= nuratio_cnt + 1
        !push_away_ghosts= .TRUE.
      ELSE
        nuratio_cnt= 0
      ENDIF

      radius_part= zero
      h_av       = zero
      cnt_array  = zero
      frac       = zero
      DO WHILE(SUM(cnt_array, DIM=1) <= ten)

        frac= frac + half

        !$OMP PARALLEL DO DEFAULT( NONE ) &
        !$OMP             SHARED( all_pos, npart_real, center, max_radius, h, &
        !$OMP                     cnt_array, frac ) &
        !$OMP             PRIVATE( a, r ) &
        !!$OMP             REDUCTION( MAX: radius_part ) &
        !$OMP             REDUCTION( +: h_av, radius_part )
        find_radius_part: DO a= 1, npart_real, 1

          r= SQRT((all_pos(1,a) - center(1))**2 &
                + (all_pos(2,a) - center(2))**2 &
                + (all_pos(3,a) - center(3))**2)

          !radius_part= MAX(radius_part, r)

          IF(.NOT.is_finite_number(h(a))) CYCLE

          IF(r > (one - frac/(ten*ten))*max_radius)THEN
            h_av        = h_av + h(a)
            radius_part = radius_part + r
            cnt_array(a)= one
          ENDIF

        ENDDO find_radius_part
        !$OMP END PARALLEL DO

      ENDDO
      IF(debug) PRINT *, "h_av=", h_av
      IF(debug) PRINT *, "radius_part=", radius_part
      IF(debug) Print *, "SUM(cnt_array, DIM=1)=", SUM(cnt_array, DIM=1)
      h_av       = h_av/SUM(cnt_array, DIM=1)
      radius_part= radius_part/SUM(cnt_array, DIM=1)
      PRINT *, " * Average larger radius among the particles=", radius_part
      PRINT *, " * Larger radius of the star=", max_radius
      PRINT *, " * Their difference: radius_part - max_radius=", &
               radius_part - max_radius
      PRINT *, " * Their relative difference: ", &
               "ABS(radius_part - max_radius)/max_radius=", &
               ABS(radius_part - max_radius)/max_radius
      PRINT *, " * Average smoothing length over the outer layers ", &
               "(r>95% of the star radius)= ", h_av, "Msun_geo="
      PRINT *, "   ", h_av*Msun_geo, "km=", &
               h_av/max_radius*ten*ten, "% of the larger radius of the star"
      PRINT *, " * Ghosts are moved if ", &
               "ABS(radius_part - max_radius) > h_av/three=", h_av/three
      PRINT *

      !IF( ABS(radius_part - max_radius)/max_radius > four*ten*tol )THEN
      IF( ABS(radius_part - max_radius) > h_av/three )THEN
        push_away_ghosts= .TRUE.
        IF( radius_part > max_radius )THEN
          ghost_displacement= - ghost_displacement
        ENDIF
      ENDIF

      ! POSSIBLE EXIT CONDITION. DEPRECATED?
      !
      !IF( ABS( err_N_mean - err_mean_old )/ABS( err_mean_old ) < iter_tol &
      !    .AND. &
      !    err_N_max < ten &
      !)THEN
      !  n_inc= n_inc + 1
      !  PRINT *, "n_inc/max_inc= ", n_inc, "/", max_inc
      !  PRINT *, "ABS( err_N_mean - err_mean_old )/ABS(err_mean_old)= ", &
      !           ABS( err_N_mean - err_mean_old )/ABS(err_mean_old)
      !ENDIF
      !IF( ABS(err_N_mean_min - err_N_mean_min_old)/ABS(err_N_mean_min_old) &
      !      < ten*iter_tol &
      !    .AND. &
      !    ABS(err_N_mean - err_N_mean_min)/ABS(err_N_mean_min) < iter_tol &
      !)THEN
      !  n_inc= n_inc + 1
      !  PRINT *, "n_inc/max_inc= ", n_inc, "/", max_inc
      !  PRINT *, err_N_mean, "err_N_mean"
      !  PRINT *, err_N_mean_min, "err_N_mean_min"
      !  PRINT *, "ABS(err_N_mean - err_N_mean_min)/ABS(err_N_mean_min)= ", &
      !           ABS(err_N_mean - err_N_mean_min)/ABS(err_N_mean_min), " < ", &
      !           iter_tol
      !ELSE
      !  n_inc= 0
      !ENDIF

      !
      !-- Estimating the particle contribution to the SPH momentum equation
      !-- Note that it is much easier to compute the momentum in
      !-- SPHINCS_BSSN, so this feature will most likely be deleted
      !

      !IF( MOD( itr, 5 ) == 0 )THEN
      !
      !  !PRINT *, "Before calling exact_nei_tree_update..."
      !  !PRINT *
      !
      !  CALL exact_nei_tree_update( nn_des, &
      !                              npart_real, &
      !                              all_pos(:,1:npart_real), &
      !                              nu_tmp(1:npart_real) )
      !
      !  CALL compute_hydro_momentum()
      !
      !ENDIF

      !STOP

      !
      !-- Exit conditions
      !
      IF(itr == apm_max_it)THEN

        PRINT *, " * Exit condition satisfied: the APM reached the maximum ", &
                 "number of iterations apm_max_it=", apm_max_it
        PRINT *
        EXIT

      ENDIF
      IF(nuratio_des > zero)THEN
      ! If there is a desired baryon number ratio...

        IF( (nuratio_tmp >= nuratio_des*(one - quarter/ten) .AND. &
             nuratio_tmp <= nuratio_des*(one + quarter/ten) .AND. &
             nuratio_tmp /= nuratio_thres) )THEN
        ! ...and the actual baryon number ratio differs from it by 2.5% at the
        ! most, but it's not equal to the baryon number ratio threshold
        ! (the 'cap' on nu), then EXIT the APM iteration. Also, EXIT if
        ! the hard limit on the number of APM steps (equal to apm_max_it)
        ! is reached.

          PRINT *, " * Exit condition satisfied: the baryon number ratio is ", &
                   nuratio_tmp, " <= ", nuratio_des, "*1.025= ", &
                   nuratio_des*(one + quarter/ten)
          PRINT *
          EXIT

        ENDIF

        IF( (nuratio_cnt >= nuratio_max_steps .AND. &
             nuratio_tmp /= nuratio_thres) &
        )THEN
        ! ...and the actual baryon number ratio did not change significantly
        ! for more than nuratio_max_steps steps, but it's not equal to the
        ! baryon number ratio threshold (the 'cap' on nu), then EXIT the APM
        ! iteration. Also, EXIT if the hard limit on the number of APM steps
        ! (equal to apm_max_it) is reached.

          PRINT *, " * Exit condition satisfied: the baryon number ratio ", &
                   "did not change by more than ", nuratio_tol*ten*ten, &
                   "% for ", nuratio_max_steps, "steps."
          PRINT *
          EXIT

        ELSE
        ! Print the counter

          PRINT *, " * nuratio_cnt/nuratio_max_steps= ", &
                   nuratio_cnt, "/", nuratio_max_steps
          PRINT *

        ENDIF

      ELSE
      ! else if there is NOT a desired baryon number ratio, EXIT the APM
      ! iteration if the relative error on the density increases max_inc times.
      ! Also, EXIT if the hard limit on the number of APM steps
      ! (equal to apm_max_it) is reached.

        PRINT *, " * n_inc= ", n_inc
        PRINT *
        IF(n_inc == max_inc)THEN

          PRINT *, " * Exit condition satisfied: the average over the ", &
                   "particles of the relative difference between the ID  ", &
                   "baryon mass density and its SPH estimate, grew ", &
                   "for ", max_inc, "steps."
          PRINT *
          EXIT

        ENDIF

      ENDIF
      err_mean_old      = err_N_mean
      err_N_mean_min_old= err_N_mean_min
      nuratio_tmp_prev  = nuratio_tmp

      !
      !-- If the EXIT conditions are not satisfied, update the particle
      !-- distribution
      !
      PRINT *, " * Updating positions..."

      all_pos_prev= all_pos

      CALL density_loop( npart_all, all_pos, &    ! input
                         nu, h, nstar_sph )      ! output

      CALL position_correction( npart_all, &
                                all_pos, h, nu_all, art_pr, nstar_sph, &
                                correction_pos )

      !$OMP PARALLEL DO DEFAULT( NONE ) &
      !$OMP             SHARED( npart_all, correction_pos, all_pos, center, &
      !$OMP                     radius_x ) &
      !$OMP             PRIVATE( a, itr2, r, theta, phi )
      find_nan_in_correction_pos: DO a= 1, npart_all, 1

        loop_over_spatial_components: DO itr2= 1, 3, 1

          IF( .NOT.is_finite_number( correction_pos( itr2, a ) ) )THEN

            CALL spherical_from_cartesian( all_pos(1,a), all_pos(2,a), &
                                           all_pos(3,a), &
                                           center(1), center(2), center(3), &
                                           r, theta, phi )

          !  correction_pos(1,a)= - one/(two*ten)*SIN(theta)*COS(phi)
          !  correction_pos(2,a)= - one/(two*ten)*SIN(theta)*SIN(phi)
          !  correction_pos(3,a)= - one/(two*ten)*COS(theta)
            !correction_pos( itr2, a )= zero

            PRINT *, "** ERROR! correction_pos(", itr2, ",", a, ") is a NaN!"
            PRINT *, " *        correction_pos: x=", correction_pos(1,a), &
                     ", y=", correction_pos(2,a), ", z=", correction_pos(3,a)
            PRINT *, " *        Particle position: x=", all_pos(1,a), &
                     ", y=", all_pos(2,a), ", z=", all_pos(3,a)

            CALL spherical_from_cartesian( &
                              all_pos(1,a), all_pos(2,a), all_pos(3,a), &
                              center(1), center(2), center(3), &
                              r, theta, phi )

            !r_tmp= SQRT( ( all_pos(1,a) - center(1) )**two + &
            !             ( all_pos(2,a) - center(2) )**two + &
            !             ( all_pos(3,a) - center(3) )**two )

            PRINT *, " *        Particle radius: r=", r, &
                     "=", r/radius_x*ten*ten, &
                     "% of the larger radius of the star."
            PRINT *, " *        Particle colatitude: theta=", theta/pi," pi"
            PRINT *, " *        Particle longitude: phi=", phi/pi, " pi"
            PRINT *, " * Stopping.."
            PRINT *
            STOP

          ENDIF

        ENDDO loop_over_spatial_components

      ENDDO find_nan_in_correction_pos
      !$OMP END PARALLEL DO

      IF( debug ) PRINT *, "After calling position_correction"

      cnt_move= 0
      !$OMP PARALLEL DO DEFAULT( NONE ) &
      !$OMP             SHARED( use_atmosphere, all_pos, correction_pos, &
      !$OMP                     dNstar, npart_real, nstar_id, cnt_move ) &
      !$OMP             PRIVATE( pos_corr_tmp, a, cnt, rand_num, rand_num2, &
      !$OMP                      rel_sign )
      real_particle_loop: DO a= 1, npart_real, 1

        adapt_displacement_to_error: &
        IF( dNstar(a) >= ten*ten &
            .AND. &
            validate_position_final( &
              all_pos(1,a) + ten*correction_pos(1,a), &
              all_pos(2,a) + ten*correction_pos(2,a), &
              all_pos(3,a) + ten*correction_pos(3,a) ) )THEN

          pos_corr_tmp= all_pos(:,a) + ten*correction_pos(:,a) ! 10


        ELSEIF( dNstar(a) >= ten &
                .AND. &
                validate_position_final( &
                  all_pos(1,a) + three*correction_pos(1,a), &
                  all_pos(2,a) + three*correction_pos(2,a), &
                  all_pos(3,a) + three*correction_pos(3,a) ) )THEN

          pos_corr_tmp= all_pos(:,a) + three*correction_pos(:,a) ! 3


        ELSE

          pos_corr_tmp= all_pos(:,a) + correction_pos(:,a) ! 1

        ENDIF adapt_displacement_to_error

        if_atmosphere: IF( use_atmosphere )THEN
        ! If the atmosphere is used...

          all_pos(:,a)= pos_corr_tmp
          cnt_move(a)= 1
          !...move the particle without any validation

        ELSE

          cnt= 0
          determine_new_position: DO

            test_position: IF( get_density( &
                pos_corr_tmp(1), pos_corr_tmp(2), pos_corr_tmp(3) ) > zero &
                .AND. &
                validate_position_final( &
                    pos_corr_tmp(1), pos_corr_tmp(2), pos_corr_tmp(3) ) &
                !.AND. &
                !check_particle_position( a - 1, &
                !                         all_pos(:,1:a-1), &
                !                         pos_corr_tmp ) == 0 &
                !.AND. &
                !check_particle_position( npart_real - a, &
                !                         all_pos(:,a+1:npart_real), &
                !                         pos_corr_tmp ) == 0 &
            )THEN
            ! If the new position is valid...

              all_pos(:,a)= pos_corr_tmp
              cnt_move(a)= 1
              EXIT
              !...move the particle, and exit the 'determine_new_position' loop

            ELSEIF( cnt <= search_pos )THEN
            ! ...else if the new position is invalid,
            !    and the current step is lower than search_pos, change the new
            !    position randomly, independently in x, y, z

              cnt= cnt + 1

              !
              !-- Add random noise to x coordinate
              !
              CALL RANDOM_NUMBER( rand_num )
              CALL RANDOM_NUMBER( rand_num2 )

              IF( rand_num2 < half )  rel_sign= - 1
              IF( rand_num2 >= half ) rel_sign=   1

              pos_corr_tmp(1)= all_pos(1,a) + &
                correction_pos(1,a)*( one + DBLE(rel_sign)*rand_num*half )

              !
              !-- Add random noise to y coordinate
              !
              CALL RANDOM_NUMBER( rand_num )
              CALL RANDOM_NUMBER( rand_num2 )

              IF( rand_num2 < half )  rel_sign= - 1
              IF( rand_num2 >= half ) rel_sign=   1

              pos_corr_tmp(2)= all_pos(2,a) + &
                correction_pos(2,a)*( one + DBLE(rel_sign)*rand_num*half )

              !
              !-- Add random noise to z coordinate
              !
              CALL RANDOM_NUMBER( rand_num )
              CALL RANDOM_NUMBER( rand_num2 )

              IF( rand_num2 < half )  rel_sign= - 1
              IF( rand_num2 >= half ) rel_sign=   1

              pos_corr_tmp(3)= all_pos(3,a) + &
                correction_pos(3,a)*( one + DBLE(rel_sign)*rand_num*half )

              !pos_corr_tmp*( one + DBLE(rel_sign)*rand_num*half*third )

            ELSEIF( cnt == search_pos + 1 )THEN
            ! ...else if the new position was changed randomly search_pos
            !    times, do not move the particle at this step,
            !    and exit the 'determine_new_position' loop

              cnt_move(a)= 0

              ! cnt= cnt + 1
              ! CALL RANDOM_NUMBER( rand_num )
              ! CALL RANDOM_NUMBER( rand_num2 )
              !
              ! IF( rand_num2 < half )  rel_sign= - 1
              ! IF( rand_num2 >= half ) rel_sign=   1
              ! all_pos(:,a)= all_pos(:,a)*( one -rand_num*half*third )

              EXIT

            ENDIF test_position

          ENDDO determine_new_position

        ENDIF if_atmosphere

      ENDDO real_particle_loop
      !$OMP END PARALLEL DO
      PRINT *, " * The fraction of particles that moved at this step is", &
               DBLE(SUM(cnt_move))/DBLE(npart_real)
      PRINT *

      !
      !-- Compute some measures of the displacement vector field over the
      !-- particles
      !
      l2norm_displacement = zero
      average_displacement= zero
      !$OMP PARALLEL DO DEFAULT( NONE ) &
      !$OMP             SHARED( npart_real, all_pos, all_pos_prev ) &
      !$OMP             PRIVATE( a ) &
      !$OMP             REDUCTION( +: l2norm_displacement, average_displacement)
      l2norm_disp: DO a= 1, npart_real, 1

        ! l2 norm
        l2norm_displacement= l2norm_displacement &
                           + NORM2(all_pos(:,a) - all_pos_prev(:,a))**2

        ! Arithmetic average of the Euclidean norm of the displacement
        average_displacement= average_displacement &
                            + NORM2(all_pos(:,a) - all_pos_prev(:,a))

      ENDDO l2norm_disp
      !$OMP END PARALLEL DO
      l2norm_displacement= SQRT(l2norm_displacement)
      average_displacement= l2norm_displacement/npart_real
      PRINT *, " * l_2 norm of the displacement of the particles= ", &
               l2norm_displacement
      PRINT *, "   (note that the l2 norm grows with the number of particles)"
      PRINT *, " * Arithmetic average of the Euclidean norm of the ", &
               "displacement of the particles= ", average_displacement
      PRINT *

      !
      !-- Displace ghosts, if needed
      !
      IF(debug) PRINT *, "push_away_ghosts:", push_away_ghosts
      IF(debug) PRINT *, "move_away_ghosts:", move_away_ghosts
      IF(debug) PRINT *, "push_away_ghosts .AND. move_away_ghosts:", &
                         push_away_ghosts .AND. move_away_ghosts
      IF(debug) PRINT *, "cnt_push_ghost=", cnt_push_ghost
      IF(debug) PRINT *, "max_push_ghost=", max_push_ghost

      IF( push_away_ghosts .AND. move_away_ghosts &
      !    .AND. cnt_push_ghost <= max_push_ghost &
      )THEN

        PRINT *, " * Displacing ghosts..."

        !max_r_ghost= (one + third)*MAXVAL([radius_x, radius_y, radius_z])

        !$OMP PARALLEL DO DEFAULT( NONE ) &
        !$OMP             SHARED( all_pos, npart_real, npart_all, center, &
        !$OMP                     ghost_displacement, min_radius ) &
        !$OMP             PRIVATE( a, r, theta, phi )
        ghost_particle_loop: DO a= npart_real + 1, npart_all, 1

          CALL spherical_from_cartesian( &
            all_pos(1,a), all_pos(2,a), all_pos(3,a), &
            center(1), center(2), center(3), &
            r, theta, phi )

          r= r + min_radius*ghost_displacement

          CALL cartesian_from_spherical( &
            r, theta, phi, &
            center(1), center(2), center(3), &
            all_pos(1,a), all_pos(2,a), all_pos(3,a) )

          !all_pos(1,a)= center(1) + r*SIN(theta)*COS(phi)
          !all_pos(2,a)= center(2) + r*SIN(theta)*SIN(phi)
          !all_pos(3,a)= center(3) + r*COS(theta)

         ! all_pos(:,a)= center + ghost_displacement*(all_pos(:,a) - center)

         ! This will break the ghost ellipsoid into eight disconnected pieces,
         ! one per quadrant in the 3D space
         ! all_pos(1,a)= all_pos(1,a) &
         !    + SIGN(min_radius*ghost_displacement, all_pos(1,a) - center(1))
         ! all_pos(2,a)= all_pos(2,a) &
         !    + SIGN(min_radius*ghost_displacement, all_pos(2,a) - center(2))
         ! all_pos(3,a)= all_pos(3,a) &
         !    + SIGN(min_radius*ghost_displacement, all_pos(3,a) - center(3))

        ENDDO ghost_particle_loop
        !$OMP END PARALLEL DO

        cnt_push_ghost= cnt_push_ghost + 1

        PRINT *, "   ...done."
        PRINT *

      ENDIF

      !$OMP PARALLEL DO DEFAULT( NONE ) &
      !$OMP             SHARED( npart_all, all_pos ) &
      !$OMP             PRIVATE( a, itr2 )
      find_nan_in_all_pos: DO a= 1, npart_all, 1

        DO itr2= 1, 3, 1
          IF( .NOT.is_finite_number( all_pos( itr2, a ) ) )THEN
            PRINT *, "** ERROR! all_pos(", itr2, ",", a, ") is a NaN!", &
                     " Stopping.."
            PRINT *
            STOP
          ENDIF
        ENDDO

      ENDDO find_nan_in_all_pos
      !$OMP END PARALLEL DO

      ! If some of the particles crossed the xy plane in the
      ! last step, reflect them back above the xy plane

      !$OMP PARALLEL DO DEFAULT( NONE ) &
      !$OMP             SHARED( all_pos, all_pos_prev, npart_real ) &
      !$OMP             PRIVATE( a )
      DO a= 1, npart_real, 1

        IF( (all_pos_prev(3,a) > zero .AND. all_pos(3,a) <= zero) &
            .OR. &
            (all_pos_prev(3,a) < zero .AND. all_pos(3,a) >= zero) &
        )THEN

          all_pos(3,a)= all_pos_prev(3,a)

        ENDIF

      ENDDO
      !$OMP END PARALLEL DO

      IF( debug ) PRINT *, "After correcting positions"

    ENDDO apm_iteration

    PRINT *, "** APM iteration completed."
    PRINT *



    !--------------------------!
    !--------------------------!
    !-- END OF APM ITERATION --!
    !--------------------------!
    !--------------------------!



    !-----------------------------!
    !-- Discard ghost particles --!
    !-----------------------------!

    pos= all_pos(:, 1:npart_real)
    IF( debug ) PRINT *, npart

    h      = h(1:npart_real)
    h_guess= h_guess(1:npart_real)
    nu     = nu(1:npart_real)

    !-----------------------------------------------!
    !-- Remove atmosphere, if present and desired --!
    !-----------------------------------------------!

    IF( use_atmosphere .AND. remove_atmosphere )THEN

      CALL discard_atmosphere( npart_real )

    ENDIF

    !---------------!
    !-- Set npart --!
    !---------------!

    npart= npart_real

    !----------------------------!
    !-- enforce centre of mass --!
    !----------------------------!

    CALL correct_center_of_mass( npart_real, pos, nu, get_density, &
                                 validate_position_final, com_star, &
                                 verbose= .TRUE. )

    !-----------------------------------------------------------------------!
    !-- Mirror the positions after having repositioned the center of mass --!
    !-----------------------------------------------------------------------!

    CALL impose_equatorial_plane_symmetry( npart_real, pos, nu )

    !-----------------------------!
    !-- Print positions to file --!
    !-----------------------------!

    IF( PRESENT(namefile_pos) )THEN
      finalnamefile= namefile_pos
    ELSE
      finalnamefile= TRIM(sph_path)//"apm_pos.dat"
    ENDIF

    INQUIRE( FILE= TRIM(finalnamefile), EXIST= exist )

    IF( exist )THEN
        OPEN( UNIT= 2, FILE= TRIM(finalnamefile), STATUS= "REPLACE", &
              FORM= "FORMATTED", &
              POSITION= "REWIND", ACTION= "WRITE", IOSTAT= ios, &
              IOMSG= err_msg )
    ELSE
        OPEN( UNIT= 2, FILE= TRIM(finalnamefile), STATUS= "NEW", &
              FORM= "FORMATTED", &
              ACTION= "WRITE", IOSTAT= ios, IOMSG= err_msg )
    ENDIF
    IF( ios > 0 )THEN
      PRINT *, "...error when opening " // TRIM(finalnamefile), &
               ". The error message is", err_msg
      STOP
    ENDIF

    DO a= 1, npart_real, 1
      tmp= get_density( pos(1,a), pos(2,a), pos(3,a) )
      WRITE( UNIT = 2, IOSTAT = ios, IOMSG = err_msg, FMT = * ) &
        1, a, &
        pos(1,a), &
        pos(2,a), &
        pos(3,a), &
        tmp, cnt_move(a)
    ENDDO

    DO a= npart_real + 1, npart_all, 1
      tmp= get_density( all_pos(1,a), all_pos(2,a), all_pos(3,a) )
      WRITE( UNIT = 2, IOSTAT = ios, IOMSG = err_msg, FMT = * ) &
        2, a, &
        all_pos(1,a), &
        all_pos(2,a), &
        all_pos(3,a), &
        tmp
    ENDDO

    CLOSE( UNIT= 2 )

    !---------------------------------------------------------------!
    !-- Assign baryon number to match profile as good as possible --!
    !---------------------------------------------------------------!

    PRINT *, " * Assign baryon number..."
    PRINT *

    IF( debug ) PRINT *, "1"

    CALL assign_h( nn_des, &           !
                   npart_real, &        !
                   pos, h_guess, & ! Input
                   h )                 ! Output

    find_h_bruteforce_timer= timer( "find_h_bruteforce_timer" )
    CALL find_h_bruteforce_timer% start_timer()
    n_problematic_h= 0
    check_h3: DO a= 1, npart_real, 1
    ! find_h_backup, called below, is OMP parallelized, so this loop
    ! should not be parallelized as well

      IF( .NOT.is_finite_number( h(a) ) .OR. h(a) <= zero )THEN

        n_problematic_h= n_problematic_h + 1
        h(a)= find_h_backup( a, npart_real, pos, nn_des )
        IF( .NOT.is_finite_number( h(a) ) .OR. h(a) <= zero )THEN
          PRINT *, "** ERROR! h=0 on particle ", a, "even with the brute", &
                   " force method."
          PRINT *, "   Particle position: ", pos(:,a)
          STOP
        ENDIF

      ENDIF

    ENDDO check_h3
    CALL find_h_bruteforce_timer% stop_timer()
    CALL find_h_bruteforce_timer% print_timer( 2 )

    IF( debug ) PRINT *, "2"

    ! Measure SPH particle number density
    nu= one
    CALL density_loop( npart_real, pos, &    ! input
                       nu, h, nstar_sph )      ! output

    IF( debug ) PRINT *, "3"

    CALL get_nstar_id_atm( npart_real, pos(1,:), pos(2,:), pos(3,:), &
                           nstar_sph, nstar_id, nlrf_sph, sqg, &
                           use_atmosphere )

    nu(1:npart_real)= nu_all
    PRINT *, " * Baryon number on all particles before correction nu_all= ", &
             nu_all

    nu_tot= zero
    !$OMP PARALLEL DO DEFAULT( NONE ) &
    !$OMP             SHARED( nu, npart_real ) &
    !$OMP             PRIVATE( a ) &
    !$OMP             REDUCTION(+: nu_tot)
    DO a= 1, npart_real, 1
      nu_tot= nu_tot + nu(a)
    ENDDO
    !$OMP END PARALLEL DO

    PRINT *, " * Total baryon number nu_tot=", nu_tot
    PRINT *, " * Total baryon mass= ", nu_tot*amu/MSun, "=", &
             ten*ten*nu_tot*amu/MSun/mass, "% of the ID baryon mass"
    PRINT *

    IF( debug ) PRINT *, "4"

    IF( debug ) PRINT *, "npart_real= ", npart_real
    IF( debug ) PRINT *, "SIZE(nu)= ", SIZE(nu)
    IF( debug ) PRINT *

    IF( debug ) nu_ratio= MAXVAL( nu, DIM= 1 )/MINVAL( nu, DIM= 1 )
    IF( debug ) PRINT *, " * nu_ratio before correction = ", nu_ratio
    IF( debug ) PRINT *

    !-----------------------------------------!
    !-- Cap nu by the desired nuratio_thres --!
    !-----------------------------------------!

    PRINT *, " * Correcting nu..."
    PRINT *

    !$OMP PARALLEL DO DEFAULT( NONE ) &
    !$OMP             SHARED( nu_tmp, nu, nstar_id, nstar_sph, &
    !$OMP                     nuratio_thres, npart_real ) &
    !$OMP             PRIVATE( nu_tmp2, a )
    DO a= 1, npart_real, 1

      nu_tmp2= nu(a)
      nu(a)= nstar_id(a)/nstar_sph(a)

        IF( nu(a) > nu_tmp2*SQRT(nuratio_thres) ) nu(a)= &
                                          nu_tmp2*SQRT(nuratio_thres)
        IF( nu(a) < nu_tmp2/SQRT(nuratio_thres) ) nu(a)= &
                                          nu_tmp2/SQRT(nuratio_thres)

    ENDDO
    !$OMP END PARALLEL DO

    !
    !-- Check that nu is acceptable
    !
    !$OMP PARALLEL DO DEFAULT( NONE ) &
    !$OMP             SHARED( nu, nstar_id, nstar_sph, npart_real ) &
    !$OMP             PRIVATE( a )
    DO a= 1, npart_real, 1

      IF( .NOT.is_finite_number( nu(a) ) )THEN
        PRINT *, " * ERROR! nu(", a, ") is a NaN."
        PRINT *, " nstar_sph(a)= ", nstar_sph(a)
        PRINT *, " nstar_id(a)= ", nstar_id(a)
        PRINT *, " Stopping..."
        PRINT *
        STOP
      ENDIF
      IF( nu(a) < zero )THEN
        PRINT *, " * ERROR! nu(", a, ") is negative."
        PRINT *, " nu(a)= ", nu(a)
        PRINT *, " nstar_sph(a)= ", nstar_sph(a)
        PRINT *, " nstar_id(a)= ", nstar_id(a)
        PRINT *, " Stopping..."
        PRINT *
        STOP
      ENDIF

    ENDDO
    !$OMP END PARALLEL DO

    nu_ratio= MAXVAL( nu, DIM= 1 )/MINVAL( nu, DIM= 1 )
    PRINT *, " * nu_ratio after correction = ", nu_ratio
    PRINT *

    max_nu= zero
    min_nu= HUGE(one)
    !$OMP PARALLEL DO DEFAULT( NONE ) &
    !$OMP             SHARED( npart_real, nu, a_numax, a_numin ) &
    !$OMP             PRIVATE( a ) &
    !$OMP             REDUCTION( MIN: min_nu ) &
    !$OMP             REDUCTION( MAX: max_nu )
    DO a= 1, npart_real, 1

      max_nu= MAX(nu(a), max_nu)
      IF( nu(a) == max_nu ) a_numax= a

      min_nu= MAX(nu(a), min_nu)
      IF( nu(a) == min_nu ) a_numin= a

    ENDDO
    !$OMP END PARALLEL DO

    !DO a= 1, npart_real, 1
    !  IF( nu(a) > max_nu )THEN
    !    max_nu= nu(a)
    !    a_numax= a
    !  ENDIF
    !  IF( nu(a) < min_nu )THEN
    !    min_nu= nu(a)
    !    a_numin= a
    !  ENDIF
    !ENDDO

    PRINT *, " * Baryon number assigned."
    PRINT *

    !
    !-- Optionally change baryon number without moving the particles
    !
    if_mass_it: IF(mass_it)THEN

      PRINT *, "** Performs a second iteration without moving", &
               " the particles, changing their mass in order to match", &
               " the star density better."

      ! just a few iterations to NOT get the nu-ratio too large
      mass_iteration: DO itr= 1, m_max_it, 1

        PRINT *, "------------------------------------------"
        PRINT *, " * Starting with mass iteration' step #: ", itr
        PRINT *

        CALL density_loop( npart_real, pos, &    ! input
                           nu, h, nstar_sph )    ! output

        CALL get_nstar_id_atm( npart_real, pos(1,:), &
                               pos(2,:), &
                               pos(3,:), nstar_sph, nstar_id, nlrf_sph, sqg, &
                               use_atmosphere )

        !nstar_id( npart_real+1:npart_all )= zero

        dN_av= zero
        !$OMP PARALLEL DO DEFAULT( NONE ) &
        !$OMP             SHARED( npart_real, dNstar, nstar_sph, &
        !$OMP                     nstar_id, nu ) &
        !$OMP             PRIVATE( a ) &
        !$OMP             REDUCTION( +: dN_av )
        DO a= 1, npart_real, 1

          dNstar(a)= (nstar_sph(a) - nstar_id(a))/nstar_id(a)
          IF(dNstar(a) > zero) dNstar(a)= MIN(dNstar(a),   five/ten/ten)
          IF(dNstar(a) < zero) dNstar(a)= MAX(dNstar(a), - five/ten/ten)
          nu(a)= nu(a)*(one - dNstar(a))
          dN_av= dN_av + dNstar(a)

        ENDDO
        !$OMP END PARALLEL DO
        dN_av= dN_av/DBLE(npart_real)

        PRINT *, " * dN_av= ", dN_av
        PRINT *, " * The mass iteration will stop when dN_av <", tol, ",", &
                 " or after", m_max_it, " steps."
        PRINT *

        ! Exit condition
        IF( dN_av < tol )THEN

          !
          !-- Cap nu by the desired nuratio_thres
          !
          !$OMP PARALLEL DO DEFAULT( NONE ) &
          !$OMP             SHARED( nu_tmp, nu, nstar_id, nstar_sph, &
          !$OMP                     nuratio_thres, npart_real ) &
          !$OMP             PRIVATE( nu_tmp2, a )
          DO a= 1, npart_real, 1

            nu_tmp2= nu(a)
            nu(a)= nstar_id(a)/nstar_sph(a)

              IF( nu(a) > nu_tmp2*SQRT(nuratio_thres) ) nu(a)= &
                                                nu_tmp2*SQRT(nuratio_thres)
              IF( nu(a) < nu_tmp2/SQRT(nuratio_thres) ) nu(a)= &
                                                nu_tmp2/SQRT(nuratio_thres)

          ENDDO
          !$OMP END PARALLEL DO

          max_nu= zero
          min_nu= HUGE(one)
          !$OMP PARALLEL DO DEFAULT( NONE ) &
          !$OMP             SHARED( npart_real, nu, a_numax, a_numin ) &
          !$OMP             PRIVATE( a ) &
          !$OMP             REDUCTION( MIN: min_nu ) &
          !$OMP             REDUCTION( MAX: max_nu )
          DO a= 1, npart_real, 1

            max_nu= MAX(nu(a), max_nu)
            IF( nu(a) == max_nu ) a_numax= a

            min_nu= MAX(nu(a), min_nu)
            IF( nu(a) == min_nu ) a_numin= a

          ENDDO
          !$OMP END PARALLEL DO

          !CALL density_loop( npart_real, pos, &    ! input
          !                   nu, h, nstar_sph )    ! output

          PRINT *, "** Second iteration, without moving", &
                   " the particles, completed."
          PRINT *
          EXIT

        ENDIF

      ENDDO mass_iteration

    ENDIF if_mass_it

    PRINT *, " * CORRECTED maximum baryon number at this step=", &
             max_nu
    PRINT *, " * CORRECTED minimum baryon number at this step=", &
             min_nu
    PRINT *, " * CORRECTED baryon number ratio at this step=", &
             max_nu/min_nu
    PRINT *

    max_nu2= zero
    min_nu2= HUGE(one)
    !$OMP PARALLEL DO DEFAULT( NONE ) &
    !$OMP             SHARED( npart_real, nu, a_numax, a_numin, &
    !$OMP                     a_numax2, a_numin2 ) &
    !$OMP             PRIVATE( a ) &
    !$OMP             REDUCTION( MIN: min_nu2 ) &
    !$OMP             REDUCTION( MAX: max_nu2 )
    DO a= 1, npart_real, 1

      IF( a /= a_numax )THEN
        max_nu2= MAX(nu(a), max_nu2)
        IF( nu(a) == max_nu2 ) a_numax2= a
      ENDIF

      IF( a /= a_numin )THEN
        min_nu2= MIN(nu(a), min_nu2)
        IF( nu(a) == min_nu2 ) a_numin2= a
      ENDIF

    ENDDO
    !$OMP END PARALLEL DO

    !DO a= 1, npart_real, 1
    !  IF( nu(a) > max_nu2 .AND. a /= a_numax )THEN
    !    max_nu2= nu(a)
    !    a_numax2= a
    !  ENDIF
    !  IF( nu(a) < min_nu2 .AND. a /= a_numin )THEN
    !    min_nu2= nu(a)
    !    a_numin2= a
    !  ENDIF
    !ENDDO

    PRINT *, " * Excluding the absolute max and min of nu:"
    PRINT *
    PRINT *, "   max_nu=", max_nu2
    PRINT *, "        at ", pos(:, a_numax2), " r= ", &
             NORM2( pos(:, a_numax2) )/radius_x
    PRINT *, "   min_nu=", min_nu2
    PRINT *, "        at ", pos(:, a_numin2), " r= ", &
             NORM2( pos(:, a_numin2) )/radius_x
    PRINT *, "   max_nu/min_nu=", max_nu2/min_nu2
    PRINT *

    nu_tot= zero
    !$OMP PARALLEL DO DEFAULT( NONE ) &
    !$OMP             SHARED( npart_real, nu ) &
    !$OMP             PRIVATE( a ) &
    !$OMP             REDUCTION( +: nu_tot )
    DO a= 1, npart_real, 1
      nu_tot= nu_tot + nu(a)
    ENDDO
    !$OMP END PARALLEL DO
    mean_nu= nu_tot/npart_real

    variance_nu = zero
    !$OMP PARALLEL DO DEFAULT( NONE ) &
    !$OMP             SHARED( npart_real, nu, mean_nu ) &
    !$OMP             PRIVATE( a ) &
    !$OMP             REDUCTION( +: variance_nu )
    DO a= 1, npart_real, 1
      variance_nu = variance_nu + (nu(a) - mean_nu)**two
    ENDDO
    !$OMP END PARALLEL DO
    variance_nu = variance_nu / DBLE(npart_real - 1)
    stddev_nu   = SQRT(variance_nu)            ! compute standard deviation

    PRINT *, "mean_nu=", mean_nu
    PRINT *, "variance_nu=", variance_nu
    PRINT *, "stddev_nu=", stddev_nu
    PRINT *, "stddev_nu/mean_nu=", stddev_nu/mean_nu
    PRINT *

    PRINT *, "Before correcting nu to match the mass of the star..."
    PRINT *
    PRINT *, "nu_tot=", nu_tot
    PRINT *, "mass estimate= ", nu_tot*amu/MSun, "=", &
             ten*ten*nu_tot*amu/MSun/mass, "% of the ID baryon mass"
    PRINT *

    IF( correct_nu )THEN

      nu= nu/(nu_tot*amu/Msun/mass)
      nu_tot= zero
      !$OMP PARALLEL DO DEFAULT( NONE ) &
      !$OMP             SHARED( npart_real, nu ) &
      !$OMP             PRIVATE( a ) &
      !$OMP             REDUCTION( +: nu_tot )
      DO a= 1, npart_real, 1
        nu_tot= nu_tot + nu(a)
      ENDDO
      !$OMP END PARALLEL DO

      PRINT *, "After correcting nu to match the mass of the star..."
      PRINT *
      PRINT *, "nu_tot=", nu_tot
      PRINT *, "mass estimate= ", nu_tot*amu/MSun, "=", &
               ten*ten*nu_tot*amu/MSun/mass, "% of the ID baryon mass"
      PRINT *

    ENDIF


  !  finalnamefile= TRIM(sph_path)//"dbg_apm_pos1.dat"
  !
  !  INQUIRE( FILE= TRIM(finalnamefile), EXIST= exist )
  !
  !  IF( exist )THEN
  !    OPEN( UNIT= 2, FILE= TRIM(finalnamefile), STATUS= "REPLACE", &
  !          FORM= "FORMATTED", &
  !          POSITION= "REWIND", ACTION= "WRITE", IOSTAT= ios, &
  !          IOMSG= err_msg )
  !  ELSE
  !    OPEN( UNIT= 2, FILE= TRIM(finalnamefile), STATUS= "NEW", &
  !          FORM= "FORMATTED", &
  !          ACTION= "WRITE", IOSTAT= ios, IOMSG= err_msg )
  !  ENDIF
  !  IF( ios > 0 )THEN
  !  PRINT *, "...error when opening " // TRIM(finalnamefile), &
  !           ". The error message is", err_msg
  !  STOP
  !  ENDIF
  !
  !  DO a= 1, npart_real, 1
  !  tmp= get_density( pos(1,a), pos(2,a), pos(3,a) )
  !  WRITE( UNIT = 2, IOSTAT = ios, IOMSG = err_msg, FMT = * ) &
  !    a, &
  !    pos(1,a), &
  !    pos(2,a), &
  !    pos(3,a), &
  !    tmp
  !  ENDDO
  !
  !  CLOSE( UNIT= 2 )

    !----------------------------!
    !-- enforce centre of mass --!
    !----------------------------!

    CALL correct_center_of_mass( npart_real, pos, nu, get_density, &
                                 validate_position_final, com_star, &
                                 verbose= .TRUE. )

    !-----------------------------------------------------------------------!
    !-- Mirror the positions after having repositioned the center of mass --!
    !-----------------------------------------------------------------------!

    CALL impose_equatorial_plane_symmetry( npart_real, pos, nu )


  !  finalnamefile= TRIM(sph_path)//"dbg_apm_pos2.dat"
  !
  !  INQUIRE( FILE= TRIM(finalnamefile), EXIST= exist )
  !
  !  IF( exist )THEN
  !    OPEN( UNIT= 2, FILE= TRIM(finalnamefile), STATUS= "REPLACE", &
  !          FORM= "FORMATTED", &
  !          POSITION= "REWIND", ACTION= "WRITE", IOSTAT= ios, &
  !          IOMSG= err_msg )
  !  ELSE
  !    OPEN( UNIT= 2, FILE= TRIM(finalnamefile), STATUS= "NEW", &
  !          FORM= "FORMATTED", &
  !          ACTION= "WRITE", IOSTAT= ios, IOMSG= err_msg )
  !  ENDIF
  !  IF( ios > 0 )THEN
  !  PRINT *, "...error when opening " // TRIM(finalnamefile), &
  !           ". The error message is", err_msg
  !  STOP
  !  ENDIF
  !
  !  DO a= 1, npart_real, 1
  !    tmp= get_density( pos(1,a), pos(2,a), pos(3,a) )
  !    WRITE( UNIT = 2, IOSTAT = ios, IOMSG = err_msg, FMT = * ) &
  !      a, &
  !      pos(1,a), &
  !      pos(2,a), &
  !      pos(3,a), &
  !      tmp
  !  ENDDO
  !
  !  CLOSE( UNIT= 2 )

    !-------------------!
    !-- monitoring... --!
    !-------------------!

    CALL density_loop( npart_real, pos, &    ! input
                       nu, h, nstar_sph )      ! output

    CALL get_nstar_id_atm( npart_real, pos(1,:), &
                           pos(2,:), &
                           pos(3,:), nstar_sph, nstar_id, nlrf_sph, sqg, &
                           use_atmosphere )

    dN_av = zero
    dN_max= zero
    cnt1= 0
    !$OMP PARALLEL DO DEFAULT( NONE ) &
    !$OMP             SHARED( npart_real, nu, pos, nstar_sph, nstar_id ) &
    !$OMP             PRIVATE( a, dN ) &
    !$OMP             REDUCTION( +: dN_av, cnt1 ) &
    !$OMP             REDUCTION( MAX: dN_max )
    DO a= 1, npart_real, 1
      IF( get_density( pos(1,a), pos(2,a), pos(3,a) ) > zero )THEN
        dN= ABS(nstar_sph(a) - nstar_id(a))/nstar_id(a)
        dN_av=  dN_av + dN
        dN_max= MAX(dN_max,dN)
        cnt1= cnt1 + 1
      ENDIF
    ENDDO
    !$OMP END PARALLEL DO
    dN_av= dN_av/DBLE(cnt1)

    variance_dN = zero
    !$OMP PARALLEL DO DEFAULT( NONE ) &
    !$OMP             SHARED( npart_real, nu, pos, nstar_sph, nstar_id, dN_av )&
    !$OMP             PRIVATE( a, dN ) &
    !$OMP             REDUCTION( +: variance_dN, cnt1 )
    DO a= 1, npart_real, 1
      IF( get_density( pos(1,a), pos(2,a), pos(3,a) ) > zero )THEN
        dN= ABS(nstar_sph(a) - nstar_id(a))/nstar_id(a)
        variance_dN = variance_dN + (dN - dN_av)**two
        cnt1= cnt1 + 1
      ENDIF
    ENDDO
    !$OMP END PARALLEL DO
    variance_dN = variance_dN/DBLE(cnt1)
    stddev_dN   = SQRT(variance_dN)            ! compute standard deviation

    PRINT *, " * Final maximum relative error between the density from the ", &
             "ID and the SPH density estimate: dN_max=", dN_max
    PRINT *, " * Final average relative error between the density from the ", &
             "ID and the SPH density estimate: dN_max=", dN_av
    PRINT *, " * Final variance of the relative error between the density ", &
             "from the ID and the SPH density estimate: variance_dN=", &
             variance_dN
    PRINT *, " * Final standard deviation of the relative error between the ", &
             "density from the ID and the SPH density estimate: stddev_dN=", &
             stddev_dN
    PRINT *, " * Final normalized standard deviation of the relative error ", &
             "between the density from the ID and the SPH density ", &
             "estimate: stddev_dN/dN_a=", stddev_dN/dN_av
    PRINT *

    IF( debug ) PRINT *, "100"

    IF( .NOT.ALLOCATED( nstar_int ) ) ALLOCATE( nstar_int( npart_real ) )

    IF( debug ) PRINT *, "101"

    PRINT *, "** Assigning final smoothing length..."
    PRINT *

    ! Determine smoothing length so that each particle has exactly
    ! 300 neighbours inside 2h
    CALL assign_h( nn_des, &
                   npart_real, &
                   pos, h_guess, & ! Input
                   h )             ! Output

    IF( debug ) PRINT *, "101.5"

    find_h_bruteforce_timer= timer( "find_h_bruteforce_timer" )
    CALL find_h_bruteforce_timer% start_timer()
    n_problematic_h= 0
    check_h4: DO a= 1, npart_real, 1
    ! find_h_backup, called below, is OMP parallelized, so this loop
    ! should not be parallelized as well

      IF( .NOT.is_finite_number( h(a) ) .OR. h(a) <= zero )THEN

        n_problematic_h= n_problematic_h + 1
        h(a)= find_h_backup( a, npart_real, pos, nn_des )
        IF( .NOT.is_finite_number( h(a) ) .OR. h(a) <= zero )THEN
          PRINT *, "** ERROR! h=0 on particle ", a, "even with the brute", &
                   " force method."
          PRINT *, "   Particle position: ", pos(:,a)
          STOP
        ENDIF

      ENDIF

    ENDDO check_h4
    CALL find_h_bruteforce_timer% stop_timer()
    CALL find_h_bruteforce_timer% print_timer( 2 )

    PRINT *, " * The smoothing length was found brute-force for ", &
             n_problematic_h, " particles."
    PRINT *

    IF( SUM(nu, DIM= 1)/SIZE(nu) == zero )THEN
      PRINT *, "** ERROR! Average nu= 0. Are you assigning values to the ", &
               "TYPE member array?"
      PRINT *, "Stopping..."
      STOP
    ENDIF

    PRINT *, "** Building neighbors tree..."
    PRINT *
 !   cnt1= 0
 !   DO
 !
 !     few_ncand= .FALSE.
 !
 !     ! Redo the previous step slightly different (it's built-in;
 !     ! exact_nei_tree_update does not work if I don't call assign_h first),
 !     ! then update the neighbour-tree and fill the neighbour-data
      CALL exact_nei_tree_update( nn_des, &
                                  npart_real, &
                                  pos, nu )

  !    ll_cell_loop: DO ill= 1, nfinal
  !
  !      itot= nprev + ill
  !      IF( nic(itot) == 0 ) CYCLE
  !
  !      IF( ncand(ill) < nn_des - 1 )THEN
  !
  !        ! Increase the smoothing lengths of the paricles inside the cell,
  !        ! and rebuild the tree
  !        few_ncand= .TRUE.
  !
  !        particle_in_cell_loop: DO l= lpart(itot), rpart(itot)
  !
  !          h(l)= three*h(l)
  !
  !        ENDDO particle_in_cell_loop
  !
  !      ELSE
  !
  !        few_ncand= .FALSE.
  !
  !      ENDIF
  !
  !    ENDDO ll_cell_loop
  !
  !    cnt1= cnt1 + 1
  !
  !    IF( debug ) PRINT *, cnt1
  !
  !    IF( .NOT.few_ncand .OR. cnt1 >= max_it_tree )THEN
  !      EXIT
  !    ENDIF
  !
  !  ENDDO

    find_h_bruteforce_timer= timer( "find_h_bruteforce_timer" )
    CALL find_h_bruteforce_timer% start_timer()
    n_problematic_h= 0
    check_h5: DO a= 1, npart_real, 1
    ! find_h_backup, called below, is OMP parallelized, so this loop
    ! should not be parallelized as well

      IF( .NOT.is_finite_number( h(a) ) .OR. h(a) <= zero )THEN

        n_problematic_h= n_problematic_h + 1
        h(a)= find_h_backup( a, npart_real, pos, nn_des )
        IF( .NOT.is_finite_number( h(a) ) .OR. h(a) <= zero )THEN
          PRINT *, "** ERROR! h=0 on particle ", a, "even with the brute", &
                   " force method."
          PRINT *, "   Particle position: ", pos(:,a)
          STOP
        ENDIF

      ENDIF

    ENDDO check_h5
    CALL find_h_bruteforce_timer% stop_timer()
    CALL find_h_bruteforce_timer% print_timer( 2 )

    PRINT *, " * The smoothing length was found brute-force for ", &
             n_problematic_h, " particles."
    PRINT *

    PRINT *, " * Smoothing lengths assigned and tree is built."
    PRINT *

    IF( debug ) PRINT *, "102"

    CALL density( npart_real, pos, nstar_int )
    !nstar_int= zero

    IF( debug ) PRINT *, "103"

  !  PRINT *, "** Finding nearest neighbors..."

    ALLOCATE( neighbors_lists( npart_real ) )
    ALLOCATE( n_neighbors( npart_real ) )
    ALLOCATE( nearest_neighbors( 2, npart_real ) )

    neighbors_lists= 0
    n_neighbors= 0
    nearest_neighbors(1,:)= 0
    nearest_neighbors(2,:)= HUGE(one)

 !   find_neighbors: DO a= 1, npart_real, 1
 !
 !     CALL get_neighbours_bf( a, npart_real, pos, h, 3, &           ! Input
 !                             n_neighbors(a), neighbors_lists(:) )  ! Output
 !
 !     DO itr= 1, n_neighbors(a), 1
 !
 !       dist= NORM2( pos(:,a) - pos(:,neighbors_lists(itr)) )
 !
 !       !PRINT *, "dist= ", dist
 !       !PRINT *, "nearest_neighbors(2,a)= ", nearest_neighbors(2,a)
 !       !PRINT *, "dist < nearest_neighbors(2,a)= ", dist < nearest_neighbors(2,a)
 !
 !       IF( dist < nearest_neighbors(2,a) )THEN
 !
 !         nearest_neighbors(1,a)= neighbors_lists(itr)
 !         nearest_neighbors(2,a)= dist
 !
 !       ENDIF
 !
 !     ENDDO
 !
 !     !PRINT *, "dist= ", dist
 !     !PRINT *, "nearest_neighbors(2,a)= ", nearest_neighbors(2,a)
 !     !PRINT *
 !
 !   ENDDO find_neighbors
 !
 !   PRINT *, " * Nearest neighbors found. "
 !   PRINT *, " * Average number of neighbors= ", DBLE(SUM(n_neighbors))/DBLE(npart_real)
 !   PRINT *

    IF( debug ) PRINT *, "0"

    !
    !-- Print final results of the APM to file
    !
    IF( PRESENT(namefile_results) )THEN
      finalnamefile= namefile_results
    ELSE
      finalnamefile= TRIM(sph_path)//"apm_results.dat"
    ENDIF

    PRINT *, "** Printing results to file ", TRIM(namefile_results), "..."
    PRINT *

    INQUIRE( FILE= TRIM(finalnamefile), EXIST= exist )

    IF( exist )THEN
        OPEN( UNIT= 2, FILE= TRIM(finalnamefile), STATUS= "REPLACE", &
              FORM= "FORMATTED", &
              POSITION= "REWIND", ACTION= "WRITE", IOSTAT= ios, &
              IOMSG= err_msg )
    ELSE
        OPEN( UNIT= 2, FILE= TRIM(finalnamefile), STATUS= "NEW", &
              FORM= "FORMATTED", &
              ACTION= "WRITE", IOSTAT= ios, IOMSG= err_msg )
    ENDIF
    IF( ios > 0 )THEN
      PRINT *, "...error when opening " // TRIM(finalnamefile), &
               ". The error message is", err_msg
      STOP
    ENDIF

    WRITE( UNIT = 2, IOSTAT = ios, IOMSG = err_msg, FMT = * ) &
    "# Run ID [ccyymmdd-hhmmss.sss]: " // run_id

    WRITE( UNIT = 2, IOSTAT = ios, IOMSG = err_msg, FMT = * ) &
    "# Values of some fields at the end of the APM iteration "&
    // "on the real particles"
    IF( ios > 0 )THEN
      PRINT *, "...error when writing line 1 in " // TRIM(finalnamefile), &
               ". The error message is", err_msg
      STOP
    ENDIF
    !CALL test_status( ios, err_msg, "...error when writing line 1 in "&
    !        // TRIM(finalnamefile) )

    WRITE( UNIT = 2, IOSTAT = ios, IOMSG = err_msg, FMT = * ) &
    "# column:      1        2       3       4       5", &
    "       6       7       8", &
    "       9       10      11"
    IF( ios > 0 )THEN
      PRINT *, "...error when writing line 2 in " // TRIM(finalnamefile), &
               ". The error message is", err_msg
      STOP
    ENDIF
    !CALL test_status( ios, err_msg, "...error when writing line 2 in "&
    !            // TRIM(finalnamefile) )

    WRITE( UNIT = 2, IOSTAT = ios, IOMSG = err_msg, FMT = * ) &
    "#      particle index      ", &
    "       x [Msun_geo]       y [Msun_geo]       z [Msun_geo]", &
    "       nstar from the ID", &
    "       SPH nstar", &
    "       SPH particle density", &
    "       SPH particle density rescaled with a mass factor (deprecated?)", &
    "       relative nstar error", &
    "       nu", &
    "       number of neighbors (deprecated)"
    IF( ios > 0 )THEN
      PRINT *, "...error when writing line 3 in " // TRIM(finalnamefile), &
               ". The error message is", err_msg
      STOP
    ENDIF

    IF( debug ) PRINT *, "1"

  !  IF( .NOT.ALLOCATED( nu_one ) ) ALLOCATE( nu_one( npart_real ) )
  !  IF( .NOT.ALLOCATED( particle_density_final ) ) &
  !    ALLOCATE( particle_density_final( npart_real ) )
    nu_one= one
    CALL density_loop( npart_real, pos, &    ! input
                       nu_one, h, particle_density_final )      ! output

    DO a= 1, npart_real, 1
      WRITE( UNIT = 2, IOSTAT = ios, IOMSG = err_msg, FMT = * ) &
        a, &
        pos(1,a), pos(2,a), pos(3,a), &
        nstar_id(a), &
        nstar_int(a), &
        particle_density_final(a), &
        particle_density_final(a)*nstar_id(1)/particle_density_final(1), &
        ABS(nstar_sph(a) - nstar_id(a))/nstar_id(a), &
        nu(a), &
        nearest_neighbors(2,a)
    ENDDO

    CLOSE( UNIT= 2 )

    IF( debug ) PRINT *, "2"

    !
    !-- Finalize output of the APM iteration
    !
    CALL reallocate_output_fields( npart_real )

    pos_input(:,1:npart_real)= pos(:,1:npart_real)
    h_output(1:npart_real)   = h(1:npart_real)
    nu_output(1:npart_real)  = nu(1:npart_real)

    npart_output= npart_real

    IF( debug ) PRINT *, "3"

    !
    !-- Deallocate global variables
    !
    IF( ALLOCATED( posmash ) ) DEALLOCATE( posmash )
    CALL deallocate_metric_on_particles()
    IF( debug ) PRINT *, "4"
    CALL deallocate_gradient()
    IF( debug ) PRINT *, "5"
    CALL deallocate_RCB_tree_memory_3D()
    IF( debug ) PRINT *, "6"
    CALL deallocate_SPH_memory()

    !
    !-- Check that there aren't multiple particles at the same position
    !
    PRINT *, "** Checking that there aren't particles with the same position..."
    PRINT *

    CALL check_particle_positions( npart_real, pos )

    !STOP



    CONTAINS



    FUNCTION validate_position_final( x, y, z ) RESULT( answer )

      !*******************************************************
      !
      !# Returns validate_position( x, y, z ) if the latter
      !  is present, 0 otherwise
      !
      !  FT 22.09.2021
      !
      !*******************************************************

      IMPLICIT NONE

      DOUBLE PRECISION, INTENT(IN):: x
      !! \(x\) coordinate of the desired point
      DOUBLE PRECISION, INTENT(IN):: y
      !! \(y\) coordinate of the desired point
      DOUBLE PRECISION, INTENT(IN):: z
      !! \(z\) coordinate of the desired point
      LOGICAL:: answer
      !! validate_position( x, y, z ) if the latter is present, 0 otherwise

      IF( PRESENT(validate_position) )THEN

        answer= validate_position( x, y, z )
        !IF( validate_position( x, y, z ) == 1 ) answer= .TRUE.
        !IF( validate_position( x, y, z ) == 0 ) answer= .FALSE.

      ELSE

        answer= .TRUE.

      ENDIF

    END FUNCTION validate_position_final


    SUBROUTINE get_nstar_id_atm &
    ( npart_real, x, y, z, nstar_sph, nstar_id, nlrf_sph, sqg, use_atmosphere )
    !, nstar_eul_id, use_atmosphere )

      !*******************************************************
      !
      !# Return various densities and the determinant of
      !  the metric, needed in the APM iteration
      !
      !  FT 5.12.2021
      !
      !*******************************************************

      IMPLICIT NONE

      INTEGER, INTENT(IN):: npart_real
      !! Number of real particles (i.e., no ghost particles included here)
      DOUBLE PRECISION, INTENT(IN):: x(npart_real)
      !! Array of \(x\) coordinates
      DOUBLE PRECISION, INTENT(IN):: y(npart_real)
      !! Array of \(y\) coordinates
      DOUBLE PRECISION, INTENT(IN):: z(npart_real)
      !! Array of \(z\) coordinates
      DOUBLE PRECISION, INTENT(IN):: nstar_sph(npart)
      !! |sph| proper baryon density
      DOUBLE PRECISION, INTENT(OUT):: nstar_id(npart)
      !! Array to store the computed proper baryon number density
      DOUBLE PRECISION, INTENT(OUT):: nlrf_sph(npart)
      !# Array to store the local rest frame baryon density computed from
      !  the |sph| proper baryon density
      DOUBLE PRECISION, INTENT(OUT):: sqg(npart)
      !# Square root of minus the determinant of the sacetime metric
      !DOUBLE PRECISION, INTENT(OUT):: nstar_eul_id(npart_real)
      !# Array to store the computed proper baryon number density seen
      !  by the Eulerian observer
      LOGICAL,  INTENT(IN):: use_atmosphere
      !# `.TRUE.` if an atmosphere should be used during the APM, to allow
      !  the real aprticles more freedom to move around and adjust;
      !  `.FALSE.` otherwise

      CALL get_nstar_id(npart_real, x, y, z, nstar_sph, nstar_id, nlrf_sph, sqg)

      IF( use_atmosphere .EQV. .TRUE. )THEN

        !$OMP PARALLEL DO DEFAULT( NONE ) &
        !$OMP             SHARED( npart_real, nstar_id, &
        !$OMP                     atmosphere_density ) &
        !$OMP             PRIVATE( a )
        DO a= 1, npart_real, 1
          IF( nstar_id(a) <= atmosphere_density )THEN
            nstar_id(a)= atmosphere_density
          ENDIF
          !IF( nstar_eul_id(a) <= atmosphere_density )THEN
          !  nstar_eul_id(a)= atmosphere_density
          !ENDIF
        ENDDO
        !$OMP END PARALLEL DO

      ELSE

        !$OMP PARALLEL DO DEFAULT( NONE ) &
        !$OMP             SHARED( npart_real, nstar_id ) &
        !$OMP             PRIVATE( a )
        DO a= 1, npart_real, 1

          IF( nstar_id( a ) < tiny_real )THEN
            PRINT *, "** ERROR! nstar_id(", a, ")=", nstar_id( a ), &
                     " in SUBROUTINE get_nstar_id_atm."
            PRINT *, " * Stopping.."
            PRINT *
            STOP
          ENDIF
          !IF( nstar_eul_id( a ) < tiny_real )THEN
          !  PRINT *, "** ERROR! nstar_eul_id(", a, ")=", nstar_eul_id( a ), &
          !           " in SUBROUTINE get_nstar_id_atm."
          !  PRINT *, " * Stopping.."
          !  PRINT *
          !  STOP
          !ENDIF

        ENDDO
        !$OMP END PARALLEL DO

      ENDIF

      !$OMP PARALLEL DO DEFAULT( NONE ) &
      !$OMP             SHARED( npart_real, nstar_id ) &
      !$OMP             PRIVATE( a )
      DO a= 1, npart_real, 1

        IF( .NOT.is_finite_number( nstar_id( a ) ) )THEN
          PRINT *, "** ERROR! nstar_id(", a, ")= ", nstar_id( a ), &
                   "is a not a finite number!", &
                   " in SUBROUTINE get_nstar_id_atm."
          PRINT *, " * Stopping.."
          PRINT *
          STOP
        ENDIF
        !IF( .NOT.is_finite_number( nstar_eul_id( a ) ) )THEN
        !  PRINT *, "** ERROR! nstar_eul_id(", a, ")= ", nstar_eul_id( a ), &
        !           "is a not a finite number!", &
        !           " in SUBROUTINE get_nstar_id_atm."
        !  PRINT *, " * Stopping.."
        !  PRINT *
        !  STOP
        !ENDIF

      ENDDO
      !$OMP END PARALLEL DO

    END SUBROUTINE get_nstar_id_atm


    SUBROUTINE read_pressure_id( npart_real, x, y, z, pressure_id )

      !*******************************************************
      !
      !# Return the pressure providd by the |id| (not the one
      !  computed from the |sph| density), needed in the APM
      !  iteration
      !
      !
      !  FT 6.12.2022
      !
      !*******************************************************

      IMPLICIT NONE

      INTEGER, INTENT(IN):: npart_real
      !! Number of real particles (i.e., no ghost particles included here)
      DOUBLE PRECISION, INTENT(IN):: x(npart_real)
      !! Array of \(x\) coordinates
      DOUBLE PRECISION, INTENT(IN):: y(npart_real)
      !! Array of \(y\) coordinates
      DOUBLE PRECISION, INTENT(IN):: z(npart_real)
      !! Array of \(z\) coordinates
      DOUBLE PRECISION, INTENT(OUT):: pressure_id(npart)
      !! Array to store the pressure read from the |id|

      !$OMP PARALLEL DO DEFAULT( NONE ) &
      !$OMP             SHARED( npart_real, pressure_id, x, y, z ) &
      !$OMP             PRIVATE( a )
      DO a= 1, npart_real, 1
        pressure_id(a)= get_pressure_id(x(a),y(a),z(a))
      ENDDO
      !$OMP END PARALLEL DO

    END SUBROUTINE read_pressure_id


    SUBROUTINE allocate_apm_fields( npart_real, npart_ghost )

      !*******************************************************
      !
      !# Allocate the fields used during the APM iteration
      !
      !  FT 20.04.2022
      !
      !*******************************************************

      IMPLICIT NONE

      INTEGER, INTENT(IN):: npart_real
      INTEGER, INTENT(IN):: npart_ghost

      INTEGER:: npart_all

      npart_all= npart_real + npart_ghost

      IF(.NOT.ALLOCATED( nstar_sph ))THEN
        ALLOCATE( nstar_sph( npart_all ), STAT= ios, ERRMSG= err_msg )
        IF( ios > 0 )THEN
           PRINT *, "...allocation error for array nstar_sph in SUBROUTINE ", &
                    "allocate_apm_fields. The error message is",&
                    err_msg
           STOP
        ENDIF
      ENDIF
      IF(.NOT.ALLOCATED( nlrf_sph ))THEN
        ALLOCATE( nlrf_sph( npart_all ), STAT= ios, ERRMSG= err_msg )
        IF( ios > 0 )THEN
           PRINT *, "...allocation error for array nlrf_real in SUBROUTINE ", &
                    "allocate_apm_fields. The error message is",&
                    err_msg
           STOP
        ENDIF
      ENDIF
      IF(.NOT.ALLOCATED( pressure_sph ))THEN
        ALLOCATE( pressure_sph( npart_all ), STAT= ios, ERRMSG= err_msg )
        IF( ios > 0 )THEN
           PRINT *, "...allocation error for array pr_real in SUBROUTINE ", &
                    "allocate_apm_fields. The error message is",&
                    err_msg
           STOP
        ENDIF
      ENDIF
      IF(.NOT.ALLOCATED( sqg ))THEN
        ALLOCATE( sqg( npart_real ), STAT= ios, ERRMSG= err_msg )
        IF( ios > 0 )THEN
           PRINT *, "...allocation error for array sqg in SUBROUTINE ", &
                    "allocate_apm_fields. The error message is",&
                    err_msg
           STOP
        ENDIF
      ENDIF
      IF(.NOT.ALLOCATED( dS ))THEN
        ALLOCATE( dS( 3, npart_real ), STAT= ios, ERRMSG= err_msg )
        IF( ios > 0 )THEN
           PRINT *, "...allocation error for array dS in SUBROUTINE ", &
                    "allocate_apm_fields. The error message is",&
                    err_msg
           STOP
        ENDIF
      ENDIF
      IF(.NOT.ALLOCATED( nstar_id ))THEN
        ALLOCATE( nstar_id( npart_all ), STAT= ios, ERRMSG= err_msg )
        IF( ios > 0 )THEN
           PRINT *, "...allocation error for array nstar_id in SUBROUTINE ", &
                    "allocate_apm_fields. The error message is",&
                    err_msg
           STOP
        ENDIF
      ENDIF
      IF(.NOT.ALLOCATED( nlrf_id ))THEN
        ALLOCATE( nlrf_id( npart_all ), STAT= ios, ERRMSG= err_msg )
        IF( ios > 0 )THEN
           PRINT *, "...allocation error for array nlrf_id in SUBROUTINE ", &
                    "allocate_apm_fields. The error message is",&
                    err_msg
           STOP
        ENDIF
      ENDIF
      IF(.NOT.ALLOCATED( pressure_id ))THEN
        ALLOCATE( pressure_id( npart_all ), STAT= ios, ERRMSG= err_msg )
        IF( ios > 0 )THEN
           PRINT *, "...allocation error for array pressure_id in SUBROUTINE ",&
                    "allocate_apm_fields. The error message is",&
                    err_msg
           STOP
        ENDIF
      ENDIF
      IF(.NOT.ALLOCATED( art_pr ))THEN
        ALLOCATE( art_pr( npart_all ), STAT= ios, ERRMSG= err_msg )
        IF( ios > 0 )THEN
           PRINT *, "...allocation error for array art_pr in SUBROUTINE ", &
                    "allocate_apm_fields. The error message is",&
                    err_msg
           STOP
        ENDIF
      ENDIF
      IF(.NOT.ALLOCATED( correction_pos ))THEN
        ALLOCATE( correction_pos( 3, npart_all ) )
        IF( ios > 0 )THEN
           PRINT *, "...allocation error for array correction_pos in ", &
                    "SUBROUTINE allocate_apm_fields. The error message is",&
                    err_msg
           STOP
        ENDIF
      ENDIF
      IF(.NOT.ALLOCATED( all_pos_prev ))THEN
        ALLOCATE( all_pos_prev( 3, npart_all ) )
        IF( ios > 0 )THEN
           PRINT *, "...allocation error for array all_pos_prev in ", &
                    "SUBROUTINE allocate_apm_fields. The error message is",&
                    err_msg
           STOP
        ENDIF
      ENDIF
      IF(.NOT.ALLOCATED( cnt_move ))THEN
        ALLOCATE( cnt_move( npart_real ) )
        IF( ios > 0 )THEN
           PRINT *, "...allocation error for array cnt_move in ", &
                    "SUBROUTINE allocate_apm_fields. The error message is",&
                    err_msg
           STOP
        ENDIF
      ENDIF
      IF(.NOT.ALLOCATED( dNstar ))THEN
        ALLOCATE( dNstar( npart_real ), STAT= ios, ERRMSG= err_msg )
        IF( ios > 0 )THEN
           PRINT *, "...allocation error for array dNstar in SUBROUTINE ", &
                    "allocate_apm_fields. The error message is",&
                    err_msg
           STOP
        ENDIF
      ENDIF
      IF(.NOT.ALLOCATED( nu_tmp ))THEN
        ALLOCATE( nu_tmp( npart_real ), STAT= ios, ERRMSG= err_msg )
        IF( ios > 0 )THEN
           PRINT *, "...allocation error for array nu_tmp in SUBROUTINE ", &
                    "allocate_apm_fields. The error message is",&
                    err_msg
           STOP
        ENDIF
      ENDIF
      IF(.NOT.ALLOCATED( pos ))THEN
        ALLOCATE( pos( 3, npart_real ), STAT= ios, ERRMSG= err_msg )
        IF( ios > 0 )THEN
           PRINT *, "...allocation error for array pos in SUBROUTINE ", &
                    "allocate_apm_fields. The error message is", &
                    err_msg
           STOP
        ENDIF
      ENDIF
      IF( .NOT.ALLOCATED( nu_one ) )THEN
        ALLOCATE( nu_one( npart_real ), STAT= ios, ERRMSG= err_msg )
        IF( ios > 0 )THEN
           PRINT *, "...allocation error for array nu_one in SUBROUTINE ", &
                    "allocate_apm_fields. The error message is", &
                    err_msg
           STOP
        ENDIF
      ENDIF
      IF( .NOT.ALLOCATED( particle_density_final ) )THEN
        ALLOCATE( particle_density_final( npart_real ), &
                  STAT= ios, ERRMSG= err_msg )
        IF( ios > 0 )THEN
           PRINT *, "...allocation error for array particle_density_final in ",&
                    "SUBROUTINE allocate_apm_fields. The error message is", &
                    err_msg
           STOP
        ENDIF
      ENDIF
      IF( .NOT.ALLOCATED( cnt_array ) )THEN
        ALLOCATE( cnt_array( npart_real ), STAT= ios, ERRMSG= err_msg )
        IF( ios > 0 )THEN
           PRINT *, "...allocation error for array cnt_array in ", &
                    "SUBROUTINE allocate_apm_fields. The error message is", &
                    err_msg
           STOP
        ENDIF
      ENDIF

    END SUBROUTINE allocate_apm_fields


    SUBROUTINE discard_atmosphere( npart_real )

      !*******************************************************
      !
      !# Remove the atmosphere
      !
      !  FT 20.04.2022
      !
      !*******************************************************

      IMPLICIT NONE

      INTEGER, INTENT(INOUT):: npart_real
      INTEGER:: a

      ALLOCATE( rho_tmp( npart_real ) )
      IF(ALLOCATED(pos_tmp)) DEALLOCATE(pos_tmp)
      ALLOCATE( pos_tmp( 3, npart_real ) )
      IF(ALLOCATED(h_tmp)) DEALLOCATE(h_tmp)
      ALLOCATE( h_tmp( npart_real ) )
      IF(ALLOCATED(h_guess_tmp)) DEALLOCATE(h_guess_tmp)
      ALLOCATE( h_guess_tmp( npart_real ) )
      IF(ALLOCATED(nu_tmp)) DEALLOCATE(nu_tmp)
      ALLOCATE( nu_tmp( npart_real ) )

      pos_tmp    = HUGE(one)
      h_tmp      = HUGE(one)
      h_guess_tmp= HUGE(one)
      nu_tmp     = HUGE(one)

      npart= 0
      !$OMP PARALLEL DO DEFAULT( NONE ) &
      !$OMP             SHARED( pos, pos_tmp, h, h_tmp, rho_tmp, npart_real, &
      !$OMP                 h_guess, h_guess_tmp, nu, nu_tmp, pvol_tmp, pvol ) &
      !$OMP             PRIVATE( a ) &
      !$OMP             REDUCTION( +: npart )
      DO a= 1, npart_real, 1
        rho_tmp(a)= get_density( pos(1,a), pos(2,a), pos(3,a) )
        IF( rho_tmp(a) > zero )THEN
          npart= npart + 1
          pos_tmp(:,a)  = pos(:,a)
          h_tmp(a)      = h(a)
          h_guess_tmp(a)= h_guess(a)
          nu_tmp(a)     = nu(a)
          !pvol_tmp(a)   = pvol(a)
        ENDIF
      ENDDO
      !$OMP END PARALLEL DO

      IF(ALLOCATED(pos)) DEALLOCATE(pos)
      ALLOCATE( pos( 3, npart ) )
      IF(ALLOCATED(h)) DEALLOCATE(h)
      ALLOCATE( h( npart ) )
      IF(ALLOCATED(h_guess)) DEALLOCATE(h_guess)
      ALLOCATE( h_guess( npart ) )
      IF(ALLOCATED(nu)) DEALLOCATE(nu)
      ALLOCATE( nu( npart ) )
      !IF(ALLOCATED(pvol)) DEALLOCATE(pvol)
      !ALLOCATE( pvol( npart ) )

  !   !$OMP PARALLEL DO DEFAULT( NONE ) &
  !   !$OMP             SHARED( pos, pos_tmp, h, h_tmp, rho_tmp, npart_real, &
  !   !$OMP                     h_guess, h_guess_tmp, nu, nu_tmp ) &
  !   !$OMP             PRIVATE( a )
      cnt1= 0
      DO a= 1, npart_real, 1
        IF( h_tmp(a) < HUGE(one) )THEN
          cnt1= cnt1 + 1
          pos(:,cnt1)  = pos_tmp(:,a)
          h(cnt1)      = h_tmp(a)
          h_guess(cnt1)= h_guess_tmp(a)
          nu(cnt1)     = nu_tmp(a)
          !pvol(cnt1)   = pvol_tmp(a)
        ENDIF
      ENDDO
  !   !$OMP END PARALLEL DO

      npart_real= npart


    END SUBROUTINE discard_atmosphere


    SUBROUTINE reallocate_output_fields( npart_real )

      !*******************************************************
      !
      !# Reallocate the fields to be returned by perform_apm
      !
      !  FT 20.04.2022
      !
      !*******************************************************

      IMPLICIT NONE

      INTEGER, INTENT(IN):: npart_real

      IF( ALLOCATED( pos_input ) ) DEALLOCATE( pos_input )
      ALLOCATE( pos_input( 3, npart_real ), STAT= ios, ERRMSG= err_msg )
      IF( ios > 0 )THEN
        PRINT *, "...allocation error for array pos_input in ", &
                 "SUBROUTINE reallocate_output_fields. The error message is", &
                 err_msg
        STOP
      ENDIF

      IF( ALLOCATED( h_output ) ) DEALLOCATE( h_output )
      ALLOCATE( h_output( npart_real ), STAT= ios, ERRMSG= err_msg )
      IF( ios > 0 )THEN
        PRINT *, "...allocation error for array h_output in ", &
                 "SUBROUTINE reallocate_output_fields. The error message is", &
                 err_msg
        STOP
      ENDIF

      IF( ALLOCATED( nu_output ) ) DEALLOCATE( nu_output )
      ALLOCATE( nu_output( npart_real ), STAT= ios, ERRMSG= err_msg )
      IF( ios > 0 )THEN
        PRINT *, "...allocation error for array nu_output in ", &
                 "SUBROUTINE reallocate_output_fields. The error message is", &
                 err_msg
        STOP
      ENDIF


    END SUBROUTINE reallocate_output_fields


    SUBROUTINE place_and_print_ghost_particles()

      !*******************************************************
      !
      !# Place ghost particles around the matter object,
      !  and print their positions together with the positions
      !  of the real particles to a formatted file
      !
      !  FT 20.04.2022
      !
      !*******************************************************

      USE constants,  ONLY: m2cm
      USE utility,    ONLY: g2kg, density_si2cu
      USE numerics,   ONLY: bilinear_interpolation

      IMPLICIT NONE

      INTEGER:: a, a_nu_min

      DOUBLE PRECISION, PARAMETER:: eps          = 1.D0 !2.0D-1 !5.0D0
      !DOUBLE PRECISION, PARAMETER:: multiple_h_av= 1.0D0

      DOUBLE PRECISION:: nu_av, max_r_real, nstar_sph_ghost_av
      DOUBLE PRECISION, DIMENSION(npart_real):: tmp, tmp2, tmp3, &
        nstar_id_arr, nstar_sph_arr, nlrf_sph_arr, nlrf_id_arr, pressure_id_arr
      DOUBLE PRECISION, DIMENSION(:,:,:,:), ALLOCATABLE:: ghost_pos_tmp
      DOUBLE PRECISION, DIMENSION(:), ALLOCATABLE:: nu_ghost_arr, &
        nstar_sph_ghost, h_ghost

      IF(adapt_ghosts)THEN
        ellipse_thickness= 1.4D0
      ELSE
        ellipse_thickness= 1.1D0
      ENDIF

      npart= npart_real

      CALL allocate_SPH_memory

      CALL allocate_RCB_tree_memory_3D(npart)
      iorig(1:npart)= (/ (a,a=1,npart) /)

      IF( debug ) PRINT *, "10"

      CALL allocate_gradient( npart )
      CALL allocate_metric_on_particles( npart )

      IF(.NOT.ALLOCATED( ghost_pos ))THEN
        ALLOCATE( ghost_pos( 3, max_npart ), STAT= ios, ERRMSG= err_msg )
        IF( ios > 0 )THEN
          PRINT *, "...allocation error for array ghost_pos in SUBROUTINE ", &
                  "perform_apm. The error message is",&
                  err_msg
          STOP
        ENDIF
      ENDIF

      ghost_pos= zero

      PRINT *, " * Placing ghost particles on a lattice between ellipsodial ", &
               "surfaces..."
      PRINT *

      IF( debug ) PRINT *, "npart_real= ", npart_real

      ! Find the maximum radial coordinate over the real particles
   !   max_r_real= zero
   !   !$OMP PARALLEL DO DEFAULT( NONE ) &
   !   !$OMP             SHARED( npart_real, pos_input, center ) &
   !   !$OMP             PRIVATE( a, r_real ) &
   !   !$OMP             REDUCTION( MAX: max_r_real )
   !   DO a= 1, npart_real, 1
   !
   !     r_real= SQRT( ( pos_input(1,a) - center(1) )**two &
   !                 + ( pos_input(2,a) - center(2) )**two &
   !                 + ( pos_input(3,a) - center(3) )**two )
   !     !IF( r_real > max_r_real ) max_r_real= r_real
   !     max_r_real= MAX( max_r_real, r_real )
   !
   !   ENDDO
   !   !$OMP END PARALLEL DO

      IF(debug) PRINT *, "max_z_real =", max_z_real

   !   CALL get_nstar_id( npart_real, pos_input(1,1:npart_real), &
   !                                  pos_input(2,1:npart_real), &
   !                                  pos_input(3,1:npart_real), tmp, &
   !                                  nstar_id_arr, tmp2, tmp3 )

      ! Determine smoothing length so that each particle has exactly
      ! 300 neighbours inside 2h
      CALL assign_h( nn_des, &
                     npart_real, &
                     pos_input(:,1:npart_real), h_guess(1:npart_real), &
                     h(1:npart_real) )

      find_h_bruteforce_timer= timer( "find_h_bruteforce_timer" )
      CALL find_h_bruteforce_timer% start_timer()
      n_problematic_h= 0
      check_h1: DO a= 1, npart_real, 1
      ! find_h_backup, called below, is OMP parallelized, so this loop
      ! should not be parallelized as well

        IF( .NOT.is_finite_number( h(a) ) .OR. h(a) <= zero )THEN

          n_problematic_h= n_problematic_h + 1
          h(a)= find_h_backup( a, npart_real, pos_input(:,1:npart_real), nn_des)
          IF( .NOT.is_finite_number( h(a) ) .OR. h(a) <= zero )THEN
            PRINT *, "** ERROR! h=0 on particle ", a, "even with the brute", &
                     " force method."
            PRINT *, "   Particle position: ", pos_input(:,a)
            STOP
          ENDIF

        ENDIF

      ENDDO check_h1
      CALL find_h_bruteforce_timer% stop_timer()
      CALL find_h_bruteforce_timer% print_timer( 2 )

      PRINT *, " * The smoothing length was found brute-force for ", &
               n_problematic_h, " particles."
      PRINT *

      PRINT *, " * Compute SPH density..."
      PRINT *

      CALL density_loop( npart_real, pos_input(:,1:npart_real), &
        nu_output(1:npart_real), h(1:npart_real), nstar_sph_arr(1:npart_real) )

      PRINT *, " * Read ID density..."
      PRINT *

      CALL get_nstar_id( npart_real, pos_input(1,1:npart_real), &
                                     pos_input(2,1:npart_real), &
                                     pos_input(3,1:npart_real), &
                                     nstar_sph_arr, &
                                     nstar_id_arr, nlrf_sph_arr, tmp3 )

      IF(use_pressure)THEN

        !$OMP PARALLEL DO DEFAULT( NONE ) &
        !$OMP             SHARED( npart_real, nlrf_id_arr, nstar_id_arr, &
        !$OMP                     nstar_sph_arr, nlrf_sph_arr ) &
        !$OMP             PRIVATE( a )
        DO a= 1, npart_real, 1
          nlrf_id_arr(a)= nstar_id_arr(a)*nlrf_sph_arr(a)/nstar_sph_arr(a)
        ENDDO
        !$OMP END PARALLEL DO

        CALL compute_pressure( npart_real, &
                               pos_input(1,1:npart_real), &
                               pos_input(2,1:npart_real), &
                               pos_input(3,1:npart_real), &
                               nlrf_id_arr, eqos, pressure_id_arr, .TRUE. )

      ENDIF

      itr           = 0
      nu_av         = zero
      nstar_id_av   = zero
      nlrf_id_av    = zero
      nstar_sph_av  = zero
      pressure_id_av= zero
      !$OMP PARALLEL DO DEFAULT( NONE ) &
      !$OMP             SHARED( npart_real, nlrf_id_arr, nstar_id_arr, &
      !$OMP                     nstar_sph_arr, pos_input, pressure_id_arr, &
      !$OMP                     max_z_real, center, nu_output ) &
      !$OMP             PRIVATE( a ) &
      !$OMP             REDUCTION(+: itr, nu_av, nstar_id_av, nlrf_id_av, &
      !$OMP                          nstar_sph_av, pressure_id_av )
      DO a= 1, npart_real, 1

        IF( SQRT( ( pos_input(1,a) - center(1) )**two &
                + ( pos_input(2,a) - center(2) )**two &
                + ( pos_input(3,a) - center(3) )**two ) &
                  > (one - one/(ten*ten))*max_z_real )THEN

          itr           = itr + 1
          nu_av         = nu_av          + nu_output(a)
          nstar_id_av   = nstar_id_av    + nstar_id_arr(a)
          nlrf_id_av    = nlrf_id_av     + nlrf_id_arr(a)
          nstar_sph_av  = nstar_sph_av   + nstar_sph_arr(a)
          pressure_id_av= pressure_id_av + pressure_id_arr(a)

        ENDIF

      ENDDO
      !$OMP END PARALLEL DO
      nu_av         = nu_av/itr
      nstar_id_av   = nstar_id_av/itr
      nlrf_id_av    = nlrf_id_av/itr
      nstar_sph_av  = nstar_sph_av/itr
      pressure_id_av= pressure_id_av/itr

      xmin= center(1) - sizes(1)*( one + eps )
      xmax= center(1) + sizes(2)*( one + eps )
      ymin= center(2) - sizes(3)*( one + eps )
      ymax= center(2) + sizes(4)*( one + eps )
      zmin= center(3) - sizes(5)*( one + eps )
      zmax= center(3) + sizes(6)*( one + eps )

      IF(adapt_ghosts)THEN

        !
        !-- Determine dx,dy,dz from desired density from the ID, and compute
        !-- nx,ny,nz,ghost_dist,nu_ghost from them
        !
        dx        = ( nu_av/nstar_id_av )**third
        dy        = dx
        dz        = dx
        ghost_dist= dx
        nu_ghost  = dx*dy*dz*nstar_id_av
        nx= NINT( ABS( xmax - xmin )/dx )
        ny= NINT( ABS( ymax - ymin )/dy )
        nz= NINT( ABS( zmax - zmin )/dz )

        IF(debug) PRINT*, "# particles over which the averages are taken=", itr
        IF(debug) PRINT*, "nstar_id_av      =", nstar_id_av
        IF(debug) PRINT*, "nstar_sph_av     =", nstar_sph_av
        IF(debug) PRINT*, "(1.D+12g/cm**3)  =", &
          1.D+12*(g2kg*m2cm**3)*density_si2cu*umass/amu
        IF(debug) PRINT*, "nu_all           =",(mass/DBLE(npart_real))*umass/amu
        IF(debug) PRINT*, "nu_av            =", nu_av
        IF(debug) PRINT*, "nu_av/nstar_id_av=", nu_av/nstar_id_av
        IF(debug) PRINT *, "nu_output(300)=", nu_output(300)
        IF(debug) PRINT *, "ghost_dist=", ghost_dist
        !STOP

      ELSE

        !
        !-- Read nx,ny,nz from parameter file, and compute dx,dy,dz from them.
        !-- The ghosts have the same mass as the real particles
        !
        nx= nx_gh
        ny= ny_gh
        nz= nz_gh
        dx= ABS( xmax - xmin )/DBLE( nx )
        dy= ABS( ymax - ymin )/DBLE( ny )
        dz= ABS( zmax - zmin )/DBLE( nz )
        nu_ghost= (mass/DBLE(npart_real))*umass/amu

      ENDIF

      IF(debug) PRINT *, "dx =", dx
      IF(debug) PRINT *, "dy =", dy
      IF(debug) PRINT *, "dz =", dz
      IF(debug) PRINT *, "nx =", nx
      IF(debug) PRINT *, "ny =", ny
      IF(debug) PRINT *, "nz =", nz
      IF(debug) PRINT *, "nu_ghost =", nu_ghost

      IF(.NOT.ALLOCATED( ghost_pos_tmp ))THEN
        ALLOCATE( ghost_pos_tmp( 3, nx, ny, nz ), STAT= ios, &
            ERRMSG= err_msg )
        IF( ios > 0 )THEN
           PRINT *, "...allocation error for array ghost_pos in SUBROUTINE ", &
                    "perform_apm. The error message is",&
                    err_msg
           STOP
        ENDIF
      ENDIF

      rad_x= radius_x + ghost_dist !+ multiple_h_av*h_av
      rad_y= radius_y + ghost_dist !+ multiple_h_av*h_av
      rad_z= radius_z + ghost_dist !+ multiple_h_av*h_av

      IF(adapt_ghosts)THEN

        rad_x= ABS( MAXVAL( ABS(pos_input(1,:) - center(1)), DIM= 1 ) )
        rad_y= ABS( MAXVAL( ABS(pos_input(2,:) - center(2)), DIM= 1 ) )
        rad_z= ABS( MAXVAL( ABS(pos_input(3,:) - center(3)), DIM= 1 ) )

      ENDIF

      PRINT *, "** Distance between the size of the object and the ghost ", &
               "particles: ghost_dist =", ghost_dist
      PRINT *

      IF( debug ) PRINT *, "radius_x= ", radius_x
      IF( debug ) PRINT *, "radius_y= ", radius_y
      IF( debug ) PRINT *, "radius_z= ", radius_z
      IF( debug ) PRINT *, "rad_x= ", rad_x
      IF( debug ) PRINT *, "rad_y= ", rad_y
      IF( debug ) PRINT *, "rad_z= ", rad_z
      IF( debug ) PRINT *

      ghost_pos_tmp= HUGE(zero)

      !PRINT *, "SIZE(surf% points(:,1,5))=", SIZE(surf% points(:,1,5))
      !PRINT *, "SIZE(surf% points(1,:,6))=", SIZE(surf% points(1,:,6))

      !$OMP PARALLEL DO DEFAULT( NONE ) &
      !$OMP             SHARED( nx, ny, nz, xmin, ymin, zmin, dx, dy, dz, &
      !$OMP                     ghost_pos_tmp, ellipse_thickness, &
      !$OMP                     center, rad_x, rad_y, rad_z, surf, ghost_dist )&
      !$OMP             PRIVATE( i, j, k, xtemp, ytemp, ztemp, &
      !$OMP                      x_ell, y_ell, z_ell, r, theta, phi, &
      !$OMP                      r_ell, theta_ell, phi_ell )
      DO k= 1, nz, 1

        ztemp= zmin + dz/two + DBLE(k - 1)*dz

        DO j= 1, ny, 1

          ytemp= ymin + dy/two + DBLE(j - 1)*dy

          DO i= 1, nx, 1

            xtemp= xmin + dx/two + DBLE(i - 1)*dx

            ! Compute te spherical polar coordinates of the point, to get
            ! its angular coordinates
            CALL spherical_from_cartesian( xtemp, ytemp, ztemp, &
                                           center(1), center(2), center(3), &
                                           r, theta, phi )

            IF(.NOT.PRESENT(surf) &
               .OR. &
               (PRESENT(surf) .AND. surf% is_known .NEQV. .TRUE.) &
            )THEN

              ! Use the angular coordinates of the point and the ellipse
              ! semiaxes, to obtain the Cartesian coordinates of the point on
              ! the ellipsoid having the same angular cordinates of the
              ! input point
              CALL cartesian_from_spherical( rad_x, theta, phi, &
                center(1), center(2), center(3), &
                x_ell, y_ell, z_ell, rad_y/rad_x, rad_z/rad_x )

              ! Compute the spherical polar coordinates of the point on the
              ! ellipsoid
              CALL spherical_from_cartesian( x_ell, y_ell, z_ell, &
                                             center(1), center(2), center(3), &
                                             r_ell, theta_ell, phi_ell )

            ELSE

              IF(surf% is_known .EQV. .TRUE.)THEN

                r_ell= bilinear_interpolation( theta, phi, &
                         SIZE(surf% points(:,1,5)), &
                         SIZE(surf% points(1,:,6)), &
                         surf% points(:,:,5:6), surf% points(:,:,4) )

                r_ell= r_ell + ghost_dist

              ENDIF

            ENDIF

            !PRINT *, "r=", r
            !PRINT *, "r_ell=", r_ell
            !PRINT *, "ellipse_thickness*r_ell=", ellipse_thickness*r_ell
            !STOP

            ! Place a ghost particle if: (i) its radial coordinate is larger
            ! than the radial coordinate the ellipsoid and lower than
            ! ellipse_thickness times it; (ii) the density is <=0
            IF( ( r <= ellipse_thickness*r_ell .AND. r >= r_ell &
                  .AND. &
                  get_density(xtemp, ytemp, ztemp) <= zero ) &
            )THEN

              ghost_pos_tmp(1, i, j, k)= xtemp
              ghost_pos_tmp(2, i, j, k)= ytemp
              ghost_pos_tmp(3, i, j, k)= ztemp

            ENDIF

           ENDDO
        ENDDO
      ENDDO
      !$OMP END PARALLEL DO

      a= 0
      DO k= 1, nz, 1

        DO j= 1, ny, 1

          DO i= 1, nx, 1

            IF( ghost_pos_tmp(1, i, j, k) < HUGE(zero) )THEN

              a= a + 1
              ghost_pos(1,a)= ghost_pos_tmp( 1, i, j, k )
              ghost_pos(2,a)= ghost_pos_tmp( 2, i, j, k )
              ghost_pos(3,a)= ghost_pos_tmp( 3, i, j, k )

            ENDIF

           ENDDO
        ENDDO
      ENDDO
      npart_ghost= a
      IF( npart_ghost == 0 )THEN
        PRINT *, "** ERROR: No ghost particles were placed. Is the ", &
                 "PARAMETER 'ghost_dist' appropriate for the physical system?"
        PRINT *, " * Stopping.."
        PRINT *
        STOP
      ENDIF
      IF( npart_ghost > max_npart )THEN
        PRINT *
        PRINT *, "** ERROR! Too many ghost particles placed!"
        PRINT *, " * npart_ghost= ", npart_ghost
        PRINT *, " * npart_ghost should not be larger than max_npart= ", &
                 max_npart
        PRINT *, " * How are the ghost particles placed? Does the algorithm ", &
                 "make reasonable sense? Perhaps try to set a smaller ", &
                 "parameter eps."
        PRINT *, " * Stopping..."
        PRINT *
        STOP
      ENDIF
      IF( npart_ghost > max_npart )THEN
        PRINT *
        PRINT *, "** ERROR! Too many ghost particles placed!"
        PRINT *, " * npart_ghost= ", npart_ghost
        PRINT *, " * npart_ghost should not be larger than max_npart= ", &
                 max_npart
        PRINT *, " * How are the ghost particles placed? Does the algorithm ", &
                 "make reasonable sense? Perhaps try to set a smaller ", &
                 "parameter eps."
        PRINT *, " * Stopping..."
        PRINT *
        STOP
      ENDIF
      ghost_pos = ghost_pos( :, 1:npart_ghost )

      PRINT *, " * ", npart_ghost, " ghost particles placed around ", &
               npart_real, "real particles."
      PRINT *

      DEALLOCATE( ghost_pos_tmp )

      npart_all= npart_real + npart_ghost

      !$OMP PARALLEL DO DEFAULT( NONE ) &
      !$OMP             SHARED( h_guess, npart_real, npart_all, dx, dy, dz ) &
      !$OMP             PRIVATE( a )
      DO a= npart_real + 1, npart_all, 1
        h_guess(a)= ( dx*dy*dz )**third
      ENDDO
      !$OMP END PARALLEL DO

      CALL deallocate_metric_on_particles()
      CALL deallocate_gradient()
      CALL deallocate_RCB_tree_memory_3D()
      CALL deallocate_SPH_memory()

      IF(debug)THEN
        !
        !-- Estimate ghost density
        !

        npart= npart_ghost

        CALL allocate_SPH_memory

        CALL allocate_RCB_tree_memory_3D(npart)
        iorig(1:npart)= (/ (a,a=1,npart) /)

        IF( debug ) PRINT *, "10"

        CALL allocate_gradient( npart )
        CALL allocate_metric_on_particles( npart )

        ALLOCATE( nu_ghost_arr(npart_ghost) )
        ALLOCATE( nstar_sph_ghost(npart_ghost) )
        ALLOCATE( h_ghost(npart_ghost) )

        ! Determine smoothing length so that each particle has exactly
        ! 300 neighbours inside 2h
        CALL assign_h( nn_des, &
                       npart_ghost, &
                       ghost_pos(:,1:npart_real), &
                       h_guess(npart_real+1:npart_all), &
                       h_ghost )

        find_h_bruteforce_timer= timer( "find_h_bruteforce_timer" )
        CALL find_h_bruteforce_timer% start_timer()
        n_problematic_h= 0
        DO a= 1, npart_ghost, 1
        ! find_h_backup, called below, is OMP parallelized, so this loop
        ! should not be parallelized as well

          IF( .NOT.is_finite_number( h(a) ) .OR. h_ghost(a) <= zero )THEN

            n_problematic_h= n_problematic_h + 1
            h_ghost(a)= &
              find_h_backup( a, npart_ghost, ghost_pos(:,1:npart_ghost), nn_des)
            IF( .NOT.is_finite_number( h_ghost(a) ) .OR. h_ghost(a) <= zero &
            )THEN
              PRINT *, "** ERROR! h=0 on particle ", a, "even with the brute", &
                       " force method."
              PRINT *, "   Particle position: ", ghost_pos(:,a)
              STOP
            ENDIF

          ENDIF

        ENDDO
        CALL find_h_bruteforce_timer% stop_timer()
        CALL find_h_bruteforce_timer% print_timer( 2 )

        PRINT *, " * The smoothing length was found brute-force for ", &
                 n_problematic_h, " particles."
        PRINT *

        PRINT *, " * Measure SPH particle number density..."
        PRINT *

        nu_ghost_arr= nu_ghost
        CALL density_loop( npart_ghost, ghost_pos, &
                           nu_ghost_arr, h_ghost, nstar_sph_ghost )

        !nstar_sph_ghost_av= SUM(nstar_sph_ghost)/npart_ghost
        PRINT *, "nstar_sph_ghost_max=", MAXVAL(nstar_sph_ghost, DIM=1)
        PRINT *, "nstar_sph_ghost_av=", SUM(nstar_sph_ghost)/npart_ghost
        PRINT *, "nstar_sph_ghost_min=", MINVAL(nstar_sph_ghost, DIM=1)

        CALL deallocate_metric_on_particles()
        CALL deallocate_gradient()
        CALL deallocate_RCB_tree_memory_3D()
        CALL deallocate_SPH_memory()

        npart= npart_real

      ENDIF

      !--------------------------------------------------!
      !--  Printing ghost particles to formatted file  --!
      !--------------------------------------------------!

      PRINT *, " * Printing ghost particles to file..."

      IF( PRESENT(namefile_pos_id) )THEN
        finalnamefile= namefile_pos_id
      ELSE
        finalnamefile= TRIM(sph_path)//"apm_pos_id.dat"
      ENDIF

      INQUIRE( FILE= TRIM(finalnamefile), EXIST= exist )

      IF( exist )THEN
        OPEN( UNIT= 2, FILE= TRIM(finalnamefile), STATUS= "REPLACE", &
              FORM= "FORMATTED", POSITION= "REWIND", ACTION= "WRITE", &
              IOSTAT= ios, IOMSG= err_msg )
      ELSE
        OPEN( UNIT= 2, FILE= TRIM(finalnamefile), STATUS= "NEW", &
              FORM= "FORMATTED", ACTION= "WRITE", IOSTAT= ios, IOMSG= err_msg )
      ENDIF
      IF( ios > 0 )THEN
        PRINT *, "...error when opening " // TRIM(finalnamefile), &
                 ". The error message is", err_msg
        STOP
      ENDIF

      WRITE( UNIT = 2, IOSTAT = ios, IOMSG = err_msg, FMT = * ) &
      "# Run ID [ccyymmdd-hhmmss.sss]: " // run_id

      WRITE( UNIT = 2, IOSTAT = ios, IOMSG = err_msg, FMT = * ) &
      "# Values of some fields at the start of the APM iteration "&
      // "on the real and ghost particles"
      IF( ios > 0 )THEN
        PRINT *, "...error when writing line 1 in " // TRIM(finalnamefile), &
                 ". The error message is", err_msg
        STOP
      ENDIF

      WRITE( UNIT = 2, IOSTAT = ios, IOMSG = err_msg, FMT = * ) &
      "# column:      1        2       3       4       5"
      IF( ios > 0 )THEN
        PRINT *, "...error when writing line 2 in " // TRIM(finalnamefile), &
                 ". The error message is", err_msg
        STOP
      ENDIF

      WRITE( UNIT = 2, IOSTAT = ios, IOMSG = err_msg, FMT = * ) &
      "# For the real particles: ", &
      "       1       particle index     ", &
      "       x [Msun_geo]       y [Msun_geo]       z [Msun_geo]"

      WRITE( UNIT = 2, IOSTAT = ios, IOMSG = err_msg, FMT = * ) &
      "# For the real particles: ", &
      "       2       particle index     ", &
      "       x [Msun_geo]       y [Msun_geo]       z [Msun_geo]"

      DO a= 1, npart_real, 1
        WRITE( UNIT = 2, IOSTAT = ios, IOMSG = err_msg, FMT = * ) &
          1, a, &
          pos_input(1,a), &
          pos_input(2,a), &
          pos_input(3,a)
      ENDDO

      DO a= 1, npart_ghost, 1
        WRITE( UNIT = 2, IOSTAT = ios, IOMSG = err_msg, FMT = * ) &
          2, a, &
          ghost_pos(1,a), &
          ghost_pos(2,a), &
          ghost_pos(3,a)
      ENDDO

      CLOSE( UNIT= 2 )

      PRINT *, " * Positions of ghost and real particles printed to ", &
               TRIM(finalnamefile), " ."

      !STOP


    END SUBROUTINE place_and_print_ghost_particles


    SUBROUTINE dump_apm_pos()

      !*******************************************************
      !
      !# Print the positions of the real and ghost particles
      !  to a formatted file
      !
      !  FT 20.04.2022
      !
      !*******************************************************

      IMPLICIT NONE

      INTEGER, PARAMETER:: unit_dump= 7314891

      IF( debug ) PRINT *, "printing positions to file..."

      IF( PRESENT(namefile_pos) )THEN
        finalnamefile= namefile_pos
      ELSE
        finalnamefile= TRIM(sph_path)//"apm_pos.dat"
      ENDIF

      INQUIRE( FILE= TRIM(finalnamefile), EXIST= exist )

      IF( exist )THEN
          OPEN( UNIT= unit_dump, FILE= TRIM(finalnamefile), STATUS= "REPLACE", &
                FORM= "FORMATTED", &
                POSITION= "REWIND", ACTION= "WRITE", IOSTAT= ios, &
                IOMSG= err_msg )
      ELSE
          OPEN( UNIT= unit_dump, FILE= TRIM(finalnamefile), STATUS= "NEW", &
                FORM= "FORMATTED", &
                ACTION= "WRITE", IOSTAT= ios, IOMSG= err_msg )
      ENDIF
      IF( ios > 0 )THEN
        PRINT *, "...error when opening " // TRIM(finalnamefile), &
                 ". The error message is", err_msg
        STOP
      ENDIF

      WRITE( UNIT = 2, IOSTAT = ios, IOMSG = err_msg, FMT = * ) &
      "# Run ID [ccyymmdd-hhmmss.sss]: " // run_id

      WRITE( UNIT = 2, IOSTAT = ios, IOMSG = err_msg, FMT = * ) &
      "# Values of some fields during the APM iteration " &
      // "on the real and ghost particles"
      IF( ios > 0 )THEN
        PRINT *, "...error when writing line 1 in " // TRIM(finalnamefile), &
                 ". The error message is", err_msg
        STOP
      ENDIF

      WRITE( UNIT = 2, IOSTAT = ios, IOMSG = err_msg, FMT = * ) &
      "# column:      1        2       3       4       5", &
      "       6       7       8       9"
      IF( ios > 0 )THEN
        PRINT *, "...error when writing line 2 in " // TRIM(finalnamefile), &
                 ". The error message is", err_msg
        STOP
      ENDIF

      WRITE( UNIT = 2, IOSTAT = ios, IOMSG = err_msg, FMT = * ) &
      "# For the real particles: ", &
      "       1       particle index     ", &
      "       x [Msun_geo]       y [Msun_geo]       z [Msun_geo]       nu", &
      "       temporary variable (now ID density, not SPH density)", &
      "       cnt_move (1=the particle mved at this step, 0=it did not)"

      WRITE( UNIT = 2, IOSTAT = ios, IOMSG = err_msg, FMT = * ) &
      "# For the real particles: ", &
      "       2       particle index     ", &
      "       x [Msun_geo]       y [Msun_geo]       z [Msun_geo]       nu", &
      "       temporary variable (now ID density, not SPH density)", &
      "       SPH nstar    ID nstar"

      DO a= 1, npart_real, 1
        tmp= get_density( all_pos(1,a), all_pos(2,a), all_pos(3,a) )
        WRITE( UNIT = unit_dump, IOSTAT = ios, IOMSG = err_msg, FMT = * ) &
          1, a, &
          all_pos(1,a), &
          all_pos(2,a), &
          all_pos(3,a), &
          nu_tmp(a), &
          tmp, cnt_move(a)
      ENDDO

      DO a= npart_real + 1, npart_all, 1
        tmp= get_density( all_pos(1,a), all_pos(2,a), all_pos(3,a) )
        WRITE( UNIT = unit_dump, IOSTAT = ios, IOMSG = err_msg, FMT = * ) &
          2, a, &
          all_pos(1,a), &
          all_pos(2,a), &
          all_pos(3,a), &
          tmp, &
          art_pr(a), nstar_sph(a), nstar_id_av
      ENDDO

      CLOSE( UNIT= unit_dump )


    END SUBROUTINE dump_apm_pos


    SUBROUTINE compute_hydro_momentum()

      !*******************************************************
      !
      !# Compute the |sph| canonical momentum
      !
      !  FT 17.06.2022
      !
      !  @warning To be checked. Deprecated?
      !
      !*******************************************************


      USE RCB_tree_3D,         ONLY: iorig, nic, nfinal, nprev, lpart, &
                                     rpart

      IMPLICIT NONE

      DOUBLE PRECISION:: ha, ha_1, ha_3, ha_4, va, xa, ya, za, &
                         hb, hb_1, hb_3, hb_4, xb, yb, zb, rab, rab2, rab_1, &
                         ha2, ha2_4, hb2_4
      !DOUBLE PRECISION:: mat_xx, mat_xy, mat_xz, mat_yy, mat_yz, mat_zz
      DOUBLE PRECISION:: &!Wdx, Wdy, Wdz, ddx, ddy, ddz, Wab, &
                         Wab_ha, Wi, Wi1, dvv, &
                         grW_ha_x, grW_ha_y, grW_ha_z, &
                         grW_hb_x, grW_hb_y, grW_hb_z, eab(3), &
                         Wa, grW, grWa, grWb, vb, prgNa, prgNb


  !    !$OMP PARALLEL DO DEFAULT( NONE ) &
  !    !$OMP             SHARED( this, nlrf_sph, pressure_sph, npart_real, &
  !    !$OMP                     m0c2_cu ) &
  !    !$OMP             PRIVATE( a )
  !    compute_pressure: DO a= 1, npart_real, 1
  !
  !      pressure_sph(a)= this% all_eos(1)% eos_parameters(poly$kappa)/m0c2_cu &
  !                 *(nlrf_sph(a)*m0c2_cu) &
  !                 **this% all_eos(1)% eos_parameters(poly$gamma)
  !
  !    ENDDO compute_pressure
  !    !$OMP END PARALLEL DO

      CALL compute_pressure( npart_real, &
                             all_pos(1,1:npart_real), &
                             all_pos(2,1:npart_real), &
                             all_pos(3,1:npart_real), &
                             nlrf_sph(1:npart_real), eqos, &
                             pressure_sph(1:npart_real) )

      !PRINT *, "Before calling ll_cell_loop..."
      !PRINT *

      dS= zero
      dS_norm_av= zero
      !$OMP PARALLEL DO DEFAULT( NONE ) &
      !$OMP             SHARED( nfinal, nprev, iorig, lpart, rpart, nic, &
      !$OMP                     ncand, h, all_pos, all_clists, nlrf_sph, &
      !$OMP                     pressure_sph, dS, sqg, nstar_sph, nu_tmp, &
      !$OMP                     m0c2_cu ) &
      !$OMP             PRIVATE( ill, itot, a, b, l, &
      !$OMP                      ha, ha_1, ha_3, ha_4, ha2, ha2_4, &
      !$OMP                      hb, hb_1, hb_3, hb_4, hb2_4, &
      !$OMP                      xa, ya, za, xb, yb, zb, dx, dy, dz, rab2, &
      !$OMP                      inde, va, dv_table_1, index1, Wi, W_no_norm, &
      !$OMP                      dvv, dv_table, Wab_ha, Wa, grW, Wi1, &
      !$OMP                      grW_ha_x, grW_ha_y, grW_ha_z, grWa, grWb, &
      !$OMP                      grW_hb_x, grW_hb_y, grW_hb_z, eab, rab, vb, &
      !$OMP                      prgNa, prgNb, rab_1 )
      ll_cell_loop2: DO ill= 1, nfinal

        itot= nprev + ill
        IF( nic(itot) == 0 ) CYCLE

        particle_loop2: DO l= lpart(itot), rpart(itot)

          a=         iorig(l)

          ha=        h(a)
          ha_1=      one/ha
          ha_3=      ha_1*ha_1*ha_1
          ha_4=      ha_3*ha_1
          ha2=       ha*ha
          ha2_4=     two*two*ha2

          xa=        all_pos(1,a)
          ya=        all_pos(2,a)
          za=        all_pos(3,a)

          prgNa= (pressure_sph(a)*sqg(a)/(nstar_sph(a)/m0c2_cu)**2)

          cand_loop2: DO k= 1, ncand(ill)

            b= all_clists(ill)%list(k)

            hb=   h(b)
            hb_1= one/hb
            hb_3= hb_1*hb_1*hb_1
            hb_4= hb_3*hb_1
            hb2_4=     two*two*hb*hb

            xb=   all_pos(1,b)  ! CONTRA-variant
            yb=   all_pos(2,b)
            zb=   all_pos(3,b)

            ! potentially bail out
            dx= xa  - xb
            dy= ya  - yb
            dz= za  - zb

            rab2 = dx*dx + dy*dy + dz*dz
            rab  = SQRT(rab2) + 1.D-30
            rab_1= 1.D0/rab
            IF( rab2 > ha2_4 .AND. rab2 > hb2_4 ) CYCLE

            !--------!
            !-- ha --!
            !--------!
            va= rab*ha_1

            ! get interpolation indices
            inde  = MIN(INT(va*dv_table_1),n_tab_entry)
            index1= MIN(inde + 1,n_tab_entry)

            ! get tabulated values
            Wi=     W_no_norm(inde)
            Wi1=    W_no_norm(index1)

            ! interpolate
            dvv=    (va - DBLE(inde)*dv_table)*dv_table_1
            Wab_ha= Wi + (Wi1 - Wi)*dvv

            ! unit vector ("a-b" --> from b to a)
            eab(1)=    dx*rab_1
            eab(2)=    dy*rab_1
            eab(3)=    dz*rab_1

            ! kernel and its gradient
            !DIR$ INLINE
            CALL interp_W_gradW_table( va, Wa, grW )
            Wa=       Wa*ha_3
            grW=      grW*ha_4

            ! nabla_a Wab(ha)
            grW_ha_x= grW*eab(1)
            grW_ha_y= grW*eab(2)
            grW_ha_z= grW*eab(3)

            grWa=     grW_ha_x*eab(1) + &
                      grW_ha_y*eab(2) + &
                      grW_ha_z*eab(3)

            !--------!
            !-- hb --!
            !--------!
            vb=       rab*hb_1

            ! kernel and its gradient
            !DIR$ INLINE
            CALL interp_gradW_table(vb,grW)
            grW=      grW*hb_4

            ! nabla_a Wab(hb)
            grW_hb_x= grW*eab(1)
            grW_hb_y= grW*eab(2)
            grW_hb_z= grW*eab(3)

            grWb= grW_hb_x*eab(1) + &
                  grW_hb_y*eab(2) + &
                  grW_hb_z*eab(3)

            prgNb= pressure_sph(b)*sqg(b)/((nstar_sph(b)/m0c2_cu)**2)

            dS(1,a)= dS(1,a) - nu_tmp(b)*( prgNa*grW_ha_x + prgNb*grW_hb_x )
            dS(2,a)= dS(2,a) - nu_tmp(b)*( prgNa*grW_ha_y + prgNb*grW_hb_y )
            dS(3,a)= dS(3,a) - nu_tmp(b)*( prgNa*grW_ha_z + prgNb*grW_hb_z )

          ENDDO cand_loop2

        ENDDO particle_loop2

      ENDDO ll_cell_loop2
      !$OMP END PARALLEL DO

      !$OMP PARALLEL DO DEFAULT( NONE ) &
      !$OMP             SHARED( dS, npart_real ) &
      !$OMP             PRIVATE( a ) &
      !$OMP             REDUCTION(+: dS_norm_av)
      particle_loop3: DO a= 1, npart_real, 1

         dS_norm_av= dS_norm_av + SQRT(dS(1,a)**2 + dS(2,a)**2 + dS(3,a)**2)

      END DO particle_loop3
      !$OMP END PARALLEL DO
      dS_norm_av= dS_norm_av/npart_real

      PRINT *, " * The average over the particles, of the norm of the", &
               " canonical momentum due to the particle distribution only is:",&
               dS_norm_av
      PRINT *

      !PRINT *, "After calling ll_cell_loop..."
      !PRINT *

      INQUIRE( FILE= TRIM("momentum.dat"), EXIST= exist )

      IF( exist )THEN
          OPEN( UNIT= 23, FILE= TRIM("momentum.dat"), STATUS= "REPLACE", &
                FORM= "FORMATTED", &
                POSITION= "REWIND", ACTION= "WRITE", IOSTAT= ios, &
                IOMSG= err_msg )
      ELSE
          OPEN( UNIT= 23, FILE= TRIM("momentum.dat"), STATUS= "NEW", &
                FORM= "FORMATTED", &
                ACTION= "WRITE", IOSTAT= ios, IOMSG= err_msg )
      ENDIF
      IF( ios > 0 )THEN
        PRINT *, "...error when opening " // TRIM("momentum.dat"), &
                 ". The error message is", err_msg
        STOP
      ENDIF

      DO a= 1, npart_real, 1
        WRITE( UNIT = 23, IOSTAT = ios, IOMSG = err_msg, FMT = * ) &
          a, &
          all_pos(1,a), &
          all_pos(2,a), &
          all_pos(3,a), &
          dS(1,a), &
          dS(2,a), &
          dS(3,a), &
          pressure_sph(a)!, &
          !nu_tmp(a), &
          !nstar_sph(a)/m0c2_cu
      ENDDO

      CLOSE( UNIT= 23 )


      !STOP


    END SUBROUTINE compute_hydro_momentum


  END PROCEDURE perform_apm

  
END SUBMODULE apm