SIESTA Compilation: Fujitsu Compilers (frt/fcc/FCC) with Fujitsu MPI

This page describes how to compile SIESTA on Fugaku using the native Fujitsu compilers (frt, fcc, FCC) together with Fujitsu’s MPI implementation. Before proceeding, make sure you have configured your Spack environment as described in Spack 1.0.1 Configuration Guide for Fugaku.

Note

The scripts on this page rely on the environment variables MY_USER, MY_PROJECT, and MY_VOLUME to construct all file paths. These variables are defined during the Spack account setup described in Spack 1.0.1 Configuration Guide for Fugaku. Make sure they are exported in your session before running any script:

export MY_USER=your_username
export MY_PROJECT=your_project
export MY_VOLUME=your_volume

If you added these variables to your ~/.bashrc as described in Step 12 of the Spack setup guide, they will be available automatically in every new session. Alternatively, the variables can be defined directly inside each script at the top of the file.

Warning

Source code patches are required for Fujitsu compilers.

The Fujitsu Fortran compiler (frt) enforces the Fortran standard more strictly than GNU Fortran (gfortran) or Intel Fortran (ifort). Several constructs in the SIESTA source tree that are silently accepted by other compilers are treated as hard errors by frt.

The main classes of issues are:

  • jwd1740i — A module name and a locally imported entity share the same identifier within the same scope (e.g., use parallel inside a subroutine that already imports parallel from the host scope). gfortran resolves this silently; frt rejects it.

  • jwd2391i — Assignment between derived types that do not have a defined assignment operator. gfortran allows this implicitly; frt requires an explicit operator or a compatible intrinsic assignment.

  • jwd2043i — An intent(out) variable may not be assigned on all execution paths. gfortran only warns; frt raises an error.

  • jwd1130iUSE statements inside BLOCK END BLOCK constructs are not supported by frt 4.12.1. The block must be dissolved and its declarations moved to the enclosing scope.

The get_siesta_fj.sh script applies a series of patches (PATCH 1–6) that resolve all of these issues before CMake configuration begins. The patches are verified automatically; the script exits with an error if any patch fails to apply cleanly.

Prerequisites

Interactive Session

All compilation steps must be run inside a compute node session on Fugaku. Do not run the scripts on a login node.

Request an interactive session before proceeding, then work entirely within that session.

Spack Environment

Once inside the interactive session, load the local Spack environment:

. /path/to/your/spack/share/spack/setup-env.sh

This is the same source call that appears at the top of each compilation script. Replace the path with the actual location of your Spack installation.

py-ruamel-yaml

The SIESTA test suite requires the ruamel.yaml Python package. Install it via Spack inside the interactive session. First load the Python module that will own the package:

spack load /qhm66vh          # python@3.13.5 built with fj@4.12.0

Then install py-ruamel-yaml pinned to that Python:

spack install py-ruamel-yaml ^/qhm66vh

After the installation finishes, verify the package is importable:

python3.13 -c "import ruamel.yaml; print('Ok')"

If the command prints Ok, the installation is correct and the test suite will be able to locate the package. If it raises an ImportError, check that the correct Python module is active in your session.

Note

The hash /qhm66vh refers to python@3.13.5 built with the Fujitsu compiler fj@4.12.0. This same hash is hard-coded in get_siesta_fj.sh via the spack load /qhm66vh call so that the build environment is consistent with the one used to install py-ruamel-yaml.

NetCDF Installation

Before compiling SIESTA, you need to install NetCDF using the install_netcdf_fj.sh script. NetCDF is not available as a pre-installed module on Fugaku and must be compiled from source.

Note

The install_netcdf_fj.sh script builds NetCDF from source using the native Fujitsu toolchain:

  • frt / fcc — Fujitsu Fortran and C compilers

  • mpifrt / mpifcc — Fujitsu MPI wrappers

The following libraries are built in order:

  1. zlib 1.3.1

  2. HDF5 1.14.3 (with parallel I/O and Fortran bindings)

  3. NetCDF-C 4.9.2

  4. NetCDF-Fortran 4.6.1

Run the script before proceeding with the SIESTA compilation:

./install_netcdf_fj.sh

You can download install_netcdf_fj.sh directly.

Show full script: install_netcdf_fj.sh
Listing 3 install_netcdf_fj.sh
#!/bin/bash
#
# install_netcdf_fj.sh – build NetCDF from source using Fujitsu compilers
#
# Requires the following environment variables (set in ~/.bashrc or exported
# before calling this script):
#   MY_USER     – your Fugaku username
#   MY_PROJECT  – your project code 
#   MY_VOLUME   – your storage volume
#
# The script builds zlib, HDF5, NetCDF-C, and NetCDF-Fortran in sequence,
# installs everything under ${WORK_DIR}/install, and writes a small
# environment file (netcdf_env.sh) that get_siesta_fj.sh sources later.
#

set -e

# Disable HugePages (avoids warnings on Fugaku compute nodes)
export XOSOPTION="off"
export XOS_MMM_L_HPAGE_TYPE="none"

###############################################################################
# Check required environment variables
###############################################################################
for var in MY_USER MY_PROJECT MY_VOLUME; do
  if [[ -z "${!var:-}" ]]; then
    echo "Error: environment variable ${var} is not set." >&2
    echo "Export it or add it to your ~/.bashrc before running this script." >&2
    exit 1
  fi
done

echo "=========================================="
echo "NetCDF installation – Fujitsu toolchain"
echo "=========================================="

###############################################################################
# Paths
###############################################################################
BASE_DIR="/${MY_VOLUME}/mdt1/data/${MY_PROJECT}/${MY_USER}"
WORK_DIR="${BASE_DIR}/netcdf_fujitsu"
SRC_DIR="${WORK_DIR}/src"
INSTALL_DIR="${WORK_DIR}/install"

FUJITSU_DIR="/opt/FJSVxtclanga/tcsds-1.2.42"

###############################################################################
# Library versions
###############################################################################
ZLIB_VERSION="1.3.1"
HDF5_VERSION="1.14.3"
NETCDF_C_VERSION="4.9.2"
NETCDF_FORTRAN_VERSION="4.6.1"

