<filename>pyviennacl/tags.py
"""
TODO docstring
"""

from pyviennacl import _viennacl as _v, backend


class SolverTag(object):
    def vcl_solve_call(self, *args):
        raise NotImplementedError("Solver not implemented")

    def vcl_inplace_solve_call(self, *args):
        raise NotImplementedError("In-place solver not implemented")

class SolverWithoutPreconditioner(object): pass

class EigenvalueTag(object): pass

class NMFTag(object): pass

class BandwidthReductionTag(object): pass

class PreconditionerTag(object):
    vcl_precond_type = None
    vcl_tag_type = None

    def instantiate(self, leaf):
        self.vcl_precond_type = getattr(_v, self.vcl_precond_type_name + "_" + type(leaf.vcl_leaf).__name__)
        vcl_precond = self.vcl_precond_type(leaf.vcl_leaf, self.vcl_tag)
        self.vcl_precond = vcl_precond


class NoPreconditioner(PreconditionerTag):
    vcl_tag_type = None
    vcl_precond_type = _v.no_precond

    def __init__(self):
        self.vcl_tag = None
        self.vcl_precond = self.vcl_precond_type()

    def instantiate(self, leaf): pass


class ICHOL0(PreconditionerTag):
    vcl_tag_type = _v.ichol0_tag
    vcl_precond_type_name = 'ichol0_precond'

    def __init__(self):
        self.vcl_tag = self.vcl_tag_type()


class ILUT(PreconditionerTag):
    """
    TODO docstring
    """
    vcl_tag_type = _v.ilut_tag
    vcl_precond_type_name = 'ilut_precond'

    def __init__(self, entries_per_row=20, drop_tolerance=1e-4, with_level_scheduling=False):
        """
        TODO docstring
        """
        self.vcl_tag = self.vcl_tag_type(entries_per_row, drop_tolerance, with_level_scheduling)

    @property
    def entries_per_row(self):
        return self.vcl_tag.entries_per_row

    @property
    def drop_tolerance(self):
        return self.vcl_tag.drop_tolerance

    @property
    def with_level_scheduling(self):
        return self.vcl_tag.with_level_scheduling


class BlockILUT(PreconditionerTag):
    """
    TODO docstring
    """
    vcl_tag_type = _v.ilut_tag
    vcl_precond_type_name = 'block_ilut_precond'

    def __init__(self, entries_per_row=20, drop_tolerance=1e-4, with_level_scheduling=False, num_blocks=8):
        """
        TODO docstring
        """
        self.num_blocks = num_blocks
        self.vcl_tag = self.vcl_tag_type(entries_per_row, drop_tolerance, with_level_scheduling)

    def instantiate(self, leaf):
        self.vcl_precond_type = getattr(_v, self.vcl_precond_type_name + "_" + type(leaf.vcl_leaf).__name__)
        vcl_precond = self.vcl_precond_type(leaf.vcl_leaf, self.vcl_tag, self.num_blocks)
        self.vcl_precond = vcl_precond

    @property
    def entries_per_row(self):
        return self.vcl_tag.entries_per_row

    @property
    def drop_tolerance(self):
        return self.vcl_tag.drop_tolerance

    @property
    def with_level_scheduling(self):
        return self.vcl_tag.with_level_scheduling


class ILU0(PreconditionerTag):
    """
    TODO docstring
    """
    vcl_tag_type = _v.ilu0_tag
    vcl_precond_type_name = 'ilu0_precond'

    def __init__(self, with_level_scheduling=False):
        """
        TODO docstring
        """
        self.vcl_tag = self.vcl_tag_type(with_level_scheduling)

    @property
    def with_level_scheduling(self):
        return self.vcl_tag.with_level_scheduling


class BlockILU0(PreconditionerTag):
    """
    TODO docstring
    """
    vcl_tag_type = _v.ilu0_tag
    vcl_precond_type_name = 'block_ilu0_precond'

    def __init__(self, with_level_scheduling=False, num_blocks=8):
        """
        TODO docstring
        """
        self.num_blocks = num_blocks
        self.vcl_tag = self.vcl_tag_type(with_level_scheduling)

    def instantiate(self, leaf):
        self.vcl_precond_type = getattr(_v, self.vcl_precond_type_name + "_" + type(leaf.vcl_leaf).__name__)
        vcl_precond = self.vcl_precond_type(leaf.vcl_leaf, self.vcl_tag, self.num_blocks)
        self.vcl_precond = vcl_precond

    @property
    def with_level_scheduling(self):
        return self.vcl_tag.with_level_scheduling


class Jacobi(PreconditionerTag):
    """
    TODO docstring
    """
    vcl_tag_type = _v.jacobi_tag
    vcl_precond_type_name = 'jacobi_precond'

    def __init__(self):
        """
        TODO docstring
        """
        self.vcl_tag = self.vcl_tag_type()


class RowScaling(PreconditionerTag):
    """
    TODO docstring
    """
    vcl_tag_type = _v.row_scaling_tag
    vcl_precond_type_name = 'row_scaling_precond'

    def __init__(self, p=2):
        """
        TODO docstring (nb - norm is l^p norm)
        """
        self.vcl_tag = self.vcl_tag_type(p)

    @property
    def norm(self):
        return self.vcl_tag.norm


