All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.nuiton.math.matrix.AbstractMatrixND Maven / Gradle / Ivy

The newest version!
/*
 * #%L
 * Nuiton Matrix :: API
 * %%
 * Copyright (C) 2004 - 2023 CodeLutin
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser 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 Lesser Public License for more details.
 * 
 * You should have received a copy of the GNU General Lesser Public 
 * License along with this program.  If not, see
 * .
 * #L%
 */

package org.nuiton.math.matrix;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Pattern;

import org.apache.commons.collections.primitives.ArrayIntList;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.nuiton.util.ArrayUtil;

/**
 * Abstract matrix class with all dimension and semantics support (but
 * no internal value storage).
 *
 * Created: 29 oct. 2004
 *
 * @author Benjamin Poussin <[email protected]>
 * @version $Revision$
 *
 * Mise a jour: $Date$
 * par : $Author$
 */
public abstract class AbstractMatrixND implements MatrixND { // AbstractMatrixND

    /** serialVersionUID. */
    private static final long serialVersionUID = -6838751468730930727L;

    /** to use log facility, just put in your code: log.info(\"...\"); */
    private static Log log = LogFactory.getLog(AbstractMatrixND.class);

    protected transient DimensionHelper dimHelper = new DimensionHelper();

    protected transient MatrixFactory factory = null;

    protected String name = "";

    protected String[] dimNames = null;

    protected int[] dim = null;

    protected List[] semantics = null;

    /**
     * @deprecated as of 2.1 seams unused
     */
    @Deprecated
    protected double defaultValue = 0;

    /**
     * Separateur CSV par défaut le point virgule.
     */
    public static final char CSV_SEPARATOR = ';';

    protected static final String NUMBER_REGEX =
            " *[+-]?[0-9]*\\.?[0-9]+([eE][+-]?[0-9]+)? *";
    protected static final Pattern NUMBER = Pattern.compile(NUMBER_REGEX);

    protected void init(int[] dim) {
        this.dim = new int[dim.length];
        System.arraycopy(dim, 0, this.dim, 0, dim.length);
        semantics = new List[dim.length];
        dimNames = new String[dim.length];
        // par defaut chaine vide pour le nom des dimensions
        Arrays.fill(dimNames, "");
    }

    protected AbstractMatrixND(MatrixFactory factory) {
        this.factory = factory;
    }

    public AbstractMatrixND(MatrixFactory factory, int[] dim) {
        this(factory);
        init(dim);
        for (int i = 0; i < getDimCount(); i++) {
            // par defaut les listes des semantiques contiennent des nulls
            semantics[i] = Collections.nCopies(dim[i], null);
        }
    }

    public AbstractMatrixND(MatrixFactory factory, List[] semantics) {
        this(factory);
        int[] dim = new int[semantics.length];
        for (int i = 0; i < dim.length; i++) {
            if (semantics[i] == null) {
                dim[i] = 0;
            } else {
                dim[i] = semantics[i].size();
            }
        }
        init(dim);
        for (int i = 0; i < getDimCount(); i++) {
            setSemantic(i, semantics[i]);
        }
    }

    public AbstractMatrixND(MatrixFactory factory, String name, int[] dim) {
        this(factory, dim);
        setName(name);
    }

    public AbstractMatrixND(MatrixFactory factory, String name, int[] dim,
            String[] dimNames) {
        this(factory, name, dim);
        for (int i = 0; dimNames != null && i < dimNames.length; i++) {
            setDimensionName(i, dimNames[i]);
        }
    }

    public AbstractMatrixND(MatrixFactory factory, String name,
            List[] semantics) {
        this(factory, semantics);
        setName(name);
    }

    public AbstractMatrixND(MatrixFactory factory, String name,
            List[] semantics, String[] dimNames) {
        this(factory, name, semantics);
        for (int i = 0; dimNames != null && i < dimNames.length; i++) {
            setDimensionName(i, dimNames[i]);
        }
    }

    @Override
    public MatrixND copy() {
        MatrixND result = getFactory().create(this);
        return result;
    }

    @Override
    public MatrixND clone() {
        return copy();
    }

    /**
     * Retourne la factory utilisée pour créer cette matrice, la factory
     * peut-être réutilisé pour créer d'autre matrice si besoin.
     */
    @Override
    public MatrixFactory getFactory() {
        return factory;
    }

    @Override
    public List[] getSemantics() {
        return Arrays.copyOf(semantics, semantics.length);
    }

    /**
     * {@inheritDoc}
     *
     * @deprecated Use #getSemantic(dim)
     */
    @Deprecated
    @Override
    public List getSemantics(int dim) {
        return getSemantic(dim);
    }

    @Override
    public List getSemantic(int dim) {
        return semantics[dim];
    }

    /**
     * {@inheritDoc}
     *
     * @deprecated Use #setSemantic(dim, List)
     */
    @Deprecated
    @Override
    public  void setSemantics(int dim, List sem) {
        setSemantic(dim, sem);
    }

    @Override
    public  void setSemantic(int dim, List sem) {
        if (!(sem instanceof SemanticList)) {
            sem = new SemanticList(sem);
        }
        // else SemanticList is immutable and can be used in many matrix in
        // same time this permit to used same indexOf optimization
        semantics[dim] = sem;
    }