###############################################################################
# Download URLs
###############################################################################
ZLIB_URL="https://zlib.net/fossils/zlib-${ZLIB_VERSION}.tar.gz"
HDF5_URL="https://support.hdfgroup.org/ftp/HDF5/releases/hdf5-1.14/hdf5-${HDF5_VERSION}/src/hdf5-${HDF5_VERSION}.tar.gz"
NETCDF_C_URL="https://downloads.unidata.ucar.edu/netcdf-c/${NETCDF_C_VERSION}/netcdf-c-${NETCDF_C_VERSION}.tar.gz"
NETCDF_FORTRAN_URL="https://downloads.unidata.ucar.edu/netcdf-fortran/${NETCDF_FORTRAN_VERSION}/netcdf-fortran-${NETCDF_FORTRAN_VERSION}.tar.gz"

###############################################################################
# Environment
###############################################################################
export PATH="${FUJITSU_DIR}/bin:${INSTALL_DIR}/bin:${PATH}"
export LD_LIBRARY_PATH="${INSTALL_DIR}/lib:${FUJITSU_DIR}/lib64:${LD_LIBRARY_PATH:-}"

mkdir -p "${SRC_DIR}" "${INSTALL_DIR}"

###############################################################################
# Helper: download and extract a tarball
###############################################################################
download_and_extract() {
    local url=$1
    local filename
    filename=$(basename "$url")
    cd "${SRC_DIR}"
    if [ ! -f "${filename}" ]; then
        echo "Downloading ${filename} ..."
        wget "${url}" || curl -L -O "${url}"
    else
        echo "${filename} already downloaded."
    fi
    echo "Extracting ${filename} ..."
    tar -xzf "${filename}"
}

###############################################################################
# zlib
###############################################################################
install_zlib() {
    echo ""
    echo "=========================================="
    echo "Installing zlib ${ZLIB_VERSION}"
    echo "=========================================="

    download_and_extract "${ZLIB_URL}"
    cd "${SRC_DIR}/zlib-${ZLIB_VERSION}"

    CC=fcc \
    CFLAGS="-Nclang -O2 -fPIC" \
    ./configure --prefix="${INSTALL_DIR}" --static

    make -j 4
    make install
    echo "✓ zlib installed"
}

###############################################################################
# HDF5
###############################################################################
install_hdf5() {
    echo ""
    echo "=========================================="
    echo "Installing HDF5 ${HDF5_VERSION}"
    echo "=========================================="

    download_and_extract "${HDF5_URL}"
    cd "${SRC_DIR}/hdf5-${HDF5_VERSION}"

    echo "Configuring HDF5 (static libraries, parallel Fortran) ..."

    CC=mpifcc \
    FC=mpifrt \
    CFLAGS="-Nclang -O2 -fPIC" \
    FCFLAGS="-O2 -fPIC" \
    ./configure --prefix="${INSTALL_DIR}" \
                --enable-fortran \
                --enable-parallel \
                --disable-shared \
                --enable-static \
                --enable-hl \
                --with-zlib="${INSTALL_DIR}"

    if [ $? -ne 0 ]; then
        echo "✗ HDF5 configure failed. Last 100 lines of config.log:"
        tail -100 config.log
        exit 1
    fi

    echo "Building HDF5 (this may take 10-15 minutes) ..."
    make -j 4 || { make clean && make -j 1; }

    echo "Installing HDF5 ..."
    make install

    echo "✓ HDF5 installed"
    ls -lh "${INSTALL_DIR}/lib/libhdf5"* | head -5
}

###############################################################################
# NetCDF-C
###############################################################################
install_netcdf_c() {
    echo ""
    echo "=========================================="
    echo "Installing NetCDF-C ${NETCDF_C_VERSION}"
    echo "=========================================="

    download_and_extract "${NETCDF_C_URL}"
    cd "${SRC_DIR}/netcdf-c-${NETCDF_C_VERSION}"

    echo "Configuring NetCDF-C ..."

    CC=mpifcc \
    CFLAGS="-Nclang -O2 -fPIC" \
    CPPFLAGS="-I${INSTALL_DIR}/include" \
    LDFLAGS="-L${INSTALL_DIR}/lib" \
    LIBS="-lhdf5_hl -lhdf5 -lz -lm" \
    ./configure --prefix="${INSTALL_DIR}" \
                --enable-netcdf-4 \
                --disable-shared \
                --enable-static \
                --disable-dap \
                --disable-byterange \
                --disable-libxml2

    if [ $? -ne 0 ]; then
        echo "✗ NetCDF-C configure failed. Last 100 lines of config.log:"
        tail -100 config.log
        exit 1
    fi

    echo "Building NetCDF-C ..."
    make -j 4

    echo "Installing NetCDF-C ..."
    make install

    echo "✓ NetCDF-C installed"

    if [ -f "${INSTALL_DIR}/bin/nc-config" ]; then
        echo "nc-config --version: $("${INSTALL_DIR}/bin/nc-config" --version)"
        echo "nc-config --libs:    $("${INSTALL_DIR}/bin/nc-config" --libs)"
    fi
}

###############################################################################
# NetCDF-Fortran
###############################################################################
install_netcdf_fortran() {
    echo ""
    echo "=========================================="
    echo "Installing NetCDF-Fortran ${NETCDF_FORTRAN_VERSION}"
    echo "=========================================="

    download_and_extract "${NETCDF_FORTRAN_URL}"
    cd "${SRC_DIR}/netcdf-fortran-${NETCDF_FORTRAN_VERSION}"

    export NCDIR="${INSTALL_DIR}"

    if [ -f "${INSTALL_DIR}/bin/nc-config" ]; then
        NC_LIBS=$(${INSTALL_DIR}/bin/nc-config --libs)
        echo "Using nc-config --libs: ${NC_LIBS}"
    else
        NC_LIBS="-lnetcdf -lhdf5_hl -lhdf5 -lz -lm"
        echo "nc-config not available; using default LIBS."
    fi

    CC=mpifcc \
    FC=mpifrt \
    F77=mpifrt \
    CFLAGS="-Nclang -O2 -fPIC" \
    FCFLAGS="-O2 -fPIC" \
    FFLAGS="-O2 -fPIC" \
    CPPFLAGS="-I${INSTALL_DIR}/include" \
    LDFLAGS="-L${INSTALL_DIR}/lib" \
    LIBS="${NC_LIBS}" \
    ./configure --prefix="${INSTALL_DIR}" \
                --disable-shared \
                --enable-static

    if [ $? -ne 0 ]; then
        echo "✗ NetCDF-Fortran configure failed. Last 100 lines of config.log:"
        tail -100 config.log
        exit 1
    fi

    echo "Building NetCDF-Fortran ..."
    make -j 4

    echo "Installing NetCDF-Fortran ..."
    make install

    echo "✓ NetCDF-Fortran installed"
}

