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

com.meliorbis.numerics.generic.impl.AbstractMappable 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.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.commons.lang.ArrayUtils;

import com.meliorbis.numerics.generic.Acrossable;
import com.meliorbis.numerics.generic.BinaryOp;
import com.meliorbis.numerics.generic.Mappable;
import com.meliorbis.numerics.generic.MappableReducible;
import com.meliorbis.numerics.generic.MappableWithSimpleOps;
import com.meliorbis.numerics.generic.MultiDimensionalArray;
import com.meliorbis.numerics.generic.MultiValuedNaryOp;
import com.meliorbis.numerics.generic.NaryOp;
import com.meliorbis.numerics.generic.ParallelIterator;
import com.meliorbis.numerics.generic.SettableIterator;
import com.meliorbis.numerics.generic.SubSpaceSplitIterator;
import com.meliorbis.numerics.threading.ComputableRecursiveAction;
import com.meliorbis.numerics.threading.ComputableRecursiveTask;
import com.meliorbis.numerics.threading.CurrentThreadExecutor;
import com.meliorbis.numerics.threading.Executor;
import com.meliorbis.utils.Timer;
import com.meliorbis.utils.Timer.Stoppable;

/**
 * An abstract implementation of IMappableWithSimpleOps that provides common functionality, and in particular enables
 * the modifying() mechanism to work for mapping operations that modify the first operand
 *
 * @param  The member type
 * @param  The return type of mapping operations
 */
