/*
 * (C) Copyright 2013 ECMWF.
 *
 * This software is licensed under the terms of the Apache Licence Version 2.0
 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
 * In applying this licence, ECMWF does not waive the privileges and immunities
 * granted to it by virtue of its status as an intergovernmental organisation
 * nor does it submit to any jurisdiction.
 */

#include <algorithm>
#include <iomanip>

#include "atlas/array/MakeView.h"
#include "atlas/field/FieldSet.h"
#include "atlas/functionspace/NodeColumns.h"
#include "atlas/functionspace/Spectral.h"
#include "atlas/functionspace/StructuredColumns.h"
#include "atlas/grid.h"
#include "atlas/grid/Distribution.h"
#include "atlas/grid/Partitioner.h"
#include "atlas/grid/detail/partitioner/EqualRegionsPartitioner.h"
#include "atlas/grid/detail/partitioner/TransPartitioner.h"
#include "atlas/mesh/Mesh.h"
#include "atlas/mesh/Nodes.h"
#include "atlas/meshgenerator.h"
#include "atlas/output/Gmsh.h"
#include "atlas/parallel/mpi/mpi.h"
#include "atlas/runtime/Trace.h"
#include "atlas/trans/Trans.h"
#include "atlas/trans/local/TransLocal.h"
#include "atlas/util/Constants.h"
#include "atlas/util/Earth.h"

#include "tests/AtlasTestEnvironment.h"

#if ATLAS_HAVE_TRANS
#include "transi/trans.h"
#endif

using namespace eckit;

using atlas::array::Array;
using atlas::array::ArrayView;
using atlas::array::make_view;