###############################################################################
# Write environment file sourced by get_siesta_fj.sh
###############################################################################
create_env_file() {
    echo ""
    echo "Writing environment file ..."
    ENV_FILE="${WORK_DIR}/netcdf_env.sh"

    # Use a temporary variable so the heredoc expands MY_* at write time,
    # but keeps NETCDF_DIR and FUJITSU_DIR as literals for later sourcing.
    cat > "${ENV_FILE}" << ENVEOF
#!/bin/bash
# netcdf_env.sh – sourced by get_siesta_fj.sh to expose NetCDF paths.
# Generated by install_netcdf_fj.sh on $(date).

# Disable HugePages warnings
export XOSOPTION="off"
export XOS_MMM_L_HPAGE_TYPE="none"

export NETCDF_DIR="${INSTALL_DIR}"
export FUJITSU_DIR="/opt/FJSVxtclanga/tcsds-1.2.42"

export PATH="\${FUJITSU_DIR}/bin:\${NETCDF_DIR}/bin:\${PATH}"
export LD_LIBRARY_PATH="\${NETCDF_DIR}/lib:\${FUJITSU_DIR}/lib64:\${LD_LIBRARY_PATH:-}"

export NETCDF_INC="-I\${NETCDF_DIR}/include"
export NETCDF_LIB="-L\${NETCDF_DIR}/lib -lnetcdff -lnetcdf -lhdf5_hl -lhdf5 -lz -lm"

echo "NetCDF environment configured."
echo "  NETCDF_DIR : \${NETCDF_DIR}"
ENVEOF

    chmod +x "${ENV_FILE}"
    echo "✓ Environment file written: ${ENV_FILE}"
}

###############################################################################
# Verify critical libraries
###############################################################################
verify_installation() {
    echo ""
    echo "=========================================="
    echo "Verifying installation"
    echo "=========================================="

    if [ -f "${INSTALL_DIR}/bin/nc-config" ]; then
        echo "nc-config --version: $("${INSTALL_DIR}/bin/nc-config" --version)"
        echo "nc-config --libs:    $("${INSTALL_DIR}/bin/nc-config" --libs)"
    fi

    if [ -f "${INSTALL_DIR}/bin/nf-config" ]; then
        echo "nf-config --version: $("${INSTALL_DIR}/bin/nf-config" --version)"
        echo "nf-config --flibs:   $("${INSTALL_DIR}/bin/nf-config" --flibs)"
    fi

    echo ""
    echo "Installed libraries:"
    ls -lh "${INSTALL_DIR}/lib/"*.a 2>/dev/null | grep -E "libz|libhdf5|libnetcdf"

    echo ""
    CRITICAL_FILES=(
        "${INSTALL_DIR}/lib/libz.a"
        "${INSTALL_DIR}/lib/libhdf5.a"
        "${INSTALL_DIR}/lib/libhdf5_hl.a"
        "${INSTALL_DIR}/lib/libhdf5_fortran.a"
        "${INSTALL_DIR}/lib/libnetcdf.a"
        "${INSTALL_DIR}/lib/libnetcdff.a"
    )

    ALL_OK=true
    for f in "${CRITICAL_FILES[@]}"; do
        if [ -f "$f" ]; then
            echo "  ✓ $(basename "$f")"
        else
            echo "  ✗ Missing: $(basename "$f")"
            ALL_OK=false
        fi
    done

    if [ "${ALL_OK}" = true ]; then
        echo ""
        echo "✓ All critical libraries present."
    fi
}

###############################################################################
# Main
###############################################################################
main() {
    echo "Start: $(date)"
    echo ""

    install_zlib
    install_hdf5
    install_netcdf_c
    install_netcdf_fortran
    create_env_file
    verify_installation

    echo ""
    echo "=========================================="
    echo "✓ Installation complete"
    echo "=========================================="
    echo ""
    echo "Elapsed: ${SECONDS} seconds"
    echo ""
    echo "To use:  source ${WORK_DIR}/netcdf_env.sh"
}

LOG_FILE="${WORK_DIR}/install_log_$(date +%Y%m%d_%H%M%S).txt"
mkdir -p "${WORK_DIR}"
main 2>&1 | tee "${LOG_FILE}"

Compilation Script

The get_siesta_fj.sh script automates the full process of downloading, extracting, patching, configuring, and compiling SIESTA on Fugaku using the native Fujitsu compiler suite.

Basic usage:

./get_siesta_fj.sh                              # default version (SIESTA 5.4.2)
./get_siesta_fj.sh 5.4.1                        # different SIESTA version
./get_siesta_fj.sh -c                           # clean extracted directory before starting
./get_siesta_fj.sh -r                           # re-download tarball if missing and re-extract
./get_siesta_fj.sh -l /path/to/local/repo       # use a local source directory