class AMG(PreconditionerTag):
    """
    TODO docstring
    """
    vcl_tag_type = _v.amg_tag
    vcl_precond_type_name = 'amg_precond'

    def __init__(self, coarse=1,
                 interpol=1,
                 threshold=0.25,
                 interpol_weight=0.2,
                 jacobi_weight=1.0,
                 presmooth=1,
                 postsmooth=1,
                 coarse_levels=0):
        """
        TODO docstring
        """
        if not backend.WITH_OPENCL:
            raise NotImplementedError("AMG preconditioner only available with OpenCL")
        self.vcl_tag = self.vcl_tag_type(coarse, interpol, threshold, interpol_weight, jacobi_weight, presmooth, postsmooth, coarse_levels)

    @property
    def coarse(self):
        return self.vcl_tag.coarse

    @property
    def interpol(self):
        return self.vcl_tag.interpol

    @property
    def threshold(self):
        return self.vcl_tag.threshold

    @property
    def interpol_weight(self):
        return self.vcl_tag.interpol_weight

    @property
    def jacobi_weight(self):
        return self.vcl_tag.jacobi_weight

    @property
    def presmooth(self):
        return self.vcl_tag.presmooth

    @property
    def postsmooth(self):
        return self.vcl_tag.postsmooth

    @property
    def coarse_levels(self):
        return self.vcl_tag.coarse_levels


class SPAI(PreconditionerTag):
    """
    TODO docstring
    """
    vcl_tag_type = _v.spai_tag
    vcl_precond_type_name = 'spai_precond'

    def __init__(self, residual_norm_threshold=1e-3,
                 iteration_limit=5,
                 residual_threshold=1e-2,
                 is_static=False,
                 is_right=False):
        if not backend.WITH_OPENCL:
            raise NotImplementedError("SPAI preconditioner only available with OpenCL")
        self.vcl_tag = self.vcl_tag_type(residual_norm_threshold, iteration_limit, residual_threshold, is_static, is_right)

    @property
    def residual_norm_threshold(self):
        return self.vcl_tag.residual_norm_threshold

    @property
    def iteration_limit(self):
        return self.vcl_tag.iteration_limit

    @property
    def residual_threshold(self):
        return self.vcl_tag.residual_threshold

    @property
    def is_static(self):
        return self.vcl_tag.is_static

    @property
    def is_right(self):
        return self.vcl_tag.is_right


class FSPAI(PreconditionerTag):
    """
    TODO docstring
    """
    vcl_tag_type = _v.fspai_tag
    vcl_precond_type_name = 'fspai_precond'

    def __init__(self, residual_norm_threshold=1e-3,
                 iteration_limit=5,
                 is_static=False,
                 is_right=False):
        if not backend.WITH_OPENCL:
            raise NotImplementedError("FSPAI preconditioner only available with OpenCL")
        self.vcl_tag = self.vcl_tag_type(residual_norm_threshold, iteration_limit, is_static, is_right)

    @property
    def residual_norm_threshold(self):
        return self.vcl_tag.residual_norm_threshold

    @property
    def iteration_limit(self):
        return self.vcl_tag.iteration_limit

    @property
    def is_static(self):
        return self.vcl_tag.is_static

    @property
    def is_right(self):
        return self.vcl_tag.is_right


class Lower(SolverTag, SolverWithoutPreconditioner):
    """
    Instruct the solver to solve for a lower triangular system matrix
    """
    vcl_tag = _v.lower_tag()
    def vcl_solve_call(self, *args):
        return _v.direct_solve(*args)

    def vcl_inplace_solve_call(self, *args):
        return _v.direct_inplace_solve(*args)


class UnitLower(SolverTag, SolverWithoutPreconditioner):
    """
    Instruct the solver to solve for a unit lower triangular system matrix
    """
    vcl_tag = _v.unit_lower_tag()
    def vcl_solve_call(self, *args):
        return _v.direct_solve(*args)

    def vcl_inplace_solve_call(self, *args):
        return _v.direct_inplace_solve(*args)


class Upper(SolverTag, SolverWithoutPreconditioner):
    """
    Instruct the solver to solve for an upper triangular system matrix
    """
    vcl_tag = _v.upper_tag()
    def vcl_solve_call(self, *args):
        return _v.direct_solve(*args)

    def vcl_inplace_solve_call(self, *args):
        return _v.direct_inplace_solve(*args)


class UnitUpper(SolverTag, SolverWithoutPreconditioner):
    """
    Instruct the solver to solve for a unit upper triangular system matrix
    """
    vcl_tag = _v.unit_upper_tag()
    def vcl_solve_call(self, *args):
        return _v.direct_solve(*args)

    def vcl_inplace_solve_call(self, *args):
        return _v.direct_inplace_solve(*args)


class CG(SolverTag):
    """
    Instruct the solver to solve using the conjugate gradient solver.

    Assumes that the system matrix is symmetric positive definite.

    Used for supplying solver parameters.
    """
    def vcl_solve_call(self, *args):
        return _v.iterative_solve(*args)

    def __init__(self, tolerance = 1e-8, max_iterations = 300):
        """
        Construct a cg_tag.

        Parameters
        ----------
        tolerance : float, optional
            Relative tolerance for the residual
            (solver quits if ||r|| < tolerance * ||r_initial|| obtains)
        max_iterations : int, optional
            The maximum number of iterations
        """
        self.vcl_tag = _v.cg_tag(tolerance, max_iterations)

    @property
    def tolerance(self):
        """
        The relative tolerance
        """
        return self.vcl_tag.tolerance

    @property
    def max_iterations(self):
        """
        The maximum number of iterations
        """
        return self.vcl_tag.max_iterations

    @property
    def iters(self):
        """
        The number of solver iterations
        """
        return self.vcl_tag.iters

    @property
    def error(self):
        """
        The estimated relative error at the end of the solver run
        """
        return self.vcl_tag.error


