"""Abstract model of a representation the monster group

Let R_p be the 196884-dimensional representation 196884x of the monster 
group modulo a small odd number p as described in [Seys19]. Class
AbstractMmRepSpace is an abstract class for modelling representation
R_p. A small odd modulus p < 256 is passed to the constructor of that
class as well as a class modelling the monster group Mm.

The user should refer to the concrete class mmgroup.MMSpace instead.

Class AbstractMmRepSpace handles the input and output of vectors 
in R_p. Here a basis vector of R_p is constructed from a tuple 
(tag, i0, i1), where the tag is a single capital letter as described 
in file mm_aux.c, and i0 and i1 are integers describing the basis 
vector.

Method unit() of class AbstractMmRepSpace constructs a basis vector
from a tuple (tag, i0, i1j). Standard operators "+" and "*" can be 
used for vector addition and scalar multiplication, and also for 
right multiplication of a vector by a group element. Optionally, a 
tuple (scalar, tag, i0, i1) can be passed with 'scalar' an integer 
or a specific small letter indicating a random scalar.

Since class AbstractMmRepSpace is a subclass of class AbstractRepSpace,
vectors of R_p can also be constructed by calling an instance of a
suitable subclass of class AbstractMmRepSpace with arguments describing 
the vector to be constructed. See method __call__ in class 
AbstractRepSpace for details.

The monster group Mm acting on R_p by right multiplication is modelled
by an instance of subclass of class mmgroup.structures.AbstractGroup.
Such a class has methods for generating elements of Mm. The operation 
of x in Mm on v in R_p is simply coded as v * x.

Class AbstractMmRepSpace is an abstract class that supports two
implementations of the concrete class mmgroup.MMVector. 


Internal operation
------------------

The representation (tag, i0, i1) of a basis vector of R_p is human
readable and in one-to-one correspondence with the description of 
basis vectors of R_p in [Seys19]. On the C level, dealing with
tuples and characters causes unnecessary overhead. Here we 
represent a scalar multiple of a basis vector in the so-called
sparse format. This means that a multiple of a basis vector is
represented as an unsigned 32-bit integer, where certain bit fields
of the integer correspond to the parts (scalar, tag, i0, i1) of the 
tuple, and another bit field corresponds to the coordinate.
Details are given in the documentation of the module mm_aux.c.
Vectors in R_p are can also be represented as arrays of 32-bit
integers. That representation of a vector is called the 'sparse'
representation. 

In python, a sparse representation of a vector in R_p is implemented
as a one-dimension numpy array of dtype np.uint32. 

Class AbstractMmRepSpace handles the operations on vectors an group
elements. It makes no specific assumptions on the internal 
representation of a vector or a group element. Thus it can easily be 
adjusted to a fast C implementation of the group operation on vectors.

However, class  AbstractMmRepSpace assumes that conversion between 
the internal format and the sparse format of a vector is supported, 
e.g. by fast C routines. It handles the conversion between the sparse
representation of a vector and the representation of a vector as a
list of tuples. This includes the implementation of the usual methods
__getitem__ and __setitem__ for vectors.

Caution!
v['A', i, j] and v['A', j, i] refer to the same coordinate of 
vector v. Item assignment is done in the same way as in numpy. 
Thus the result of the following assignment is not well defined:

    v['A',1:3,1:3] = [[11,12], [21,22]]

Depending on the internal order of the item assignment it may set
v['A',1,2] and v['A',2,1] either to 12 or to 21. A similar problem 
may occur with tags 'B' and 'C'.


Subspaces spanned by basis vectors
----------------------------------

We also use the sparse representation to describe subspaces of R_p 
generated by basis vectors. Function sparse_subspace() creates a 
numpy array describing such a subspace. Note that the
characteristic p need not be known for describing such a subspace.

Subclasses of class AbstractMmRepSpace
--------------------------------------

A subclass of class AbstractMmRepSpace must implement the following
methods:

zero:
should return the zero vector

iadd:
vector addition, see class AbstractRepSpace for details.

imul_scalar:
scalar multiplication, see class AbstractRepSpace for details.

imul_group_word:
multiply a vector with an atom of the group Mm,  see class 
AbstractRepSpace for details. See class MGroupN for decomposing
an element of Mm into atoms.

reduce:  
Reduce the representation of a vector to a standard form. This is  
not necessary if each vector has a unique internal representation.

as_sparse:
Converts a vector (which is an instance of a subclass of class)
AbstractRepVector to its sparse representation. This function 
should return a numpy array containing the sparse representation.


getitems_sparse:
Support for reading components of a vector with __getitem__.
This method takes an array of integers containing the descriptions
of the basis vectors for which the co-ordinates are to be read.
Here the basis vectors must be given in sparse format.
In that array, the function must update the fields containing 
the coordinates.

setitems_sparse:
Support for writing components of a vector with __setitem__.
This method takes an array of integers containing the descriptions
of the basis vectors for which the co-ordinates are to be written,
and the co-ordinates.
Here the basis vectors must be given in sparse format.


additems_sparse:
Support for item assignment on a vector with __setitem__.
This method takes an array of integers containing the descriptions
of a vector v in sparse format. It add vector v to a given vector.


equal_vectors:
Check if two vectors are equal.

optional methods:

set_rand_uniform:
randomize a vector a uniform distributed


"""