What the script does:

  1. Loads the Spack and NetCDF environments by sourcing the Spack setup script and the NetCDF environment file produced by install_netcdf_fj.sh.

  2. Downloads the SIESTA source tarball from the GitLab release page if not already present. It falls back from curl to wget if needed.

  3. Extracts the tarball and prepares two build directories inside the source tree: _build_fj for intermediate build artifacts and _siesta_bin_fj for the final installed binaries.

  4. Loads the software environment by sourcing Spack and loading the required packages: Fujitsu SSL2 (/fomls7l), netlib-ScaLAPACK (/ldigwm3), Python 3.13.5 (/qhm66vh), CMake (/hn27egk), py-pip (/ptmidr7), Flex (/ei25gqb), FFTW (/latufg2), Bison (/5hspcpy), libxc (/2nmda3o), and py-ruamel-yaml (/h5qnbwj).

  5. Applies source patches (PATCH 1–6) to resolve Fujitsu compiler incompatibilities in the following files:

    • Src/m_ts_voltage.F90 — name collision between the parallel module and a local entity (PATCH 1)

    • Src/flook_siesta.F90, Src/matel_table.F90, Src/m_elsi_interface.F90, Src/m_new_dm.F90, Src/m_w90_wrapper.F90, Src/Orphans/new_dm.F — analogous parallel module collisions in nested scopes (PATCH 2)

    • Src/spinorbit.f — special-case scope treatment for the same collision pattern (PATCH 2b)

    • Src/m_new_dm.F90 — case-sensitivity correction for node vs Node after the rename (PATCH 2c)

    • Pseudo/vnl-operator/psop.f90 — commented-out assignments between incompatible derived types (PATCH 3)

    • Src/siesta_init.F — narrowed use netcdf_ncdf to avoid the parallel member variable collision (PATCH 4)

    • Util/QMMM-driver/Src/mm_assign.f90 — initialisation of an intent(out) variable that may be unset on some paths (PATCH 5)

    • Src/amn.F90 — dissolution of a BLOCK END BLOCK construct unsupported by frt 4.12.1 (PATCH 6)

    Each patch is verified after application; the script exits immediately if any check fails.

  6. Configures SIESTA with CMake, using the Fujitsu compilers (frt, fcc, FCC) and MPI wrappers (mpifrt, mpifcc, mpiFCC). Explicit paths are set for Fujitsu MPI headers and libraries under /opt/FJSVxtclanga/.common/MECA032/, and the build links against Fujitsu SSL2 (for BLAS/LAPACK), netlib-ScaLAPACK, and the previously installed NetCDF libraries. Notable build options include:

    • OpenMP enabled (-DSIESTA_WITH_OPENMP=ON)

    • MPI enabled via Fujitsu MPI libraries

    • NetCDF enabled (-DSIESTA_WITH_NETCDF=ON)

    • ELSI, ELPA, DFTD3, Flook, and LibXC disabled

    • Compiler flags -Kopenmp -O2 -Kident_mpi for all languages

    Note

    SIESTA 5.4.2 does not compile with the legacy MPI interface. The script automatically sets -DSIESTA_WITH_MPI_INTERFACES=none for version 5.4.2 and legacy for all other versions.

  7. Builds and installs using cmake --build (with 8 parallel jobs) and cmake --install, placing the final binaries in _siesta_bin_fj. Build output is saved to build_log.txt. If the build fails, the script prints a summary of all Fujitsu compiler error codes found in the log and exits with a non-zero status.

You can download get_siesta_fj.sh directly.

Show full script: get_siesta_fj.sh
Listing 4 get_siesta_fj.sh
#!/usr/bin/env bash
#
# get_siesta_fj.sh – download, patch, and compile SIESTA with Fujitsu compilers
#
# Requires the following environment variables (set in ~/.bashrc or exported
# before calling this script):
#   MY_USER     – your Fugaku username
#   MY_PROJECT  – your project code 
#   MY_VOLUME   – your storage volume 
#
# Usage examples
#   ./get_siesta_fj.sh                    # default version (5.4.2)
#   ./get_siesta_fj.sh 5.4.1              # different version
#   ./get_siesta_fj.sh -c                 # delete extracted dir if it exists
#   ./get_siesta_fj.sh -r                 # refresh (re-download if tarball missing)
#   ./get_siesta_fj.sh -l /path/to/repo   # use a local source directory
#

#set -euo pipefail

###############################################################################
# Helper: display usage
###############################################################################
usage() {
  cat <<EOF
Usage: $0 [version] [-c|--clean] [-r|--refresh] [-l|--local <path>]

  version    SIESTA version to download (default: 5.4.2)
  -c, --clean
             Delete the extracted directory if it already exists.
  -r, --refresh
             If the directory exists *and* the tarball does not, download the
             tarball, delete the directory, and extract again.
  -l, --local <path>
             Use a local directory instead of downloading the tarball.

Only one of -c or -r can be supplied at the same time.
EOF
  exit 1
}

###############################################################################
# Parse command-line arguments
###############################################################################
VERSION="5.4.2"
CLEAN=false
REFRESH=false
LOCAL_PATH=""

while [[ $# -gt 0 ]]; do
  case "$1" in
    -c|--clean)
      CLEAN=true
      ;;
    -r|--refresh)
      REFRESH=true
      ;;
    -l|--local)
      LOCAL_PATH="$2"
      shift
      ;;
    -h|--help)
      usage
      ;;
    -*)
      echo "Unknown option: $1" >&2
      usage
      ;;
    *)
      VERSION="$1"
      ;;
  esac
  shift
done

# Reject simultaneous -c and -r
if $CLEAN && $REFRESH; then
  echo "Error: -c/--clean and -r/--refresh are mutually exclusive." >&2
  exit 1
fi

###############################################################################
# Check required environment variables
###############################################################################
for var in MY_USER MY_PROJECT MY_VOLUME; do
  if [[ -z "${!var:-}" ]]; then
    echo "Error: environment variable ${var} is not set." >&2
    echo "Export it or add it to your ~/.bashrc before running this script." >&2
    exit 1
  fi
done

###############################################################################
# Variables derived from version and environment
###############################################################################
PKG="siesta-${VERSION}"
TARBALL="${PKG}.tar.gz"
URL="https://gitlab.com/siesta-project/siesta/-/releases/${VERSION}/downloads/${TARBALL}"

SPACK_SETUP="/${MY_VOLUME}/mdt1/data/${MY_PROJECT}/${MY_USER}/tmp/spack/share/spack/setup-env.sh"
NETCDF_ENV="/${MY_VOLUME}/mdt1/data/${MY_PROJECT}/${MY_USER}/netcdf_fujitsu/netcdf_env.sh"

###############################################################################
# Helper: download tarball if absent
###############################################################################
download_tarball() {
  echo "Downloading ${TARBALL} ..."
  if command -v curl &>/dev/null; then
    curl -L -o "${TARBALL}" "${URL}"
  elif command -v wget &>/dev/null; then
    wget -O "${TARBALL}" "${URL}"
  else
    echo "Error: neither curl nor wget is available." >&2
    exit 1
  fi
}