class MixedPrecisionCG(SolverTag):
    """
    TODO docstring -- EXPERIMENTAL

    Instruct the solver to solve using the conjugate gradient solver.

    Assumes that the system matrix is symmetric positive definite.

    Used for supplying solver parameters.
    """
    def vcl_solve_call(self, *args):
        return _v.iterative_solve(*args)

    def __init__(self, tolerance = 1e-8, max_iterations = 300, inner_tolerance = 1e-2):
        """
        TODO docstring

        Parameters
        ----------
        tolerance : float, optional
            Relative tolerance for the residual
            (solver quits if ||r|| < tolerance * ||r_initial|| obtains)
        max_iterations : int, optional
            The maximum number of iterations
        """
        self.vcl_tag = _v.mixed_precision_cg_tag(tolerance, max_iterations, inner_tolerance)

    @property
    def tolerance(self):
        """
        The relative tolerance
        """
        return self.vcl_tag.tolerance

    @property
    def inner_tolerance(self):
        """
        TODO docstring
        """
        return self.vcl_tag.inner_tolerance

    @property
    def max_iterations(self):
        """
        The maximum number of iterations
        """
        return self.vcl_tag.max_iterations

    @property
    def iters(self):
        """
        The number of solver iterations
        """
        return self.vcl_tag.iters

    @property
    def error(self):
        """
        The estimated relative error at the end of the solver run
        """
        return self.vcl_tag.error


class BiCGStab(SolverTag):
    """
    Instruct the solver to solve using the stabilised bi-conjugate gradient
    (BiCGStab) solver.

    Assumes that the system matrix is non-symmetric.

    Used for supplying solver parameters.
    """
    def vcl_solve_call(self, *args):
        return _v.iterative_solve(*args)

    def __init__(self, tolerance = 1e-8, 
                 max_iterations = 400, max_iterations_before_restart = 200):
        """
        Construct a bicgstab_tag.

        Parameters
        ----------
        tolerance : float, optional
            Relative tolerance for the residual
            (solver quits if ||r|| < tolerance * ||r_initial|| obtains)
        max_iterations : int, optional
            Maximum number of iterations
        max_iterations_before restart : int, optional
            Maximum number of iterations before BiCGStab is reinitialised,
            to avoid accumulation of round-off errors.
        """
        self.vcl_tag = _v.bicgstab_tag(tolerance, max_iterations,
                                       max_iterations_before_restart)

    @property
    def tolerance(self):
        """
        The relative tolerance
        """
        return self.vcl_tag.tolerance

    @property
    def max_iterations(self):
        """
        The maximum number of iterations
        """
        return self.vcl_tag.max_iterations

    @property
    def max_iterations(self):
        """
        The maximum number of iterations before a restart
        """
        return self.vcl_tag.max_iterations_before_restart

    @property
    def iters(self):
        """
        The number of solver iterations
        """
        return self.vcl_tag.iters

    @property
    def error(self):
        """
        The estimated relative error at the end of the solver run
        """
        return self.vcl_tag.error


class GMRES(SolverTag):
    """
    Instruct the solver to solve using the GMRES solver.

    Used for supplying solver parameters.
    """
    def vcl_solve_call(self, *args):
        return _v.iterative_solve(*args)

    def __init__(self, tolerance=1e-8, max_iterations=300, krylov_dim=20):
        """
        TODO docstring

        Parameters
        ----------
        tolerance : float, optional
            Relative tolerance for the residual
            (solver quits if ||r|| < tolerance * ||r_initial|| obtains)
        max_iterations : int, optional
            Maximum number of iterations, including restarts
        krylov_dim : int, optional
            The maximum dimension of the Krylov space before restart
            (number of restarts can be computed as max_iterations / krylov_dim)
        """
        self.vcl_tag = _v.gmres_tag(tolerance, max_iterations, krylov_dim)

    @property
    def tolerance(self):
        """
        The relative tolerance
        """
        return self.vcl_tag.tolerance

    @property
    def max_iterations(self):
        """
        The maximum number of iterations
        """
        return self.vcl_tag.max_iterations

    @property
    def krylov_dim(self):
        """
        The maximum dimension of the Krylov space before restart
        """
        return self.vcl_tag.krylov_dim

    @property
    def max_restarts(self):
        """
        The maximum number of GMRES restarts
        """
        return self.vcl_tag.max_restarts

    @property
    def iters(self):
        """
        The number of solver iterations
        """
        return self.vcl_tag.iters

    @property
    def error(self):
        """
        The estimated relative error at the end of the solver run
        """
        return self.vcl_tag.error


class PowerIteration(EigenvalueTag):
    """
    Instruct the eigenvalue computation to use the power iteration algorithm.

    Used for supplying eigenvalue computation parameters.
    """
    def __init__(self, factor = 1e-8, max_iterations = 50000):
        """
        TODO docstring

        Parameters
        ----------
        factor : float, optional
            Halt when the eigenvalue does not change more than this value.
        max_iterations : int, optional
            Maximum number of iterations to compute.
        """
        self.vcl_tag = _v.power_iter_tag(factor, max_iterations)

    @property
    def factor(self):
        """
        The termination factor.

        If the eigenvalue does not change more than this value, the algorithm
        stops.
        """
        return self.vcl_tag.factor

    @property
    def max_iterations(self):
        """
        The maximum number of iterations
        """
        return self.vcl_tag.max_iterations