#234567890123456789012345678901234567890123456789012345678901234567890

from __future__ import absolute_import, division, print_function
from __future__ import  unicode_literals

import warnings
import copy
import numbers
import re
from numbers import Integral
from random import randint, randrange, sample
from functools import partial
from collections import defaultdict
import numpy as np


from mmgroup.structures.abstract_rep_space import mod_rand_invertible
from mmgroup.structures.abstract_rep_space import mod_rand_unit
from mmgroup.structures.abstract_rep_space import AbstractRepSpace
from mmgroup.structures.abstract_rep_space import AbstractRepVector


from mmgroup.structures.parse_atoms import AtomDict 
from mmgroup.structures.parse_atoms import eval_atom_expression, ihex 


from mmgroup.structures.mm_space_indices import tuple_to_sparse
from mmgroup.structures.mm_space_indices import sparse_from_indices
from mmgroup.structures.mm_space_indices import sparse_add_data
from mmgroup.structures.mm_space_indices import sparse_to_ndarray
from mmgroup.structures.mm_space_indices import sparse_to_str
from mmgroup.structures.mm_space_indices import sparse_to_tuples
from mmgroup.structures.mm_space_indices import purge_sparse_entry






ERR_MM_CONV = "Sparse representation of MM vectors not fully supported"


try:
    from mmgroup.mm import mm_aux_mul_sparse
except (ImportError, ModuleNotFoundError):
    warnings.warn(ERR_MM_CONV, UserWarning)   


#######################################################################
# Import derived classed
#######################################################################

import_pending = True

def complete_import():
    """Internal function of this module

    If you need any of the objects declared above, do the following:

    if import_pending:
        complete_import()
    """
    global import_pending, XLeech2, mm_aux_index_leech2_to_sparse
    from mmgroup.mm import mm_aux_index_leech2_to_sparse 
    from mmgroup.structures.xleech2 import XLeech2
    import_pending = False


#######################################################################
# class AbstractMmRepVector
#######################################################################



def xleech2_to_sparse(p, v):
    if import_pending:
        complete_import()
    assert isinstance(v, XLeech2)
    v2 = v.value
    sp = mm_aux_index_leech2_to_sparse(v2)
    if sp == 0:
        err = "Instance of class XLeech2 is not of type 2"
        raise ValueError(err)
    sign = p-1 if v2 & 0x1000000 else 1
    return np.array([sp + sign], dtype = np.uint32)