###############################################################################
# 1-3. Obtain source code
###############################################################################
if [[ -n "${LOCAL_PATH}" ]]; then
  if [[ ! -d "${LOCAL_PATH}" ]]; then
    echo "Error: local directory '${LOCAL_PATH}' does not exist." >&2
    exit 1
  fi
  echo "Using local directory: ${LOCAL_PATH}"
  PKG="${LOCAL_PATH}"
else
  if $CLEAN && [[ -d "${PKG}" ]]; then
    echo "Removing existing directory ${PKG} (requested with --clean) ..."
    rm -rf "${PKG}"
  fi

  if $REFRESH && [[ -d "${PKG}" ]] && [[ ! -f "${TARBALL}" ]]; then
    echo "Refresh requested and tarball missing."
    download_tarball
    echo "Removing existing directory ${PKG} ..."
    rm -rf "${PKG}"
  fi

  if [[ ! -f "${TARBALL}" ]]; then
    download_tarball
  else
    echo "Tarball ${TARBALL} already exists. Skipping download."
  fi

  if [[ ! -d "${PKG}" ]]; then
    echo "Extracting ${TARBALL} ..."
    tar -xzf "${TARBALL}"
  else
    echo "Directory ${PKG} already exists. Re-extracting ..."
    rm -rf "${PKG}"
    tar -xzf "${TARBALL}"
  fi
fi

###############################################################################
# 4. Create the required build directories
###############################################################################
cd "${PKG}"
build_="_build_fj"
bin_="_siesta_bin_fj"
mkdir -p "${build_}" "${bin_}"
echo "Directories ${build_} and ${bin_} are ready in $(pwd)."

###############################################################################
# 5. Load Spack environment
###############################################################################
echo "Loading Spack environment ..."
source "${SPACK_SETUP}"
echo "Spack environment loaded."

###############################################################################
# 6. Load Spack packages
###############################################################################
echo "Loading Spack packages ..."
spack load /fomls7l  # fujitsu-ssl2@head
spack load /ldigwm3  # netlib-scalapack@2.2.2
spack load /qhm66vh  # python@3.13.5 %fj@4.12.0
spack load /hn27egk  # cmake@3.31.8
spack load /ptmidr7  # py-pip@25.1.1
spack load /ei25gqb  # flex@2.6.4
spack load /latufg2  # fftw@3.3.10
spack load /5hspcpy  # bison@3.8.2

#Those were local installation
spack load /2nmda3o  # libxc@6.2.2
spack load /h5qnbwj  # py-ruamel-yaml@0.17.32 ^python@3.13.5 /qhm66vh %fj@4.12.0
echo "Spack packages loaded."
ml list

###############################################################################
# 7. Load NetCDF environment
###############################################################################
echo "Loading NetCDF environment ..."
source "${NETCDF_ENV}"
echo "NetCDF environment loaded."

###############################################################################
# 8. Resolve additional paths
###############################################################################
export LD_LIBRARY_PATH=/lib64:${LD_LIBRARY_PATH}

export FLEX_PATH=$(spack location -i flex%fj@4.12.0)
export BISON_PATH=$(spack location -i bison%fj@4.12.0)
export PATH="${FLEX_PATH}/bin:${BISON_PATH}/bin:${PATH}"

export FUJITSU_SSL2_PATH="/opt/FJSVxtclanga/tcsds-ssl2-latest"
export SCALAPACK_PATH=$(spack location -i netlib-scalapack%fj@4.12.0)

echo "Paths resolved."

###############################################################################
# 9. Set MPI interface flag (5.4.2 does not support legacy interface)
###############################################################################
if [ "${VERSION}" == "5.4.2" ]; then
    interface="none"
else
    interface="legacy"
fi

###############################################################################
# 10. Apply source patches for Fujitsu compiler compatibility
#
# frt 4.12.1 enforces the Fortran standard more strictly than gfortran.
# The patches below fix constructs that gfortran accepts silently but
# frt rejects as errors:
#   - jwd1740i: module name collides with a locally imported entity
#   - jwd2391i: assignment between derived types without a defined operator
#   - jwd2043i: intent(out) variable may be unassigned on some paths
#   - jwd1130i: USE inside BLOCK...END BLOCK is not supported
###############################################################################
echo "Applying frt 4.12.1 compatibility patches ..."

# ============================================================
# PATCH 1: m_ts_voltage.F90
# jwd1740i: 'parallel' is both a module name and a local entity
# in ts_ncdf_voltage_assert. Rename IONode with an explicit alias
# and pass ts_IONode as an argument to the inner subroutine.
# ============================================================
sed -i 's/    use parallel, only : IONode$/    use parallel, only : ts_IONode => IONode/' \
    Src/m_ts_voltage.F90
sed -i 's/    use parallel,     only : IONode$/    use parallel,     only : ts_IONode => IONode/' \
    Src/m_ts_voltage.F90
sed -i 's/if ( IONode ) then/if ( ts_IONode ) then/g' Src/m_ts_voltage.F90
sed -i 's/if ( IONode .and./if ( ts_IONode .and./g' Src/m_ts_voltage.F90
sed -i 's/else if ( IONode .and./else if ( ts_IONode .and./g' Src/m_ts_voltage.F90
sed -i 's/if ( .not. IONode ) return/if ( .not. ts_IONode ) return/g' Src/m_ts_voltage.F90
sed -i 's/if ( IONode ) &/if ( ts_IONode ) \&/g' Src/m_ts_voltage.F90
sed -i 's/call ts_ncdf_voltage_assert(Hartree_fname,cell,nmesh)/call ts_ncdf_voltage_assert(Hartree_fname,cell,nmesh,ts_IONode)/' \
    Src/m_ts_voltage.F90
sed -i 's/subroutine ts_ncdf_voltage_assert(fname, cell, nmesh)/subroutine ts_ncdf_voltage_assert(fname, cell, nmesh, ts_IONode)/' \
    Src/m_ts_voltage.F90
awk 'BEGIN{c=0} /use parallel/{c++; if(c==3)next} 1' \
    Src/m_ts_voltage.F90 > /tmp/m_ts_voltage.F90 && \
    mv /tmp/m_ts_voltage.F90 Src/m_ts_voltage.F90