class Lanczos(EigenvalueTag):
    """
    Instruct the eigenvalue computation to use the Lanczos algorithm.

    Used for supplying eigenvalue computation parameters.
    """
    def __init__(self, factor = 0.75, num_eig = 10, method = 0, krylov = 100):
        """
        Construct a lanczos_tag.

        Parameters
        ----------
        factor : float
            Exponent of epsilon (reorthogonalisation batch tolerance)
        num_eig : int
            Number of eigenvalues to return
        method : {0, 1, 2}
            0 for partial reorthogonalisation
            1 for full reorthogonalisation
            2 for Lanczos without reorthogonalisation
        krylov : int
            Maximum Krylov-space size
        """
        self.vcl_tag = _v.lanczos_tag(factor, num_eig, method, krylov)

    @property
    def factor(self):
        """
        The tolerance factor for reorthogonalisation batches, expressed as
        the exponent of epsilon.
        """
        return self.vcl_tag.factor

    @property
    def num_eigenvalues(self):
        """
        The number of eigenvalues to return.
        """
        return self.vcl_tag.num_eigenvalues

    @property
    def krylov_size(self):
        """
        The size of the Kylov space.
        """
        return self.vcl_tag.krylov_size

    @property
    def method(self):
        """
        The reorthogonalisation method choice.
        """
        return self.vcl_tag.method


class NMF(NMFTag):
    """
    TODO docstring
    """
    def __init__(self, tolerance=1e-4, stagnation_tolerance=1e-5,
                 max_iterations=10000, check_after_steps=100,
                 print_relative_error=False):
        """
        TODO docstring
        """
        self.vcl_tag = _v.nmf_config(tolerance, stagnation_tolerance,
                                     max_iterations, check_after_steps)
        self.vcl_tag.print_relative_error = print_relative_error

    @property
    def iterations(self):
        """
        TODO docstring
        """
        return self.vcl_tag.iterations

    @property
    def tolerance(self):
        """
        TODO docstring
        """
        return self.vcl_tag.tolerance

    @property
    def stagnation_tolerance(self):
        """
        TODO docstring
        """
        return self.vcl_tag.stagnation_tolerance

    @property
    def max_iterations(self):
        """
        TODO docstring
        """
        return self.vcl_tag.max_iterations

    @property
    def check_after_steps(self):
        """
        TODO docstring
        """
        return self.vcl_tag.check_after_steps

    @property
    def print_relative_error(self):
        """
        TODO docstring
        """
        return self.vcl_tag.print_relative_error


class CuthillMcKee(BandwidthReductionTag):
    """
    TODO docstring
    """
    vcl_tag = _v.cuthill_mckee_tag()


class GibbsPooleStockmeyer(BandwidthReductionTag):
    """
    TODO docstring
    """
    vcl_tag = _v.gibbs_poole_stockmeyer_tag()


class AdvancedCuthillMcKee(BandwidthReductionTag):
    """
    TODO docstring
    """

    def __init__(self, a=0.0, gmax=1):
        """
        TODO docstring
        """
        self.vcl_tag = _v.advanced_cuthill_mckee_tag(a, gmax)

    @property
    def starting_node_param(self):
        """
        TODO docstring
        """
        return self.vcl_tag.starting_node_param

    @property
    def max_root_nodes(self):
        """
        TODO docstring
        """
        return self.vcl_tag.max_root_nodes

__all__ = ['NoPreconditioner', 'ICHOL0', 'ILUT', 'BlockILUT', 'ILU0',
           'BlockILU0', 'Jacobi', 'RowScaling', 'AMG', 'SPAI', 'FSPAI',
           'Lower', 'UnitLower', 'Upper', 'UnitUpper', 'CG',
           'MixedPrecisionCG', 'BiCGStab', 'GMRES', 'PowerIteration',
           'Lanczos', 'NMF', 'CuthillMcKee', 'GibbsPooleStockmeyer',
           'AdvancedCuthillMcKee']

<filename>pyviennacl/tags.py
"""
TODO docstring
"""

from pyviennacl import _viennacl as _v, backend


class SolverTag(object):
    def vcl_solve_call(self, *args):
        raise NotImplementedError("Solver not implemented")

    def vcl_inplace_solve_call(self, *args):
        raise NotImplementedError("In-place solver not implemented")

class SolverWithoutPreconditioner(object): pass

class EigenvalueTag(object): pass

class NMFTag(object): pass

class BandwidthReductionTag(object): pass

class PreconditionerTag(object):
    vcl_precond_type = None
    vcl_tag_type = None

    def instantiate(self, leaf):
        self.vcl_precond_type = getattr(_v, self.vcl_precond_type_name + "_" + type(leaf.vcl_leaf).__name__)
        vcl_precond = self.vcl_precond_type(leaf.vcl_leaf, self.vcl_tag)
        self.vcl_precond = vcl_precond


class NoPreconditioner(PreconditionerTag):
    vcl_tag_type = None
    vcl_precond_type = _v.no_precond

    def __init__(self):
        self.vcl_tag = None
        self.vcl_precond = self.vcl_precond_type()

    def instantiate(self, leaf): pass


class ICHOL0(PreconditionerTag):
    vcl_tag_type = _v.ichol0_tag
    vcl_precond_type_name = 'ichol0_precond'

    def __init__(self):
        self.vcl_tag = self.vcl_tag_type()


class ILUT(PreconditionerTag):
    """
    TODO docstring
    """
    vcl_tag_type = _v.ilut_tag
    vcl_precond_type_name = 'ilut_precond'

    def __init__(self, entries_per_row=20, drop_tolerance=1e-4, with_level_scheduling=False):
        """
        TODO docstring
        """
        self.vcl_tag = self.vcl_tag_type(entries_per_row, drop_tolerance, with_level_scheduling)

    @property
    def entries_per_row(self):
        return self.vcl_tag.entries_per_row

    @property
    def drop_tolerance(self):
        return self.vcl_tag.drop_tolerance

    @property
    def with_level_scheduling(self):
        return self.vcl_tag.with_level_scheduling


