/*
 * Normaliz
 * Copyright (C) 2007-2025  W. Bruns, B. Ichim, Ch. Soeger, U. v. d. Ohe
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * As an exception, when this program is distributed through (i) the App Store
 * by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or (iii) Google Play
 * by Google Inc., then that store may impose any digital rights management,
 * device limits and/or redistribution restrictions that are required by its
 * terms of service.
 */

/**
 * The class Sublattice_Representation represents a sublattice of Z^n as Z^r.
 * To transform vectors of the sublattice  use:
 *    Z^r --> Z^n    and    Z^n -->  Z^r
 *     v  |-> vA             u  |-> (uB)/c
 * A  r x n matrix
 * B  n x r matrix
 * c  Integer
 */

#include "libnormaliz/sublattice_representation.h"
#include "libnormaliz/vector_operations.h"

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

namespace libnormaliz {
using namespace std;

/**
 * creates a representation of Z^n as a sublattice of itself
 */
template <typename Integer>
Sublattice_Representation<Integer>::Sublattice_Representation(size_t n) {
    dim = n;
    rank = n;
    external_index = 1;
    A = Matrix<Integer>(n);
    B = Matrix<Integer>(n);
    c = 1;
    Equations_computed = false;
    Congruences_computed = false;
    is_identity = true;
    B_is_projection = true;
    projection_key = identity_key(n);
}

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

/**
 * Main Constructor
 * creates a representation of a sublattice of Z^n
 * if direct_summand is false the sublattice is generated by the rows of M
 * otherwise it is a direct summand of Z^n containing the rows of M
 */

template <typename Integer>
Sublattice_Representation<Integer>::Sublattice_Representation(const Matrix<Integer>& M, bool take_saturation, bool use_LLL) {
    bool success;
    initialize(M, take_saturation, success);
    if (success) {
        if (use_LLL)
            LLL_improve();
    }
    else {
        Matrix<mpz_class> mpz_M(M.nr, M.nc);
        // mat_to_mpz(M,mpz_M);
        convert(mpz_M, M);
        Sublattice_Representation<mpz_class> mpz_SLR;
        mpz_SLR.initialize(mpz_M, take_saturation, success);
        if (use_LLL)
            mpz_SLR.LLL_improve();
        A.resize(mpz_SLR.A.nr, mpz_SLR.A.nc);
        B.resize(mpz_SLR.B.nr, mpz_SLR.B.nc);
        // mat_to_Int(mpz_SLR.A,A);
        convert(A, mpz_SLR.A);
        // mat_to_Int(mpz_SLR.B,B);
        convert(B, mpz_SLR.B);
        convert(c, mpz_SLR.c);
        rank = mpz_SLR.rank;
    }
}

/* Creates a representation from given maps and the factor c
 *
 */

template <typename Integer>
Sublattice_Representation<Integer>::Sublattice_Representation(const Matrix<Integer>& GivenA,
                                                              const Matrix<Integer>& GivenB,
                                                              Integer GivenC) {
    dim = GivenA.nc;
    rank = GivenA.nr;
    assert(GivenB.nr == dim);
    assert(GivenB.nc == rank);
    Matrix<Integer> Test(rank);
    Test.scalar_multiplication(GivenC);
    Matrix<Integer> Test1 = GivenA.multiplication(GivenB);
    assert(Test1.equal(Test));

    external_index = 1;  // to have a value, will be computed if asked for
    A = GivenA;
    B = GivenB;
    c = GivenC;
    Equations_computed = false;
    Congruences_computed = false;
    is_identity = false;
    // Test1=Matrix<Integer>(rank);
    if (c == 1 && A.equal(Test)) {
        is_identity = true;
    }
    B_is_projection = B.check_projection(projection_key);
}

template <typename Integer>
void Sublattice_Representation<Integer>::initialize(const Matrix<Integer>& M, bool take_saturation, bool& success) {
    Equations_computed = false;
    Congruences_computed = false;
    is_identity = false;
    B_is_projection = false;

    success = true;

    // cout << "=============================================" << endl;

    dim = M.nr_of_columns();
    Matrix<Integer> N = M;
    // M.debug_print('M');

    rank = N.row_echelon_reduce(success);  // reduce is importnat here, will be used
    if (!success)
        return;

    if (rank == dim && take_saturation) {
        A = B = Matrix<Integer>(dim);
        c = 1;
        is_identity = true;
        B_is_projection = true;
        projection_key = identity_key(dim);
        return;
    }

    mpz_class row_index = 1;  // product of the corner elements in the row echelon form
    vector<key_t> col(rank);
    vector<bool> col_is_corner(dim, false);  // indicates whether the column is a corner in the
    for (size_t k = 0; k < rank; ++k) {      // row echelin form
        size_t j = 0;
        for (; j < dim; ++j)
            if (N[k][j] != 0)
                break;
        col_is_corner[j] = true;
        col[k] = static_cast<key_t>(j);
        if (N[k][j] < 0)
            v_scalar_multiplication<Integer>(N[k], -1);  // make corner positive
        row_index *= convertTo<mpz_class>(N[k][j]);
    }

    if (row_index == 1 && rank == dim) {  // the sublattice is the full lattice and no saturation needed
        A = B = Matrix<Integer>(dim);
        c = 1;
        is_identity = true;
        B_is_projection = true;
        projection_key = identity_key(dim);
        return;
    }

    A.resize(rank, dim);
    B.resize(dim, rank);

    if (row_index == 1) {  // no saturation needed since sublattice is direct summand

        for (size_t k = 0; k < rank; ++k)
            A[k] = N[k];  // A is just the basis of our sublattice
        size_t j = 0;
        for (size_t k = 0; k < dim; ++k) {
            if (col_is_corner[k]) {
                B[k][j] = 1;  // projection to the corner columns, allowed because of reduction!
                j++;
            }
        };
        c = 1;
        B_is_projection = true;
        B_is_projection = B.check_projection(projection_key);
        return;
    }

    if (!take_saturation) {
        Matrix<Integer> P(dim, dim);  // A augmented by unit vectors to full rank
        for (size_t k = 0; k < rank; ++k)
            A[k] = P[k] = N[k];
        size_t k = rank;
        for (size_t j = 0; j < dim; ++j) {
            if (col_is_corner[j])
                continue;
            P[k][j] = 1;
            k++;
        }
        Matrix<Integer> Q = P.invert_unprotected(c, success);
        if (!success)
            return;

        for (k = 0; k < dim; ++k)  // we take the partial inverse belonging to the first rankk rows of A
            for (size_t j = 0; j < rank; ++j)
                B[k][j] = Q[k][j];
        B_is_projection = B.check_projection(projection_key);
        return;
    }

    // now we must take the saturation.
    // We do it by computing a complement of the smallest direct summand containing
    // the sublattice and then taking its complement.

    Matrix<Integer> R_inv(dim);
    success = N.column_trigonalize(rank, R_inv);
    Matrix<Integer> R = R_inv.invert_unprotected(c, success);  // yields c=1 as it should be in this case
    if (!success)
        return;
    assert(c==1);

    for (size_t i = 0; i < rank; i++) {
        for (size_t j = 0; j < dim; j++) {
            A[i][j] = R[i][j];
            B[j][i] = R_inv[j][i];
        }
    }
    B_is_projection = B.check_projection(projection_key);
    /*
    cout << "how Whow Whow" << endl;
    A.debug_print('A');
    B.debug_print('B');
    Matrix<Integer> PP = A.multiplication(B);
    PP.debug_print('P');
    Matrix<Integer> QQ = M.multiplication(B);
    M.debug_print('M');
    QQ = QQ.multiplication(A);
    QQ.debug_print('Q');
    */
    return;
}

#ifdef ENFNORMALIZ
template <>
void Sublattice_Representation<renf_elem_class>::initialize(const Matrix<renf_elem_class>& M,
                                                            bool take_saturation,
                                                            bool& success) {
    success = true;  // no ovberflow possible

    Equations_computed = false;
    is_identity = false;

    dim = M.nr_of_columns();
    Matrix<renf_elem_class> N = M;

    rank = N.row_echelon_reduce(success);

    if (rank == dim) {
        A = B = Matrix<renf_elem_class>(dim);
        c = 1;
        is_identity = true;
        projection_key = identity_key(dim);
        return;
    }

    vector<key_t> col(rank);
    vector<bool> col_is_corner(dim, false);
    for (size_t k = 0; k < rank; ++k) {
        size_t j = 0;
        for (; j < dim; ++j)
            if (N[k][j] != 0)
                break;
        col_is_corner[j] = true;
        col[k] = j;
        if (N[k][j] != 1) {
            renf_elem_class pivot = N[k][j];
            v_scalar_division<renf_elem_class>(N[k], pivot);
        }
    }

    A.resize(rank, dim);
    B.resize(dim, rank);

    for (size_t k = 0; k < rank; ++k)
        A[k] = N[k];
    size_t j = 0;
    for (size_t k = 0; k < dim; ++k) {
        if (col_is_corner[k]) {
            B[k][j] = 1;  // the corner elements in A are equal to 1
            j++;
        }
    };
    c = 1;

    B_is_projection = B.check_projection(projection_key);
    return;
}
#endif

template <typename Integer>
void Sublattice_Representation<Integer>::LLL_improve() {
    if (using_renf<Integer>() || using_float<Integer>())
        return;

    if (is_identity)
        return;
    // We want to give the matrix B small entries since it deternines
    // the transformation to the sublattice
    Sublattice_Representation LLL_trans = LLL_coordinates<Integer>(B);
    compose(LLL_trans);
}

//---------------------------------------------------------------------------
//                       Manipulation
//---------------------------------------------------------------------------

/* first this then SR when going from Z^n to Z^r */
template <typename Integer>
void Sublattice_Representation<Integer>::compose(const Sublattice_Representation& SR) {
    assert(rank == SR.dim);  // TODO vielleicht doch exception?

    /* cout << "======= " << projection_key;
    B.pretty_print(cout);
    cout << "+++++++ " << SR.projection_key;
    SR.B.pretty_print(cout);*/

    if (SR.is_identity)
        return;

    if (is_identity) {
        *this = SR;
        return;
    }

    Equations_computed = false;
    Congruences_computed = false;

    rank = SR.rank;
    // A = SR.A * A
    A = SR.A.multiplication(A);
    // B = B * SR.B
    B = B.multiplication(SR.B);
    c = c * SR.c;

    // check if a factor can be extracted from B  //TODO necessary?
    if (!using_float<Integer>() && !using_renf<Integer>()) {
        Integer g = B.matrix_gcd();
        g = libnormaliz::gcd(g, c);  // TODO necessary??
        if (g > 1) {
            c /= g;
            B.scalar_division(g);
        }
    }
    is_identity &= SR.is_identity;
    B_is_projection = B.check_projection(projection_key);
}

template <typename Integer>
void Sublattice_Representation<Integer>::compose_dual(const Sublattice_Representation& SR) {
    assert(rank == SR.dim);  //
    assert(SR.c == 1);

    // B_is_projection=false;

    if (SR.is_identity)
        return;

    Equations_computed = false;
    Congruences_computed = false;
    rank = SR.rank;

    if (is_identity) {
        A = SR.B.transpose();
        B = SR.A.transpose();
        is_identity = false;
        B_is_projection = B.check_projection(projection_key);
        return;
    }

    // Now we compose with the dual of SR
    A = SR.B.transpose().multiplication(A);
    // B = B * SR.B
    B = B.multiplication(SR.A.transpose());

    if (!using_float<Integer>() && !using_renf<Integer>()) {
        // check if a factor can be extracted from B  //TODO necessary?
        Integer g = B.matrix_gcd();
        g = libnormaliz::gcd(g, c);  // TODO necessary??
        if (g > 1) {
            c /= g;
            B.scalar_division(g);
        }
    }
    is_identity &= SR.is_identity;
    B_is_projection = B.check_projection(projection_key);
}

// Let *this represent the sublattice L. We want to pass to L/Sub and to compute Perp, namely
// (L/Sub)^* \subset L^*.
// Instrad of Sub we can also give its "orthogonal" Perp. Then Sub is computed.
// One of the matrices must be empty.
// If Sub is empty, we assume it must be computed.
// If Sub is nonempty, we assume that Perp must be computed.
// Suib and Perp are given/computed in the coordinates of the ambient space
//
// If Sub is
template <typename Integer>
void Sublattice_Representation<Integer>::compose_with_passage_to_quotient(Matrix<Integer>& Sub, Matrix<Integer>& Perp) {
    assert(Sub.nr_of_rows() == 0 || Perp.nr_of_rows() == 0);

    // go to L
    Matrix<Integer> Sub_L;
    Sub_L = to_sublattice(Sub);
    Matrix<Integer> Perp_L;
    Perp_L = to_sublattice_dual(Perp);

    // compute the "other"
    bool useLLL = false;
    if (!using_renf<Integer>())
        useLLL = true;
    if (Sub_L.nr_of_rows() == 0)
        Sub_L = Perp_L.kernel(useLLL);
    else
        Perp_L = Sub_L.kernel(useLLL);

    // back to ambient space
    Sub = from_sublattice(Sub_L);
    Perp = from_sublattice_dual(Perp_L);

    Sub.standardize_basis();
    Perp.standardize_basis();

    Sublattice_Representation<Integer> QuotentDual(Perp_L, true,useLLL);

    compose_dual(QuotentDual);
}

//---------------------------------------------------------------------------
//                       Transformations
//---------------------------------------------------------------------------

template <typename Integer>
Matrix<Integer> Sublattice_Representation<Integer>::to_sublattice(const Matrix<Integer>& M) const {
    Matrix<Integer> N;
    if (is_identity)
        N = M;
    else {
        if (B_is_projection)
            N = M.select_coordinates(projection_key);
        else
            N = M.multiplication(B);
    }
    if (c != 1)
        N.scalar_division(c);  // on the sublattice this is multiplication by c!!
    return N;
}
template <typename Integer>
Matrix<Integer> Sublattice_Representation<Integer>::from_sublattice(const Matrix<Integer>& M) const {
    Matrix<Integer> N;
    if (is_identity)
        N = M;
    else
        N = M.multiplication(A);
    return N;
}

template <typename Integer>
void Sublattice_Representation<Integer>::convert_from_sublattice(Matrix<Integer>& ret, const Matrix<Integer>& val) const {
    ret.resize(val.nr_of_rows(), dim);

    bool skip_remaining = false;
    std::exception_ptr tmp_exception;

#pragma omp parallel for
    for (size_t i = 0; i < val.nr_of_rows(); ++i) {
        if (skip_remaining)
            continue;
        try {
            INTERRUPT_COMPUTATION_BY_EXCEPTION

            if (is_identity)
                ret[i] = val[i];
            else
                ret[i] = from_sublattice(val[i]);

        } catch (const std::exception&) {
            tmp_exception = std::current_exception();
            skip_remaining = true;
#pragma omp flush(skip_remaining)
        }
    }

    if (!(tmp_exception == 0))
        std::rethrow_exception(tmp_exception);
}

template <typename Integer>
void Sublattice_Representation<Integer>::convert_from_sublattice_dual(Matrix<Integer>& ret, const Matrix<Integer>& val) const {
    ret.resize(val.nr_of_rows(), dim);

    bool skip_remaining = false;
    std::exception_ptr tmp_exception;

#pragma omp parallel for
    for (size_t i = 0; i < val.nr_of_rows(); ++i) {
        if (skip_remaining)
            continue;
        try {
            INTERRUPT_COMPUTATION_BY_EXCEPTION

            if (is_identity)
                ret[i] = val[i];
            else
                ret[i] = from_sublattice_dual(val[i]);

        } catch (const std::exception&) {
            tmp_exception = std::current_exception();
            skip_remaining = true;
#pragma omp flush(skip_remaining)
        }
    }

    if (!(tmp_exception == 0))
        std::rethrow_exception(tmp_exception);
}

template <typename Integer>
template <typename FromType>
void Sublattice_Representation<Integer>::convert_from_sublattice(Matrix<Integer>& ret, const Matrix<FromType>& val) const {
    ret.resize(val.nr_of_rows(), dim);

    bool skip_remaining = false;
    std::exception_ptr tmp_exception;

#pragma omp parallel
    {
        vector<Integer> v;
#pragma omp for
        for (size_t i = 0; i < val.nr_of_rows(); ++i) {
            if (skip_remaining)
                continue;
            try {
                INTERRUPT_COMPUTATION_BY_EXCEPTION

                convert(v, val[i]);
                if (is_identity)
                    swap(ret[i], v);
                else
                    ret[i] = from_sublattice(v);

            } catch (const std::exception&) {
                tmp_exception = std::current_exception();
                skip_remaining = true;
#pragma omp flush(skip_remaining)
            }
        }
    }  // parallel

    if (!(tmp_exception == 0))
        std::rethrow_exception(tmp_exception);
}

template <typename Integer>
template <typename FromType>
void Sublattice_Representation<Integer>::convert_from_sublattice_dual(Matrix<Integer>& ret, const Matrix<FromType>& val) const {
    ret.resize(val.nr_of_rows(), dim);

    bool skip_remaining = false;
    std::exception_ptr tmp_exception;

#pragma omp parallel
    {
        vector<Integer> v;
#pragma omp for
        for (size_t i = 0; i < val.nr_of_rows(); ++i) {
            if (skip_remaining)
                continue;
            try {
                INTERRUPT_COMPUTATION_BY_EXCEPTION

                convert(v, val[i]);
                if (is_identity)
                    swap(ret[i], v);
                else
                    ret[i] = from_sublattice_dual(v);

            } catch (const std::exception&) {
                tmp_exception = std::current_exception();
                skip_remaining = true;
#pragma omp flush(skip_remaining)
            }
        }
    }  // parallel
}

template <typename Integer>
Matrix<Integer> Sublattice_Representation<Integer>::to_sublattice_dual(const Matrix<Integer>& M) const {
    Matrix<Integer> N;
    if (is_identity)
        N = M;
    else
        N = M.multiplication_trans(A);
    N.make_prime();
    return N;
}

template <typename Integer>
Matrix<Integer> Sublattice_Representation<Integer>::from_sublattice_dual(const Matrix<Integer>& M) const {
    Matrix<Integer> N;
    if (is_identity)
        N = M;
    else {
        if (B_is_projection)
            N = M.insert_coordinates(projection_key, dim);
        else
            N = M.multiplication_trans(B);
    }
    N.make_prime();
    return N;
}

template <typename Integer>
vector<Integer> Sublattice_Representation<Integer>::to_sublattice(const vector<Integer>& V) const {
    if (is_identity)
        return V;
    vector<Integer> N;
    if (B_is_projection)
        N = v_select_coordinates(V, projection_key);
    else
        N = B.VxM(V);
    if (c != 1)
        v_scalar_division(N, c);
    return N;
}

template <typename Integer>
vector<Integer> Sublattice_Representation<Integer>::from_sublattice(const vector<Integer>& V) const {
    if (is_identity)
        return V;
    vector<Integer> N = A.VxM(V);
    return N;
}

template <typename Integer>
vector<Integer> Sublattice_Representation<Integer>::to_sublattice_dual(const vector<Integer>& V) const {
    vector<Integer> N;
    if (is_identity)
        N = V;
    else
        N = A.MxV(V);
    v_make_prime(N);
    return N;
}

template <typename Integer>
vector<Integer> Sublattice_Representation<Integer>::from_sublattice_dual(const vector<Integer>& V) const {
    vector<Integer> N;
    if (is_identity)
        N = V;
    else {
        if (B_is_projection)
            N = v_insert_coordinates(V, projection_key, dim);
        else
            N = B.MxV(V);
    }
    v_make_prime(N);
    return N;
}

template <typename Integer>
vector<Integer> Sublattice_Representation<Integer>::to_sublattice_dual_no_div(const vector<Integer>& V) const {
    if (is_identity)
        return V;
    vector<Integer> N = A.MxV(V);
    return N;
}

//---------------------------------------------------------------------------
//                       Data access
//---------------------------------------------------------------------------

/* returns the dimension of the ambient space */
template <typename Integer>
size_t Sublattice_Representation<Integer>::getDim() const {
    return dim;
}

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

/* returns the rank of the sublattice */
template <typename Integer>
size_t Sublattice_Representation<Integer>::getRank() const {
    return rank;
}

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

template <typename Integer>
const Matrix<Integer>& Sublattice_Representation<Integer>::getEmbeddingMatrix() const {
    return A;
}

template <typename Integer>
const vector<vector<Integer> >& Sublattice_Representation<Integer>::getEmbedding() const {
    return getEmbeddingMatrix().get_elements();
}

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

template <typename Integer>
const Matrix<Integer>& Sublattice_Representation<Integer>::getProjectionMatrix() const {
    return B;
}

template <typename Integer>
const vector<vector<Integer> >& Sublattice_Representation<Integer>::getProjection() const {
    return getProjectionMatrix().get_elements();
}

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

template <typename Integer>
Integer Sublattice_Representation<Integer>::getAnnihilator() const {
    return c;
}

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

template <typename Integer>
bool Sublattice_Representation<Integer>::IsIdentity() const {
    return is_identity;
}

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

template <typename Integer>
bool Sublattice_Representation<Integer>::equal(const Sublattice_Representation& SLR) const {
    return A.equal(SLR.A) && B.equal(SLR.B) && c == SLR.c;
}

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

/* returns the congruences defining the sublattice */

template <typename Integer>
const Matrix<Integer>& Sublattice_Representation<Integer>::getEquationsMatrix() const {
    if (!Equations_computed)
        make_equations();
    return Equations;
}

template <typename Integer>
const vector<vector<Integer> >& Sublattice_Representation<Integer>::getEquations() const {
    return getEquationsMatrix().get_elements();
}

template <typename Integer>
void Sublattice_Representation<Integer>::make_equations() const {
    if (rank == dim)
        Equations.resize(0, dim);
    else
        Equations = A.kernel(false);
    Equations_computed = true;
}

template <typename Integer>
const Matrix<Integer>& Sublattice_Representation<Integer>::getCongruencesMatrix() const {
    if (!Congruences_computed)
        make_congruences();
    return Congruences;
}

template <typename Integer>
const vector<vector<Integer> >& Sublattice_Representation<Integer>::getCongruences() const {
    return getCongruencesMatrix().get_elements();
}

template <typename Integer>
mpz_class Sublattice_Representation<Integer>::getExternalIndex() const {
    if (!Congruences_computed)
        make_congruences();
    return external_index;
}

template <typename Integer>
void Sublattice_Representation<Integer>::make_congruences() const {
    if (c == 1) {  // no congruences then
        Congruences.resize(0, dim + 1);
        Congruences_computed = true;
        external_index = 1;
        return;
    }

    size_t dummy;
    Matrix<Integer> A_Copy = A;
    Matrix<Integer> Transf = A_Copy.SmithNormalForm(dummy);

    // Congruences given by first rank columns of Transf transposed and with an extra column for the modulus m
    // The moduli are the diagonal elements of the Smith normal form

    // Transf.pretty_print(cout);

    Transf.append(Matrix<Integer>(1, dim));
    Transf = Transf.transpose();
    Matrix<Integer> Transf2(0, dim + 1);  // only the relevant congruences
    for (size_t k = 0; k < rank; ++k) {
        if (A_Copy[k][k] != 1) {
            Transf2.append(Transf[k]);
            Transf2[Transf2.nr - 1][dim] = A_Copy[k][k];
            for (size_t j = 0; j < dim; ++j) {
                Transf2[Transf2.nr - 1][j] %= A_Copy[k][k];
                if (Transf2[Transf2.nr - 1][j] < 0)
                    Transf2[Transf2.nr - 1][j] += A_Copy[k][k];
            }
        }
    }
    Congruences = Transf2;
    Congruences_computed = true;
    external_index = 1;
    for (size_t i = 0; i < Transf2.nr; ++i)
        external_index *= convertTo<mpz_class>(Transf2[i][dim]);
}

#ifdef ENFNORMALIZ
template <>
void Sublattice_Representation<renf_elem_class>::make_congruences() const {
    Congruences.resize(0, dim + 1);
}
#endif

#ifndef NMZ_MIC_OFFLOAD  // offload with long is not supported
template class Sublattice_Representation<long>;
template void Sublattice_Representation<long>::convert_from_sublattice_dual<long long>(Matrix<long>& ret,
                                                                                       const Matrix<long long>& val) const;
template void Sublattice_Representation<long>::convert_from_sublattice<long long>(Matrix<long>& ret,
                                                                                  const Matrix<long long>& val) const;

#endif
template class Sublattice_Representation<long long>;
template class Sublattice_Representation<mpz_class>;
template void Sublattice_Representation<mpz_class>::convert_from_sublattice_dual<long long>(Matrix<mpz_class>& ret,
                                                                                            const Matrix<long long>& val) const;
template void Sublattice_Representation<mpz_class>::convert_from_sublattice<long long>(Matrix<mpz_class>& ret,
                                                                                       const Matrix<long long>& val) const;
#ifdef ENFNORMALIZ
template class Sublattice_Representation<renf_elem_class>;
template void Sublattice_Representation<renf_elem_class>::convert_from_sublattice_dual<long long>(
    Matrix<renf_elem_class>& ret, const Matrix<long long>& val) const;
template void Sublattice_Representation<renf_elem_class>::convert_from_sublattice<long long>(Matrix<renf_elem_class>& ret,
                                                                                             const Matrix<long long>& val) const;
#endif

}  // namespace libnormaliz