grep -q "logical, intent(in) :: ts_IONode" Src/m_ts_voltage.F90 || \
    sed -i 's/    integer, intent(in) :: nmesh(3)$/    integer, intent(in) :: nmesh(3)\n    logical, intent(in) :: ts_IONode/' \
    Src/m_ts_voltage.F90
echo "  PATCH 1 applied: Src/m_ts_voltage.F90"

# ============================================================
# PATCH 2: mass rename of imported entities in nested scopes
# jwd1740i: same collision pattern in 6 additional files.
# Python handles inline comments correctly during the rename.
# Note: spinorbit.f receives special treatment in PATCH 2b.
# ============================================================
python3 << 'PYEOF'
import re

def rename_entities_in_use(line):
    m = re.match(r'(\s*use\s+parallel\s*,\s*only\s*:\s*)(.*)', line, re.IGNORECASE)
    if not m:
        return line, {}
    prefix = m.group(1)
    rest = m.group(2)
    comment_match = re.search(r'\s*!.*$', rest)
    comment = comment_match.group(0) if comment_match else ''
    entities_str = rest[:comment_match.start()].rstrip() if comment_match else rest.rstrip()
    has_continuation = entities_str.endswith('&')
    if has_continuation:
        entities_str = entities_str[:-1].rstrip()
    entities = [e.strip() for e in entities_str.split(',') if e.strip()]
    renamed = {}
    new_entities = []
    for e in entities:
        if '=>' in e:
            new_entities.append(e)
        else:
            new_name = f'par_{e}'
            new_entities.append(f'{new_name} => {e}')
            renamed[e] = new_name
    new_line = prefix + ', '.join(new_entities)
    if has_continuation:
        new_line += ' &'
    new_line += comment + '\n'
    return new_line, renamed

problems = {
    "Src/flook_siesta.F90":      [964],
    "Src/matel_table.F90":       [666, 739],
    "Src/m_elsi_interface.F90":  [267, 413, 886, 1491],
    "Src/m_new_dm.F90":          [1369, 1686, 1812, 1905, 1998],
    "Src/m_w90_wrapper.F90":     [212, 1730],
    "Src/Orphans/new_dm.F":      [458],
}

for fname, problem_lines in problems.items():
    try:
        with open(fname, 'r') as fh:
            lines = fh.readlines()
    except FileNotFoundError:
        print(f"  WARNING: {fname} not found, skipping...")
        continue

    all_renamed = {}
    for pline in problem_lines:
        idx = pline - 1
        if idx >= len(lines):
            continue
        new_line, renamed = rename_entities_in_use(lines[idx])
        if renamed:
            lines[idx] = new_line
            all_renamed.update(renamed)

    if not all_renamed:
        continue

    for old, new in all_renamed.items():
        pattern = re.compile(r'\b' + re.escape(old) + r'\b')
        for i, line in enumerate(lines):
            if i+1 in problem_lines:
                continue
            stripped = line.lstrip()
            if stripped.startswith('!') or stripped.startswith('#'):
                continue
            new_line = pattern.sub(new, line)
            if new_line != line:
                lines[i] = new_line

    with open(fname, 'w') as fh:
        fh.writelines(lines)
    print(f"  PATCH 2 applied: {fname}")

PYEOF

# ============================================================
# PATCH 2b: spinorbit.f – special-case scope handling
# jwd1740i: inner subroutine uses 'Node' which already exists
# at module level. Manual rename only in the inner scope to
# avoid clobbering the module-level references.
# ============================================================
sed -i 's/      use parallel, only: par_Node, Nodes/      use parallel, only: Node, Nodes/' \
    Src/spinorbit.f
sed -i 's/      use parallel,        only: Node$/      use parallel,        only: par_Node => Node/' \
    Src/spinorbit.f
sed -i 's/if (par_Node .eq. 0) then/if (Node .eq. 0) then/g' \
    Src/spinorbit.f
sed -i 's/call LocalToGlobalOrb(io_l,par_Node,Nodes,io_u)/call LocalToGlobalOrb(io_l,Node,Nodes,io_u)/g' \
    Src/spinorbit.f
echo "  PATCH 2b applied: Src/spinorbit.f"

# ============================================================
# PATCH 2c: m_new_dm.F90 – case correction after mass rename
# The mass rename changed Node->par_Node but the source also
# uses 'node' (lowercase) which frt treats as a distinct entity.
# ============================================================
sed -i 's/if (node==0) print \*, " # of linearly independent vectors: ", m/if (par_Node==0) print *, " # of linearly independent vectors: ", m/' \
    Src/m_new_dm.F90
sed -i 's/if (node==0) print \*, " Rank of DIIS matrix: ", m/if (par_Node==0) print *, " Rank of DIIS matrix: ", m/' \
    Src/m_new_dm.F90
sed -i 's/if (node==0) print \*, " Estimated Rank of xi\*xj matrix: ", m/if (par_Node==0) print *, " Estimated Rank of xi*xj matrix: ", m/' \
    Src/m_new_dm.F90
sed -i 's/if (node==0) then/if (par_Node==0) then/g' \
    Src/m_new_dm.F90
echo "  PATCH 2c applied: Src/m_new_dm.F90"

# ============================================================
# PATCH 3: psop.f90
# jwd2391i: assignment between ps_annotation_t (alias of
# assoc_list_t) without a defined assignment operator.
# These are metadata annotations only; commenting them out
# does not affect physical calculations.
# ============================================================
sed -i 's/^    gannot = grid_annotation/    !gannot = grid_annotation  ! patched: frt 4.12.1/' \
    Pseudo/vnl-operator/psop.f90
sed -i 's/^    ps%local%annotation = annotation/    !ps%local%annotation = annotation  ! patched: frt 4.12.1/' \
    Pseudo/vnl-operator/psop.f90
sed -i 's/^    nlp%annotation = annotation/    !nlp%annotation = annotation  ! patched: frt 4.12.1/' \
    Pseudo/vnl-operator/psop.f90
echo "  PATCH 3 applied: Pseudo/vnl-operator/psop.f90"

# ============================================================
# PATCH 4: siesta_init.F
# jwd1740i: netcdf_ncdf has a member variable named 'parallel'
# which collides with the 'parallel' module imported on lines
# 55-57. Narrow the USE to import only what is actually needed.
# ============================================================
sed -i 's/      use netcdf_ncdf$/      use netcdf_ncdf, only: ncdf_IONode/' \
    Src/siesta_init.F