class BlockILUT(PreconditionerTag):
    """
    TODO docstring
    """
    vcl_tag_type = _v.ilut_tag
    vcl_precond_type_name = 'block_ilut_precond'

    def __init__(self, entries_per_row=20, drop_tolerance=1e-4, with_level_scheduling=False, num_blocks=8):
        """
        TODO docstring
        """
        self.num_blocks = num_blocks
        self.vcl_tag = self.vcl_tag_type(entries_per_row, drop_tolerance, with_level_scheduling)

    def instantiate(self, leaf):
        self.vcl_precond_type = getattr(_v, self.vcl_precond_type_name + "_" + type(leaf.vcl_leaf).__name__)
        vcl_precond = self.vcl_precond_type(leaf.vcl_leaf, self.vcl_tag, self.num_blocks)
        self.vcl_precond = vcl_precond

    @property
    def entries_per_row(self):
        return self.vcl_tag.entries_per_row

    @property
    def drop_tolerance(self):
        return self.vcl_tag.drop_tolerance

    @property
    def with_level_scheduling(self):
        return self.vcl_tag.with_level_scheduling


class ILU0(PreconditionerTag):
    """
    TODO docstring
    """
    vcl_tag_type = _v.ilu0_tag
    vcl_precond_type_name = 'ilu0_precond'

    def __init__(self, with_level_scheduling=False):
        """
        TODO docstring
        """
        self.vcl_tag = self.vcl_tag_type(with_level_scheduling)

    @property
    def with_level_scheduling(self):
        return self.vcl_tag.with_level_scheduling


class BlockILU0(PreconditionerTag):
    """
    TODO docstring
    """
    vcl_tag_type = _v.ilu0_tag
    vcl_precond_type_name = 'block_ilu0_precond'

    def __init__(self, with_level_scheduling=False, num_blocks=8):
        """
        TODO docstring
        """
        self.num_blocks = num_blocks
        self.vcl_tag = self.vcl_tag_type(with_level_scheduling)

    def instantiate(self, leaf):
        self.vcl_precond_type = getattr(_v, self.vcl_precond_type_name + "_" + type(leaf.vcl_leaf).__name__)
        vcl_precond = self.vcl_precond_type(leaf.vcl_leaf, self.vcl_tag, self.num_blocks)
        self.vcl_precond = vcl_precond

    @property
    def with_level_scheduling(self):
        return self.vcl_tag.with_level_scheduling


class Jacobi(PreconditionerTag):
    """
    TODO docstring
    """
    vcl_tag_type = _v.jacobi_tag
    vcl_precond_type_name = 'jacobi_precond'

    def __init__(self):
        """
        TODO docstring
        """
        self.vcl_tag = self.vcl_tag_type()


class RowScaling(PreconditionerTag):
    """
    TODO docstring
    """
    vcl_tag_type = _v.row_scaling_tag
    vcl_precond_type_name = 'row_scaling_precond'

    def __init__(self, p=2):
        """
        TODO docstring (nb - norm is l^p norm)
        """
        self.vcl_tag = self.vcl_tag_type(p)

    @property
    def norm(self):
        return self.vcl_tag.norm


class AMG(PreconditionerTag):
    """
    TODO docstring
    """
    vcl_tag_type = _v.amg_tag
    vcl_precond_type_name = 'amg_precond'

    def __init__(self, coarse=1,
                 interpol=1,
                 threshold=0.25,
                 interpol_weight=0.2,
                 jacobi_weight=1.0,
                 presmooth=1,
                 postsmooth=1,
                 coarse_levels=0):
        """
        TODO docstring
        """
        if not backend.WITH_OPENCL:
            raise NotImplementedError("AMG preconditioner only available with OpenCL")
        self.vcl_tag = self.vcl_tag_type(coarse, interpol, threshold, interpol_weight, jacobi_weight, presmooth, postsmooth, coarse_levels)

    @property
    def coarse(self):
        return self.vcl_tag.coarse

    @property
    def interpol(self):
        return self.vcl_tag.interpol

    @property
    def threshold(self):
        return self.vcl_tag.threshold

    @property
    def interpol_weight(self):
        return self.vcl_tag.interpol_weight

    @property
    def jacobi_weight(self):
        return self.vcl_tag.jacobi_weight

    @property
    def presmooth(self):
        return self.vcl_tag.presmooth

    @property
    def postsmooth(self):
        return self.vcl_tag.postsmooth

    @property
    def coarse_levels(self):
        return self.vcl_tag.coarse_levels


class SPAI(PreconditionerTag):
    """
    TODO docstring
    """
    vcl_tag_type = _v.spai_tag
    vcl_precond_type_name = 'spai_precond'

    def __init__(self, residual_norm_threshold=1e-3,
                 iteration_limit=5,
                 residual_threshold=1e-2,
                 is_static=False,
                 is_right=False):
        if not backend.WITH_OPENCL:
            raise NotImplementedError("SPAI preconditioner only available with OpenCL")
        self.vcl_tag = self.vcl_tag_type(residual_norm_threshold, iteration_limit, residual_threshold, is_static, is_right)

    @property
    def residual_norm_threshold(self):
        return self.vcl_tag.residual_norm_threshold

    @property
    def iteration_limit(self):
        return self.vcl_tag.iteration_limit

    @property
    def residual_threshold(self):
        return self.vcl_tag.residual_threshold

    @property
    def is_static(self):
        return self.vcl_tag.is_static

    @property
    def is_right(self):
        return self.vcl_tag.is_right