@SuppressWarnings("unchecked")
public abstract class AbstractMappable> 
																	implements Acrossable
{
	public static AtomicInteger callCount = new AtomicInteger(0);
	
    public static final int POINTWISE_BATCH_COUNT = 5;
    protected final Executor _executor;

    public AbstractMappable(Executor executor_)
    {
        super();
        _executor = executor_;
    }

    @Override
    public  RM map(final NaryOp op_) throws E
    {
        // Perform a mapping without any other operands
        return with().map(op_);
    }
    
    @Override
    public  RM[] map(final MultiValuedNaryOp op_) throws E
    {
        // Perform a mapping without any other operands
        return with().map(op_);
    }

    protected boolean isModifying()
    {
        return false;
    }

    @Override
    public abstract MappableWithSimpleOps> modifying();

    @Override
    public  RM multiply(MultiDimensionalArray other_)
    {
        return with(other_).map(getMultOp());
    }

    @Override
    public  RM divide(MultiDimensionalArray other_)
    {
        return with(other_).map(getDivisionOp());
    }

    @Override
    public  RM add(MultiDimensionalArray other_)
    {
        return with(other_).map(getAddOp());
    }

    @Override
    public  RM subtract(MultiDimensionalArray other_)
    {
        return with(other_).map(getSubtractionOp());
    }
    
    /**
     * This method creates a callable task that performs the provided operation on each of the elements of the provided
     * of the provided input iterators. Storing the result in the output iterator. All iterators must, therefore, have
     * the same number of remaining elements.
     *
     * @param op_     The operation to perform
     * @param inputs_ The inputs to the operation
     * @param result_ The iterator in which to store the results
     * @param iterateResult_ Indicates whether the result iterator should also be iterated. If it is the same as one of the
     * inputs it is not a good idea to iterate it too
     * @param      The type of exception thrown by the operation
     * @return An NaryOpCallable which, when executed, will perform the operation as described
     */
    protected abstract  NaryOpCallable createNaryOpCallable(
            NaryOp op_,
            SettableIterator[] inputs_,
            SettableIterator result_, boolean iterateResult_);

    /**
     * @return The binary add operation for the underlying type
     */
    abstract public BinaryOp getAddOp();

    /**
     * @return The binary subtract operation for the underlying type
     */
    abstract public BinaryOp getSubtractionOp();

    /**
     * @return The binary multiplication operation for the underlying type
     */
    abstract public BinaryOp getMultOp();

    /**
     * @return The binary multiplication operation for the underlying type
     */
    abstract public BinaryOp getDivisionOp();

    /**
     * @return The result target of pointwise operations
     */
    abstract protected RM getPointwiseResult();

    @Override
    final public MappableReducible across(int... dimensions_)
    {
        return new AcrossOperand(dimensions_);
    }
    
    /**
     * This function is actually in IMultiDimensionalArray, which this class does not implement, but it needs to be available
     * 
     * @param dimensions_ The dimensions to iterate over in parallel
     * 
     * @return A list of parallel iterators
     * 
     * @see MultiDimensionalArray#parallelIterators(int[])
     */
    abstract public List> parallelIterators(int[] dimensions_);

    /**
     * @param dimensions_ The dimensions to iterate over
     * 
     * @return A split iterator that iterates over the requested dimensions
     * 
     * @see MultiDimensionalArray#iteratorAcross(int[])
     */
    abstract public SubSpaceSplitIterator iteratorAcross(int[] dimensions_);
    
    /**
     * @return The max reduction for this mappable
     */
    abstract public Reduction getMaxOp();
	
    /**
     * @return The min reduction for this mappable
     */
    abstract public Reduction getMinOp();
	
    /**
     * @return The sum reduction for this mappable
     */
    abstract public Reduction getSumOp();
	
    /**
     * Create an array that has the dimensions not listed from this array
     * 
     * @param dimensions_ The dimensions to ignore
     * 
     * @return An array with the same dimensions as this array except for the dimensions passed, which are dropped
     */
    abstract protected MultiDimensionalArray createOtherDimsArray(int[] dimensions_);
    
    /**
     * Create an array that has the dimensions not listed from this array, and fill it with the provided values
     * 
     * @param dimensions_ The dimensions to ignore
     * @param array_ The values to fill the resulting array with
     * 
     * @return An array with the same dimensions as this array except for the dimensions passed, which are dropped
     */
	abstract protected MultiDimensionalArray createOtherDimsArray(int[] dimensions_, T[] array_);
	
	/**
	 * @param reduction_ The reduction to perform
	 * @param currentIterator_ The iterator to perform the reduction over
	 * @param target_ The target_ mappable to fill with the result
	 * 
	 * @param  The type of the exception thrown by the reduction
	 * 
	 * @return Creates an action that performs the specified reduction
	 */
	protected abstract  ComputableRecursiveAction createReduceAction(
			ReductionBase, E> reduction_,
			final SettableIterator currentIterator_, AbstractMappable target_);
	
    abstract protected SettableIterator createMultiSettableIterator(SettableIterator[] iters_);

    abstract protected   RM[] createPointWiseMultiValuedResult(MultiValuedNaryOp op_,  MultiDimensionalArray[] others_);
    
    /**
     * Manages 'across' operations that apply other arguments only in certain dimensions, but at each point of the
     * dimensions orthogonal to the across dimensions
     */
    private class AcrossOperand implements MappableReducible
    {
        MultiDimensionalArray[] _others;
        final int[] _dimensions;

        private AcrossOperand(int[] dimensions_)
        {
            _dimensions = dimensions_;
            
            // Check that all the across dimensions are in range
            for(int dim : dimensions_) {
            	
            	if( dim >= AbstractMappable.this.numberOfDimensions()) {
            		throw new ArrayIndexOutOfBoundsException("Dimensions " + dim +" requested, "+AbstractMappable.this.numberOfDimensions() +" dimensions available");
            	}
            }
        }


        @Override
        public MappableWithSimpleOps> modifying()
        {
            return ((Acrossable)AbstractMappable.this.modifying()).across(_dimensions);
        }
        
        @Override
        public MappableWithSimpleOps> nonModifying()
        {
            return ((Acrossable)AbstractMappable.this.nonModifying()).across(_dimensions);
        }

        @Override
        public  RM multiply(MultiDimensionalArray other_)
        {
            _others = new MultiDimensionalArray[]{other_};
            return map(getMultOp());
        }

		@Override
        public  RM divide(MultiDimensionalArray other_)
        {
            _others = new MultiDimensionalArray[]{other_};
            return map(getDivisionOp());
        }

        @Override
        public  RM add(MultiDimensionalArray other_)
        {
            _others = new MultiDimensionalArray[]{other_};
            return map(getAddOp());
        }

        @Override
        public  RM subtract(MultiDimensionalArray other_)
        {
            _others = new MultiDimensionalArray[]{other_};
            return map(getSubtractionOp());
        }
        
        
        @Override
        public  Mappable with(MultiDimensionalArray... otherArrays_)
        {
            _others = otherArrays_;

            /* Wrap the AcrossOperand in an IArrayOperand to prevent access to the ISingleOperand functions
             */
            return new Mappable()
            {
                @Override
                public  Mappable with(MultiDimensionalArray... otherArrays_)
                {
                    // Add the new arrays to the already added ones
                    _others = (MultiDimensionalArray[]) ArrayUtils.addAll(_others, otherArrays_);

                    // Return self
                    return this;
                }

                @Override
                public  RM map(NaryOp operation_) throws E
                {
                    // Call the outer operand's map operation
                    return AbstractMappable.AcrossOperand.this.map(operation_);
                }
                
                @Override
                public  RM[] map(MultiValuedNaryOp operation_) throws E
                {
                    // Call the outer operand's map operation
                    return AbstractMappable.AcrossOperand.this.map(operation_);
                }
            };
        }

        @Override
        final public  RM map(NaryOp operation_) throws E
        {
            RM result = getPointwiseResult();

            boolean iterateResult = !result.isModifying();

            // Get parallel iterators over the relevant dimensions
        	List> sourceIterators = (List>) AbstractMappable.this.parallelIterators(_dimensions);
            
        	// Some generics hoops to jump through...
			List> targetIterators = iterateResult ? (List>) (List) ((MultiDimensionalArray)result).parallelIterators(_dimensions) : (List>)(List) sourceIterators;
        	
        	mapToResult(operation_, sourceIterators, targetIterators, iterateResult);
        	
            return result;
        }
        
        @Override
        final public  RM[] map(MultiValuedNaryOp operation_) throws E
        {
        	RM[] targetArrays = createPointWiseMultiValuedResult(operation_, _others);

            List>[] resultIters = new List[targetArrays.length];
            
            for (int i = 0; i < targetArrays.length; i++)
			{
            	resultIters[i] = (List>) targetArrays[i].parallelIterators(_dimensions);
			}
            
            List> targetIterators = new ArrayList>();
            
            for(int i = 0; i < resultIters[0].size(); i++) 
            {
            	SettableIterator[] iters = new SettableIterator[targetArrays.length];
            	
            	for(int j = 0; j < resultIters.length; j++)
            	{
            		iters[j] = resultIters[j].get(i);
            	}
            	
            	targetIterators.add(createMultiSettableIterator(iters));
            }
            
            // Get parallel iterators over the relevant dimensions
        	List> sourceIterators = (List>) AbstractMappable.this.parallelIterators(_dimensions);
        	
            mapToResult(operation_, sourceIterators, targetIterators, true);
        	
            return  (RM[]) targetArrays;
        }


		protected  void mapToResult(NaryOp operation_, List> sourceIterators, List> targetIterators, boolean iterateResult_)
        {
        	
            List tasks = new ArrayList(sourceIterators.size());

            // Iterate over the orthogonal dimension, finding the max at each point
            // along the max dimensions
            for (int i = 0; i < sourceIterators.size(); i++)
            {
            	final SettableIterator[] inputs = new SettableIterator[_others.length + 1];
                
            	inputs[0] = sourceIterators.get(i);
            	
                for (int j = 0; j < _others.length; j++)
                {
                    inputs[j + 1] = _others[j].iterator();
                }
                
                final SettableIterator targetIter = targetIterators.get(i);
				final NaryOpCallable op = createNaryOpCallable(operation_, inputs, targetIter, iterateResult_);
                
				
                tasks.add(()->
				{
					boolean first = true;
					
					while(((ParallelIterator)inputs[0]).hasNextParallel())
					{
						((ParallelIterator)inputs[0]).nextParallel();
						
						if(iterateResult_)
						{
							((ParallelIterator)targetIter).nextParallel();
						}
						
						// Reset the other inputs if this is not the first call
						if(!first)
						{
							for (int j = 1; j < inputs.length; j++)
			                {
								inputs[j].reset();
			                }
						}
						
						op.compute();
						
						first = false;
					}
				});
                
            }
            
            _executor.executeAndWait(tasks);
        }

		@Override
		public  RM reduce(ReductionBase, E> reduction_) throws E
		{
			
			
			if(reduction_ instanceof Reduction)
			{
				final List tasks;
				List> sourceIterators = (List>) AbstractMappable.this.parallelIterators(_dimensions);
	
				tasks = new ArrayList(sourceIterators.size());
				
				final RM resultArray = (RM) AbstractMappable.this.createOtherDimsArray(_dimensions);
				
				for (int i = 0; i < sourceIterators.size(); i++)
				{
					final SettableIterator currentIterator = sourceIterators.get(i);
					tasks.add(createReduceAction(reduction_, currentIterator, resultArray));
				}	
				Timer timer = new Timer();
				Stoppable stoppable = timer.start("execReduceAcross");
				
				_executor.executeAndWait(tasks);
				 
				stoppable.stop();
				 
				return resultArray;
			}
			else
			{
				ArrayList> tasks = new ArrayList>();
				
				IndexedReduction reduction = (IndexedReduction)reduction_;
				
				SubSpaceSplitIterator dimIter = iteratorAcross(_dimensions);
				
				SubSpaceSplitIterator orthogonalIterator = dimIter.getOrthogonalIterator();
				
				while(orthogonalIterator.hasNext())
				{
					orthogonalIterator.next();
					
					final SubSpaceSplitIterator currentIterator = orthogonalIterator.getOrthogonalIterator();
					
					tasks.add(()->{
						return reduction.perform(currentIterator);
					});
				}
				
				List results;
				Timer timer = new Timer();
				Stoppable stoppable = timer.start("execReduceAcrossIndexed");
				results = new CurrentThreadExecutor().executeAndGet(tasks);				
				stoppable.stop();
				return (RM) AbstractMappable.this.createOtherDimsArray(_dimensions,(T[])results.toArray());
			}			
		}


		


		@Override
		public RM max()
		{
			return reduce(getMaxOp());
		}


		@Override
		public RM sum()
		{
			return reduce(getSumOp());
		}


		@Override
		public RM min()
		{
			return reduce(getMinOp());
		}
    }
}