echo "  PATCH 4 applied: Src/siesta_init.F"

# ============================================================
# PATCH 5: mm_assign.f90
# jwd2043i: intent(out) variable atxres may not be assigned on
# all execution paths in subroutine paramats.
# Initialise to 0 at the start of the subroutine.
# ============================================================
python3 << 'PYEOF'
fname = "Util/QMMM-driver/Src/mm_assign.f90"
with open(fname, 'r') as fh:
    lines = fh.readlines()

target = "type(temp_residue), allocatable :: ff_residues(:)"
for i, line in enumerate(lines):
    if target in line:
        insert_idx = i + 2
        new_line = "    atxres = 0  ! patched: frt 4.12.1 intent(out) must be assigned\n"
        if new_line not in lines[insert_idx]:
            lines.insert(insert_idx, new_line)
        break

with open(fname, 'w') as fh:
    fh.writelines(lines)
print("  PATCH 5 applied: Util/QMMM-driver/Src/mm_assign.f90")
PYEOF

# ============================================================
# PATCH 6: amn.F90
# jwd1130i: USE inside BLOCK...END BLOCK is not supported by
# frt 4.12.1. Dissolve the block, move USE statements and
# declarations to subroutine scope, and rename the local
# variable orb_gindex -> blk_orb_gindex to avoid a collision
# with the orb_gindex function imported from atmfuncs.
# ============================================================
python3 << 'PYEOF'
fname = "Src/amn.F90"
with open(fname, 'r') as fh:
    content = fh.read()

old_block = """          block
            use radial, only: radial_rescale_radfunc, rad_func
            use m_matel_registry, only: peek_at_registered_radfunc
            use m_matel_registry, only: register_in_rf_pool
            
            integer :: orb_gindex, l, m
            type(rad_func), pointer :: rfunc, func_new => null()
            
            orb_gindex = tf%iorb_gindex
            call peek_at_registered_radfunc(orb_gindex,rfunc,l,m)
            if ( modulo(m,2) == 1 ) then
               allocate(func_new)
               call radial_rescale_radfunc(rfunc,func_new,scale=-1.0_dp)
               call register_in_rf_pool(func_new, l, m, "wann_twin_orb", [iproj], gindex)
            else
               call register_in_rf_pool(rfunc, l, m, "wann_orb", [iproj], gindex)
            endif
         end block"""

new_block = """          blk_orb_gindex = tf%iorb_gindex
            call peek_at_registered_radfunc(blk_orb_gindex,rfunc,l,m)
            if ( modulo(m,2) == 1 ) then
               allocate(func_new)
               call radial_rescale_radfunc(rfunc,func_new,scale=-1.0_dp)
               call register_in_rf_pool(func_new, l, m, "wann_twin_orb", [iproj], gindex)
            else
               call register_in_rf_pool(rfunc, l, m, "wann_orb", [iproj], gindex)
            endif"""

if old_block in content:
    content = content.replace(old_block, new_block)
    print("  Step 1 OK: BLOCK...END BLOCK dissolved")
else:
    print("  ERROR step 1: block not found")

old_use = "  use mpi_siesta"
new_use = """  use radial, only: radial_rescale_radfunc, rad_func  ! patched: frt 4.12.1
  use m_matel_registry, only: peek_at_registered_radfunc  ! patched: frt 4.12.1
  use m_matel_registry, only: register_in_rf_pool  ! patched: frt 4.12.1
  use mpi_siesta"""

if old_use in content:
    content = content.replace(old_use, new_use, 1)
    print("  Step 2 OK: USE statements added at subroutine scope")
else:
    print("  ERROR step 2: 'use mpi_siesta' not found")

old_decl = "  integer  :: gindex          ! Global index of the trial projector function"
new_decl = """  integer  :: blk_orb_gindex, l, m  ! patched: frt 4.12.1 block not supported
  type(rad_func), pointer :: rfunc => null(), func_new => null()  ! patched: frt 4.12.1
  integer  :: gindex          ! Global index of the trial projector function"""

if old_decl in content:
    content = content.replace(old_decl, new_decl, 1)
    print("  Step 3 OK: declarations added at subroutine scope")
else:
    print("  ERROR step 3: declaration not found")

with open(fname, 'w') as fh:
    fh.write(content)

print("  PATCH 6 applied: Src/amn.F90")
PYEOF

###############################################################################
# 11. Verify all patches
###############################################################################
echo ""
echo "Verifying patches ..."
errors=0

count=$(grep -c "use parallel" Src/m_ts_voltage.F90)
if [ "$count" -eq 2 ]; then
    echo "  ✓ Src/m_ts_voltage.F90"
else
    echo "  ✗ Src/m_ts_voltage.F90 – 'use parallel' count: $count (expected: 2)"
    errors=$((errors+1))
fi

for f in Src/flook_siesta.F90 Src/matel_table.F90 Src/m_elsi_interface.F90 \
         Src/m_new_dm.F90 Src/m_w90_wrapper.F90 Src/Orphans/new_dm.F; do
    count=$(grep -c "par_" "$f" 2>/dev/null)
    if [ "$count" -gt 0 ]; then
        echo "  ✓ $f"
    else
        echo "  ✗ $f – no par_ references found"
        errors=$((errors+1))
    fi
done

if grep -q "use parallel, only: Node, Nodes" Src/spinorbit.f && \
   grep -q "par_Node => Node" Src/spinorbit.f; then
    echo "  ✓ Src/spinorbit.f"
else
    echo "  ✗ Src/spinorbit.f"
    errors=$((errors+1))
fi

if ! grep -q "node==0" Src/m_new_dm.F90 && \
   grep -q "par_Node==0" Src/m_new_dm.F90; then
    echo "  ✓ Src/m_new_dm.F90 (PATCH 2c)"
else
    echo "  ✗ Src/m_new_dm.F90 (PATCH 2c)"
    errors=$((errors+1))
fi

count=$(grep -c "patched: frt 4.12.1" Pseudo/vnl-operator/psop.f90)
if [ "$count" -eq 4 ]; then
    echo "  ✓ Pseudo/vnl-operator/psop.f90"
