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

com.meliorbis.numerics.generic.impl.AbstractArray Maven / Gradle / Ivy

Go to download

A library for working with large multi-dimensional arrays and the functions they represent

There is a newer version: 1.2
Show newest version
package com.meliorbis.numerics.generic.impl;

import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import org.apache.commons.lang.ArrayUtils;

import com.meliorbis.numerics.NumericsException;
import com.meliorbis.numerics.generic.Acrossable;
import com.meliorbis.numerics.generic.BinaryOp;
import com.meliorbis.numerics.generic.Mappable;
import com.meliorbis.numerics.generic.MappableWithSimpleOps;
import com.meliorbis.numerics.generic.MultiDimensionalArray;
import com.meliorbis.numerics.generic.MultiDimensionalArrayException;
import com.meliorbis.numerics.generic.MultiValuedNaryOp;
import com.meliorbis.numerics.generic.NaryOp;
import com.meliorbis.numerics.generic.SettableIndexedIterator;
import com.meliorbis.numerics.generic.SettableIterator;
import com.meliorbis.numerics.generic.SubSpaceSplitIterator;
import com.meliorbis.numerics.generic.UnaryOp;
import com.meliorbis.numerics.generic.primitives.impl.DoubleArrayFunctions;
import com.meliorbis.numerics.index.Index;
import com.meliorbis.numerics.threading.ComputableRecursiveAction;
import com.meliorbis.numerics.threading.CurrentThreadExecutor;
import com.meliorbis.numerics.threading.Executor;
import com.meliorbis.utils.Timer;
import com.meliorbis.utils.Timer.Stoppable;
import com.meliorbis.utils.Utils;