class AbstractMmRepVector(AbstractRepVector):
    space = None # Vector space associated with vector type

    def __init__(self, p, tag = 0, i0 = None, i1 = None):
        self.set_zero(self, p)
        add_vector(self, tag, i0, i1)

    def set_zero(self, p = 0):
        """set a vector to zero

        The vector is to be interpreted as a vevtor of integers
        modulo ``p``. In case ``p == 0`` it is to be inerpreted
        as a vector of rational numers
        """
        raise NotImplementedError("Abstract method")


    def as_bytes(self):
        """Return vector as an array of bytes.

        The function returns a one-dimensional numpy array of ``196884``
        unsigned integers of bit length ``8``.

        For a vector ``v`` the expression ``v.as_bytes()`` is equivalent 
        to ``v['E':]``.
        """
        return self.space.as_bytes(self)

    def as_sparse(self):
        """Return vector in sparse representation

        The sparse representation of a vector is returned as a 
        one-dimensional numpy array of 32-bit integers. Here each 
        entry of that array represents a nonzero component of
        the vector. Such an entry it interpreted as described
        in method ``from_sparse`` of class |MMSpace|.

        Entries with value zero are dropped.
        """
        return self.space.as_sparse(self)

    def as_tuples(self):
        """Return vector in tuple representation

        The function returns a list of tuples ``(factor, tag, i0, i1)``.
        Here ``(tag, i0, i1)`` describes a basis vector and ``value``
        is the coordinate of that vector.

        Entries with coordinate zero are dropped.
        """
        return self.space.as_tuples(self)

    def get_sparse(self, a_sparse):
        """Get coordinates of vectors in sparse representation.

        Here 'a_sparse' is an array-like object containing the
        indices of basis vectors to be read in sparse representation. 

        The function returns a numpy array 'a' in sparse representation
        with the indices of the basis vectors read from the input 
        'a_sparse'. It updates the corresponding values in 'a'
        a with the coordinates of the basis vector taken from v. 
        """
        a = np.array(a_sparse, dtype = np.uint32)
        a1 = a.reshape(-1)
        self.space.getitems_sparse(self, a1)
        return a

    def set_sparse(self, a_sparse):
        """Set coordinates of vectors given by sparse representation.

        Here 'a_sparse' is an array-like object containing the
        indices of the basis vectors and the values to be written
        in sparse representation. 

        The function updates the coordinates of the vector given
        by the entries of 'a_sparse' with the corresponding values.

        The order of these write operations is unspecified. Therefore
        array 'a_sparse' should not contain any duplicate basic 
        vector indices with inconsistent values. 
        """
        a = np.array(a_sparse, dtype = np.uint32)
        a1 = a.reshape(-1)
        self.space.setitems_sparse(self, a1)

    def projection(self, *args):
        """Return projection of the vector ``v`` onto a subspace.

        Each argument describes a subspace of this space ``V``. 
        The projection of the vector to the space 
        ``W`` spanned by  all these subspaces is returned.

        In the current version each argument must be a tuple
        ``(tag, i0, i1)`` that describes a subspace generated by a
        basis vector. A legal ``tag`` is letter in the string
        ``ABCTXYZD``, as explained in table :ref:`table-vector-tags` 
        and class |MMSpace|. 
        """
        a = sparse_subspace(*args)
        self.space.getitems_sparse(self, a)
        v = self.space.zero(self.p)
        self.space.setitems_sparse(v, a)
        return v
        
    def raw_str(self):
        return self.space.raw_str_vector(self)
        


######################################################################
# class AbstractMmRepSpace
######################################################################