    @Override
    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public String[] getDimensionNames() {
        return dimNames;
    }

    @Override
    public void setDimensionNames(String[] names) {
        for (int i = 0; names != null && i < names.length; i++) {
            setDimensionName(i, names[i]);
        }
    }

    /**
     * {@inheritDoc}
     *
     * @deprecated Use #getDimensionNames()
     */
    @Deprecated
    @Override
    public String[] getDimensionName() {
        return getDimensionNames();
    }

    /**
     * {@inheritDoc}
     *
     * @deprecated Use #setDimensionName(String[])
     */
    @Deprecated
    @Override
    public void setDimensionName(String[] names) {
        setDimensionNames(names);
    }

    @Override
    public void setDimensionName(int dim, String name) {
        dimNames[dim] = name;
    }

    @Override
    public String getDimensionName(int dim) {
        return dimNames[dim];
    }

    @Override
    @Deprecated
    public double getMaxOccurence() {
        return getMaxOccurrence();
    }

    @Override
    public double getMaxOccurrence() {
        // on creer un tableau dans cette classe, car on ne sait pas sur quelle
        // implantation on s'appuie. Mais dans les sous classes, si on a deja
        // un tableau il ne faut pas le recréer, on peut le passer directement
        int nbelem = 1;
        for (int i = 0; i < getDimCount(); i++) {
            nbelem *= getDim(i);
        }
        double[] data = new double[nbelem];
        int i = 0;
        for (MatrixIterator mi = iterator(); mi.next();) {
            data[i++] = mi.getValue();
        }
        return MatrixHelper.maxOccurrence(data);
    }

    /**
     * {@inheritDoc}
     *
     * @deprecated use #getDimCount() instead
     */
    @Override
    @Deprecated
    public int getNbDim() {
        return getDimCount();
    }

    @Override
    public int getDimCount() {
        return dim.length;
    }

    @Override
    public int[] getDim() {
        return dim;
    }

    @Override
    public int getDim(int d) {
        return dim[d];
    }

    @Override
    public long size() {
        return MatrixHelper.getVectorSize(getDim());
    }

    /**
     * Retourne la matrice elle meme. Les modifications sont faites directement
     * dessus
     */
    @Override
    public MatrixND map(MapFunction f) {
        for (MatrixIterator i = iterator(); i.next();) {
            i.setValue(f.apply(i.getValue()));
        }
        return this;
    }

    @Override
    public double getValue(Object[] coordinates) {
        // don't use getter, because getter create new array not needed here
        List[] sems = semantics;
        return getValue(MatrixHelper.semanticsToDimension(sems,
                coordinates));
    }

    @Override
    public double getValue(Object x) {
        // on peut utiliser dimHelper car le get ne le reutilisera pas en
        // interne
        return getValue(dimHelper.get(x));
    }

    @Override
    public double getValue(Object x, Object y) {
        return getValue(dimHelper.get(x, y));
    }

    @Override
    public double getValue(Object x, Object y, Object z) {
        return getValue(dimHelper.get(x, y, z));
    }

    @Override
    public double getValue(Object x, Object y, Object z, Object t) {
        return getValue(dimHelper.get(x, y, z, t));
    }

    @Override
    public double getValue(int x) {
        // on peut utiliser dimHelper car le get ne le reutilisera pas en
        // interne
        return getValue(dimHelper.get(x));
    }

    @Override
    public double getValue(int x, int y) {
        return getValue(dimHelper.get(x, y));
    }

    @Override
    public double getValue(int x, int y, int z) {
        return getValue(dimHelper.get(x, y, z));
    }

    @Override
    public double getValue(int x, int y, int z, int t) {
        return getValue(dimHelper.get(x, y, z, t));
    }

    @Override
    public void setValue(Object[] coordinates, double d) {
        setValue( // on accede directement a semantics (pas de get) pour eviter la creation d'un tableau
                MatrixHelper.semanticsToDimension(semantics, coordinates),
                d);
    }

    @Override
    public void setValue(Object x, double d) {
        setValue(dimHelper.get(x), d);
    }

    @Override
    public void setValue(Object x, Object y, double d) {
        setValue(dimHelper.get(x, y), d);
    }

    @Override
    public void setValue(Object x, Object y, Object z, double d) {
        setValue(dimHelper.get(x, y, z), d);
    }

    @Override
    public void setValue(Object x, Object y, Object z, Object t, double d) {
        setValue(dimHelper.get(x, y, z, t), d);
    }

    @Override
    public void setValue(int x, double d) {
        setValue(dimHelper.get(x), d);
    }

    @Override
    public void setValue(int x, int y, double d) {
        setValue(dimHelper.get(x, y), d);
    }

    @Override
    public void setValue(int x, int y, int z, double d) {
        setValue(dimHelper.get(x, y, z), d);
    }

    @Override
    public void setValue(int x, int y, int z, int t, double d) {
        setValue(dimHelper.get(x, y, z, t), d);
    }

    // TODO peut-etre faire une variante de equals qui regarde par rapport au
    // coordonnées sémantique
    @Override
    public boolean equals(Object o) {
        return this == o || o instanceof MatrixND && equals((MatrixND) o);
    }