class FSPAI(PreconditionerTag):
    """
    TODO docstring
    """
    vcl_tag_type = _v.fspai_tag
    vcl_precond_type_name = 'fspai_precond'

    def __init__(self, residual_norm_threshold=1e-3,
                 iteration_limit=5,
                 is_static=False,
                 is_right=False):
        if not backend.WITH_OPENCL:
            raise NotImplementedError("FSPAI preconditioner only available with OpenCL")
        self.vcl_tag = self.vcl_tag_type(residual_norm_threshold, iteration_limit, is_static, is_right)

    @property
    def residual_norm_threshold(self):
        return self.vcl_tag.residual_norm_threshold

    @property
    def iteration_limit(self):
        return self.vcl_tag.iteration_limit

    @property
    def is_static(self):
        return self.vcl_tag.is_static

    @property
    def is_right(self):
        return self.vcl_tag.is_right


class Lower(SolverTag, SolverWithoutPreconditioner):
    """
    Instruct the solver to solve for a lower triangular system matrix
    """
    vcl_tag = _v.lower_tag()
    def vcl_solve_call(self, *args):
        return _v.direct_solve(*args)

    def vcl_inplace_solve_call(self, *args):
        return _v.direct_inplace_solve(*args)


class UnitLower(SolverTag, SolverWithoutPreconditioner):
    """
    Instruct the solver to solve for a unit lower triangular system matrix
    """
    vcl_tag = _v.unit_lower_tag()
    def vcl_solve_call(self, *args):
        return _v.direct_solve(*args)

    def vcl_inplace_solve_call(self, *args):
        return _v.direct_inplace_solve(*args)


class Upper(SolverTag, SolverWithoutPreconditioner):
    """
    Instruct the solver to solve for an upper triangular system matrix
    """
    vcl_tag = _v.upper_tag()
    def vcl_solve_call(self, *args):
        return _v.direct_solve(*args)

    def vcl_inplace_solve_call(self, *args):
        return _v.direct_inplace_solve(*args)


class UnitUpper(SolverTag, SolverWithoutPreconditioner):
    """
    Instruct the solver to solve for a unit upper triangular system matrix
    """
    vcl_tag = _v.unit_upper_tag()
    def vcl_solve_call(self, *args):
        return _v.direct_solve(*args)

    def vcl_inplace_solve_call(self, *args):
        return _v.direct_inplace_solve(*args)


class CG(SolverTag):
    """
    Instruct the solver to solve using the conjugate gradient solver.

    Assumes that the system matrix is symmetric positive definite.

    Used for supplying solver parameters.
    """
    def vcl_solve_call(self, *args):
        return _v.iterative_solve(*args)

    def __init__(self, tolerance = 1e-8, max_iterations = 300):
        """
        Construct a cg_tag.

        Parameters
        ----------
        tolerance : float, optional
            Relative tolerance for the residual
            (solver quits if ||r|| < tolerance * ||r_initial|| obtains)
        max_iterations : int, optional
            The maximum number of iterations
        """
        self.vcl_tag = _v.cg_tag(tolerance, max_iterations)

    @property
    def tolerance(self):
        """
        The relative tolerance
        """
        return self.vcl_tag.tolerance

    @property
    def max_iterations(self):
        """
        The maximum number of iterations
        """
        return self.vcl_tag.max_iterations

    @property
    def iters(self):
        """
        The number of solver iterations
        """
        return self.vcl_tag.iters

    @property
    def error(self):
        """
        The estimated relative error at the end of the solver run
        """
        return self.vcl_tag.error


class MixedPrecisionCG(SolverTag):
    """
    TODO docstring -- EXPERIMENTAL

    Instruct the solver to solve using the conjugate gradient solver.

    Assumes that the system matrix is symmetric positive definite.

    Used for supplying solver parameters.
    """
    def vcl_solve_call(self, *args):
        return _v.iterative_solve(*args)

    def __init__(self, tolerance = 1e-8, max_iterations = 300, inner_tolerance = 1e-2):
        """
        TODO docstring

        Parameters
        ----------
        tolerance : float, optional
            Relative tolerance for the residual
            (solver quits if ||r|| < tolerance * ||r_initial|| obtains)
        max_iterations : int, optional
            The maximum number of iterations
        """
        self.vcl_tag = _v.mixed_precision_cg_tag(tolerance, max_iterations, inner_tolerance)

    @property
    def tolerance(self):
        """
        The relative tolerance
        """
        return self.vcl_tag.tolerance

    @property
    def inner_tolerance(self):
        """
        TODO docstring
        """
        return self.vcl_tag.inner_tolerance

    @property
    def max_iterations(self):
        """
        The maximum number of iterations
        """
        return self.vcl_tag.max_iterations

    @property
    def iters(self):
        """
        The number of solver iterations
        """
        return self.vcl_tag.iters

    @property
    def error(self):
        """
        The estimated relative error at the end of the solver run
        """
        return self.vcl_tag.error