class AbstractMmRepSpace(AbstractRepSpace):
    """Models the 198884 dimensional representation the monster group. 

    A basis vector is modeled as a tuple  vtype = (tag, i0, i1)  as
    described in file mm_aux.c. 'tag' is a capital letter. 'i0' and
    'i1' are two integers or small letters indicating a random
    number. A scalar multiple of a basis vector may be written as 
    (scalar, tag, i0, i1) where 'scalar' is an integer or a small 
    letter indicateing a random scalar.

    Method self.unit(scalar, tag, i0, i1) or self.unit(tag, i0, i1) 
    can be used to construct a (scalar multiple of a) unit vector.

    Here 'tag' is a single capital letter representing the tag 
    of the unit vector, and i0 and i1 are integers.

    Valid tags are 'A', 'B', 'C', 'T', 'X', 'Y', 'Z' as described       
    in mm_aux.c, and also 'D' with 'D_i' meaning 'A_i_i'.
      
    The optional the expression 'scalar' may be an integer 
    denoting a scalar factor to be multiplied with the unit
    vector or one of following letters for generating a random 
    multiple of a unit vector:
      
      'r'   generate a random multiple of a unit vector which may  
            be a zero or a zerodivisor
      'n':  generate a random multiple of a unit vector which is  
            not a zerodivisor
      's'   generate a (possibly negated) unit vector  
      'u'   generate a unit vector 

    i0 or i1 may also be the string 'r' indicating a random value.

    Scalars are integers modulo a small odd number p given in 
    the constructor.   Operations  + and - are supported for vectors.
    Operation and *, /, << and >> are supported for vectors and 
    integers modulo p.  Here 1/b is the modular inverse of b,  a << b 
    means a * 2**b,  a >> b means a * 2**(-b).

    Each instance of this class has a group operating on it, which 
    is passed in the constructor. Such a group should be an instance 
    of class mmgroup.structures.MGroupN. Elements of the group operate 
    on vectors by right multiplication. Division of  a vector by a 
    group element means multiplication with the inverse element.
    
    Vectors in a space may be created by calling an instance of this
    class representing that space with any number of arguments. All
    these arguments are added up, and each argument v may be one of 
    the following.
    
    - v may be a string which is a valid python expression.
      Here identifiers of the following format are recognized as unit
      vectors:
      
         <scalar>_<tag>_<i0>_<i1>   or   <tag>_<i0>_<i1> .

      Here letters and integers are interpreted as in method
      self.unit(). Integers may be decimal, hexadecimal 0xH...H as 
      in C or of the form DX...Xh with D a decimal and X a 
      hexadecimal digit. 
   
      Addition, subtraction and scalar multiplication may be encoded
      in the python expression to generate more general vectors.
      
    - v may be a vector in that space.
      
    - v may be an element of another space V.
      If there is a homomorphism phi from V to this space 
      (set by  method set_preimage) this evaluates to phi(v).

    - v may be a tuple (tag, i0, i1) or (scalar, tag, i0, i1).
      Here scalar, tag, i0, and i1 are interpreted as in
      method self.unit().

    - v may be the integer 0. This means the zero vector.
    """

    space_name = "AbstractMmRepSpace"

    vector_type = AbstractRepVector


    #######################################################################
    # Constructor
    #######################################################################


    def __init__(self):
        """Create a 196884-dimensional representation of the monster

        All calculations are done modulo the odd number 3 <= p < 256.
        'group' is the group operating on the vector space by right
        multiplication. This should be an instance of class 
        mgroup_n.MGroupN.
        """
        pass

    #######################################################################
    # Obtaining and setting components via sparse vectors
    #######################################################################

    def getitems_sparse(self, v, a_sparse):
        """Obtain selected components of a vector 
        
        Support for reading components of a vector v with __getitem__().

        This method takes a numpy array 'a_sparse' of integers of dtype 
        numpy.uint32 containing the descriptions of the basis vectors for 
        which the co-ordinates are to be read.  Here the basis vectors 
        must be given in sparse format. In that array, the function must 
        update the fields 'scalar' in each entry of the array with the 
        coordinate of the vector  v  corresponding to the basis vector
        given be the fields  (tag, i0, i1). 

        Here vector v is a standard vector in this space.
        """
        raise NotImplementedError("Abstract internal method") 

    def setitems_sparse(self, v, a_sparse):
        """Update selected components of a vector 

        Support for writing components of a vector v with __setitem__().

        This method takes a numpy array 'a_sparse' of integers of dtype 
        numpy.uint32 containing the descriptions of the basis vectors for 
        which the co-ordinates are to be written. The array must also
        contain the values to be written. Here the basis vectors and
        their values must be given in sparse format. The function must 
        update the corresponding coordinates of the vector v.

        Caution:
        If duplicate coordinates are given in a_sparse, these coordinates  
        may be updated in any order. The user is responsible for not 
        assigning different values to the same co-ordinate.

        Here vector v is a standard vector in this space.
        """
        raise NotImplementedError("Abstract internal method") 


    def additems_sparse(self, v, a_sparse):
        """Add a vector in sparse representation to vector v.

        This method takes a numpy array 'a_sparse' of integers of dtype 
        numpy.uint32 containing the description of a vector v2 in sparse 
        representation. It computes 

             v  =  v + v2 .

        Here vector v is a standard vector in this space.
        """
        raise NotImplementedError("Abstract internal method") 


    #######################################################################
    # Conversion from and to sparse representation 
    #######################################################################


    def from_sparse(self, a_sparse):
        """Return vector a given by its sparse representation.

        Here ``a_sparse`` is an array-like list of unsigned 
        integers that contains the sparse representation of a vector. 
        Each  entry of that array represents a component of
        the vector. Such an entry it interpreted as follows:

        .. table:: Bit fields in an entry of a sparse representation
          :widths: 28 24 24 24 24 
 
          ========= =========== =========== ========== ========== 
          Component ``tag``     ``i0``      ``i1``     ``factor``
          Bits      ``27 - 25`` ``24 - 14`` ``13 - 8`` ``7 - 0``
          ========= =========== =========== ========== ==========

        This corresponds to the tuple ``(factor, tag, i0, i1)``
        which denotes a multiple of a basis vector as described in
        the section :ref:`mmrep-label`. 

        Tags are mapped to integers as follows:

        .. table:: Mapping of integers to tags
          :widths: 14 14 14 14 14 15 15

          ======= ======= ======= ======= ======= ======= ======= 
          ``'A'`` ``'B'`` ``'C'`` ``'T'`` ``'X'`` ``'Z'`` ``'Y'``
           ``1``   ``2``   ``3``   ``4``   ``5``   ``6``   ``7``
          ======= ======= ======= ======= ======= ======= ======= 


        The array-like object ``a_sparse`` is interpreted in the same
        way as in the construction of a numpy array of unsigned
        ``32``-bit integers.

        .. warning::
          If duplicate coordinates are given in ``a_sparse``, these 
          coordinates may be updated in any order. The user is responsible 
          for not assigning different values to the same coordinate.
        """
        v = self.zero()
        self.setitems_sparse(v, a_sparse)
        return v
    

    def as_sparse(self, v1):
        """Return sparse representation of vector v1
        
        This should return the sparse representation of a vector as
        a list of integers. The list is not sorted.
        
        The function may be implemented as a generator.
        """
        raise NotImplementedError("Abstract method")


    def via_sparse(self, v1):
        """Conversion from another vector space via sparse rep"""
        raise NotImplementedError
        assert v1.space.p % self.p == 0
        sp = v1.space.as_sparse(v1)
        sp = (sp & 0xffffff00) + (sp & 0xff) % self.p
        return self.from_sparse(sp)
        
    #######################################################################
    # Conversion to a list of tuples 
    #######################################################################

    def as_tuples(self, v1):
        """Convert vector v1 from sparse representation to list of tuples

        The function returns a list of tuples

           (scalar, tag, i0, i1)

        Here (tag, i0, i1) represents a unit vector and the integer
        'scalar' is the coordinate of that unit vector.
        """
        a_sparse = self.as_sparse(v1)
        return sparse_to_tuples(a_sparse) 

    #######################################################################
    # Creating vectors 
    #######################################################################

        
    def unit(self, *data):
        """Return unit vector given by ([scalar], tag, i0, i1)

        Here (possibly a random multiple of) a basis vector is
        created according to the input parameters 
        ([scalar], tag, i0, i1). 
    
        The 'scalar' is optional and defaults to 1.
        """        
        raise NotImplementedError
        return  self.from_sparse(tuple_to_sparse(self.p, *data))   


    def rand_vector(self, tags = None, min_weight=1, max_weight=0):
        """return a random vector

        'tags' is a string as in method unit(). The vector contains 
        between 'min_weight' and 'max_weight' nonzero components, 
        as returned by method rand_unit().
        In is possible that such components may cancel.

        If no argument is given, we return a uniform random vector, 
        if the class supports this.
        """
        raise NotImplementedError
        if tags is None:
            return self.rand_uniform()
        n = randint(min_weight, max(min_weight,max_weight))
        v = self.zero()
        for i in range(n):
            tag = sample(tags, 1)[0]
            v += self.unit(tag, "r")
        return v

    def rand_uniform(self):
        """Create a random vector with uniform distribution"""
        raise NotImplementedError("Cannot generate a uniform random vector") 

    def set_rand_uniform(self, vector):
        """Create a random vector with uniform distribution"""
        raise NotImplementedError("Cannot generate a uniform random vector") 


    def iadd_tuple(self, v1, t):
        """Compute v += self.unit(*t), return v"""
        raise NotImplementedError
        v2 = tuple_to_sparse(v1.p, *t)
        return self.additems_sparse(v1, v2)

    #######################################################################
    # getitem and setitem
    #######################################################################


    def vector_get_item(self, v1, item):
        assert v1.space == self
        if not isinstance(item, tuple):
            item = (item,) 
        shape, a_sparse = sparse_from_indices(v1.p, *item)
        self.getitems_sparse(v1, a_sparse)
        return sparse_to_ndarray(v1.p, shape, a_sparse)

    def vector_set_item(self, v1, item, value):
        assert v1.space == self
        if not isinstance(item, tuple):
            item = (item,) 
        shape, a_sparse = sparse_from_indices(v1.p, *item)
        if isinstance(value, AbstractRepVector):
            self.getitems_sparse(value, a_sparse)
        else:
            sparse_add_data(v1.p, shape, a_sparse, value)
        self.setitems_sparse(v1, a_sparse) 

         

    #######################################################################
    # Formatting a vector 
    #######################################################################

    


    def str_vector(self, v1):
        s = sparse_to_str(v1.p, self.as_sparse(v1))
        return "%s<%d;%s>" % (self.space_name, v1.p, s)
           

    def raw_str_vector(self, v1):
        return sparse_to_str(v1.p, self.as_sparse(v1))