    public boolean equals(MatrixND mat) {
        
        boolean result = true;
        
        if (mat != this) {
            // le nom doit être le même
            result = result && getName().equals(mat.getName());
    
            result = result && equalsValues(mat);
    
            if (result) {
                // les sémantiques doivent-être identique
                for (int i = 0; result && i < getDimCount(); i++) {
                    String dimName1 = getDimensionName(i);
                    String dimName2 = mat.getDimensionName(i);
                    result = ObjectUtils.equals(dimName1, dimName2);
                    if (log.isTraceEnabled()) {
                        log.trace("dimName1(" + dimName1 + ")==dimName2(" + dimName2
                                + ")=" + result);
                    }
                    // System.out.println("dimName1("+dimName1+")==dimName2("+dimName2+
                    // ")="+result);
        
                    List sem1 = getSemantic(i);
                    List sem2 = mat.getSemantic(i);
                    result = result && ObjectUtils.equals(sem1, sem2);
                    if (log.isTraceEnabled()) {
                        log.trace("sem1(" + sem1 + ")==sem2(" + sem2 + ")=" + result);
                    }
                    // System.out.println("sem1("+sem1+")==sem1("+sem2+ ")="+result);
                }
            }
    
            if (log.isTraceEnabled()) {
                log.trace("result=" + result);
            }
            // System.out.println("result="+result);
        }

        return result;
    }

    /**
     * Verifie si les matrices sont egales en ne regardant que les valeurs et
     * pas les semantiques
     *
     * @param mat
     * @return equality on values
     */
    @Override
    public boolean equalsValues(MatrixND mat) {
        boolean result = true;
        // les dimensions doivent-être identique
        result = result && MatrixHelper.sameDimension(getDim(), mat.getDim());

        if (result) {
            // toutes les données doivent être identique
            for (MatrixIterator i = mat.iterator(); result && i.next();) {
                double v1 = i.getValue();
                double v2 = getValue(i.getCoordinates());
                result = v1 == v2;
                if (log.isTraceEnabled()) {
                    log.trace("v1(" + v1 + ")==v2(" + v2 + ")=" + result);
                }
            }
        }

        return result;
    }

    @Override
    public String toString() {
        StringBuffer result = new StringBuffer();
        result.append("dimensions = [\n");
        for (int i = 0; i < getDim().length; i++) {
            result.append(getDim()[i] + ",");
        }
        result.append("\n]\nmatrice = [\n");
        for (MatrixIterator i = this.iterator(); i.next();) {
            result.append(i.getValue() + ",");
        }
        result.append("\n]\n");
        return result.toString();
    }

    @Override
    public List toList() {
        List result = new ArrayList();
        // [3,2,5,4]
        for (MatrixIterator i = iterator(); i.next();) {
            int[] coord = i.getCoordinates();
            double value = i.getValue();
            List tmp = (List) result;
            for (int dim = 0; dim < coord.length - 1; dim++) {
                while (tmp.size() <= coord[dim]) {
                    tmp.add(new ArrayList());
                }
                tmp = (List) tmp.get(coord[dim]);
            }
            while (tmp.size() <= coord[coord.length - 1]) {
                tmp.add(NumberUtils.DOUBLE_ZERO);
            }

            tmp.set(coord[coord.length - 1], value);
        }

        return result;
    }

    @Override
    public void fromList(List list) {
        // on suppose que les listes sont bien formé, c-a-d qu'elles sont
        // toutes de la meme dimension pour une dimension donnée.
        ArrayIntList dim = new ArrayIntList();
        List tmp = list;
        while (tmp.get(tmp.size() - 1) instanceof List) {
            dim.add(tmp.size());
            tmp = (List) tmp.get(tmp.size() - 1);
        }
        dim.add(tmp.size());
        MatrixND mat = getFactory().create(dim.toArray());

        for (MatrixIterator i = mat.iterator(); i.next();) {
            int[] coord = i.getCoordinates();
            tmp = list;
            for (int d = 0; d < coord.length - 1; d++) {
                tmp = (List) tmp.get(coord[d]);
            }

            Double value = (Double) tmp.get(coord[coord.length - 1]);
            i.setValue(value);
        }
        paste(mat);
    }

    public boolean isValidCoordinates(int[] pos) {
        return MatrixHelper.isValidCoordinates(getDim(), pos);
    }

    public boolean isValidCoordinates(Object[] pos) {
        // on n'utilise pas le getter qui cree un tableau (trop couteux)
        List[] sems = semantics;
        return MatrixHelper.isValidCoordinates(sems, pos);
    }

    @Override
    public double sumAll() {
        double result = 0;
        for (MatrixIterator i = iteratorNotZero(); i.next();) {
            result += i.getValue();
        }
        return result;
    }