@SuppressWarnings("unchecked")
public abstract class AbstractArray> extends AbstractMappable
                                                                        implements MultiDimensionalArray
{
	protected final Index _index;
    protected final ModifyingWrapper _modifyingWrapper;

    public AbstractArray(Executor executor_, int[] dimensions_)
	{
		this(new com.meliorbis.numerics.index.impl.Index(dimensions_), executor_);
	}

	public AbstractArray(Index logicalDimensions_, Executor executor_)
	{
        super(executor_);
		_index = logicalDimensions_;
        _modifyingWrapper = createModifyingWrapper();
    }

	/**
	 * @return The same array but in a reference that will cause results of any operations to be applied to this array
	 */
	protected AbstractArray.ModifyingWrapper createModifyingWrapper()
	{
		return new ModifyingWrapper();
	}

	@Override
	public  T reduce(ReductionBase, E> reduction_) throws E
	{
		if(reduction_ instanceof Reduction)
		{
			Timer timer = new Timer();
			Stoppable stoppable = timer.start("execReduce");
			T result = ((Reduction)reduction_).perform(iterator());
			stoppable.stop();
			return result;
		}
		else
		{
			throw new UnsupportedOperationException("Only instances of IReduction supported");
		}
	}
	
	@Override
	public int numberOfElements()
	{
		return _index.numberOfElements();
	}
	
	@Override
	public int numberOfDimensions()
	{
		return _index.numberOfDimensions();
	}
	

	@Override
	public int[] size()
	{
		return _index.getSizes();
	}

	public Reduction getMaxOp()
	{
		return iter -> {
			T max = null;
			
			while(iter.hasNext()) {
				T cur = iter.next();
				
				if(max == null || getComparator().compare(max, cur) < 0) {
					max = cur;
				}
			}
			
			return max;
		};
	}
	
	public Reduction getMinOp()
	{
		return iter -> {
			T min = null;
			
			while(iter.hasNext()) {
				T cur = iter.next();
				
				if(min == null || getComparator().compare(min, cur) > 0) {
					min = cur;
				}
			}
			
			return min;
		};
	}
	
	public Reduction getSumOp()
	{
        return iterator_-> {
            T sum = getZero();

            while (iterator_.hasNext())
            {
                // Add the next item to the running total
                sum = getAddOp().perform(sum, iterator_.next());
            }

            return sum;
		};
	}

	@Override
	protected RM getPointwiseResult()
	{
		return createNew(size());
	}

	protected abstract RM createNew(int... dimensions_);

	@Override
	public RM matrixMultiply(
			MultiDimensionalArray other_)
			throws MultiDimensionalArrayException
	{
		// Call the product with a do-nothing transformer
		return matrixMultiply(other_, getIdentityOp());
	}
	
	protected UnaryOp getIdentityOp()
	{
		return x -> x;
	}

    /*
	 * (non-Javadoc)
	 *
	 * @see
	 * com.meliorbis.numerics.generic.IMultiDimensionalArray#matrixMultiply(
	 * com.meliorbis.numerics.generic.IMultiDimensionalArray,
	 * com.meliorbis.numerics.generic.ITransformer)
	 */
    @Override
    public RM matrixMultiply(
            MultiDimensionalArray other_,
            UnaryOp transformer_)
            throws MultiDimensionalArrayException
    {
        if(size().length > 2 || other_.size().length > 2)
        {
            throw new MultiDimensionalArrayException("Only two dimensional matrices may be multiplied");
        }

        // Handle the case where we are essentially dealing with (a) vector(s)
        if(numberOfDimensions() == 1)
        {
            if(other_.numberOfDimensions() == 1)
            {
                RM result = createNew(1);

                final SettableIndexedIterator resIter = result.iterator();
                resIter.next();
                resIter.set(calcDotProduct(iterator(), other_.iterator(), transformer_));

                return result;
            }

            // Create a matrix with one row to multiply with
            final RM vecAsMatrix = createNew(1,numberOfElements());
            vecAsMatrix.fillDimensions(this, 1);

            return vecAsMatrix.matrixMultiply(other_, transformer_);
        }

        // Check that the matrices are conformable
        if (size()[1] != other_.size()[0])
        {
            throw new MultiDimensionalArrayException(
                    "The matrices are not conformable");
        }

        // Create the output array
        RM result = createNew(new int[] { size()[0],
                other_.numberOfDimensions() == 1 ? 1 : other_.size()[1] });

        SubSpaceSplitIterator rowIter = iteratorAcross(new int[]{0});


        SettableIndexedIterator resultIterator = result.iterator();
        // Iterate over the rows of the left matrix
        while(rowIter.hasNext())
        {
            rowIter.next();

            // If the other is a vector just multiply it with each row
            if(other_.numberOfDimensions() == 1)
            {
                resultIterator.next();
                resultIterator.set(calcDotProduct(rowIter.getOrthogonalIterator(), other_.iterator(), transformer_));
            }
            else
            {
                SubSpaceSplitIterator otherColIter = other_.iteratorAcross(new int[]{1});

                // And, for each row, over the columns of the right matrix
                while(otherColIter.hasNext())
                {
                    otherColIter.next();

                    // Get orthogonal iterators for each matrix, which will be the same length (conformable!)
                    SubSpaceSplitIterator leftValIter = rowIter.getOrthogonalIterator();
                    SubSpaceSplitIterator rightValIter = otherColIter.getOrthogonalIterator();

                    T value = calcDotProduct(leftValIter, rightValIter, transformer_);

                    // The result matrix is iterated in the right order and will be the right size, so just go to the next
                    // and set it
                    resultIterator.next();
                    resultIterator.set(value);
                }
            }
        }

        return result;
    }

    /**
     * @param leftValIter the value on the left
     * @param rightValIter The values on the right
     * 
     * @param transformer_ The transformation to apply to each individual product before summing
     *  
     * @return The resulting value
     */
    protected T calcDotProduct(Iterator leftValIter, Iterator rightValIter, UnaryOp transformer_)
    {
        T value = getZero();

        while(leftValIter.hasNext())
        {
            // Multiply the appropriate values and add them to the
            // running total
            T individualProduct = getMultOp().perform(leftValIter.next(), rightValIter.next());

            // Transform the individual product using the provided transformer
            T transformedProduct = transformer_.perform(individualProduct);

            value = getAddOp().perform(
                    transformedProduct,
                    value);
        }
        return value;
    }

	
	@Override
	public RM fill(MultiDimensionalArray values_)
    {
        modifying().with(values_).map(getSecondOperandOp());

        return (RM) this;
	}

	@Override
    public RM fill(Iterator values_)
    {
        fillIterator(iterator(), values_);

        return (RM) this;
    }

    @Override
    public RM lastDimSlice(int index_)
    {
        final int[] selector = Utils.repeatArray(-1, numberOfDimensions());

        selector[numberOfDimensions() - 1] = index_;

        return at(selector);
    }

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.meliorbis.numerics.generic.IMultiDimensionalArray#fillAt(com.meliorbis
	 * .numerics.generic.IMultiDimensionalArray, int[])
	 */
	@Override
	public void fillAt(MultiDimensionalArray values_,int... index_)
	{
        at(index_).modifying().with(values_).map(getSecondOperandOp());
	}

	protected void fillIterator(SettableIndexedIterator targetIterator_, Iterator values_)
    {
		Iterator inputIter = values_;

		// Iterate over the dimensions to be filled
		while (targetIterator_.hasNext())
		{
			targetIterator_.next();

			if(!inputIter.hasNext())
			{
                throw new MultiDimensionalArrayException("Not enough elements in input");
//				// Start again
//				inputIter = values_.iterator();
			}

			// Get the value from the appropriate place in source and copy to
			// the current target location
			targetIterator_.set(inputIter.next());
		}
		
		// If there is another input we had incorrectly sized inputs
		if(inputIter.hasNext())
		{
			throw new MultiDimensionalArrayException("The target size must be an exact multiple of the input size");
		}
	}

	@Override
	public void fillDimensions(MultiDimensionalArray values_, int... dimensions_)
	{
        try
        {
            modifying().across(dimensions_).with(values_).map(getSecondOperandOp());
        } catch (Exception e)
        {
            throw new MultiDimensionalArrayException("Error during fill", e);
        }
    }

    protected abstract NaryOp getSecondOperandOp();

	public int[] getSubDimensions(int... dimensions_)
	{
		int[] targetSize = new int[dimensions_.length];

		// Determine the sizes of the target dimensions, as well as the total
		// size
		for (int dimIndex = 0; dimIndex < targetSize.length; dimIndex++)
		{
			targetSize[dimIndex] = size()[dimensions_[dimIndex]];
		}

		return targetSize;
	}

    public int[] getOtherSubDimensions(int... excluded_)
    {
        // Create an array to contain the dimensions except the ones requested
        int[] targetSize = new int[numberOfDimensions() - excluded_.length];

        // Initialise counter for all three lists - all dimensions, targetDimensions and the dimensions specified
        int excludedIndex = 0;
        int targetIndex = 0;

        for (int dimIndex = 0; dimIndex < numberOfDimensions(); dimIndex++)
        {
            // If the current dimenion is the next one specified
            if(excludedIndex < excluded_.length && excluded_[excludedIndex] == dimIndex)
            {
                // Increment the excluded dimensions index
                excludedIndex++;

                // don't copy this dimension's size
                continue;
            }

            // Otherwise, copy this dimensions size
            targetSize[targetIndex++] = size()[dimIndex];
        }

        return targetSize;
    }

    final protected RM createOtherDimsArray(int[] dimensions_, T[] array_)
	{
		RM created = createOtherDimsArray(dimensions_);
		created.fill(Arrays.asList(array_).iterator());
		
		return created;
		
	}
    
    final protected RM createOtherDimsArray(int[] dimensions_)
	{
		int[] otherDims = getOtherSubDimensions(dimensions_);
		
		// If there are no other dims, create a 1-element array (i.e. a 'scalar')
		if(otherDims.length == 0)
		{
			otherDims = new int[]{1};
		}
		
		RM created = createNew(otherDims);
		
		return created;
	}

    /*
         * (non-Javadoc)
         *
         * @see com.meliorbis.numerics.generic.IMultiDimensionalArray#max(int[])
         */
	@Override
	public T sum()
	{

		return reduce(getSumOp());
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.meliorbis.numerics.generic.IMultiDimensionalArray#max(int[])
	 */
	@Override
	public T max()
	{
		return reduce(getMaxOp());
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see com.meliorbis.numerics.generic.IMultiDimensionalArray#max(int[])
	 */
	@Override
	public T min()
	{
		return reduce(getMinOp());
	}

	protected int[] ensureDimensions(int... dimensions_) {
		return (dimensions_.length == 0) ? Utils.sequence(0,numberOfDimensions()): dimensions_;
	}

    

	public  void performSubspaceOpST(IndexedSubSpaceOperation operation_, int...dimensions_)
            throws E
	{
		// Always the possibility that no dimensions were specified, i.e. the entire space as subspace is to be used...
		// Get an iterator over the dimensions to maximise...
		SubSpaceSplitIterator maxIter = iteratorAcross(dimensions_);

		// ... and its orthogonal twin!
		SubSpaceSplitIterator otherIterator = maxIter.getOrthogonalIterator();

		// Initialise the operation wit the orthogonal iterator
		operation_.initialise(otherIterator);
		
		// Iterate over the orthogonal dimension, finding the max at each point
		// along the max dimensions
		while (otherIterator.hasNext())
		{
			otherIterator.next();
			
			operation_.perform(otherIterator.getOrthogonalIterator());
		}

	}

	@Override
	public RM stack(MultiDimensionalArray... others_)
	{
		DoubleArrayFunctions.ensureAllEqualSize(this, others_);
		
		// Create a new array with one extra dimension as this one, which is the stack dimension
		int[] newArraySize = new int[this.numberOfDimensions()+1];
		System.arraycopy(this.size(), 0, newArraySize, 0, this.numberOfDimensions());
		newArraySize[newArraySize.length-1] = others_.length + 1;
		
		RM result = createNew(newArraySize);
		
		// Create an array to choose the appropriate slice
		int[] fillIndex = Utils.repeatArray(-1, newArraySize.length);
		
		int stackDimension = newArraySize.length-1;
		
		fillIndex[stackDimension] = 0;
		
		// Put this in the first slice
		result.fillAt(this, fillIndex);
		
		// And fill the others into the other slices
		for(int index = 0; index < others_.length; index++)
		{
			fillIndex[stackDimension] = index+1;
			result.fillAt(others_[index], fillIndex);
		}
		
		return result;
	}
	
	@Override
	public RM stackFinal(MultiDimensionalArray... others_)
	{
		//DoubleArrayFunctions.ensureAllEqualSize(this, others_);
		
		// Create a new array with one extra dimension as this one, which is the stack dimension
		int[] newArraySize = new int[this.numberOfDimensions()];
		System.arraycopy(this.size(), 0, newArraySize, 0, this.numberOfDimensions());
		
		for (MultiDimensionalArray other : others_)
		{
			newArraySize[newArraySize.length-1] += other.size()[newArraySize.length-1];
		}
		
		RM result = createNew(newArraySize);
		
		// Create an array to choose the appropriate slice
		int[] fillIndex = Utils.repeatArray(-1, newArraySize.length);
		int[] srcIndex = Utils.repeatArray(-1, newArraySize.length);
		
		int stackDimension = newArraySize.length-1;
		
		fillIndex[stackDimension] = 0;
		
		for(int i = 0; i < size()[stackDimension];i++)
		{
			srcIndex[stackDimension] = i;
			// Put this in the first slice
			result.fillAt(this.at(srcIndex), fillIndex);
			fillIndex[stackDimension]++;
		}
		
		
		// And fill the others into the other slices
		for (MultiDimensionalArray other : others_)
		{
			for(int i = 0; i < other.size()[stackDimension];i++)
			{
				srcIndex[stackDimension] = i;
				// Put this in the first slice
				result.fillAt(other.at(srcIndex), fillIndex);
				fillIndex[stackDimension]++;
			}
		}
		
		return result;
	}

	@Override
	public IModifyableMappable> modifying()
	{
		return _modifyingWrapper;
	}
	
	@Override
	public AbstractArray nonModifying()
	{
		return this;
	}
	
	@Override
	public String toString() 
	{
		StringBuilder result = new StringBuilder();
		result.append(String.format("(size=%s)\n",Arrays.toString(size())));
		result.append("[");		
		toString(result);
		result.append("]");
		return result.toString();
	}
	
	public void toString(StringBuilder builder_)
	{	
		if(numberOfDimensions() > 2)
		{	
			SubSpaceSplitIterator outerDims = iteratorAcross(Utils.sequence(0,numberOfDimensions() - 2));
			
			while (outerDims.hasNext()) {
				outerDims.next();
				
				builder_.append(indexString(outerDims.getIndex()));
				builder_.append("\n");
				at(outerDims.getFullIndex()).toString(builder_);
				builder_.append("\n");
			}	
		}
		else if(numberOfDimensions() == 2)
		{
			SubSpaceSplitIterator rowIter = iteratorAcross(new int[]{0});
			
			while (rowIter.hasNext()) {
				rowIter.next();
				SubSpaceSplitIterator colIter = rowIter.getOrthogonalIterator();
				
				while (colIter.hasNext()) {
					T t = (T) colIter.next();
					builder_.append(t);
					builder_.append(", ");
				}
				
				builder_.append(";\n");
			}
		}
		else // Only other option is one dimension
		{
			SettableIndexedIterator colIter = iterator();
			
			while (colIter.hasNext()) {
				T t = (T) colIter.next();
				builder_.append(t);
				builder_.append("\t");
			}
			
			builder_.append("\n");
		}
	}
	
	public String indexString(int[] index_)
	{
		StringBuilder result = new StringBuilder("(");
		int i;
		
		for (i = 0; i < index_.length-1; i++) {
			result.append(i);
			result.append(",");
		}
		
		result.append(index_[i]);
		result.append(")");
		
		return result.toString();
	}

	/**
	 * @return The object that represents 0
	 */
	protected abstract T getZero();

	/**
	 * @return The object that represents -1
	 */
	protected abstract T getMinusOne();

	public abstract T[] createTypedArray(int length_);


	protected interface IDimTransposer
	{
		int[] changeIndex(int[] in_);

		IDimTransposer transpose(int numDims_, int dimA_, int dimB_);
	}

    protected  NaryOpCallable createBatchedNaryOpCallable(final NaryOp op_,
                                                                                              MultiDimensionalArray result_,
                                                                                              int from_, int to_,
                                                                                              final MultiDimensionalArray... other_)
    {
    	Stoppable timer = new Timer().start("createBatchedNaryOpCallable");
    	
        // If the result is the same as this, there is no need to need to create a separate result iterator
        boolean iterateResult = !((AbstractMappable) result_).isModifying();

        SettableIndexedIterator[] inputs = new SettableIndexedIterator[other_.length + 1];

        final SettableIndexedIterator selfIterator = from_ == -1 ? iterator() : rangeIterator(from_, to_);

        inputs[0] = selfIterator;

        for (int i = 0; i < other_.length; )
        {
            // Note the increment happens and then it is assigned to (prior) i+1
            SettableIndexedIterator iter = from_ == -1 ? other_[i++].iterator() :
                    other_[i++].rangeIterator(from_, to_);

            inputs[i] = iter;
        }

        SettableIndexedIterator resultIter = iterateResult ?
                (from_ == -1 ? result_.iterator() : result_.rangeIterator(from_, to_)) : selfIterator;

        timer.stop();
            	
        return createNaryOpCallable(op_, inputs, resultIter, iterateResult);
    }
    
	protected  NaryOpCallable createBatchedNaryOpCallable(final MultiValuedNaryOp op_,
            MultiDimensionalArray[] results_,
            int from_, int to_,
            final MultiDimensionalArray... other_)
	{
		SettableIndexedIterator[] inputs = new SettableIndexedIterator[other_.length + 1];
		
		final SettableIndexedIterator selfIterator = from_ == -1 ? iterator() : rangeIterator(from_, to_);
		
		inputs[0] = selfIterator;
		
		for (int i = 0; i < other_.length; )
		{
			// Note the increment happens and then it is assigned to (prior) i+1
			SettableIndexedIterator iter = from_ == -1 ? other_[i++].iterator() :
			other_[i++].rangeIterator(from_, to_);
			
			inputs[i] = iter;
		}		

		SettableIterator resultIter = createMultiSettableIterator(Arrays.stream(results_).map(array -> from_ == -1 ? array.iterator() : array.rangeIterator(from_, to_)).toArray(len->new SettableIterator[len]));

		return createNaryOpCallable(op_, inputs, resultIter, true);
	}

    @SafeVarargs
	@Override
    final public  Mappable with(final MultiDimensionalArray... otherOperands_)
    {
        return this.withInternal(otherOperands_, null, null);
    }

    private > Mappable withInternal(
                                                                final MultiDimensionalArray[] otherOperands_,
                                                                final AbstractArray targetArray_,
                                                                final alt_R returnValue_)
    {

        return new Mappable()
        {
        	@Override
            public  Mappable with(MultiDimensionalArray... otherOperands2_)
            {
                final MultiDimensionalArray[] allOtherOperands = (MultiDimensionalArray[]) ArrayUtils.addAll(otherOperands_, otherOperands2_);
                return AbstractArray.this.withInternal(allOtherOperands, targetArray_, returnValue_);
            }
            
			@Override
			public  alt_R[] map(MultiValuedNaryOp operation_) throws E
			{
            	RM[] target = createPointWiseMultiValuedResult(operation_, otherOperands_);
            	
                DoubleArrayFunctions.ensureAllEqualSize(AbstractArray.this, otherOperands_);

                int batchSize;
                
				int batches = POINTWISE_BATCH_COUNT;

				if(numberOfElements() < 10000) {
					batches = 1;
					batchSize = numberOfElements();
				}
				else
				{
					batchSize = numberOfElements()/batches + (numberOfElements()%batches == 0? 0 : 1);
					batches = numberOfElements()/batchSize + (numberOfElements()%batchSize == 0? 0 : 1);
				}
				
				Timer timer = new Timer();
				
				
                if (batches > 1)
                {
                	Stoppable stoppable = timer.start("prep");
                    List> callables = new LinkedList>();

                    for (int batch = 0; batch < batches; )
                    {
                        int from = batch++ * batchSize;
                        int to = Math.min(batch * batchSize, numberOfElements());
                        callables.add(createBatchedNaryOpCallable(operation_, target, from, to, otherOperands_));
                    }
                    stoppable.stop();
                    stoppable = timer.start("execMap");
                    try
                    {
                    	_executor.executeAndWait(callables);
                    } catch (NumericsException e)
                    {
                        throw new MultiDimensionalArrayException(e);
                    }
                    stoppable.stop();
                } else
                {
                	try
                    {
                		Stoppable stoppable = timer.start("execMapSingle");
                		new CurrentThreadExecutor().executeAndWait(Arrays.asList(createBatchedNaryOpCallable(operation_, target, -1, -1, otherOperands_)));
                		stoppable.stop();
                    }
	                catch (NumericsException e)
	                {
	                    throw new MultiDimensionalArrayException(e);
	                }
                }
                
                return (alt_R[])target;
			}

			@Override
            public  alt_R map(NaryOp operation_) throws E
            {
            	// Make sure there is a target array
            	AbstractArray target = targetArray_ != null ? targetArray_ : getPointwiseResult();
            	
                DoubleArrayFunctions.ensureAllEqualSize(AbstractArray.this, otherOperands_);

                int batchSize;
                
				int batches = POINTWISE_BATCH_COUNT;

				if(numberOfElements() < 10000) {
					batches = 1;
					batchSize = numberOfElements();
				}
				else
				{
					batchSize = numberOfElements()/batches + (numberOfElements()%batches == 0? 0 : 1);
					batches = numberOfElements()/batchSize + (numberOfElements()%batchSize == 0? 0 : 1);
				}
				
				Timer timer = new Timer();
				
				
                if (batches > 1)
                {
                	Stoppable stoppable = timer.start("prep");
                    List> callables = new LinkedList>();

                    for (int batch = 0; batch < batches; )
                    {
                        int from = batch++ * batchSize;
                        int to = Math.min(batch * batchSize, numberOfElements());
                        callables.add(createBatchedNaryOpCallable(operation_, target, from, to, otherOperands_));
                    }
                    stoppable.stop();
                    stoppable = timer.start("execMap");
                    try
                    {
                    	_executor.executeAndWait(callables);
                    } catch (NumericsException e)
                    {
                        throw new MultiDimensionalArrayException(e);
                    }
                    stoppable.stop();
                } else
                {
                	try
                    {
                		Stoppable stoppable = timer.start("execMapSingle");
                		new CurrentThreadExecutor().executeAndWait(Arrays.asList(createBatchedNaryOpCallable(operation_, target, -1, -1, otherOperands_)));
                		stoppable.stop();
                    }
	                catch (NumericsException e)
	                {
	                    throw new MultiDimensionalArrayException(e);
	                }
                }
                
                return returnValue_ != null ? returnValue_ : (alt_R) target;
            }
        };
    }
	
    public interface IModifyableMappable> extends MappableWithSimpleOps, Acrossable
    {
    	boolean isModifying();
    }
    
	public class ModifyingWrapper extends AbstractMappable implements IModifyableMappable
	{
        public ModifyingWrapper()
        {
            super(AbstractArray.this._executor);
        }
        
        @Override
        public int numberOfDimensions() {
        	return AbstractArray.this.numberOfDimensions();
        }
        
        @Override
        public boolean isModifying()
        {
            return true;
        }

        @Override
        public  Mappable with(MultiDimensionalArray... otherArrays_)
        {
            return AbstractArray.this.withInternal(otherArrays_, AbstractArray.this, getPointwiseResult());
        }
        
        @Override
        public ModifyingWrapper getPointwiseResult()
        {
            return this;
        }

        public void fill(Iterator values_)
        {
            AbstractArray.this.fill(values_);
        }

        public void fill(MultiDimensionalArray values_)
        {
            AbstractArray.this.fill(values_);
        }

        public void fillDimensions(MultiDimensionalArray values_, int... dimensions_)
        {
            AbstractArray.this.fillDimensions(values_, dimensions_);
        }

        public void fillAt(MultiDimensionalArray values_, int... index_)
        {
            AbstractArray.this.fillAt(values_, index_);
        }

        @Override
        protected  NaryOpCallable createNaryOpCallable(NaryOp op_, SettableIterator[] inputs_, SettableIterator result_, boolean iterateResult_)
        {
            return AbstractArray.this.createNaryOpCallable(op_, inputs_, result_, iterateResult_);
        }

        @Override
        public BinaryOp getAddOp()
        {
            return AbstractArray.this.getAddOp();
        }

        @Override
        public BinaryOp getSubtractionOp()
        {
            return AbstractArray.this.getSubtractionOp();
        }

        @Override
        public BinaryOp getMultOp()
        {
            return AbstractArray.this.getMultOp();
        }

        @Override
        public BinaryOp getDivisionOp()
        {
            return AbstractArray.this.getDivisionOp();
        }

		@Override
		public  T reduce(ReductionBase, E> reduction_) throws E
		{
			return AbstractArray.this.reduce(reduction_);
		}

		@Override
		public List> parallelIterators(int[] dimensions_)
		{
			return AbstractArray.this.parallelIterators(dimensions_);
		}

		@Override
		public T max()
		{
			return AbstractArray.this.max();
		}

		@Override
		public T sum()
		{
			return AbstractArray.this.sum();
		}

		@Override
		public T min()
		{
			return AbstractArray.this.min();
		}

		@Override
		protected MultiDimensionalArray createOtherDimsArray(int[] dimensions_, T[] array_)
		{
			return AbstractArray.this.createOtherDimsArray(dimensions_, array_);
		}

		@Override
		public Reduction getMaxOp()
		{
			return AbstractArray.this.getMaxOp();
		}

		@Override
		public Reduction getMinOp()
		{
			return AbstractArray.this.getMinOp();
		}

		@Override
		public Reduction getSumOp()
		{
			return AbstractArray.this.getSumOp();
		}

		@Override
		public SubSpaceSplitIterator iteratorAcross(int[] dimensions_)
		{
			return AbstractArray.this.iteratorAcross(dimensions_);
		}

		@Override
		public AbstractMappable modifying()
		{
			return this;
		}

		@Override
		protected MultiDimensionalArray createOtherDimsArray(int[] dimensions_)
		{
			return AbstractArray.this.createOtherDimsArray(dimensions_);
		}

		@Override
		protected  ComputableRecursiveAction createReduceAction(ReductionBase, E> reduction_,
				SettableIterator currentIterator_, AbstractMappable target_)
		{
			return AbstractArray.this.createReduceAction(reduction_, currentIterator_, target_);
		}

		@Override
		public  AbstractArray.ModifyingWrapper[] map(MultiValuedNaryOp operation_) throws E
		{
			throw new UnsupportedOperationException("Multimap can't be used to modify an array");
		}

		@Override
		protected SettableIterator createMultiSettableIterator(SettableIterator[] iters_)
		{
			return AbstractArray.this.createMultiSettableIterator(iters_);
		}

		@Override
		protected  ModifyingWrapper[] createPointWiseMultiValuedResult(MultiValuedNaryOp op_, MultiDimensionalArray[] others_)
		{
			throw new UnsupportedOperationException("Can't multimap a modifyable");
		}

		@Override
		public MappableWithSimpleOps> nonModifying()
		{
			return AbstractArray.this;
		}
    }
}