######################################################################
# Creating a sparse representation of a subspace
######################################################################


def sparse_subspace(*args):
    """Create a sparse representation of a subspace of MM vectors
    
    The function returns the description of a subspace of the space 
    R_p spanned by basis vectors of R_p. That description is returned
    as a sorted numpy array of type uint32. Each entry of that array 
    describes a basis vector in sparse representation (with component 
    'value' set to zero). 

    Each argument describes a subspace of R_p; and the span of all 
    these subspaces is returned. An argument may be:

    - A set or an array-like object containing integers
        Then all entries are interpreted as sparse representations
        of basis vectors, ignoring the scalar factor. Integers not
        representing basis vectors correctly are ignored.
        This feature is not implemented in the current version!!!

    - A tuple (tag, i0, i1) representing a basis vector.
        Here a tuple is interpreted as a vector in the same way as 
        in a call to an instance of class AbstractMmRepVector. 
        Note that such a call is used for generating a vector.

    - A vector v in a vector space R_p
        Here the vector space R_p must be an instance of a 
        (subclass of) class AbstractMmRepVector, and v must be a
        vector in that space. Then v describes the subspace spanned 
        by all basis vectors where the coordinate of v is not zero. 
        This feature is not implemented in the current version!!!
    """
    a = [np.empty(0, dtype = np.uint32)]
    for v in args:
        if isinstance (v, set):
            v = list(v)
        if isinstance(v, tuple):
            a.append(tuple_to_sparse(1, *v))
        elif isinstance(v, AbstractMmRepVector):
            err = "MM Vector input in function sparse_subspace not supported"
            raise(NotImplementedError, err)
            a.append(v.as_sparse())
        else:
            err = "Array input in function sparse_subspace not supported"
            raise(NotImplementedError, err)
            va = np.array(v, dtype = np.uint32).reshape(-1)
            for i, x in enumerate(va):
                va[i] = purge_sparse_entry(x)
            a.append(va)
    a_out = np.unique(np.concatenate(a) & 0xffffff00)
    if len(a_out) and a_out[0] == 0:
        a_out = a_out[1:]
    return a_out