class BiCGStab(SolverTag):
    """
    Instruct the solver to solve using the stabilised bi-conjugate gradient
    (BiCGStab) solver.

    Assumes that the system matrix is non-symmetric.

    Used for supplying solver parameters.
    """
    def vcl_solve_call(self, *args):
        return _v.iterative_solve(*args)

    def __init__(self, tolerance = 1e-8, 
                 max_iterations = 400, max_iterations_before_restart = 200):
        """
        Construct a bicgstab_tag.

        Parameters
        ----------
        tolerance : float, optional
            Relative tolerance for the residual
            (solver quits if ||r|| < tolerance * ||r_initial|| obtains)
        max_iterations : int, optional
            Maximum number of iterations
        max_iterations_before restart : int, optional
            Maximum number of iterations before BiCGStab is reinitialised,
            to avoid accumulation of round-off errors.
        """
        self.vcl_tag = _v.bicgstab_tag(tolerance, max_iterations,
                                       max_iterations_before_restart)

    @property
    def tolerance(self):
        """
        The relative tolerance
        """
        return self.vcl_tag.tolerance

    @property
    def max_iterations(self):
        """
        The maximum number of iterations
        """
        return self.vcl_tag.max_iterations

    @property
    def max_iterations(self):
        """
        The maximum number of iterations before a restart
        """
        return self.vcl_tag.max_iterations_before_restart

    @property
    def iters(self):
        """
        The number of solver iterations
        """
        return self.vcl_tag.iters

    @property
    def error(self):
        """
        The estimated relative error at the end of the solver run
        """
        return self.vcl_tag.error


class GMRES(SolverTag):
    """
    Instruct the solver to solve using the GMRES solver.

    Used for supplying solver parameters.
    """
    def vcl_solve_call(self, *args):
        return _v.iterative_solve(*args)

    def __init__(self, tolerance=1e-8, max_iterations=300, krylov_dim=20):
        """
        TODO docstring

        Parameters
        ----------
        tolerance : float, optional
            Relative tolerance for the residual
            (solver quits if ||r|| < tolerance * ||r_initial|| obtains)
        max_iterations : int, optional
            Maximum number of iterations, including restarts
        krylov_dim : int, optional
            The maximum dimension of the Krylov space before restart
            (number of restarts can be computed as max_iterations / krylov_dim)
        """
        self.vcl_tag = _v.gmres_tag(tolerance, max_iterations, krylov_dim)

    @property
    def tolerance(self):
        """
        The relative tolerance
        """
        return self.vcl_tag.tolerance

    @property
    def max_iterations(self):
        """
        The maximum number of iterations
        """
        return self.vcl_tag.max_iterations

    @property
    def krylov_dim(self):
        """
        The maximum dimension of the Krylov space before restart
        """
        return self.vcl_tag.krylov_dim

    @property
    def max_restarts(self):
        """
        The maximum number of GMRES restarts
        """
        return self.vcl_tag.max_restarts

    @property
    def iters(self):
        """
        The number of solver iterations
        """
        return self.vcl_tag.iters

    @property
    def error(self):
        """
        The estimated relative error at the end of the solver run
        """
        return self.vcl_tag.error


class PowerIteration(EigenvalueTag):
    """
    Instruct the eigenvalue computation to use the power iteration algorithm.

    Used for supplying eigenvalue computation parameters.
    """
    def __init__(self, factor = 1e-8, max_iterations = 50000):
        """
        TODO docstring

        Parameters
        ----------
        factor : float, optional
            Halt when the eigenvalue does not change more than this value.
        max_iterations : int, optional
            Maximum number of iterations to compute.
        """
        self.vcl_tag = _v.power_iter_tag(factor, max_iterations)

    @property
    def factor(self):
        """
        The termination factor.

        If the eigenvalue does not change more than this value, the algorithm
        stops.
        """
        return self.vcl_tag.factor

    @property
    def max_iterations(self):
        """
        The maximum number of iterations
        """
        return self.vcl_tag.max_iterations


class Lanczos(EigenvalueTag):
    """
    Instruct the eigenvalue computation to use the Lanczos algorithm.

    Used for supplying eigenvalue computation parameters.
    """
    def __init__(self, factor = 0.75, num_eig = 10, method = 0, krylov = 100):
        """
        Construct a lanczos_tag.

        Parameters
        ----------
        factor : float
            Exponent of epsilon (reorthogonalisation batch tolerance)
        num_eig : int
            Number of eigenvalues to return
        method : {0, 1, 2}
            0 for partial reorthogonalisation
            1 for full reorthogonalisation
            2 for Lanczos without reorthogonalisation
        krylov : int
            Maximum Krylov-space size
        """
        self.vcl_tag = _v.lanczos_tag(factor, num_eig, method, krylov)

    @property
    def factor(self):
        """
        The tolerance factor for reorthogonalisation batches, expressed as
        the exponent of epsilon.
        """
        return self.vcl_tag.factor

    @property
    def num_eigenvalues(self):
        """
        The number of eigenvalues to return.
        """
        return self.vcl_tag.num_eigenvalues

    @property
    def krylov_size(self):
        """
        The size of the Kylov space.
        """
        return self.vcl_tag.krylov_size

    @property
    def method(self):
        """
        The reorthogonalisation method choice.
        """
        return self.vcl_tag.method


class NMF(NMFTag):
    """
    TODO docstring
    """
    def __init__(self, tolerance=1e-4, stagnation_tolerance=1e-5,
                 max_iterations=10000, check_after_steps=100,
                 print_relative_error=False):
        """
        TODO docstring
        """
        self.vcl_tag = _v.nmf_config(tolerance, stagnation_tolerance,
                                     max_iterations, check_after_steps)
        self.vcl_tag.print_relative_error = print_relative_error

    @property
    def iterations(self):
        """
        TODO docstring
        """
        return self.vcl_tag.iterations

    @property
    def tolerance(self):
        """
        TODO docstring
        """
        return self.vcl_tag.tolerance

    @property
    def stagnation_tolerance(self):
        """
        TODO docstring
        """
        return self.vcl_tag.stagnation_tolerance

    @property
    def max_iterations(self):
        """
        TODO docstring
        """
        return self.vcl_tag.max_iterations

    @property
    def check_after_steps(self):
        """
        TODO docstring
        """
        return self.vcl_tag.check_after_steps

    @property
    def print_relative_error(self):
        """
        TODO docstring
        """
        return self.vcl_tag.print_relative_error