else
    echo "  ✗ Pseudo/vnl-operator/psop.f90 – patched count: $count (expected: 4)"
    errors=$((errors+1))
fi

if grep -q "use netcdf_ncdf, only: ncdf_IONode" Src/siesta_init.F; then
    echo "  ✓ Src/siesta_init.F"
else
    echo "  ✗ Src/siesta_init.F"
    errors=$((errors+1))
fi

count=$(grep -c "patched: frt 4.12.1" Util/QMMM-driver/Src/mm_assign.f90)
if [ "$count" -eq 1 ]; then
    echo "  ✓ Util/QMMM-driver/Src/mm_assign.f90"
else
    echo "  ✗ Util/QMMM-driver/Src/mm_assign.f90 – patched count: $count (expected: 1)"
    errors=$((errors+1))
fi

if grep -q "use radial, only: radial_rescale_radfunc, rad_func  ! patched" Src/amn.F90 && \
   ! grep -q "end block" Src/amn.F90 && \
   grep -q "blk_orb_gindex, l, m  ! patched" Src/amn.F90; then
    echo "  ✓ Src/amn.F90"
else
    echo "  ✗ Src/amn.F90"
    errors=$((errors+1))
fi

if [ $errors -eq 0 ]; then
    echo ""
    echo "All patches applied successfully."
else
    echo ""
    echo "ERROR: $errors patch(es) failed. Aborting."
    exit 1
fi

###############################################################################
# 12. Configure SIESTA with CMake
###############################################################################
echo ""
echo "Starting CMake configuration ..."

FC=frt CC=fcc CXX=FCC cmake -S . -B "${build_}" \
        -DCMAKE_Fortran_COMPILER=frt \
        -DCMAKE_C_COMPILER=fcc \
        -DCMAKE_CXX_COMPILER=FCC \
        -DCMAKE_INSTALL_PREFIX="${bin_}" \
        -DCMAKE_Fortran_FLAGS="-Kopenmp -O2 -Kident_mpi -w -X9 -I/opt/FJSVxtclanga/.common/MECA032/include/mpi/fujitsu ${NETCDF_INC}" \
        -DCMAKE_C_FLAGS="-Kopenmp -O2 -Kident_mpi -Nclang -I/opt/FJSVxtclanga/.common/MECA032/include/mpi/fujitsu ${NETCDF_INC}" \
        -DCMAKE_CXX_FLAGS="-Kopenmp -O2 -Kident_mpi -I/opt/FJSVxtclanga/.common/MECA032/include/mpi/fujitsu -DMPICH_SKIP_MPICXX -DOMPI_SKIP_MPICXX -DMPI_NO_CPPBIND" \
        -DMPI_Fortran_COMPILER=mpifrt \
        -DMPI_C_COMPILER=mpifcc \
        -DMPI_CXX_COMPILER=mpiFCC \
        -DMPI_Fortran_LIBRARIES="/opt/FJSVxtclanga/.common/MECA032/lib64/libmpi_usempif08.so;/opt/FJSVxtclanga/.common/MECA032/lib64/libmpi_mpifh.so;/opt/FJSVxtclanga/.common/MECA032/lib64/libmpi.so" \
        -DMPI_C_LIBRARIES="/opt/FJSVxtclanga/.common/MECA032/lib64/libmpi.so" \
        -DMPI_CXX_LIBRARIES="/opt/FJSVxtclanga/.common/MECA032/lib64/libmpi_cxx.so;/opt/FJSVxtclanga/.common/MECA032/lib64/libmpi.so" \
        -DCMAKE_EXE_LINKER_FLAGS="-L/opt/FJSVxtclanga/.common/MECA032/lib64 -Wl,-rpath,/opt/FJSVxtclanga/.common/MECA032/lib64 -lfjstring_internal ${NETCDF_LIB}" \
        -DMPIEXEC_EXECUTABLE="/opt/FJSVxtclanga/tcsds-1.2.42/bin/mpiexec" \
        -DMPIEXEC_MAX_NUMPROCS=4 \
        -DBLAS_LIBRARY="${FUJITSU_SSL2_PATH}/lib64/libfjlapack.so" \
        -DLAPACK_LIBRARY="${FUJITSU_SSL2_PATH}/lib64/libfjlapack.so" \
        -DSCALAPACK_LIBRARY="${SCALAPACK_PATH}/lib/libscalapack.so" \
        -DFLEX_EXECUTABLE="${FLEX_PATH}/bin/flex" \
        -DBISON_EXECUTABLE="${BISON_PATH}/bin/bison" \
        -DSIESTA_WITH_LIBXC=OFF \
        -DSIESTA_WITH_NETCDF=ON \
        -DSIESTA_WITH_OPENMP=ON \
        -DSIESTA_WITH_MPI_INTERFACES="${interface}" \
        -DCMAKE_BUILD_TYPE=Release \
        -DSIESTA_WITH_ELPA=OFF \
        -DSIESTA_WITH_ELSI=OFF \
        -DSIESTA_WITH_DFTD3=OFF \
        -DSIESTA_WITH_FLOOK=OFF

###############################################################################
# 13. Build and install
###############################################################################
if [ $? -eq 0 ]; then
    echo ""
    echo "CMake configuration successful. Building ..."
    cmake --build "${build_}" -j 8 2>&1 | tee build_log.txt
    build_status=${PIPESTATUS[0]}

    if [ ${build_status} -eq 0 ]; then
        echo ""
        echo "Build successful. Installing ..."
        cmake --install "${build_}"
    else
        echo ""
        echo "ERROR: Build failed (exit code: ${build_status})"
        echo ""
        echo "========================================"
        echo "Source files with Fujitsu errors:"
        echo "========================================"
        grep "jwd1740i" build_log.txt | \
            grep -oP '"[^"]+\.f90|[^"]+\.F90|[^"]+\.F|[^"]+\.f"' | \
            sort -u
        echo ""
        echo "All compiler errors:"
        echo "========================================"
        grep -E "jwd[0-9]+i-s|Error [0-9]+" build_log.txt | \
            grep -v "^gmake" | \
            sort -u
        echo "========================================"
        echo "Full log: cat build_log.txt"
        exit 1
    fi
else
    echo ""
    echo "ERROR: CMake configuration failed."
    exit 1
fi