######################################################################
# Constructing a vector
######################################################################


FRAME = re.compile(r"^([A-Za-z_])+\<([0-9]+;)?(.+)\>$") 

MMVector = None

def parse_mm_space_string(space, p, s):
    global MMVector
    m = FRAME.match(s)
    p_intern, space_intern = None, None
    if m:
        space_name, p_str, string = (m[1], m[2], m[3])
        if p_str: p_intern = int(p_str[:-1])
        space_intern = SPACES_BY_NAME.get(space_name)
    else:
        space_intern = space.vector_type
        string = s
        p_intern = p
    if p_intern is None:
        p_intern = p
    if space_intern is None:
        if MMVector is None:
            from mmgroup import MMVector
        space_intern = MMVector
    f = AtomDict(partial(space_intern, p_intern))
    return eval_atom_expression(string, f)
    



ERR_MMV_TYPE = "Cannot convert type '%s' object to MM vector"


def add_conv_vector(vector, tag, factor = 1):
    p = vector.p
    space = vector.space
    if isinstance(tag, str):
        a = parse_mm_space_string(space, p, tag)
    else:
        a = tag
    if (a.space == space and a.p == p):
        factor %= p
        if factor <= 1:
            if factor: space.iadd(vector, a)
        else:
            space.imul_scalar(a, factor)
            space.iadd(vector, a)
    elif (a.space, space) in MM_VECTOR_CONVERSIONS:
        f = MM_VECTOR_CONVERSIONS[(a.space, space)]
        return f(p, factor, a)
    else:
        v = a.as_sparse()
        v1 = np.zeros(len(v), dtype = np.uint32)
        res = mm_aux_mul_sparse(a.p, v, len(v), factor, p, v1)
        if res < 0:
            err = "Cannot reduce sparse MM vector modulo %d"
            raise ValueError(err, p)
        space.additems_sparse(vector, v1[:res])