class CuthillMcKee(BandwidthReductionTag):
    """
    TODO docstring
    """
    vcl_tag = _v.cuthill_mckee_tag()


class GibbsPooleStockmeyer(BandwidthReductionTag):
    """
    TODO docstring
    """
    vcl_tag = _v.gibbs_poole_stockmeyer_tag()


class AdvancedCuthillMcKee(BandwidthReductionTag):
    """
    TODO docstring
    """

    def __init__(self, a=0.0, gmax=1):
        """
        TODO docstring
        """
        self.vcl_tag = _v.advanced_cuthill_mckee_tag(a, gmax)

    @property
    def starting_node_param(self):
        """
        TODO docstring
        """
        return self.vcl_tag.starting_node_param

    @property
    def max_root_nodes(self):
        """
        TODO docstring
        """
        return self.vcl_tag.max_root_nodes

__all__ = ['NoPreconditioner', 'ICHOL0', 'ILUT', 'BlockILUT', 'ILU0',
           'BlockILU0', 'Jacobi', 'RowScaling', 'AMG', 'SPAI', 'FSPAI',
           'Lower', 'UnitLower', 'Upper', 'UnitUpper', 'CG',
           'MixedPrecisionCG', 'BiCGStab', 'GMRES', 'PowerIteration',
           'Lanczos', 'NMF', 'CuthillMcKee', 'GibbsPooleStockmeyer',
           'AdvancedCuthillMcKee']

TODO docstring TODO docstring TODO docstring TODO docstring TODO docstring TODO docstring TODO docstring TODO docstring TODO docstring TODO docstring TODO docstring TODO docstring TODO docstring (nb - norm is l^p norm) TODO docstring TODO docstring TODO docstring TODO docstring Instruct the solver to solve for a lower triangular system matrix Instruct the solver to solve for a unit lower triangular system matrix Instruct the solver to solve for an upper triangular system matrix Instruct the solver to solve for a unit upper triangular system matrix Instruct the solver to solve using the conjugate gradient solver.      Assumes that the system matrix is symmetric positive definite.      Used for supplying solver parameters. Construct a cg_tag.          Parameters         ----------         tolerance : float, optional             Relative tolerance for the residual             (solver quits if ||r|| < tolerance * ||r_initial|| obtains)         max_iterations : int, optional             The maximum number of iterations The relative tolerance The maximum number of iterations The number of solver iterations The estimated relative error at the end of the solver run TODO docstring -- EXPERIMENTAL      Instruct the solver to solve using the conjugate gradient solver.      Assumes that the system matrix is symmetric positive definite.      Used for supplying solver parameters. TODO docstring          Parameters         ----------         tolerance : float, optional             Relative tolerance for the residual             (solver quits if ||r|| < tolerance * ||r_initial|| obtains)         max_iterations : int, optional             The maximum number of iterations The relative tolerance TODO docstring The maximum number of iterations The number of solver iterations The estimated relative error at the end of the solver run Instruct the solver to solve using the stabilised bi-conjugate gradient     (BiCGStab) solver.      Assumes that the system matrix is non-symmetric.      Used for supplying solver parameters. Construct a bicgstab_tag.          Parameters         ----------         tolerance : float, optional             Relative tolerance for the residual             (solver quits if ||r|| < tolerance * ||r_initial|| obtains)         max_iterations : int, optional             Maximum number of iterations         max_iterations_before restart : int, optional             Maximum number of iterations before BiCGStab is reinitialised,             to avoid accumulation of round-off errors. The relative tolerance The maximum number of iterations The maximum number of iterations before a restart The number of solver iterations The estimated relative error at the end of the solver run Instruct the solver to solve using the GMRES solver.      Used for supplying solver parameters. TODO docstring          Parameters         ----------         tolerance : float, optional             Relative tolerance for the residual             (solver quits if ||r|| < tolerance * ||r_initial|| obtains)         max_iterations : int, optional             Maximum number of iterations, including restarts         krylov_dim : int, optional             The maximum dimension of the Krylov space before restart             (number of restarts can be computed as max_iterations / krylov_dim) The relative tolerance The maximum number of iterations The maximum dimension of the Krylov space before restart The maximum number of GMRES restarts The number of solver iterations The estimated relative error at the end of the solver run Instruct the eigenvalue computation to use the power iteration algorithm.      Used for supplying eigenvalue computation parameters. TODO docstring          Parameters         ----------         factor : float, optional             Halt when the eigenvalue does not change more than this value.         max_iterations : int, optional             Maximum number of iterations to compute. The termination factor.          If the eigenvalue does not change more than this value, the algorithm         stops. The maximum number of iterations Instruct the eigenvalue computation to use the Lanczos algorithm.      Used for supplying eigenvalue computation parameters. Construct a lanczos_tag.          Parameters         ----------         factor : float             Exponent of epsilon (reorthogonalisation batch tolerance)         num_eig : int             Number of eigenvalues to return         method : {0, 1, 2}             0 for partial reorthogonalisation             1 for full reorthogonalisation             2 for Lanczos without reorthogonalisation         krylov : int             Maximum Krylov-space size The tolerance factor for reorthogonalisation batches, expressed as         the exponent of epsilon. The number of eigenvalues to return. The size of the Kylov space. The reorthogonalisation method choice. TODO docstring TODO docstring TODO docstring TODO docstring TODO docstring TODO docstring TODO docstring TODO docstring TODO docstring TODO docstring TODO docstring TODO docstring TODO docstring TODO docstring