    @Override
    public MatrixND sumOverDim(int dim) {
        return sumOverDim(dim, getDim(dim));
    }

//    @Override
//    public MatrixND sumOverDim(int dim, int step) {
//        if (step < 0) {
//            step = getDim(dim);
//        } else if (step <= 1) {
//            // il n'y a rien a faire, on fait une copie et on la retrourne
//            return getFactory().create(this);
//        }
//
//        // le nombre d'element qu'il y aura dans la dim pour le resultat
//        int nbDim = getDim(dim) / step;
//
//        List[] semantics = new List[getDimCount()];
//        System.arraycopy(getSemantics(), 0, semantics, 0, getDimCount());
//        semantics[dim] = semantics[dim].subList(0, nbDim);
//
//        // creation du resultat
//        MatrixND result = getFactory().create(getName(), semantics,
//                getDimensionNames());
//
//        for (int i = 0, maxi=result.getDim(dim); i < maxi; i++) {
//            MatrixND temp = getSubMatrix(dim, i * step, step);
//            MatrixND sum = result.getSubMatrix(dim, i, 1);
//            for (int s = 0, maxs=temp.getDim(dim); s < maxs; s++) {
//                sum.add(temp.getSubMatrix(dim, s, 1));
//            }
//        }
//        return result;
//    }