def add_vector(vector, tag = 0, i0 = None, i1 = None):
    if not tag:
        return
    p = vector.p
    space = vector.space
    if isinstance(tag, str) and len(tag) == 1:
        if tag in "ABCTXZYDEIJUS0":
            space.additems_sparse(vector,
                tuple_to_sparse(p, tag, i0, i1))
        elif tag == "R":
            space.set_rand_uniform(vector)
        elif tag == "V":
            v1 = space.from_bytes(p, i0)
            space.iadd(vector, v1)
        else:
            err = "Illegal tag '%s' for MM vector"
            raise ValueError(err % tag)
    elif isinstance(tag, (AbstractMmRepVector, str)):
         add_conv_vector(vector, tag, factor = 1)
    elif isinstance(tag, list):
        for x in tag:
            if isinstance(x, tuple):
                space.additems_sparse(vector,
                    tuple_to_sparse(p, *x))
            elif isinstance(x, (str, AbstractMmRepVector)):
                add_vector(vector, x)  
            elif x:
                raise TypeError(ERR_MMV_TYPE % type(x))                                                     
    elif isinstance(tag, Integral):
        add_conv_vector(vector, i0, factor = tag)
    else:
        if import_pending:
            complete_import()
        if isinstance(tag, XLeech2):
            space.additems_sparse(vector, (xleech2_to_sparse(p, tag)))
        else:
            raise TypeError(ERR_MMV_TYPE % type(tag))       

  
MM_VECTOR_CONVERSIONS = {
}


SPACES_BY_NAME = {
}