namespace atlas {
namespace test {

//-----------------------------------------------------------------------------

struct AtlasTransEnvironment : public AtlasTestEnvironment {
    AtlasTransEnvironment( int argc, char* argv[] ) : AtlasTestEnvironment( argc, argv ) {
#if ATLAS_HAVE_TRANS
        trans_use_mpi( mpi::comm().size() > 1 );
        trans_init();
#endif
    }

#if ATLAS_HAVE_TRANS
    ~AtlasTransEnvironment() { trans_finalize(); }
#endif
};

//-----------------------------------------------------------------------------
// Routine to compute the spherical harmonics analytically at one point
// (up to wave number 3)
//
// Author:
// Andreas Mueller *ECMWF*
//
double sphericalharmonics_analytic_point(
    const double n,       // total wave number (implemented so far for n<4
    const double m,       // zonal wave number (implemented so far for m<4, m<n
    const int imag,       // 0: test real part, 1: test imaginary part
    const double lon,     // longitude in radians
    const double lat,     // latitude in radians
    const int ivar_in,    // variable that is set to 1 for wave number n,m. 0:
                          // vorticity, 1: divergence, 2: scalar
    const int ivar_out )  // variable returned by this function. 0: u, 1: v, 2: scalar
{
    double latsin = std::sin( lat ), latcos = std::cos( lat );
    double lonsin = std::sin( m * lon ), loncos = std::cos( m * lon );
    double a = util::Earth::radius();
    // Fourier part of the spherical harmonics:
    double rft = 1.;
    if ( m > 0 ) {
        rft *= 2.;  // the famous factor 2 that noone really understands
    }
    if ( imag == 0 ) {
        rft *= loncos;
    }
    else {
        rft *= -lonsin;
    }
    // Legendre part of the spherical harmonics (following
    // http://mathworld.wolfram.com/SphericalHarmonic.html
    // multiplied with -2*sqrt(pi) due to different normalization and different
    // coordinates):
    // (can also be computed on http://www.wolframalpha.com with:
    // LegendreP[n, m, x]/Sqrt[1/2*Integrate[LegendreP[n, m, y]^2, {y, -1, 1}]])
    // n, m need to be replaced by hand with the correct values
    // (otherwise the command will be too long for the free version of
    // wolframalpha)

    // scalar:
    if ( ivar_in == 2 ) {
        if ( ivar_out == 2 ) {
            if ( m == 0 && n == 0 ) {
                return rft;
            }
            if ( m == 0 && n == 1 ) {
                return std::sqrt( 3. ) * latsin * rft;
            }
            if ( m == 0 && n == 2 ) {
                return std::sqrt( 5. ) / 2. * ( 3. * latsin * latsin - 1. ) * rft;  // sign?
            }
            if ( m == 0 && n == 3 ) {
                return std::sqrt( 7. ) / 2. * ( 5. * latsin * latsin - 3. ) * latsin * rft;  // sign?
            }
            if ( m == 1 && n == 1 ) {
                return std::sqrt( 3. / 2. ) * latcos * rft;  // sign?
            }
            if ( m == 1 && n == 2 ) {
                return std::sqrt( 15. / 2. ) * latsin * latcos * rft;  // sign?
            }
            if ( m == 1 && n == 3 ) {
                return std::sqrt( 21. ) / 4. * latcos * ( 5. * latsin * latsin - 1. ) * rft;  // sign?
            }
            if ( m == 2 && n == 2 ) {
                return std::sqrt( 15. / 2. ) / 2. * latcos * latcos * rft;
            }
            if ( m == 2 && n == 3 ) {
                return std::sqrt( 105. / 2. ) / 2. * latcos * latcos * latsin * rft;
            }
            if ( m == 3 && n == 3 ) {
                return std::sqrt( 35. ) / 4. * latcos * latcos * latcos * rft;  // sign?
            }
            if ( m == 4 && n == 4 ) {
                return ( 3 * std::sqrt( 17.5 ) * std::pow( latcos, 4 ) ) / 8. * rft;
            }
            if ( m == 5 && n == 5 ) {
                return ( 3 * std::sqrt( 77 ) * std::pow( latcos, 5 ) ) / 16. * rft;
            }
            if ( m == 6 && n == 6 ) {
                return ( std::sqrt( 3003 ) * std::pow( latcos, 6 ) ) / 32. * rft;
            }
            if ( m == 7 && n == 7 ) {
                return ( 3 * std::sqrt( 357.5 ) * std::pow( latcos, 7 ) ) / 32. * rft;
            }
            if ( m == 8 && n == 8 ) {
                return ( 3 * std::sqrt( 6077.5 ) * std::pow( latcos, 8 ) ) / 128. * rft;
            }
            if ( m == 9 && n == 9 ) {
                return ( std::sqrt( 230945 ) * std::pow( latcos, 9 ) ) / 256. * rft;
            }
            if ( m == 10 && n == 10 ) {
                return ( std::sqrt( 969969 ) * std::pow( latcos, 10 ) ) / 512. * rft;
            }
            if ( m == 11 && n == 11 ) {
                return ( std::sqrt( 1.0140585e6 ) * std::pow( latcos, 11 ) ) / 512. * rft;
            }
            if ( m == 12 && n == 12 ) {
                return ( 5 * std::sqrt( 676039 ) * std::pow( latcos, 12 ) ) / 2048. * rft;
            }
            if ( m == 13 && n == 13 ) {
                return ( 15 * std::sqrt( 78004.5 ) * std::pow( latcos, 13 ) ) / 2048. * rft;
            }
            if ( m == 14 && n == 14 ) {
                return ( 15 * std::sqrt( 323161.5 ) * std::pow( latcos, 14 ) ) / 4096. * rft;
            }
            if ( m == 15 && n == 15 ) {
                return ( 3 * std::sqrt( 33393355 ) * std::pow( latcos, 15 ) ) / 8192. * rft;
            }
            if ( m == 16 && n == 16 ) {
                return ( 3 * std::sqrt( 5.509903575e8 ) * std::pow( latcos, 16 ) ) / 32768. * rft;
            }
            if ( m == 17 && n == 17 ) {
                return ( 15 * std::sqrt( 90751353 ) * std::pow( latcos, 17 ) ) / 65536. * rft;
            }
            if ( m == 18 && n == 18 ) {
                return ( 5 * std::sqrt( 3357800061 ) * std::pow( latcos, 18 ) ) / 131072. * rft;
            }
            if ( m == 19 && n == 19 ) {
                return ( 15 * std::sqrt( 3.829070245e8 ) * std::pow( latcos, 19 ) ) / 131072. * rft;
            }
            if ( m == 20 && n == 20 ) {
                return ( 3 * std::sqrt( 156991880045 ) * std::pow( latcos, 20 ) ) / 524288. * rft;
            }
            if ( m == 21 && n == 21 ) {
                return ( std::sqrt( 1.4465680375575e12 ) * std::pow( latcos, 21 ) ) / 524288. * rft;
            }
            if ( m == 22 && n == 22 ) {
                return ( 15 * std::sqrt( 2.63012370465e10 ) * std::pow( latcos, 22 ) ) / 1.048576e6 * rft;
            }
            if ( m == 23 && n == 23 ) {
                return ( 15 * std::sqrt( 107492012277 ) * std::pow( latcos, 23 ) ) / 2.097152e6 * rft;
            }
            if ( m == 24 && n == 24 ) {
                return ( 105 * std::sqrt( 35830670759 ) * std::pow( latcos, 24 ) ) / 8.388608e6 * rft;
            }
            if ( m == 25 && n == 25 ) {
                return ( 21 * std::sqrt( 9.136821043545e11 ) * std::pow( latcos, 25 ) ) / 8.388608e6 * rft;
            }
            if ( m == 26 && n == 26 ) {
                return ( 21 * std::sqrt( 3.7250116562145e12 ) * std::pow( latcos, 26 ) ) / 1.6777216e7 * rft;
            }
            if ( m == 27 && n == 27 ) {
                return ( 7 * std::sqrt( 136583760727865. ) * std::pow( latcos, 27 ) ) / 3.3554432e7 * rft;
            }
            if ( m == 28 && n == 28 ) {
                return ( std::sqrt( 2.7248460265209068e16 ) * std::pow( latcos, 28 ) ) / 6.7108864e7 * rft;
            }
            if ( m == 29 && n == 29 ) {
                return ( std::sqrt( 110873045217057585. ) * std::pow( latcos, 29 ) ) / 1.34217728e8 * rft;
            }
            if ( m == 30 && n == 30 ) {
                return ( std::sqrt( 450883717216034179. ) * std::pow( latcos, 30 ) ) / 2.68435456e8 * rft;
            }
            if ( m == 31 && n == 31 ) {
                return ( 21 * std::sqrt( 1.0389025742304935e15 ) * std::pow( latcos, 31 ) ) / 2.68435456e8 * rft;
            }
            if ( m == 32 && n == 32 ) {
                return ( 21 * std::sqrt( 6.752866732498208e16 ) * std::pow( latcos, 32 ) ) / 2.147483648e9 * rft;
            }
            if ( m == 33 && n == 33 ) {
                return ( 7 * std::sqrt( 2467865842240254105. ) * std::pow( latcos, 33 ) ) / 4.294967296e9 * rft;
            }
            if ( m == 34 && n == 34 ) {
                return ( 21 * std::sqrt( 1112959105324036165. ) * std::pow( latcos, 34 ) ) / 8.589934592e9 * rft;
            }
            if ( m == 35 && n == 35 ) {
                return ( 3 * std::sqrt( 5.53140675346046e19 ) * std::pow( latcos, 35 ) ) / 8.589934592e9 * rft;
            }
            if ( m == 36 && n == 36 ) {
                return ( std::sqrt( 8075853860052271220473. ) * std::pow( latcos, 36 ) ) / 3.4359738368e10 * rft;
            }
            if ( m == 37 && n == 37 ) {
                return ( 5 * std::sqrt( 3.2739948081292994e20 ) * std::pow( latcos, 37 ) ) / 3.4359738368e10 * rft;
            }
            if ( m == 38 && n == 38 ) {
                return ( 35 * std::sqrt( 2.707815254843781e19 ) * std::pow( latcos, 38 ) ) / 6.8719476736e10 * rft;
            }
            if ( m == 39 && n == 39 ) {
                return ( 35 * std::sqrt( 109701233401363445369. ) * std::pow( latcos, 39 ) ) / 1.37438953472e11 * rft;
            }
            if ( m == 40 && n == 40 ) {
                return ( 63 * std::sqrt( 548506167006817226845. ) * std::pow( latcos, 40 ) ) / 5.49755813888e11 * rft;
            }
            if ( m == 41 && n == 41 ) {
                return ( 63 * std::sqrt( 5.551952666044613e20 ) * std::pow( latcos, 41 ) ) / 5.49755813888e11 * rft;
            }
            if ( m == 42 && n == 42 ) {
                return ( 15 * std::sqrt( 3.964094203555854e22 ) * std::pow( latcos, 42 ) ) / 1.099511627776e12 * rft;
            }
            if ( m == 43 && n == 43 ) {
                return ( 45 * std::sqrt( 17823059209786010066579. ) * std::pow( latcos, 43 ) ) / 2.199023255552e12 *
                       rft;
            }
            if ( m == 44 && n == 44 ) {
                return ( 45 * std::sqrt( 7.210237589413431e22 ) * std::pow( latcos, 44 ) ) / 4.398046511104e12 * rft;
            }
            if ( m == 45 && n == 45 ) {
                return ( 21 * std::sqrt( 1339044123748208678378695. ) * std::pow( latcos, 45 ) ) / 8.796093022208e12 *
                       rft;
            }
            //    return
            //    std::pow(latcos,45)*rft*21.*std::sqrt(1339044123748208678378695.)/8796093022208.;
            //    // sign?
        }
        else {
            return 0.;
        }
    }

    // for the remainder the factor 2 from rft is already included in the
    // formulas:

    // vorticity:
    if ( ivar_in == 0 ) {
        if ( ivar_out == 0 ) {  // u:
            if ( m == 0 && n == 0 ) {
                return 0.;
            }
            if ( m == 0 && n == 1 ) {
                if ( imag == 0 ) {
                    return std::sqrt( 3. ) * a / 2. * latcos;
                }
                else {
                    return 0.;
                }
            }
            if ( m == 1 && n == 1 ) {
                if ( imag == 0 ) {
                    return -a * std::sqrt( 3. / 2. ) * loncos * latsin;
                }
                else {
                    return a * std::sqrt( 3. / 2. ) * lonsin * latsin;
                }
            }
        }
        else if ( ivar_out == 1 ) {  // v:
            if ( m == 0 && n == 0 ) {
                return 0.;
            }
            if ( m == 0 && n == 1 ) {
                return 0.;
            }
            if ( m == 1 && n == 1 ) {
                if ( imag == 0 ) {
                    return a * std::sqrt( 3. / 2. ) * lonsin;
                }
                else {
                    return a * std::sqrt( 3. / 2. ) * loncos;
                }
            }
        }
        else {
            return 0.;
        }
    }

    // divergence:
    if ( ivar_in == 1 ) {
        if ( ivar_out == 0 ) {  // u:
            if ( m == 0 && n == 0 ) {
                return 0.;
            }
            if ( m == 0 && n == 1 ) {
                return 0.;
            }
            if ( m == 1 && n == 1 ) {
                if ( imag == 0 ) {
                    return a * std::sqrt( 3. / 2. ) * lonsin;
                }
                else {
                    return a * std::sqrt( 3. / 2. ) * loncos;
                }
            }
        }
        else if ( ivar_out == 1 ) {  // v:
            if ( m == 0 && n == 0 ) {
                return 0.;
            }
            if ( m == 0 && n == 1 ) {
                if ( imag == 0 ) {
                    return -std::sqrt( 3. ) * a / 2. * latcos;
                }
                else {
                    return 0.;
                }
            }
            if ( m == 1 && n == 1 ) {
                if ( imag == 0 ) {
                    return a * std::sqrt( 3. / 2. ) * loncos * latsin;
                }
                else {
                    return -a * std::sqrt( 3. / 2. ) * lonsin * latsin;  // sign?
                }
            }
        }
        else {
            return 0.;
        }
    }

    return -1.;
}

//-----------------------------------------------------------------------------
// Routine to compute the spherical harmonics analytically on a grid
// (up to wave number 3)
//
// Author:
// Andreas Mueller *ECMWF*
//
void spectral_transform_grid_analytic(
    const int trc,        // truncation (in)
    bool trcFT,           // truncation for Fourier transformation (in)
    const double n,       // total wave number (implemented so far for n<4)
    const double m,       // zonal wave number (implemented so far for m<4, m<n)
    const int imag,       // 0: test real part, 1: test imaginary part
    const Grid grid,      // call with something like Grid("O32")
    double rspecg[],      // spectral data, size (trc+1)*trc (out)
    double rgp[],         // resulting grid point data (out)
    const int ivar_in,    // variable that is set to 1 for wave number n,m. 0:
                          // vorticity, 1: divergence, 2: scalar
    const int ivar_out )  // variable returned by this function. 0: u, 1: v, 2: scalar
{
    int N = ( trc + 2 ) * ( trc + 1 ) / 2;
    for ( int jm = 0; jm < 2 * N; jm++ ) {
        rspecg[jm] = 0.;
    }
    int k = 0;
    for ( int jm = 0; jm <= trc; jm++ ) {
        for ( int jn = jm; jn <= trc; jn++ ) {
            if ( jm == m && jn == n ) {
                rspecg[2 * k + imag]         = 1.;
                rspecg[2 * k + ( 1 - imag )] = 0.;
            }
            k++;
        }
    }

    for ( int jm = 0; jm < grid.size(); jm++ ) {
        rgp[jm] = 0.;
    }

    if ( StructuredGrid( grid ) ) {
        StructuredGrid g( grid );
        Grid gridGlobal;
        StructuredGrid gs_global;
        int jlatMin = 0;
        if ( trcFT ) {
            gridGlobal      = Grid( grid.name() );
            gs_global       = StructuredGrid( gridGlobal );
            int nlatsGlobal = gs_global.ny();
            for ( int jlat = 0; jlat < nlatsGlobal; jlat++ ) {
                if ( gs_global.y( jlat ) > g.y( 0 ) ) {
                    jlatMin++;
                };
            }
        }

        int idx = 0;
        for ( idx_t j = 0; j < g.ny(); ++j ) {
            double lat = g.y( j ) * util::Constants::degreesToRadians();
            int ftrc   = trc + 1;
            if ( trcFT ) {
                ftrc = trans::fourier_truncation( trc, gs_global.nx( jlatMin + j ), gs_global.nxmax(), gs_global.ny(),
                                                  lat, RegularGrid( gs_global ) );
            }
            /*Log::info() << "j=" << j << " ftrc=" << ftrc << " trc=" << trc << " nx=" << gs_global.nx( jlatMin + j )
                        << " nxmax=" << gs_global.nxmax() << " nlats=" << gs_global.ny() << " lat=" << g.y( j )
                        << " jlatMin=" << jlatMin << std::endl;*/
            for ( idx_t i = 0; i < g.nx( j ); ++i ) {
                double lon = g.x( i, j ) * util::Constants::degreesToRadians();

                // compute spherical harmonics:
                if ( ftrc > m ) {
                    rgp[idx++] = sphericalharmonics_analytic_point( n, m, imag, lon, lat, ivar_in, ivar_out );
                }
                else {
                    rgp[idx++] = 0.;
                }
            }
        }
    }
    else {
        int idx = 0;
        for ( const PointXY& p : grid.xy() ) {
            double lon = p.x() * util::Constants::degreesToRadians();
            double lat = p.y() * util::Constants::degreesToRadians();
            // compute spherical harmonics:
            rgp[idx++] = sphericalharmonics_analytic_point( n, m, imag, lon, lat, ivar_in, ivar_out );
        }
    }
}

//-----------------------------------------------------------------------------
// Compute root mean square of difference between two arrays divided by maximum
// absolute value of second array
//
// Author:
// Andreas Mueller *ECMWF*
//
double compute_rms( const size_t N,    // length of the arrays
                    double array1[],   // first of the two arrays
                    double array2[] )  // second of the two arrays
{
    double rms = 0., rmax = 0.;
    for ( size_t idx = 0; idx < N; idx++ ) {
        double diff = array1[idx] - array2[idx];
        rms += diff * diff;
        rmax = std::max( rmax, std::abs( array2[idx] ) );
    }
    if ( rmax == 0. ) {
        rms = 0.;
    }
    else {
        rms = std::sqrt( rms / N ) / rmax;
    }
    return rms;
}

//-----------------------------------------------------------------------------
#if 1
CASE( "test_trans_vordiv_with_translib" ) {
    Log::info() << "test_trans_vordiv_with_translib" << std::endl;
    // test transgeneral by comparing its result with the trans library
    // this test is based on the test_nomesh case in test_trans.cc

    double tolerance = 1.e-13;

    // Grid: (Adjust the following line if the test takes too long!)
    Grid g( "F64" );

    StructuredGrid gs( g );
    int ndgl = gs.ny();
    //int trc  = ndgl - 1;  // linear
    int trc = ndgl / 2. - 1;  // cubic
#if ATLAS_HAVE_TRANS
    trans::Trans transIFS( g, trc, util::Config( "type", "ifs" ) );
    double rav = 0.;  // compute average rms error of trans library in rav
#endif
    trans::Trans transLocal1( g, trc, util::Config( "type", "local" ) );
    trans::Trans transLocal2( g, trc, util::Config( "type", "local" ) );
    double rav1 = 0., rav2 = 0.;  // compute average rms errors of transLocal1 and transLocal2

    functionspace::Spectral spectral( trc );
    functionspace::StructuredColumns gridpoints( g );

    int nb_scalar = 1, nb_vordiv = 0;
    int N = ( trc + 2 ) * ( trc + 1 ) / 2, nb_all = nb_scalar + 2 * nb_vordiv;
    std::vector<double> sp( 2 * N * nb_scalar );
    std::vector<double> vor( 2 * N * nb_vordiv );
    std::vector<double> div( 2 * N * nb_vordiv );
    std::vector<double> rspecg( 2 * N );
    std::vector<double> gp( nb_all * g.size() );
    std::vector<double> rgp1( nb_all * g.size() );
    std::vector<double> rgp2( nb_all * g.size() );
    std::vector<double> rgp_analytic( g.size() );

    int icase = 0;
    for ( int ivar_in = 2; ivar_in < 3; ivar_in++ ) {         // vorticity, divergence, scalar
        for ( int ivar_out = 2; ivar_out < 3; ivar_out++ ) {  // u, v, scalar
            int nb_fld = 1;
            if ( ivar_out == 2 ) {
                tolerance = 1.e-13;
                nb_fld    = nb_scalar;
            }
            else {
                tolerance = 2.e-6;
                nb_fld    = nb_vordiv;
            }
            for ( int jfld = 0; jfld < nb_fld; jfld++ ) {  // multiple fields
                int k = 0;
                for ( int m = 0; m <= trc; m++ ) {                 // zonal wavenumber
                    for ( int n = m; n <= trc; n++ ) {             // total wavenumber
                        for ( int imag = 0; imag <= 1; imag++ ) {  // real and imaginary part

                            if ( sphericalharmonics_analytic_point( n, m, true, 0., 0., ivar_in, ivar_in ) == 0. ) {
                                for ( int j = 0; j < 2 * N * nb_scalar; j++ ) {
                                    sp[j] = 0.;
                                }
                                for ( int j = 0; j < 2 * N * nb_vordiv; j++ ) {
                                    vor[j] = 0.;
                                    div[j] = 0.;
                                }
                                if ( ivar_in == 0 ) {
                                    vor[k * nb_vordiv + jfld] = 1.;
                                }
                                if ( ivar_in == 1 ) {
                                    div[k * nb_vordiv + jfld] = 1.;
                                }
                                if ( ivar_in == 2 ) {
                                    sp[k * nb_scalar + jfld] = 1.;
                                }

                                for ( int j = 0; j < nb_all * g.size(); j++ ) {
                                    gp[j]   = 0.;
                                    rgp1[j] = 0.;
                                    rgp2[j] = 0.;
                                }
                                for ( int j = 0; j < g.size(); j++ ) {
                                    rgp_analytic[j] = 0.;
                                }

                                spectral_transform_grid_analytic( trc, trc, n, m, imag, g, rspecg.data(),
                                                                  rgp_analytic.data(), ivar_in, ivar_out );

                                EXPECT_NO_THROW( transLocal1.invtrans( nb_scalar, sp.data(), nb_vordiv, vor.data(),
                                                                       div.data(), rgp1.data() ) );

                                EXPECT_NO_THROW( transLocal2.invtrans( nb_scalar, sp.data(), nb_vordiv, vor.data(),
                                                                       div.data(), rgp2.data() ) );

                                int pos = ( ivar_out * nb_vordiv + jfld );

                                double rms_gen1 =
                                    compute_rms( g.size(), rgp1.data() + pos * g.size(), rgp_analytic.data() );

                                double rms_gen2 =
                                    compute_rms( g.size(), rgp2.data() + pos * g.size(), rgp_analytic.data() );

                                rav1 += rms_gen1;
                                rav2 += rms_gen2;
                                if ( !( rms_gen1 < tolerance ) || !( rms_gen2 < tolerance ) ) {
                                    Log::info()
                                        << "Case " << icase << " ivar_in=" << ivar_in << " ivar_out=" << ivar_out
                                        << " m=" << m << " n=" << n << " imag=" << imag << " k=" << k << std::endl;
                                    ATLAS_DEBUG_VAR( rms_gen1 );
                                    ATLAS_DEBUG_VAR( rms_gen2 );
                                    ATLAS_DEBUG_VAR( tolerance );
                                }
                                EXPECT( rms_gen1 < tolerance );
                                EXPECT( rms_gen2 < tolerance );
                                icase++;

#if ATLAS_HAVE_TRANS
                                EXPECT_NO_THROW( transIFS.invtrans( nb_scalar, sp.data(), nb_vordiv, vor.data(),
                                                                    div.data(), gp.data() ) );
                                double rms_trans =
                                    compute_rms( g.size(), gp.data() + pos * g.size(), rgp_analytic.data() );
                                rav += rms_trans;
                                double rms_diff =
                                    compute_rms( g.size(), rgp1.data() + pos * g.size(), gp.data() + pos * g.size() );
                                EXPECT( rms_trans < tolerance );
                                if ( !( rms_trans < tolerance ) || !( rms_diff < tolerance ) ) {
                                    Log::info()
                                        << "Case " << icase << " ivar_in=" << ivar_in << " ivar_out=" << ivar_out
                                        << " m=" << m << " n=" << n << " imag=" << imag << " k=" << k << std::endl;
                                    ATLAS_DEBUG_VAR( rms_gen1 );
                                    ATLAS_DEBUG_VAR( rms_gen2 );
                                    ATLAS_DEBUG_VAR( rms_trans );
                                    ATLAS_DEBUG_VAR( rms_diff );
                                    ATLAS_DEBUG_VAR( tolerance );
                                }
#endif
                                EXPECT( icase < 300 );
                            }
                            k++;
                        }
                    }
                }
            }
        }
    }
    Log::info() << "Vordiv+scalar comparison with trans: all " << icase << " cases successfully passed!" << std::endl;
    rav1 /= icase;
    Log::info() << "average RMS error of transLocal1: " << rav1 << std::endl;
    rav2 /= icase;
    Log::info() << "average RMS error of transLocal2: " << rav2 << std::endl;
#if ATLAS_HAVE_TRANS
    rav /= icase;
    Log::info() << "average RMS error of transIFS: " << rav << std::endl;
#endif
}
#endif
//-----------------------------------------------------------------------------
#if 0
CASE( "test_trans_hires" ) {
    Log::info() << "test_trans_hires" << std::endl;
    // test transgeneral by comparing its result with the trans library
    // this test is based on the test_nomesh case in test_trans.cc

    std::ostream& out = Log::info();
    double tolerance  = 1.e-13;

#if ATLAS_HAVE_TRANS
    //std::string transTypes[4] = {"localopt", "localopt2", "Local", "ifs"};
    //std::string transTypes[2] = {"localopt2", "Local"};
    std::string transTypes[3] = {"Local", "localopt2", "localopt"};
    //std::string transTypes[1] = {"Local"};
#else
    std::string transTypes[1] = {"localopt2"};
#endif

    //Domain testdomain = ZonalBandDomain( {-90., 90.} );
    //Domain testdomain = ZonalBandDomain( {-.5, .5} );
    //Domain testdomain = RectangularDomain( {0., 30.}, {-.05, .05} );
    //Domain testdomain = ZonalBandDomain( {-85., -86.} );
    ///Domain testdomain = RectangularDomain( {-.01, .01}, {-.01, .01} );
    Domain testdomain = RectangularDomain( {-1, 1}, {-1, 1} );
    // Grid: (Adjust the following line if the test takes too long!)
    Grid g( "F22000", testdomain );
    Grid g_global( g.name() );

    grid::StructuredGrid gs( g );
    grid::StructuredGrid gs_global( g_global );
    Log::info() << "nlats: " << gs.ny() << " nlons:" << gs.nxmax() << std::endl;
    int ndgl = gs_global.ny();
    //int trc  = ndgl - 1;  // linear
    int trc = ndgl / 2. - 1;  // cubic

    int nb_scalar = 1, nb_vordiv = 0;

    for ( auto transType : transTypes ) {
        int N = ( trc + 2 ) * ( trc + 1 ) / 2, nb_all = nb_scalar + 2 * nb_vordiv;
        int icase = 0;
        trans::Trans trans( g, trc, util::Config( "type", transType ) );
        for ( int ivar_in = 2; ivar_in < 3; ivar_in++ ) {         // vorticity, divergence, scalar
            for ( int ivar_out = 2; ivar_out < 3; ivar_out++ ) {  // u, v, scalar
                int nb_fld = 1;
                if ( ivar_out == 2 ) { nb_fld = nb_scalar; }
                else {
                    nb_fld = nb_vordiv;
                }
                for ( int jfld = 0; jfld < 1; jfld++ ) {  // multiple fields
                    int k = 0;
                    for ( int m = 0; m <= trc; m++ ) {                 // zonal wavenumber
                        for ( int n = m; n <= trc; n++ ) {             // total wavenumber
                            for ( int imag = 0; imag <= 1; imag++ ) {  // real and imaginary part

                                if ( sphericalharmonics_analytic_point( n, m, true, 0., 0., ivar_in, ivar_in ) == 0. &&
                                     icase < 1 ) {
                                    auto start = std::chrono::system_clock::now();
                                    std::vector<double> sp( 2 * N * nb_scalar );
                                    std::vector<double> gp( nb_all * g.size() );
                                    if ( ivar_in == 2 ) sp[k * nb_scalar + jfld] = 1.;
                                    EXPECT_NO_THROW( trans.invtrans( nb_scalar, sp.data(), nb_vordiv, nullptr, nullptr,
                                                                     gp.data() ) );
                                    icase++;
                                    auto end = std::chrono::system_clock::now();  //
                                    std::chrono::duration<double> elapsed_seconds = end - start;
                                    std::time_t end_time = std::chrono::system_clock::to_time_t( end );
                                    std::string time_str = std::ctime( &end_time );
                                    Log::info()
                                        << transType << ": case " << icase
                                        << ", elapsed time: " << elapsed_seconds.count()
                                        << "s. Now: " << time_str.substr( 0, time_str.length() - 1 ) << std::endl;
                                }
                                k++;
                            }
                        }
                    }
                }
            }
        }
        Log::info() << "Vordiv+scalar comparison with trans::" << transType << ": all " << icase
                    << " cases successfully passed!" << std::endl;
    }
}
#endif
//-----------------------------------------------------------------------------
#if 1
CASE( "test_trans_domain" ) {
    Log::info() << "test_trans_domain" << std::endl;
    // test transgeneral by comparing with analytic solution on a cropped domain
    // this test also includes testing caching

    //Domain testdomain = ZonalBandDomain( {-90., 90.} );
    //Domain testdomain = ZonalBandDomain( {-.5, .5} );
    //Domain testdomain = RectangularDomain( {0., 30.}, {-.05, .05} );
    //Domain testdomain1 = ZonalBandDomain( {-10., 5.} );
    Domain testdomain1 = RectangularDomain( {-5., 5.}, {-2.5, 0.} );
    //Domain testdomain1 = RectangularDomain( {-1., 1.}, {50., 55.} );
    Domain testdomain2 = RectangularDomain( {-5., 5.}, {-2.5, 0.} );
    // Grid: (Adjust the following line if the test takes too long!)

    Grid global_grid( "O64" );
    //Grid g1( global_grid, testdomain1 );
    Grid g2( global_grid, testdomain2 );
    Grid g1( global_grid );
    //Grid g2( global_grid );

    bool fourierTrc1 = true;
    bool fourierTrc2 = false;
    // fourierTrc1, fourierTrc2: need to be false if no global grid can be constructed
    //                           (like for grids created with LinearSpacing)

    //using LinearSpacing = grid::LinearSpacing;
    //StructuredGrid g2( LinearSpacing( {0., 180.}, 3 ), LinearSpacing( {89., 90.}, 2 ) );
    // when using LinearSpacing: set fourierTrc2 to false

    int trc = 63;
    //Log::info() << "rgp1:" << std::endl;
    if ( eckit::PathName( "legcache.bin" ).exists() ) {
        eckit::PathName( "legcache.bin" ).unlink();
    }
    Trace t1( Here(), "translocal1 construction" );
    trans::Trans transLocal1( global_grid, g1.domain(), trc,
                              option::type( "local" ) | option::write_legendre( "legcache.bin" ) );
    t1.stop();
    //Log::info() << "rgp2:" << std::endl;
    trans::Cache cache;
    ATLAS_TRACE_SCOPE( "Read cache" ) cache = trans::LegendreCache( "legcache.bin" );
    Trace t2( Here(), "translocal2 construction" );
    trans::Trans transLocal2( cache, global_grid, g2.domain(), trc,
                              option::type( "local" ) | option::write_legendre( "legcache2.bin" ) );
    //trans::Trans transLocal2( cache, g2, trc, option::type( "local" ) );
    //trans::Trans transLocal2( cache, g2, trc,
    //                          option::type( "local" ) | option::no_fft() );
    //trans::Trans transLocal2( g2, trc, option::type( "local" ) );
    t2.stop();

    double rav1 = 0., rav2 = 0.;  // compute average rms errors of transLocal1 and transLocal2

    functionspace::Spectral spectral( trc );

    int nb_scalar = 1, nb_vordiv = 1;
    int N = ( trc + 2 ) * ( trc + 1 ) / 2, nb_all = nb_scalar + 2 * nb_vordiv;
    std::vector<double> sp( 2 * N * nb_scalar );
    std::vector<double> vor( 2 * N * nb_vordiv );
    std::vector<double> div( 2 * N * nb_vordiv );
    std::vector<double> rspecg( 2 * N );
    std::vector<double> rgp1( nb_all * g1.size() );
    std::vector<double> rgp2( nb_all * g2.size() );
    std::vector<double> rgp1_analytic( g1.size() );
    std::vector<double> rgp2_analytic( g2.size() );

    StructuredGrid gs1( g1 );
    StructuredGrid gs2( g2 );
    double latPole   = 89.9999999;
    bool gridHasPole = gs1.y( 0 ) > latPole || gs2.y( 0 ) > latPole;
    double tolerance;
    int icase = 0;
    for ( int ivar_in = 0; ivar_in < 3; ivar_in++ ) {         // vorticity, divergence, scalar
        for ( int ivar_out = 0; ivar_out < 3; ivar_out++ ) {  // u, v, scalar
            int nb_fld = 1;
            if ( ivar_out == 2 && !gridHasPole ) {
                tolerance = 1.e-13;
                nb_fld    = nb_scalar;
            }
            else {
                tolerance = 2.e-6;
                nb_fld    = nb_vordiv;
            }
            for ( int jfld = 0; jfld < nb_fld; jfld++ ) {  // multiple fields
                int k = 0;
                for ( int m = 0; m <= trc; m++ ) {                 // zonal wavenumber
                    for ( int n = m; n <= trc; n++ ) {             // total wavenumber
                        for ( int imag = 0; imag <= 1; imag++ ) {  // real and imaginary part

                            if ( sphericalharmonics_analytic_point( n, m, true, 0., 0., ivar_in, ivar_in ) == 0. &&
                                 icase < 1000 ) {
                                auto start = std::chrono::system_clock::now();
                                for ( int j = 0; j < 2 * N * nb_scalar; j++ ) {
                                    sp[j] = 0.;
                                }
                                for ( int j = 0; j < 2 * N * nb_vordiv; j++ ) {
                                    vor[j] = 0.;
                                    div[j] = 0.;
                                }
                                if ( ivar_in == 0 ) {
                                    vor[k * nb_vordiv + jfld] = 1.;
                                }
                                if ( ivar_in == 1 ) {
                                    div[k * nb_vordiv + jfld] = 1.;
                                }
                                if ( ivar_in == 2 ) {
                                    sp[k * nb_scalar + jfld] = 1.;
                                }

                                for ( int j = 0; j < nb_all * g1.size(); j++ ) {
                                    rgp1[j] = 0.;
                                }
                                for ( int j = 0; j < nb_all * g2.size(); j++ ) {
                                    rgp2[j] = 0.;
                                }
                                for ( int j = 0; j < g1.size(); j++ ) {
                                    rgp1_analytic[j] = 0.;
                                }
                                for ( int j = 0; j < g2.size(); j++ ) {
                                    rgp2_analytic[j] = 0.;
                                }

                                spectral_transform_grid_analytic( trc, fourierTrc1, n, m, imag, g1, rspecg.data(),
                                                                  rgp1_analytic.data(), ivar_in, ivar_out );

                                spectral_transform_grid_analytic( trc, fourierTrc2, n, m, imag, g2, rspecg.data(),
                                                                  rgp2_analytic.data(), ivar_in, ivar_out );

                                //Log::info() << std::endl << "rgp1:";
                                ATLAS_TRACE_SCOPE( "translocal1" )
                                EXPECT_NO_THROW( transLocal1.invtrans( nb_scalar, sp.data(), nb_vordiv, vor.data(),
                                                                       div.data(), rgp1.data() ) );

                                //Log::info() << std::endl << "rgp2:";
                                ATLAS_TRACE_SCOPE( "translocal2" )
                                EXPECT_NO_THROW( transLocal2.invtrans( nb_scalar, sp.data(), nb_vordiv, vor.data(),
                                                                       div.data(), rgp2.data() ) );

                                int pos = ( ivar_out * nb_vordiv + jfld );

                                double rms_gen1 =
                                    compute_rms( g1.size(), rgp1.data() + pos * g1.size(), rgp1_analytic.data() );

                                double rms_gen2 =
                                    compute_rms( g2.size(), rgp2.data() + pos * g2.size(), rgp2_analytic.data() );

                                //Log::info() << "Case " << icase << " ivar_in=" << ivar_in << " ivar_out=" << ivar_out
                                //            << " m=" << m << " n=" << n << " imag=" << imag << " k=" << k << std::endl
                                //            << "rgp1:";
                                //for ( int j = 0; j < g1.size(); j++ ) {
                                //    Log::info() << rgp1[pos * g1.size() + j] << " ";
                                //};
                                //Log::info() << std::endl << "analytic1:";
                                //for ( int j = 0; j < g1.size(); j++ ) {
                                //    Log::info() << rgp1_analytic[j] << " ";
                                //};
                                //Log::info() << std::endl << "rgp2:";
                                //for ( int j = 0; j < g2.size(); j++ ) {
                                //    Log::info() << rgp2[pos * g2.size() + j] << " ";
                                //};
                                //Log::info() << std::endl << "analytic2:";
                                //for ( int j = 0; j < g2.size(); j++ ) {
                                //    Log::info() << rgp2_analytic[j] << " ";
                                //};
                                //Log::info() << std::endl;
                                rav1 += rms_gen1;
                                rav2 += rms_gen2;
                                if ( !( rms_gen1 < tolerance ) || !( rms_gen2 < tolerance ) ) {
                                    Log::info()
                                        << "Case " << icase << " ivar_in=" << ivar_in << " ivar_out=" << ivar_out
                                        << " m=" << m << " n=" << n << " imag=" << imag << " k=" << k << std::endl;
                                    ATLAS_DEBUG_VAR( rms_gen1 );
                                    ATLAS_DEBUG_VAR( rms_gen2 );
                                    ATLAS_DEBUG_VAR( tolerance );
                                }
                                EXPECT( rms_gen1 < tolerance );
                                EXPECT( rms_gen2 < tolerance );
                                icase++;
                                auto end                                      = std::chrono::system_clock::now();  //
                                std::chrono::duration<double> elapsed_seconds = end - start;
                                std::time_t end_time = std::chrono::system_clock::to_time_t( end );
                                std::string time_str = std::ctime( &end_time );
                                Log::debug() << "case " << icase << ", elapsed time: " << elapsed_seconds.count()
                                             << "s. Now: " << time_str.substr( 0, time_str.length() - 1 ) << std::endl;
                            }
                            k++;
                        }
                    }
                }
            }
        }
    }
    Log::info() << "Vordiv+scalar comparison with trans: all " << icase << " cases successfully passed!" << std::endl;
    rav1 /= icase;
    Log::info() << "average RMS error of transLocal1: " << rav1 << std::endl;
    rav2 /= icase;
    Log::info() << "average RMS error of transLocal2: " << rav2 << std::endl;
}
//-----------------------------------------------------------------------------
CASE( "test_trans_pole" ) {
    Log::info() << "test_trans_pole" << std::endl;
    // test transform at the pole and with LinearSpacing grids
    // not using caching in this test because not useful for LinearSpacing grids

    //Domain testdomain = ZonalBandDomain( {-90., 90.} );
    //Domain testdomain = ZonalBandDomain( {-.5, .5} );
    //Domain testdomain = RectangularDomain( {0., 30.}, {-.05, .05} );
    //Domain testdomain1 = ZonalBandDomain( {-10., 5.} );
    Domain testdomain1 = RectangularDomain( {-5., 5.}, {-2.5, 0.} );
    //Domain testdomain1 = RectangularDomain( {-1., 1.}, {50., 55.} );
    Domain testdomain2 = RectangularDomain( {-5., 5.}, {-2.5, 0.} );
    // Grid: (Adjust the following line if the test takes too long!)

    Grid global_grid1( "L3" );
    Grid global_grid2( "L3" );
    //Grid g1( global_grid, testdomain1 );
    //Grid g2( global_grid, testdomain2 );
    Grid g1( global_grid1 );
    //Grid g2( global_grid2 );

    bool fourierTrc1 = true;
    bool fourierTrc2 = false;
    // fourierTrc1, fourierTrc2: need to be false if no global grid can be constructed
    //                           (like for grids created with LinearSpacing)

    using LinearSpacing = grid::LinearSpacing;
    StructuredGrid g2( LinearSpacing( {0., 180.}, 3 ), LinearSpacing( {89., 90.}, 2 ) );
    // when using LinearSpacing: set fourierTrc2 to false

    int trc = 2;
    Trace t1( Here(), "translocal1 construction" );
    trans::Trans transLocal1( global_grid1, g1.domain(), trc, option::type( "local" ) );
    t1.stop();
    Trace t2( Here(), "translocal2 construction" );
    trans::Trans transLocal2( g2, trc, option::type( "local" ) );
    t2.stop();

    double rav1 = 0., rav2 = 0.;  // compute average rms errors of transLocal1 and transLocal2

    functionspace::Spectral spectral( trc );

    int nb_scalar = 1, nb_vordiv = 1;
    int N = ( trc + 2 ) * ( trc + 1 ) / 2, nb_all = nb_scalar + 2 * nb_vordiv;
    std::vector<double> sp( 2 * N * nb_scalar );
    std::vector<double> vor( 2 * N * nb_vordiv );
    std::vector<double> div( 2 * N * nb_vordiv );
    std::vector<double> rspecg( 2 * N );
    std::vector<double> rgp1( nb_all * g1.size() );
    std::vector<double> rgp2( nb_all * g2.size() );
    std::vector<double> rgp1_analytic( g1.size() );
    std::vector<double> rgp2_analytic( g2.size() );

    StructuredGrid gs1( g1 );
    StructuredGrid gs2( g2 );
    double latPole   = 89.9999999;
    bool gridHasPole = gs1.y( 0 ) > latPole || gs2.y( 0 ) > latPole;
    double tolerance;
    int icase = 0;
    for ( int ivar_in = 0; ivar_in < 3; ivar_in++ ) {         // vorticity, divergence, scalar
        for ( int ivar_out = 0; ivar_out < 3; ivar_out++ ) {  // u, v, scalar
            int nb_fld = 1;
            if ( ivar_out == 2 && !gridHasPole ) {
                tolerance = 1.e-13;
                nb_fld    = nb_scalar;
            }
            else {
                tolerance = 2.e-5;
                nb_fld    = nb_vordiv;
            }
            for ( int jfld = 0; jfld < nb_fld; jfld++ ) {  // multiple fields
                int k = 0;
                for ( int m = 0; m <= trc; m++ ) {                 // zonal wavenumber
                    for ( int n = m; n <= trc; n++ ) {             // total wavenumber
                        for ( int imag = 0; imag <= 1; imag++ ) {  // real and imaginary part

                            if ( sphericalharmonics_analytic_point( n, m, true, 0., 0., ivar_in, ivar_in ) == 0. &&
                                 icase < 1000 ) {
                                auto start = std::chrono::system_clock::now();
                                for ( int j = 0; j < 2 * N * nb_scalar; j++ ) {
                                    sp[j] = 0.;
                                }
                                for ( int j = 0; j < 2 * N * nb_vordiv; j++ ) {
                                    vor[j] = 0.;
                                    div[j] = 0.;
                                }
                                if ( ivar_in == 0 ) {
                                    vor[k * nb_vordiv + jfld] = 1.;
                                }
                                if ( ivar_in == 1 ) {
                                    div[k * nb_vordiv + jfld] = 1.;
                                }
                                if ( ivar_in == 2 ) {
                                    sp[k * nb_scalar + jfld] = 1.;
                                }

                                for ( int j = 0; j < nb_all * g1.size(); j++ ) {
                                    rgp1[j] = 0.;
                                }
                                for ( int j = 0; j < nb_all * g2.size(); j++ ) {
                                    rgp2[j] = 0.;
                                }
                                for ( int j = 0; j < g1.size(); j++ ) {
                                    rgp1_analytic[j] = 0.;
                                }
                                for ( int j = 0; j < g2.size(); j++ ) {
                                    rgp2_analytic[j] = 0.;
                                }

                                spectral_transform_grid_analytic( trc, fourierTrc1, n, m, imag, g1, rspecg.data(),
                                                                  rgp1_analytic.data(), ivar_in, ivar_out );

                                spectral_transform_grid_analytic( trc, fourierTrc2, n, m, imag, g2, rspecg.data(),
                                                                  rgp2_analytic.data(), ivar_in, ivar_out );

                                //Log::info() << std::endl << "rgp1:";
                                ATLAS_TRACE_SCOPE( "translocal1" )
                                EXPECT_NO_THROW( transLocal1.invtrans( nb_scalar, sp.data(), nb_vordiv, vor.data(),
                                                                       div.data(), rgp1.data() ) );

                                //Log::info() << std::endl << "rgp2:";
                                ATLAS_TRACE_SCOPE( "translocal2" )
                                EXPECT_NO_THROW( transLocal2.invtrans( nb_scalar, sp.data(), nb_vordiv, vor.data(),
                                                                       div.data(), rgp2.data() ) );

                                int pos = ( ivar_out * nb_vordiv + jfld );

                                double rms_gen1 =
                                    compute_rms( g1.size(), rgp1.data() + pos * g1.size(), rgp1_analytic.data() );

                                double rms_gen2 =
                                    compute_rms( g2.size(), rgp2.data() + pos * g2.size(), rgp2_analytic.data() );

                                //Log::info() << "Case " << icase << " ivar_in=" << ivar_in << " ivar_out=" << ivar_out
                                //            << " m=" << m << " n=" << n << " imag=" << imag << " k=" << k << std::endl
                                //            << "rgp1:";
                                //for ( int j = 0; j < g1.size(); j++ ) {
                                //    Log::info() << rgp1[pos * g1.size() + j] << " ";
                                //};
                                //Log::info() << std::endl << "analytic1:";
                                //for ( int j = 0; j < g1.size(); j++ ) {
                                //    Log::info() << rgp1_analytic[j] << " ";
                                //};
                                //Log::info() << std::endl << "rgp2:";
                                //for ( int j = 0; j < g2.size(); j++ ) {
                                //    Log::info() << rgp2[pos * g2.size() + j] << " ";
                                //};
                                //Log::info() << std::endl << "analytic2:";
                                //for ( int j = 0; j < g2.size(); j++ ) {
                                //    Log::info() << rgp2_analytic[j] << " ";
                                //};
                                //Log::info() << std::endl;
                                rav1 += rms_gen1;
                                rav2 += rms_gen2;
                                if ( !( rms_gen1 < tolerance ) || !( rms_gen2 < tolerance ) ) {
                                    Log::info()
                                        << "Case " << icase << " ivar_in=" << ivar_in << " ivar_out=" << ivar_out
                                        << " m=" << m << " n=" << n << " imag=" << imag << " k=" << k << std::endl;
                                    ATLAS_DEBUG_VAR( rms_gen1 );
                                    ATLAS_DEBUG_VAR( rms_gen2 );
                                    ATLAS_DEBUG_VAR( tolerance );
                                }
                                EXPECT( rms_gen1 < tolerance );
                                EXPECT( rms_gen2 < tolerance );
                                icase++;
                                auto end                                      = std::chrono::system_clock::now();  //
                                std::chrono::duration<double> elapsed_seconds = end - start;
                                std::time_t end_time = std::chrono::system_clock::to_time_t( end );
                                std::string time_str = std::ctime( &end_time );
                                Log::debug() << "case " << icase << ", elapsed time: " << elapsed_seconds.count()
                                             << "s. Now: " << time_str.substr( 0, time_str.length() - 1 ) << std::endl;
                            }
                            k++;
                        }
                    }
                }
            }
        }
    }
    Log::info() << "Vordiv+scalar comparison with trans: all " << icase << " cases successfully passed!" << std::endl;
    rav1 /= icase;
    Log::info() << "average RMS error of transLocal1: " << rav1 << std::endl;
    rav2 /= icase;
    Log::info() << "average RMS error of transLocal2: " << rav2 << std::endl;
}
#endif
//-----------------------------------------------------------------------------
#if 1
CASE( "test_trans_southpole" ) {
    Log::info() << "test_trans_southpole" << std::endl;
    // test created for MIR-283 (limited area domain on the southern hemisphere with L-grid)


    //Domain testdomain = ZonalBandDomain( {-90., 90.} );
    //Domain testdomain = ZonalBandDomain( {-.5, .5} );
    //Domain testdomain = RectangularDomain( {0., 30.}, {-.05, .05} );
    //Domain testdomain1 = ZonalBandDomain( {-10., 5.} );
    //Domain testdomain1 = RectangularDomain( {-5., 5.}, {-2.5, 0.} );
    Domain testdomain1 = RectangularDomain( {0., 10.}, {-90., -10.} );
    //Domain testdomain1 = RectangularDomain( {-1., 1.}, {50., 55.} );
    //Domain testdomain2 = RectangularDomain( {-5., 5.}, {-2.5, 0.} );
    Domain testdomain2 = RectangularDomain( {0., 10.}, {10., 90.} );
    // Grid: (Adjust the following line if the test takes too long!)

    Grid global_grid1( "L9" );
    Grid global_grid2( "L9" );
    Grid g1( global_grid1, testdomain1 );
    Grid g2( global_grid2, testdomain2 );
    //Grid g1( global_grid1 );
    //Grid g2( global_grid2 );

    bool fourierTrc1 = true;
    bool fourierTrc2 = true;
    // fourierTrc1, fourierTrc2: need to be false if no global grid can be constructed
    //                           (like for grids created with LinearSpacing)

    //using LinearSpacing = grid::LinearSpacing;
    //StructuredGrid g2( LinearSpacing( {0., 10.}, 2 ), LinearSpacing( {-10., -90.}, 9 ) );
    // when using LinearSpacing: set fourierTrc2 to false

    int trc = 8;
    Trace t1( Here(), "translocal1 construction" );
    trans::Trans transLocal1( global_grid1, g1.domain(), trc, option::type( "local" ) );
    t1.stop();
    Trace t2( Here(), "translocal2 construction" );
    //trans::Trans transLocal2( g2, trc, option::type( "local" ) );
    trans::Trans transLocal2( global_grid2, g2.domain(), trc, option::type( "local" ) );
    t2.stop();

    double rav1 = 0., rav2 = 0.;  // compute average rms errors of transLocal1 and transLocal2

    functionspace::Spectral spectral( trc );

    int nb_scalar = 1, nb_vordiv = 1;
    int N = ( trc + 2 ) * ( trc + 1 ) / 2, nb_all = nb_scalar + 2 * nb_vordiv;
    std::vector<double> sp( 2 * N * nb_scalar );
    std::vector<double> vor( 2 * N * nb_vordiv );
    std::vector<double> div( 2 * N * nb_vordiv );
    std::vector<double> rspecg( 2 * N );
    std::vector<double> rgp1( nb_all * g1.size() );
    std::vector<double> rgp2( nb_all * g2.size() );
    std::vector<double> rgp1_analytic( g1.size() );
    std::vector<double> rgp2_analytic( g2.size() );

    StructuredGrid gs1( g1 );
    StructuredGrid gs2( g2 );
    double latPole   = 89.9999999;
    bool gridHasPole = gs1.y( 0 ) > latPole || gs2.y( 0 ) > latPole;
    double tolerance;
    int icase = 0;
    for ( int ivar_in = 0; ivar_in < 3; ivar_in++ ) {         // vorticity, divergence, scalar
        for ( int ivar_out = 0; ivar_out < 3; ivar_out++ ) {  // u, v, scalar
            int nb_fld = 1;
            if ( ivar_out == 2 && !gridHasPole ) {
                tolerance = 1.e-13;
                nb_fld    = nb_scalar;
            }
            else {
                tolerance = 2.e-6;
                nb_fld    = nb_vordiv;
            }
            for ( int jfld = 0; jfld < nb_fld; jfld++ ) {  // multiple fields
                int k = 0;
                for ( int m = 0; m <= trc; m++ ) {                 // zonal wavenumber
                    for ( int n = m; n <= trc; n++ ) {             // total wavenumber
                        for ( int imag = 0; imag <= 1; imag++ ) {  // real and imaginary part

                            if ( sphericalharmonics_analytic_point( n, m, true, 0., 0., ivar_in, ivar_in ) == 0. &&
                                 icase < 1000 ) {
                                auto start = std::chrono::system_clock::now();
                                for ( int j = 0; j < 2 * N * nb_scalar; j++ ) {
                                    sp[j] = 0.;
                                }
                                for ( int j = 0; j < 2 * N * nb_vordiv; j++ ) {
                                    vor[j] = 0.;
                                    div[j] = 0.;
                                }
                                if ( ivar_in == 0 ) {
                                    vor[k * nb_vordiv + jfld] = 1.;
                                }
                                if ( ivar_in == 1 ) {
                                    div[k * nb_vordiv + jfld] = 1.;
                                }
                                if ( ivar_in == 2 ) {
                                    sp[k * nb_scalar + jfld] = 1.;
                                }

                                for ( int j = 0; j < nb_all * g1.size(); j++ ) {
                                    rgp1[j] = 0.;
                                }
                                for ( int j = 0; j < nb_all * g2.size(); j++ ) {
                                    rgp2[j] = 0.;
                                }
                                for ( int j = 0; j < g1.size(); j++ ) {
                                    rgp1_analytic[j] = 0.;
                                }
                                for ( int j = 0; j < g2.size(); j++ ) {
                                    rgp2_analytic[j] = 0.;
                                }

                                spectral_transform_grid_analytic( trc, fourierTrc1, n, m, imag, g1, rspecg.data(),
                                                                  rgp1_analytic.data(), ivar_in, ivar_out );

                                spectral_transform_grid_analytic( trc, fourierTrc2, n, m, imag, g2, rspecg.data(),
                                                                  rgp2_analytic.data(), ivar_in, ivar_out );

                                //Log::info() << "Case " << icase << " ivar_in=" << ivar_in << " ivar_out=" << ivar_out
                                //            << " m=" << m << " n=" << n << " imag=" << imag << " k=" << k << std::endl;

                                ATLAS_TRACE_SCOPE( "translocal1" )
                                EXPECT_NO_THROW( transLocal1.invtrans( nb_scalar, sp.data(), nb_vordiv, vor.data(),
                                                                       div.data(), rgp1.data() ) );

                                int pos = ( ivar_out * nb_vordiv + jfld );

                                double rms_gen1 =
                                    compute_rms( g1.size(), rgp1.data() + pos * g1.size(), rgp1_analytic.data() );

                                //Log::info() << "rgp1:";
                                //for ( int j = 0; j < g1.size(); j++ ) {
                                //    Log::info() << rgp1[pos * g1.size() + j] << " ";
                                //};
                                //Log::info() << std::endl << "analytic1:";
                                //for ( int j = 0; j < g1.size(); j++ ) {
                                //    Log::info() << rgp1_analytic[j] << " ";
                                //};
                                //Log::info() << std::endl;

                                ATLAS_TRACE_SCOPE( "translocal2" )
                                EXPECT_NO_THROW( transLocal2.invtrans( nb_scalar, sp.data(), nb_vordiv, vor.data(),
                                                                       div.data(), rgp2.data() ) );

                                double rms_gen2 =
                                    compute_rms( g2.size(), rgp2.data() + pos * g2.size(), rgp2_analytic.data() );

                                //Log::info() << std::endl << "rgp2:";
                                //for ( int j = 0; j < g2.size(); j++ ) {
                                //    Log::info() << rgp2[pos * g2.size() + j] << " ";
                                //};
                                //Log::info() << std::endl << "analytic2:";
                                //for ( int j = 0; j < g2.size(); j++ ) {
                                //    Log::info() << rgp2_analytic[j] << " ";
                                //};
                                //Log::info() << std::endl;
                                rav1 += rms_gen1;
                                rav2 += rms_gen2;
                                if ( !( rms_gen1 < tolerance ) || !( rms_gen2 < tolerance ) ) {
                                    Log::info()
                                        << "Case " << icase << " ivar_in=" << ivar_in << " ivar_out=" << ivar_out
                                        << " m=" << m << " n=" << n << " imag=" << imag << " k=" << k << std::endl;
                                    ATLAS_DEBUG_VAR( rms_gen1 );
                                    ATLAS_DEBUG_VAR( rms_gen2 );
                                    ATLAS_DEBUG_VAR( tolerance );
                                }
                                EXPECT( rms_gen1 < tolerance );
                                EXPECT( rms_gen2 < tolerance );
                                icase++;
                                auto end                                      = std::chrono::system_clock::now();  //
                                std::chrono::duration<double> elapsed_seconds = end - start;
                                std::time_t end_time = std::chrono::system_clock::to_time_t( end );
                                std::string time_str = std::ctime( &end_time );
                                Log::debug() << "case " << icase << ", elapsed time: " << elapsed_seconds.count()
                                             << "s. Now: " << time_str.substr( 0, time_str.length() - 1 ) << std::endl;
                            }
                            k++;
                        }
                    }
                }
            }
        }
    }
    Log::info() << "Vordiv+scalar comparison with trans: all " << icase << " cases successfully passed!" << std::endl;
    rav1 /= icase;
    Log::info() << "average RMS error of transLocal1: " << rav1 << std::endl;
    rav2 /= icase;
    Log::info() << "average RMS error of transLocal2: " << rav2 << std::endl;
}
#endif
//-----------------------------------------------------------------------------
#if 1
CASE( "test_trans_unstructured" ) {
    Log::info() << "test_trans_unstructured" << std::endl;
    // test transgeneral by comparing with analytic solution on an unstructured grid

    double tolerance = 1.e-13;

    //Domain testdomain = RectangularDomain( {20., 25.}, {40., 60.} );
    Domain testdomain = RectangularDomain( {0., 90.}, {0., 90.} );
    // Grid: (Adjust the following line if the test takes too long!)
    Grid grid_global( "F32" );
    Grid g( grid_global, testdomain );
    int trc = 31;
    StructuredGrid gs( g );
    std::vector<PointXY> pts( g.size() );
    idx_t idx( 0 );
    for ( idx_t j = 0; j < gs.ny(); ++j ) {
        double lat = gs.y( j );
        for ( idx_t i = 0; i < gs.nx( j ); ++i ) {
            double lon = gs.x( i, j );
            if ( i == j && lat > 0 ) {
                //Log::info() << "idx=" << idx << " lon=" << lon << " lat=" << lat << std::endl;
                pts[idx++].assign( lon, lat );
            }
        }
    }
    Grid gu = UnstructuredGrid( new std::vector<PointXY>( &pts[0], &pts[idx] ) );
    Log::info() << "gu: size=" << gu.size() << std::endl;
    double rav1 = 0., rav2 = 0.;  // compute average rms errors of transLocal1 and transLocal2

    int nb_scalar = 1, nb_vordiv = 1;
    int N = ( trc + 2 ) * ( trc + 1 ) / 2, nb_all = nb_scalar + 2 * nb_vordiv;
    std::vector<double> sp( 2 * N * nb_scalar );
    std::vector<double> vor( 2 * N * nb_vordiv );
    std::vector<double> div( 2 * N * nb_vordiv );
    std::vector<double> rspecg( 2 * N );
    std::vector<double> gp( nb_all * g.size() );
    std::vector<double> rgp1( nb_all * g.size() );
    std::vector<double> rgp2( nb_all * g.size() );
    std::vector<double> rgp_analytic1( g.size() );
    std::vector<double> rgp_analytic2( gu.size() );

    trans::Trans transLocal1( grid_global, testdomain, trc, option::type( "local" ) );

    // ATLAS-173 : This should also work with precompute = true, ans should give same.
    trans::Trans transLocal2( gu, trc, option::type( "local" ) | util::Config( "precompute", false ) );

    int icase = 0;
    for ( int ivar_in = 0; ivar_in < 3; ivar_in++ ) {         // vorticity, divergence, scalar
        for ( int ivar_out = 0; ivar_out < 3; ivar_out++ ) {  // u, v, scalar
            int nb_fld = 1;
            if ( ivar_out == 2 ) {
                tolerance = 1.e-13;
                nb_fld    = nb_scalar;
            }
            else {
                tolerance = 2.e-6;
                nb_fld    = nb_vordiv;
            }
            for ( int jfld = 0; jfld < nb_fld; jfld++ ) {  // multiple fields
                int k = 0;
                for ( int m = 0; m <= trc; m++ ) {                 // zonal wavenumber
                    for ( int n = m; n <= trc; n++ ) {             // total wavenumber
                        for ( int imag = 0; imag <= 1; imag++ ) {  // real and imaginary part

                            if ( sphericalharmonics_analytic_point( n, m, true, 0., 0., ivar_in, ivar_in ) == 0. &&
                                 icase < 1000 ) {
                                auto start = std::chrono::system_clock::now();
                                for ( int j = 0; j < 2 * N * nb_scalar; j++ ) {
                                    sp[j] = 0.;
                                }
                                for ( int j = 0; j < 2 * N * nb_vordiv; j++ ) {
                                    vor[j] = 0.;
                                    div[j] = 0.;
                                }
                                if ( ivar_in == 0 ) {
                                    vor[k * nb_vordiv + jfld] = 1.;
                                }
                                if ( ivar_in == 1 ) {
                                    div[k * nb_vordiv + jfld] = 1.;
                                }
                                if ( ivar_in == 2 ) {
                                    sp[k * nb_scalar + jfld] = 1.;
                                }

                                for ( int j = 0; j < nb_all * g.size(); j++ ) {
                                    gp[j]   = 0.;
                                    rgp1[j] = 0.;
                                    rgp2[j] = 0.;
                                }
                                for ( int j = 0; j < g.size(); j++ ) {
                                    rgp_analytic1[j] = 0.;
                                }

                                for ( int j = 0; j < gu.size(); j++ ) {
                                    rgp_analytic2[j] = 0.;
                                }

                                spectral_transform_grid_analytic( trc, false, n, m, imag, g, rspecg.data(),
                                                                  rgp_analytic1.data(), ivar_in, ivar_out );

                                //Log::info() << icase << " m=" << m << " n=" << n << " imag=" << imag << " structured: ";
                                ATLAS_TRACE_SCOPE( "structured" )
                                EXPECT_NO_THROW( transLocal1.invtrans( nb_scalar, sp.data(), nb_vordiv, vor.data(),
                                                                       div.data(), rgp1.data() ) );

                                spectral_transform_grid_analytic( trc, false, n, m, imag, gu, rspecg.data(),
                                                                  rgp_analytic2.data(), ivar_in, ivar_out );

                                //Log::info() << icase << " m=" << m << " n=" << n << " imag=" << imag << " unstructured: ";
                                ATLAS_TRACE_SCOPE( "unstructured" )
                                EXPECT_NO_THROW( transLocal2.invtrans( nb_scalar, sp.data(), nb_vordiv, vor.data(),
                                                                       div.data(), rgp2.data() ) );

                                int pos = ( ivar_out * nb_vordiv + jfld );

                                double rms_gen1 =
                                    compute_rms( g.size(), rgp1.data() + pos * g.size(), rgp_analytic1.data() );

                                double rms_gen2 =
                                    compute_rms( gu.size(), rgp2.data() + pos * gu.size(), rgp_analytic2.data() );

                                rav1 += rms_gen1;
                                rav2 += rms_gen2;
                                if ( !( rms_gen1 < tolerance ) || !( rms_gen2 < tolerance ) ) {
                                    Log::info()
                                        << "Case " << icase << " ivar_in=" << ivar_in << " ivar_out=" << ivar_out
                                        << " m=" << m << " n=" << n << " imag=" << imag << " k=" << k << std::endl;
                                    ATLAS_DEBUG_VAR( rms_gen1 );
                                    ATLAS_DEBUG_VAR( rms_gen2 );
                                    ATLAS_DEBUG_VAR( tolerance );
                                }
                                EXPECT( rms_gen1 < tolerance );
                                EXPECT( rms_gen2 < tolerance );
                                icase++;
                                auto end                                      = std::chrono::system_clock::now();  //
                                std::chrono::duration<double> elapsed_seconds = end - start;
                                std::time_t end_time = std::chrono::system_clock::to_time_t( end );
                                std::string time_str = std::ctime( &end_time );
                                Log::debug() << "case " << icase << ", elapsed time: " << elapsed_seconds.count()
                                             << "s. Now: " << time_str.substr( 0, time_str.length() - 1 ) << std::endl;
                            }
                            k++;
                        }
                    }
                }
            }
        }
    }
    Log::info() << "Vordiv+scalar comparison with trans: all " << icase << " cases successfully passed!" << std::endl;
    rav1 /= icase;
    Log::info() << "average RMS error of transLocal1: " << rav1 << std::endl;
    rav2 /= icase;
    Log::info() << "average RMS error of transLocal2: " << rav2 << std::endl;
}
#endif

//-----------------------------------------------------------------------------

#if 0
CASE( "test_trans_fourier_truncation" ) {
    Log::info() << "test_trans_fourier_truncation" << std::endl;
    // test transgeneral by comparing its result with the trans library
    // this test is based on the test_nomesh case in test_trans.cc

    Grid g( "F640" );
    grid::StructuredGrid gs( g );
    int ndgl = gs.ny();
    //int trc = 2*ndgl; // extreme high truncation (below linear)
    int trc = ndgl - 1;  // linear
    //int trc = 5./6.*ndgl-1; // between linear and quadratic
    //int trc = 2./3.*ndgl-1; // quadratic
    //int trc = ndgl/2. -1; // cubic
    trans::Trans trans( g, trc );
    for ( int j = 0; j < gs.ny(); j += 80 ) {
        double lat = gs.y( j ) * util::Constants::degreesToRadians();
        int trcFT  = trans::fourier_truncation( trc, gs.nx( j ), gs.nxmax(), gs.ny(), lat, grid::RegularGrid( g ) );
        Log::info() << trcFT << "         " << gs.nx( j ) << std::endl;
    }
    // TODO: create some real criterion to test fourier_truncation. So far only comparison with trans library through print statements.
}
#endif
//-----------------------------------------------------------------------------

}  // namespace test
}  // namespace atlas

int main( int argc, char** argv ) {
    return atlas::test::run<atlas::test::AtlasTransEnvironment>( argc, argv );
}