    @Override
    public MatrixND sumOverDim(int dim, int step) {
        int[] matDim = getDim();
        if (step < 0) {
            step = matDim[dim];
        }

        if (step <= 1) {
            // il n'y a rien a faire, on fait une copie et on la retrourne
            return copy();
        }

        // le nombre d'element qu'il y aura dans la dim pour le resultat
        // si le nombre d'element dans la dimension, n'est pas divisible
        // par step, alors un groupe supplementaire est cree qui contiendra
        // les dernieres valeurs. ex: dim=4 step=3 mat=[2, 3, 1, 5] -> result=[6, 5]
        int nbDim = (int)Math.ceil(matDim[dim] / (double)step);

        List[] sems = new List[matDim.length];
        System.arraycopy(semantics, 0, sems, 0, matDim.length);
        List sem = new ArrayList(nbDim);
        for (int i=0; i(sems[dim]);

        // creation d'un liste qui agrege les elements sommés
        List newElem = new ArrayList();
        for (int i = 0; i < nb; i++) {
            newElem.add(sems[dim].remove(start));
        }
        // on ajout la liste comme nouvel element de la semantique
        sems[dim].add(start, newElem);

        // creation du resultat
        MatrixND result = getFactory().create(getName(), sems,
                getDimensionNames());

        int end = start + nb;
        for(MatrixIterator i=iteratorNotZero(); i.hasNext(); ) {
            i.next();
            double val = i.getValue();
            int[] pos = i.getCoordinates();
            if (start < pos[dim] && pos[dim] < end) {
                pos[dim] = start;
            } else if (end <= pos[dim]) {
                pos[dim] -= nb - 1;
            }

            double oldVal = result.getValue(pos);
            result.setValue(pos, oldVal + val);
        }


//        MatrixND sub1 = this.getSubMatrix(dim, 0, start);
//        MatrixND sub2 = this.getSubMatrix(dim, start, nb).sumOverDim(dim);
//        MatrixND sub3 = this.getSubMatrix(dim, start + nb, getDim(dim)
//                - (start + nb));
//
//        int[] origin = new int[getDimCount()];
//        result.paste(origin, sub1);
//        origin[dim] = start;
//        result.paste(origin, sub2);
//        if (start + 1 < result.getDim(dim)) {
//            origin[dim] = start + 1;
//            result.paste(origin, sub3);
//        }

        return result;
    }
    
    @Override
    public double meanAll() {
        double sum = sumAll();
        double number = size();
        double result = sum / number;
        return result;
    }

    @Override
    public MatrixND meanOverDim(int dim) {
        return meanOverDim(dim, getDim(dim));
    }

    @Override
    public MatrixND meanOverDim(int dim, int step) {
        int initialDimSize = getDim(dim);
        if (step < 0) {
            step = initialDimSize;
        } else if (step <= 1) {
            // il n'y a rien a faire, on fait une copie et on la retrourne
            return getFactory().create(this);
        }

        int dimSize = initialDimSize / step;
        int residual = initialDimSize % step;

        // on fait les sommes ...
        MatrixND result = sumOverDim(dim, step);

        // ... et on divise par le nombre d'element somme
        for(MatrixIterator i=result.iteratorNotZero(); i.hasNext(); ) {
            i.next();
            int[] pos = i.getCoordinates();
            
            double divisor = step;
            if (pos[dim] >= dimSize) {
                divisor = residual;
            }

            if (divisor != 1) { // diviser par 1 ne sert a rien sauf perdre du temps
                double val = i.getValue();
                double newVal = val / divisor;
                i.setValue(newVal);
            }
        }
//
//
//        // le nombre d'element qu'il y aura dans la dim pour le resultat
//        int nbDim = getDim(dim) / step;
//
//        List[] semantics = new List[getDimCount()];
//        System.arraycopy(getSemantics(), 0, semantics, 0, getDimCount());
//        semantics[dim] = semantics[dim].subList(0, nbDim);
//
//        // creation du resultat
//        MatrixND result = getFactory().create(getName(), semantics,
//                getDimensionNames());
//
//        for (int i = 0; i < result.getDim(dim); i++) {
//            MatrixND temp = getSubMatrix(dim, i * step, step);
//            MatrixND sum = result.getSubMatrix(dim, i, 1);
//            for (int s = 0; s < temp.getDim(dim); s++) {
//                sum.add(temp.getSubMatrix(dim, s, 1));
//            }
//            sum.divs(temp.getDim(dim)); // mean specifics
//        }
        return result;
    }

    @Override
    public MatrixND cut(int dim, int[] toCut) {
        throw new UnsupportedOperationException("Méthode non implantée");
    }

    /**
     * Modifie la matrice actuel en metant les valeurs de mat passé en parametre
     */
    @Override
    public MatrixND paste(MatrixND mat) {
        return paste(new int[getDimCount()], mat);
    }

    /**
     * Modifie la matrice actuel en metant les valeurs de mat passé en parametre
     *
     * @param origin le point d'origine a partir duquel on colle la matrice
     * @param mat une matrice avec le meme nombre de dimension, si la matrice
     *            que l'on colle est trop grande, les valeurs qui depasse ne
     *            sont pas prises en compte
     */
    @Override
    public MatrixND paste(int[] origin, MatrixND mat) {
        if (mat != null) {
            for (MatrixIterator mi = mat.iterator(); mi.next();) {
                int[] coordinates = ArrayUtil.sum(origin, mi.getCoordinates());
                if (isValidCoordinates(coordinates)) {
                    setValue(coordinates, mi.getValue());
                }
            }
        }
        return this;
    }

    /**
     * Modifie la matrice actuel en metant les valeurs de mat passé en parametre
     * La copie se fait en fonction de la semantique, si un element dans une
     * dimension n'est pas trouvé, alors il est passé
     */
    @Override
    public MatrixND pasteSemantics(MatrixND mat) {
        if (mat != null) {
            for (MatrixIterator mi = mat.iterator(); mi.next();) {
                Object[] sems = mi.getSemanticsCoordinates();
                if (MatrixHelper.isValidCoordinates(getSemantics(), sems)) {
                    setValue(sems, mi.getValue());
                }
            }
        }
        return this;
    }

    @Override
    public MatrixND getSubMatrix(int dim, int start, int nb) {
        if (dim < 0) {
            dim = getDimCount() + dim;
        }
        if (start < 0) {
            start = getDim(dim) + start;
        }
        if (nb <= 0) {
            nb = getDim(dim) - start;
        }
        return new SubMatrix(this, dim, start, nb);
    }

    @Override
    public MatrixND getSubMatrix(int dim, Object start, int nb) {
        int begin = MatrixHelper.indexOf(getSemantics(), dim, start);
        return getSubMatrix(dim, begin, nb);
    }

    /**
     * Add to desambiguas some call with xpath engine, but do the same thing
     * {@link #getSubMatrix(int, Object[])}
     *
     * @param dim
     * @param elem
     * @return new matrix
     */
    public MatrixND getSubMatrixOnSemantic(int dim, Object... elem) {
        MatrixND result = getSubMatrix(dim, elem);
        return result;
    }

    @Override
    public MatrixND getSubMatrix(int dim, Object... elem) {
        int[] ielem = new int[elem.length];
        for (int i = 0; i < ielem.length; i++) {
            ielem[i] = MatrixHelper.indexOf(getSemantics(), dim, elem[i]);
        }
        return getSubMatrix(dim, ielem);
    }

    @Override
    public MatrixND getSubMatrix(Object[]... elems) {

        // la reduction doit se faire sur le meme nombre de dimension
        if (elems.length != dim.length) {
            throw new IllegalArgumentException(String.format("Can't get sub matrix with different dimension count (expected: %d, got %d)", dim.length, elems.length));
        }

        MatrixND result = this;
        for (int i = 0; i < elems.length; ++i) {
            if (elems[i] != null) {
                result = result.getSubMatrix(i, elems[i]);
            }
        }
        return result;
    }

    @Override
    public MatrixND getSubMatrix(int dim, int[] elem) {
        return new SubMatrix(this, dim, elem);
    }

    @Override
    public MatrixND getSubMatrix(int[]... elems) {

        // la reduction doit se faire sur le meme nombre de dimension
        if (elems.length != dim.length) {
            throw new IllegalArgumentException(String.format("Can't get sub matrix with different dimension count (expected: %d, got %d)", dim.length, elems.length));
        }

        MatrixND result = this;
        for (int i = 0; i < elems.length; ++i) {
            if (elems[i] != null) {
                result = new SubMatrix(result, i, elems[i]);
            }
        }
        return result;
    }

    /**
     * Modifie la matrice actuelle en lui ajoutant les valeurs de la matrice
     * passé en parametre. La matrice passé en parametre doit avoir le meme
     * nombre de dimension, et chacune de ses dimensions doit avoir un nombre
     * d'element au moins egal a cette matrice.
     */
    @Override
    public MatrixND add(MatrixND m) {
        for (MatrixIterator i=m.iteratorNotZero(); i.next();) {
            int[] pos = i.getCoordinates();
            double val = getValue(pos);
            setValue(pos, val + i.getValue());
        }
//        // TODO si les dimensions sont exactment les memes, on doit pouvoir
//        // gagner du temps en travaillant directement au niveau du vector
//        for (MatrixIterator i = iterator(); i.next();) {
//            i.setValue(i.getValue() + m.getValue(i.getCoordinates()));
//            // TODO faire une variante de add avec les semantiques
//        }
        return this;
    }

    /**
     * Modifie la matrice actuelle en lui soustrayant les valeurs de la matrice
     * passé en parametre. La matrice passé en parametre doit avoir le meme
     * nombre de dimension, et chacune de ses dimensions doit avoir un nombre
     * d'element au moins egal a cette matrice.
     */
    @Override
    public MatrixND minus(MatrixND m) {
        for (MatrixIterator i=m.iteratorNotZero(); i.next();) {
            int[] pos = i.getCoordinates();
            double val = getValue(pos);
            setValue(pos, val - i.getValue());
        }
//        // TODO si les dimensions sont exactment les memes, on doit pouvoir
//        // gagner du temps en travaillant directement au niveau du vector
//        for (MatrixIterator i = iterator(); i.next();) {
//            i.setValue(i.getValue() - m.getValue(i.getSemanticsCoordinates()));
//        }
        return this;
    }

    @Override
    public MatrixND transpose() {
        MatrixND result = null;

        if (getDimCount() > 2) {
            throw new MatrixException(
                    "La transpose ne peut-être fait que sur une matrice ayant 2 dimensions ou moins");
        }
        if (getDimCount() == 1) {
            result = getFactory()
                    .create(
                            getName(),
                            new List[] { Collections.nCopies(1, null),
                                    getSemantic(0) },
                            new String[] { "Dimension 0", getDimensionName(0) });
            for (int x = 0; x < getDim(0); x++) {
                result.setValue(0, x, getValue(x));
            }
        } else {
            result = getFactory().create(getName(),
                    new List[] { getSemantic(1), getSemantic(0) },
                    new String[] { getDimensionName(1), getDimensionName(0) });
            for (int x = 0; x < getDim(0); x++) {
                for (int y = 0; y < getDim(1); y++) {
                    result.setValue(y, x, getValue(x, y));
                }
            }
        }
        return result;
    }

    @Override
    public MatrixND reduce() {
        return reduce(1);
    }

    @Override
    public MatrixND reduceDims(int... dims) {
        Arrays.sort(dims);
        // tableau permettant de faire la correspondance entre les dimensions
        // de la matrice actuelle et les dimensions de la nouvelle matrice
        // l'element i du tableau qui correspond à la dimension i de la
        // nouvelle matrice contient la dimension equivalente dans
        // la matrice actuelle
        int[] correspondance = new int[getDimCount()];
        // les nouvelles semantiques
        List sem = new ArrayList();
        // les nouveaux noms de dimensions
        List dimName = new ArrayList();
        // il faut au moins une dimension pour la matrice
        int minNbDim = 1;
        for (int j = getDimCount() - 1; j >= 0; j--) {
            // si la dimension à plus d'un élément ou qu'il n'est pas dans dims
            // on garde la dimension
            if (getDim(j) > 1 || Arrays.binarySearch(dims, j) < 0
                    || j < minNbDim) {
                // on ne conserve que les dimensions supérieure à 1
                correspondance[sem.size()] = j;
                sem.add(getSemantic(j));
                dimName.add(getDimensionName(j));
                minNbDim--;
            }
        }
        MatrixND result = reduce(dimName, sem, correspondance);
        return result;
    }

    @Override
    public MatrixND reduce(int minNbDim) {
        // tableau permettant de faire la correspondance entre les dimensions
        // de la matrice actuelle et les dimensions de la nouvelle matrice
        // l'element i du tableau qui correspond à la dimension i de la
        // nouvelle matrice contient la dimension equivalente dans
        // la matrice actuelle
        int[] correspondance = new int[getDimCount()];
        // les nouvelles semantiques
        List sem = new ArrayList();
        // les nouveaux noms de dimensions
        List dimName = new ArrayList();
        for (int j = getDimCount() - 1; j >= 0; j--) {
            // si la dimension à plus d'un élément ou si on a pas assez de
            // dimension pour avoir le minimum demandé on prend la dimension
            if (getDim(j) > 1 || j < minNbDim) {
                // on ne conserve que les dimensions supérieure à 1
                correspondance[sem.size()] = j;
                sem.add(getSemantic(j));
                dimName.add(getDimensionName(j));
                // on vient de prendre une dimension il nous en faut une de
                // moins
                minNbDim--;
            }
        }

        MatrixND result = reduce(dimName, sem, correspondance);
        return result;
    }

    /**
     * Create new matrice from the current matrix.
     *
     * @param dimName dimension name for new matrix
     * @param sem semantic for new matrix
     * @param correspondance array to do the link between current matrix and
     *            returned matrix
     * @return new matrix
     */
    protected MatrixND reduce(List dimName, List sem,
            int[] correspondance) {
        // on converti les listes en tableau en inversant l'ordre car on
        // a fait un parcours en sens inverse
        int nbDim = sem.size();
        List[] newSemantics = new List[nbDim];
        String[] newDimNames = new String[nbDim];
        int[] tmpcorrespondance = new int[nbDim];
        for (int i = 0; i < nbDim; i++) {
            newSemantics[i] = (List) sem.get(nbDim - 1 - i);
            newDimNames[i] = (String) dimName.get(nbDim - 1 - i);
            tmpcorrespondance[i] = correspondance[nbDim - 1 - i];
        }
        correspondance = tmpcorrespondance;

        MatrixND result = getFactory().create(getName(), newSemantics,
                newDimNames);

        // on reprend les valeurs
        int[] newCoordinates = new int[result.getDimCount()];
        for (MatrixIterator mi = iteratorNotZero(); mi.next();) {
            int[] oldCoordinates = mi.getCoordinates();
            for (int i = 0; i < newCoordinates.length; i++) {
                newCoordinates[i] = oldCoordinates[correspondance[i]];
            }
            result.setValue(newCoordinates, mi.getValue());
        }
        return result;
    }

    @Override
    public MatrixND mult(MatrixND m) throws MatrixException {
        if (this.getDimCount() > 2 || m.getDimCount() > 2) {
            throw new MatrixException(
                    "La multiplication de matrice n'est pas applicable aux matrices de plus de 2 dimensions");
        }
        if (this.getDim(1) != m.getDim(0)) {
            throw new MatrixException(
                    "Le nombre de colonnes de la matrice m1 doit etre egal au nombre de lignes de la matrice m2");
        }

        MatrixND result = getFactory().create(
                new int[] { this.getDim(0), m.getDim(1) });
        double d;
        for (int x = 0; x < this.getDim(0); x++) {
            for (int y = 0; y < m.getDim(1); y++) {
                d = this.getValue(x, 0) * m.getValue(0, y);
                for (int k = 1; k < this.getDim(1); k++) {
                    d += this.getValue(x, k) * m.getValue(k, y);
                }
                result.setValue(x, y, d);
            }
        }
        return result;
    }

    @Override
    public MatrixND mults(final double d) {
        map(new MapFunction() {
            @Override
            public double apply(double val) {
                return val * d;
            }
        });
        return this;
    }

    @Override
    public MatrixND divs(final double d) {
        map(new MapFunction() {
            @Override
            public double apply(double val) {
                return val / d;
            }
        });
        return this;
    }

    @Override
    public MatrixND adds(final double d) {
        map(new MapFunction() {
            @Override
            public double apply(double val) {
                return val + d;
            }
        });
        return this;
    }

    @Override
    public MatrixND minuss(final double d) {
        map(new MapFunction() {
            @Override
            public double apply(double val) {
                return val - d;
            }
        });
        return this;
    }

    /**
     * Determine si la matrice supporte l'import et l'export CSV
     *
     * @return support du CSV
     * @deprecated since 2.2, always return {@code true}, CSV import/export is
     *      always supported
     */
    @Override
    @Deprecated
    public boolean isSupportedCSV() {
        return true;
    }

    /**
     * Import depuis un reader au format CSV des données dans la matrice
     *
     * @param reader le reader à importer
     * @param origin le point à partir duquel il faut faire l'importation
     *            int[]{x,y}
     */
    @Override
    public void importCSV(Reader reader, int[] origin) throws IOException {

        // input reader doesn't supprt mark operation
        BufferedReader bReader = new BufferedReader(reader);

        // test file format
        bReader.mark(1);
        char firstChar = (char)bReader.read();
        bReader.reset();
        if (firstChar == '[') {
            importCSVND(bReader, origin);
            return;
        }

        // if run here, it's a normal 1D or 2D import
        // as before 2.2 version
        int rowsCount = 0;
        List row = new ArrayList();
        StringBuffer number = new StringBuffer(20);
        boolean stop = false;

        for (int c = bReader.read(); !stop; c = bReader.read()) {
            if (c == -1) {
                stop = true;
            }
            if (c == ' ') {
                // skip space
            } else if (c == CSV_SEPARATOR) {
                if (NUMBER.matcher(number.toString()).matches()) {
                    Double val = Double.valueOf(number.toString());
                    row.add(val);
                }
                number.setLength(0);
            } else if (c == -1 || c == '\n' || c == '\r') {
                // is line return or equivalent char because space is already
                // skiped
                // or end of stream

                // at end of line, we must see if the leave number
                if (NUMBER.matcher(number.toString()).matches()) {
                    Double val = Double.valueOf(number.toString());
                    row.add(val);
                }
                number.setLength(0);

                if (!row.isEmpty()) {
                    if (getDimCount() == 1) {
                        int columnNumber = origin[0];
                        for (Double value : row) {
                            if (columnNumber < getDim(0)) {
                                setValue(new int[] { columnNumber }, value);
                                columnNumber++;
                            }
                        }
                    } else if (getDim().length == 2) {
                        MatrixND matrix = getFactory().create(
                                new int[] { 1, row.size() });
                        int columnNumber = 0;
                        for (Double value : row) {
                            matrix.setValue(new int[] { 0, columnNumber },
                                    value);
                            columnNumber++;
                        }
                        paste(new int[] { origin[0] + rowsCount, origin[1] },
                                matrix);
                        rowsCount++;
                        row.clear();
                    } else {
                        throw new MatrixException(
                                "Can't import matrix with more than 2 dimensions.");
                    }
                }
            } else {
                number.append((char) c);
            }
        }
    }

    /**
     * Import depuis un reader au format CSV des données dans la matrice.
     *
     * @param reader le reader à importer
     * @param origin le point à partir duquel il faut faire l'importation
     * @param matrixName le nom de la matrice
     * @throws IOException
     */
    public void importCSV(Reader reader, int[] origin, String matrixName) throws IOException {
        importCSV(reader, origin);
        setName(matrixName);
    }
    
    /**
     * Import depuis un reader au format CSV des données dans la matrice.
     * 
     * Call {importCSV(Reader, int[], String)} with file name as matrix name.
     * 
     * @param file file to read
     * @param origin le point à partir duquel il faut faire l'importation
     * @throws IOException
     */
    @Override
    public void importCSV(File file, int[] origin) throws IOException {
        Reader reader = null;
        try {
            reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8));
            String matrixName = file.getName();
            if (matrixName.lastIndexOf('.') != -1) { // remove extension
                matrixName = matrixName.substring(0, matrixName.lastIndexOf('.'));
            }
            importCSV(reader, origin, matrixName);
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException ex) {
                    if (log.isErrorEnabled()) {
                        log.error("Can't close reader", ex);
                    }
                }
            }
        }
    }

    /**
     * Import CSV file defined in Matrix ND format.
     * 
     * @param reader reader containing content
     * @param origin not used
     * @throws IOException 
     */
    protected void importCSVND(Reader reader, int[] origin) throws IOException {
        MatrixND matrix = MatrixFactory.getInstance().create(reader);
        // finally paste loaded matrix into this
        pasteSemantics(matrix);
    }

    /**
     * Export dans un writer au format CSV de la matrice
     *
     * @param writer le writer ou copier la matrice
     * @param withSemantics export ou pas des semantiques de la matrice dans le
     *            writer
     */
    @Override
    public void exportCSV(Writer writer, boolean withSemantics) throws IOException {
        if (getDimCount() <= 2) {
            exportCSV2D(writer, withSemantics);
        }
        else {
            exportCSVND(writer, withSemantics);
        }
    }

    /**
     * Export dans un writer au format CSV de la matrice
     *
     * @param writer le writer ou copier la matrice
     * @param withSemantics export ou pas des semantiques de la matrice dans le
     *            writer
     */
    protected void exportCSV2D(Writer writer, boolean withSemantics)
            throws IOException {
        int dimsCount = getDimCount();
        int rowsCount = dimsCount == 1 ? 1 : getDim(0);
        int columnsCount = dimsCount == 1 ? getDim(0) : getDim(1);
        int[] coordinates;

        /* Création de l'entete */
        if (withSemantics) {
            /* Recuperation de la liste sur la bonne dimenssion */
            List listSemantics = getSemantic(dimsCount - 1);
            /* Ajout d'un décalage de l'entete pour la dimenssion 2 */
            writer.append(dimsCount == 2 ? " " + CSV_SEPARATOR : "");
            for (Object semantic : listSemantics) {
                writer.append("\"" + semantic + "\"" + CSV_SEPARATOR);
            }
            writer.append("\n");
        }

        for (int rowNb = 0; rowNb < rowsCount; rowNb++) {
            /* Ajout de la semantic devant la ligne pour la dimenssion 2 */
            if (withSemantics && dimsCount == 2) {
                Object semantic = getSemantic(0).get(rowNb);
                writer.append("\"" + semantic + "\"" + CSV_SEPARATOR);
            }

            for (int columnNb = 0; columnNb < columnsCount; columnNb++) {
                /* Calcul des coordonnees */
                coordinates = dimsCount == 1 ? new int[] { columnNb }
                        : new int[] { rowNb, columnNb };
                writer.append(getValue(coordinates) + "" + CSV_SEPARATOR);
            }
            writer.append("\n");
        }
    }
    
    /**
     * Export dans un writer au format CSV de la matrice
     *
     * @param writer le writer ou copier la matrice
     * @param withSemantics export ou pas des semantiques de la matrice dans le
     *            writer
     */
    @Override
    public void exportCSVND(Writer writer, boolean withSemantics)
            throws IOException {
        
        SemanticMapper mapper = MatrixFactory.getSemanticMapper();

        // add meta
        writer.append(Arrays.toString(getDim())).append("\n");
        for (List semantic : getSemantics()) {
            if (semantic != null) {
                Object first = semantic.get(0);
                if (first != null) {
                    writer.append(mapper.getTypeName(first));
                    writer.append(':');
                    Iterator itValue = semantic.iterator();
                    while (itValue.hasNext()) {
                        Object value = itValue.next();
                        writer.append(mapper.getValueId(value));
                        if (itValue.hasNext()) {
                            writer.append(',');
                        }
                    }
                }
            }
            writer.append('\n');
        }
        
        // add data
        MatrixIterator matrixIterator = iterator();
        while (matrixIterator.hasNext()) {
            matrixIterator.next();
            int[] coordinates = matrixIterator.getCoordinates();
            for (int i = 0 ; i < coordinates.length ; ++i) {
                writer.append(String.valueOf(coordinates[i]));
                writer.append(CSV_SEPARATOR);
            }
            // add data value
            writer.append(String.valueOf(matrixIterator.getValue()));
            writer.append('\n');
        }
    }

} // AbstractMatrixND