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

com.meliorbis.economics.infrastructure.AbstractModel Maven / Gradle / Ivy

Go to download

A library for solving economic models, particularly macroeconomic models with heterogeneous agents who have model-consistent expectations

There is a newer version: 1.1
Show newest version
/**
 * 
 */
package com.meliorbis.economics.infrastructure;

import static com.meliorbis.numerics.DoubleArrayFactories.createArrayOfSize;
import static com.meliorbis.numerics.generic.primitives.impl.Interpolation.interp;
import static com.meliorbis.numerics.generic.primitives.impl.Interpolation.interpolateFunction;
import static com.meliorbis.numerics.generic.primitives.impl.Interpolation.spec;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.NotImplementedException;

import com.meliorbis.economics.aggregate.AggregateProblemSolver;
import com.meliorbis.economics.individual.IndividualProblemSolver;
import com.meliorbis.economics.infrastructure.notifications.ArrayObserver;
import com.meliorbis.economics.infrastructure.notifications.Notifier;
import com.meliorbis.economics.infrastructure.simulation.DiscretisedDistribution;
import com.meliorbis.economics.infrastructure.simulation.DiscretisedDistributionSimulatorImpl;
import com.meliorbis.economics.infrastructure.simulation.SimState;
import com.meliorbis.economics.model.AggregateFixedPointState;
import com.meliorbis.economics.model.Model;
import com.meliorbis.economics.model.ModelConfig;
import com.meliorbis.economics.model.ModelException;
import com.meliorbis.economics.model.State;
import com.meliorbis.economics.model.StateWithControls;
import com.meliorbis.numerics.fixedpoint.FixedPointValueDelegate;
import com.meliorbis.numerics.function.primitives.DoubleRectangularGridDomain;
import com.meliorbis.numerics.generic.MultiDimensionalArray;
import com.meliorbis.numerics.generic.impl.IndexedReduction;
import com.meliorbis.numerics.generic.impl.IntegerArray;
import com.meliorbis.numerics.generic.primitives.DoubleArray;
import com.meliorbis.numerics.generic.primitives.DoubleSubspaceSplitIterator;
import com.meliorbis.numerics.generic.primitives.impl.Interpolation.Params;
import com.meliorbis.numerics.generic.primitives.impl.Interpolation.Specification;
import com.meliorbis.numerics.index.IndexIterator;
import com.meliorbis.numerics.index.impl.Index;
import com.meliorbis.numerics.io.NumericsReader;
import com.meliorbis.numerics.io.NumericsWriter;
import com.meliorbis.numerics.threading.ComputableRecursiveAction;
import com.meliorbis.utils.Utils;

/**
 * An abstract base class for models which provides some of the common data
 * structures.
 * 
 * @author Tobias Grasl
 * 
 * @param  The Config type
 * @param  The State type
 */
public abstract class AbstractModel> extends Base implements Model
{
	protected C _config;
	
	public DoubleRectangularGridDomain _individualVarDomain;
	
	protected int[] _individualVarDimensions;
	protected int[] _aggregateVarDimensions;
	
	protected int[] _individualTransitionDimensions;
	protected int[] _aggregateExpectationDimensions;
	protected int[] _individualExpectationDimensions;
	
	protected int[] _simulationGridDimensions;

	private int[] _aggVarAtDimensions;
	private int[] _transitionArrangeDimensions;
	private int[] _expectationFillDimensions;

	private DiscretisedDistributionSimulatorImpl _simulator;

	private IndividualProblemSolver _indSolver;

	private AggregateProblemSolver _aggSolver;
	
	
	private final Notifier _aggExpNotifier = new Notifier();

	private int _aggDetStateStart;
	private int _aggStochStateStart;
	
	/**
	 * Sets up grid dimensions etc 
	 */
	public void initialise() 
	{
        /* NOTE: These ignore lifecycle dimension, since many arrays don't have that
		 */
        setAggDetStateStart(_config.getIndividualEndogenousStateCount() + _config.getIndividualExogenousStateCount());
        
        int stochStateStart = getAggDetStateStart() +
        		_config.getAggregateEndogenousStateCount();

        
        stochStateStart += _config.getAggregateControlCount();
        
        setAggStochStateStart(stochStateStart);
        
		_individualVarDimensions = initIndividualVarDimensions();
		_aggregateVarDimensions = initAggregateVarDimensions();
		
		_individualTransitionDimensions = initIndividualTransitionDimensions();
		_aggregateExpectationDimensions = initAggregateExpectationDimensions();
		_individualExpectationDimensions = initIndividualExpectationDimensions();
		_simulationGridDimensions = initSimulationGridDimensions();
		
		// The transition transpose dimensions to swap the order of endogenous and exogenous states
		// For filling expecations
		final int nAggStates = _config.getAggregateEndogenousStateCount();
		final int nAggShocks = _config.getAggregateExogenousStateCount();

		_aggVarAtDimensions = Utils.repeatArray(-1,_aggregateVarDimensions.length);

		for (int i = 0; i < _config.getAggregateControlCount(); i++)
		{
			// should be constant across controls not affecting expectations, so select a slice
			if(!ArrayUtils.contains(_config.getControlsAffectingExpectations(), i)) {
				_aggVarAtDimensions[_config.getAggregateEndogenousStateCount() + i] = 0;
			}
		}
		
		int nExpnAffectingControls = _config.getControlsAffectingExpectations().length;
		
		/* NOTE: By the time the arrange happens, only expectation-affecting control dimensions 
		 * apply due to the prior at operation
		 */
		_transitionArrangeDimensions = ArrayUtils.add(ArrayUtils.addAll(
				// Current shocks come first
				Utils.sequence(nAggStates + nExpnAffectingControls,nAggStates + nExpnAffectingControls + nAggShocks),
				// After that, current states and expectation affecting controls
				Utils.sequence(0, nAggStates + nExpnAffectingControls)),
				// Finally, the last dimension, with the number of states
				nAggStates+nExpnAffectingControls+nAggShocks);

		// The dimensions in the expected states across which to fill the transposed transition
		_expectationFillDimensions = ArrayUtils.addAll(
				// Current Shocks
				Utils.sequence(0, nAggShocks), 
				// States, controls and the number-of-states dimension
				Utils.sequence(2*nAggShocks+_config.getAggregateNormalisingStateCount(),
								2*nAggShocks+_config.getAggregateNormalisingStateCount()+nAggStates
								+ nExpnAffectingControls + 1));
		
		_individualVarDomain = individualVariableDomain();
	}
	
	@Override
	final public int getAggStochStateStart()
	{
		return _aggStochStateStart;
	}

	@Override
	final public int getAggDetStateStart()
	{
		return _aggDetStateStart;
	}	
	
	final void setAggStochStateStart(int val_)
	{
		_aggStochStateStart = val_;
	}
	
	final void setAggDetStateStart(int val_)
	{
		_aggDetStateStart = val_;
	}
	
	final public void setConfig(C config_)
	{
		_config = config_;
	}

	final public void initIndividualSolverInstance(IndividualProblemSolver indSolver_)
	{
		_indSolver = indSolver_;
	}

	@Override
	final public IndividualProblemSolver getIndividualSolverInstance()
	{
		return _indSolver;
	}
	
	final public void initAggregateSolverInstance(AggregateProblemSolver aggSolver_)
	{
		_aggSolver = aggSolver_;
	}
	
	@Override
	final public AggregateProblemSolver getAggregateSolverInstance()
	{
		return _aggSolver;
	}
	
	public DiscretisedDistributionSimulatorImpl getSimulator()
	{
		return _simulator;
	}

	public void setSimulator(DiscretisedDistributionSimulatorImpl simulator_)
	{
		_simulator = simulator_;
	}

	private int[] initSimulationGridDimensions()
	{
		List dimensions = new ArrayList();
		
		// Endogenous States
		Utils.addLengthsToList(dimensions, _config.getIndividualEndogenousStatesForSimulation());
		
		// Exogenous States
		Utils.addLengthsToList(dimensions, _config.getIndividualExogenousStates());
		
		return toIntArray(dimensions);
	}
	
	protected int[] initAggregateExpectationDimensions()
	{
		List dimensions = new ArrayList();
		
		addAggregateExpectationDimensions(dimensions);
		
		// The primary expectations are over aggregate states, so initialise it for that size
		dimensions.add(_config.getAggregateEndogenousStateCount());
		
		return toIntArray(dimensions);
	}

	/**
	 * @param dimensions
	 */
	private void addAggregateExpectationDimensions(List dimensions)
	{		
		// Current Shocks
		Utils.addLengthsToList(dimensions, _config.getAggregateExogenousStates());
		
		// Future temporary Shocks
		Utils.addLengthsToList(dimensions, _config.getAggregateExogenousStates());
		
		// Future persistent Shocks
		Utils.addLengthsToList(dimensions, _config.getAggregateNormalisingExogenousStates());

		// Current endogenous States
		Utils.addLengthsToList(dimensions, _config.getAggregateEndogenousStates());

		/* Only include those controls specified as affecting expectations
		 */
		for(int idx : _config.getControlsAffectingExpectations())
		{
			dimensions.add(_config.getAggregateControls().get(idx).numberOfElements());
		}
	}
	
	protected int[] initIndividualExpectationDimensions()
	{
		List dimensions = new ArrayList();
		
		addAggregateExpectationDimensions(dimensions);
		
		// Individual endogenous states
		Utils.addLengthsToList(dimensions, _config.getIndividualEndogenousStates());

		// Individual Shocks
		Utils.addLengthsToList(dimensions, _config.getIndividualExogenousStates());

		// The primary expectations are over individual states, so initialise it for that size
		dimensions.add(_config.getIndividualEndogenousStateCount());
		
		return toIntArray(dimensions);
	}

	protected int[] initIndividualTransitionDimensions()
	{
		List dimensions = individualDimensionsList();
		
		Utils.addLengthsToList(dimensions, _config.getAggregateNormalisingExogenousStates());
		
		dimensions.add(1);
		
		return toIntArray(dimensions);
	}

	@Override
	public DoubleArray createIndividualTransitionGrid()
	{
		return createIndividualTransitionGrid(1);
	}
	
	@Override
	public DoubleArray createIndividualTransitionGrid(int numVars_)
    {
		return createVariableGrid(_individualTransitionDimensions, numVars_);
    }
	
	@Override
	public DoubleArray createIndividualVariableGrid()
	{
		return createIndividualVariableGrid(1);
	}
	
	@Override
	public DoubleArray createIndividualVariableGrid(int numVars_)
    {
		return createVariableGrid(_individualVarDimensions, numVars_);
    }
	
	@Override
	public DoubleArray createAggregateVariableGrid()
	{
		return createAggregateVariableGrid(1);
	}
	
	@Override
	public DoubleArray createAggregateVariableGrid(int numVars_)
    {
		return createVariableGrid(_aggregateVarDimensions, numVars_);
    }
	
	@Override
	public DoubleArray createAggregateExpectationGrid()
	{
		return createAggregateExpectationGrid(_config.getAggregateEndogenousStateCount());
	}
	
	@Override
	public DoubleArray createAggregateExpectationGrid(int numVars_)
    {
		return createVariableGrid(_aggregateExpectationDimensions, numVars_);
    }
	
	@Override
	public DoubleArray createIndividualExpectationGrid()
	{
		return createIndividualExpectationGrid(_config.getIndividualEndogenousStateCount());
	}
	
	@Override
	public DoubleArray createIndividualExpectationGrid(int numVars_)
    {
		return createVariableGrid(_individualExpectationDimensions, numVars_);
    }
	
	@Override
	public DoubleArray createSimulationGrid()
    {
		// Note that because the simulation grid only has one variable (density),
		// it is not necessary to use createVariableGrid here
		return createArrayOfSize(_simulationGridDimensions);
    }
	
	/**
	 * Creates a grid with the specfied dimensions for the specified number of variables,
	 * which is used in place of the last dimension
	 * 
	 * @param dimensions_ The dimensions of the grid, with an additional place for the number of variables
	 * @param numVars_ The number of variables
	 * 
	 * @return A grid with the specified dimensions
	 */
	private DoubleArray createVariableGrid(int[] dimensions_, int numVars_)
	{
		if(numVars_ == dimensions_[dimensions_.length-1])
		{
			return createArrayOfSize(dimensions_);
		}
		else
		{
			// Have to copy so as not to pollute state!
			int[] dims = Arrays.copyOf(dimensions_,dimensions_.length);
			dims[dims.length-1] = numVars_;
			
			return createArrayOfSize(dims);
		}
	}

    protected int[] initIndividualVarDimensions()
    {
        List dimensions = individualDimensionsList();

        // By default, the grid is for a single variable
        dimensions.add(1);
        
        return toIntArray(dimensions);
    }
    
    protected DoubleRectangularGridDomain individualVariableDomain()
    {
    	List> dimensions = new ArrayList>();
    	
    	dimensions.addAll(_config.getIndividualEndogenousStates());
    	dimensions.addAll(_config.getIndividualExogenousStates());
    	
    	addAggregateGridPoints(dimensions);
    	
    	return _functionFactory.createDomain(dimensions);
    }
    
    private final void addAggregateGridPoints(List> dimensions)
    {
		/* The aggregate deterministic state dims
		 */
        dimensions.addAll(_config.getAggregateEndogenousStates());

        dimensions.addAll(_config.getAggregateControls());
        
		/* Finally, the aggregate stochastic state dims
		 */
        dimensions.addAll(_config.getAggregateExogenousStates());
    }

	/**
	 * @return A list containing individual variable dimensions for a grid
	 */
	protected List individualDimensionsList()
	{
		List dimensions = new ArrayList();
		
		/* First the individual deterministic state dims
		 */
        Utils.addLengthsToList(dimensions, _config.getIndividualEndogenousStates());
		
		/* Then the individual stochastic state dims
		 */
        Utils.addLengthsToList(dimensions, _config.getIndividualExogenousStates());
		
		/* Add the aggregate dimensions
		 */
        addAggregateDimensions(dimensions);
		return dimensions;
	}
    
    private int[] initAggregateVarDimensions()
    {
        List dimensions = new ArrayList();

        addAggregateDimensions(dimensions);
        dimensions.add(1);
        
        return toIntArray(dimensions);
    }

	/**
	 * @param dimensions The list of integers to convert to an int array
	 * 
	 * @return A primitive array containing the values from the List
	 */
	protected int[] toIntArray(List dimensions)
	{
		return ArrayUtils.toPrimitive(dimensions.toArray(new Integer[dimensions.size()]));
	}

    protected void addAggregateDimensions(List dimensions)
    {
		/* The aggregate deterministic state dims
		 */
        Utils.addLengthsToList(dimensions, _config.getAggregateEndogenousStates());

        Utils.addLengthsToList(dimensions, _config.getAggregateControls());
        
		/* Finally, the aggregate stochastic state dims
		 */
        Utils.addLengthsToList(dimensions, _config.getAggregateExogenousStates());
    }
    
    
    /**
     * Default does nothing
     */
	@Override
	public void writeAdditional(S state_, NumericsWriter writer_) throws IOException
	{
		
	}

	/**
     * Default does nothing
     */
	@Override
	public void readAdditional(S state_, NumericsReader reader_) throws IOException
	{
		
	}

	/**
	 * The implementation checks whether an IDoubleArray is passed for the shocks and, if so, delegates to the
	 * double-specific method. Otherwise, it assumes integer shocks and delegates to the integer specific method
	 * 
	 * @param simState_ The simulation state for which to calculate aggregates
	 * @param currentAggShock_ The aggregate shock for which to calculate aggregates
	 * @param calcState_ The calculation state
	 * 
	 * @return The aggregate state values given the inputs
	 * 
	 * @throws ModelException If there are errors performing the calculation
	 * 
	 * @param  The type of shock, should be either Double or Integer
	 */
	@Override
	final public  double[] calculateAggregateStates(SimState simState_, MultiDimensionalArray currentAggShock_, S calcState_) throws ModelException
	{
		if(currentAggShock_ instanceof DoubleArray) {
			return calculateAggregateStates(simState_, (DoubleArray)currentAggShock_, calcState_);
		}
		
		return calculateAggregateStates(simState_, (IntegerArray)currentAggShock_, calcState_);
	}
	
	/**
	 * Calculates aggregate states when continuous shocks are being simulated.
	 * 
	 * The implementation in this class throws an expectation if called. Subclasses to be used with continuous shocks must override
	 * it.
	 * 
	 * @param simState_ The simulation state for which to calculate aggregates
	 * @param currentAggShock_ The aggregate shock for which to calculate aggregates
	 * @param calcState_ The calculation state
	 * 
	 * @return The aggregate state values given the inputs
	 * 
	 * @throws ModelException If there are errors performing the calculation
	 */
	public double[] calculateAggregateStates(SimState simState_, DoubleArray currentAggShock_, S calcState_) throws ModelException
	{
		throw new NotImplementedException("This method must be implemented in Models that are simulated with continuous shocks");
	}
	
	/**
	 * Calculates aggregate states when discrete shocks are being simulated.
	 * 
	 * The implementation in this class throws an expectation if called. Subclasses to be used with discrete shocks must override
	 * it.
	 * 
	 * @param simState_ The simulation state for which to calculate aggregates
	 * @param currentAggShock_ The aggregate shock for which to calculate aggregates
	 * @param calcState_ The calculation state
	 * 
	 * @return The aggregate state values given the inputs
	 * 
	 * @throws ModelException If there are errors performing the calculation
	 */
	public double[] calculateAggregateStates(SimState simState_, IntegerArray currentAggShock_, S calcState_) throws ModelException
	{
		throw new NotImplementedException("This method must be implemented in Models that are simulated with discrete shocks");
	}

	/**
	 * Default does nothing
	 */
	@Override
	public void beginIteration(S state_) throws ModelException
	{

	}

	/**
	 * @return The current configuration applied to this model
	 */
	@Override
	public C getConfig()
	{
		return _config;
	}

	// @Override
	// public void postProcessAggregateTransition(IDoubleArray
	// currentAggState_, Integer[] priorShockIndex_,
	// Integer[] currentShockIndex_, ICalcState state_) throws
	// MultiDimensionalArrayException
	// {
	//
	// }

	public int[] initialShocks()
	{
		int[] shocks = Utils.repeatArray(0, _config.getAggregateExogenousStateCount());
		int i = 0;

		// Get the central value for each shock
		for (DoubleArray shock : _config.getAggregateExogenousStates())
		{
			shocks[i++] = shock.numberOfElements() / 2;
		}

		return shocks;
	}

	public int[] initialShocks(DiscretisedDistribution state_)
	{
		return initialShocks();
	}

	/**
	 * The default fixed point value delegate does nothing - i.e. it assumes
	 * that no fixed point needs to be found
	 */
	@Override
	public FixedPointValueDelegate>> getFixedPointDelegate()
	{
		return new FixedPointValueDelegate>>()
		{
			@Override
			public void setInputs(double[] inputs_)
			{

			}

			@Override
			public double[] getOutputs(AggregateFixedPointState> state_)
			{
				return new double[0];
			}

			@Override
			public double[] getInitialInputs()
			{
				return new double[0];
			}
		};
	}
	
	 /**
     * Calculates the sums of the values of the provides function over the provided distribution, first interpolating
     * to get the overflow function values if necessary
     *
     * @param valOnGrid_    The values of the function defined on the grid underlying the distribution
     * @param gridXValues_  The dimension 0 values of the coordinates on the grid
     * @param distribution_ The distribution across which to calculate it
     * 
     * @return The calculated sum
     */
    public DoubleArray sumAcrossDistribution(
    		DoubleArray valOnGrid_, 
    		DoubleArray gridXValues_, 
    		DiscretisedDistribution distribution_)
    {            
    	// First, interpolate the values on the grid to the overflow averages
        final DoubleArray overflowVals = overflowValues(valOnGrid_, gridXValues_, distribution_);
        return sumAcrossDistributionOF(valOnGrid_, overflowVals, distribution_);
    }

    /**
     * Calculates the sums of the values of the provides function over the provided distribution
     *
     * @param valOnGrid_    The values of the function defined on the grid underlying the distribution
     * @param overflowVals_ The values of the function at the overflow points
     * @param distribution_ The distribution across which to calculate it
     * @return The calculated sum
     */
    public DoubleArray sumAcrossDistributionOF(DoubleArray valOnGrid_, DoubleArray overflowVals_, DiscretisedDistribution distribution_)
    {
        // Then, multiply each grid value by the weight at that point and sum, along with the corresponding sum from
        // the overflow
        return valOnGrid_.across(0, 1).multiply(distribution_._density).across(0,1).sum().add(
                overflowVals_.across(0, 1).multiply(distribution_._overflowProportions).across(0,1).sum());
    }

    /**
     * Calculates the overflow values of a (possible multivalued) function defined on the grid
     *
     * @param valOnGrid_    The values defined on the grid
     * @param gridXValues_  The dimension 0 values of the coordinates on the grid
     * @param distribution_ The distribution to use, which will specify the state values at the overflow points
     * @return The function values at the overflow points of the distribution
     */
    protected DoubleArray overflowValues(
    		DoubleArray valOnGrid_, 
    		DoubleArray gridXValues_, 
    		DiscretisedDistribution distribution_)
    {
        // If the function is one dimensional...
        if (valOnGrid_.numberOfDimensions() == distribution_._density.numberOfDimensions())
        {
            // ...just interpolate
            return interpolateFunction(gridXValues_, valOnGrid_, 0, distribution_._overflowAverages, new Params().constrained() );
        } else
        {
            // Otherwise, iterate over the function dimensions and recursively call self for each
            final int[] resultSize = valOnGrid_.size().clone();
            resultSize[0] = 1;

            final DoubleArray results = createArrayOfSize(resultSize);

            valOnGrid_.across(Utils.sequence(0, distribution_._density.numberOfDimensions())).reduce(
            		(IndexedReduction)(subSpaceIterator_ -> {
            			
                // Don't need to clone - returns a copy
                final int[] fullIndex = subSpaceIterator_.getFullIndex();
                fullIndex[0] = -1;

                results.fillAt(overflowValues(valOnGrid_.at(fullIndex), gridXValues_, distribution_),
                        fullIndex);
                
                // The result is not meaningful
                return Double.NaN;
            }));

            return results;
        }
    }
    
    /**
     * Calculates the expected aggregates from the aggregate transition on the provided state class, this
     * should be used whenever the aggregate transition rules have been set
     * 
     * @param state_ The current state of the calculation
     */
    @Override
	public void adjustExpectedAggregates(S state_)
	{
    	DoubleArray oldExpStates = state_.getExpectedAggregateStates();
    	
		DoubleArray expectedStates = createAggregateExpectationGrid();
		
		expectedStates.fillDimensions(
				state_.getAggregateTransition().at(_aggVarAtDimensions).
					copy().arrangeDimensions(_transitionArrangeDimensions),
						_expectationFillDimensions);
		
		state_.setExpectedAggregateStates(expectedStates);
		
		notifyAggregateExpectationListeners(oldExpStates, expectedStates, state_);
	}

	/**
	 * Notifies all listeners of the updated expected aggregate states
	 * 
	 * @param oldExpStates_ The expected states before this update
	 * @param expectedStates_ The expected states after this update
	 * @param state_ The current calculation state
	 */
	protected void notifyAggregateExpectationListeners(DoubleArray oldExpStates_, DoubleArray expectedStates_, S state_)
	{
		_aggExpNotifier.changed(oldExpStates_, expectedStates_, state_);
	}
    
    /**
     * Adds the provided listener to the list of those notified when the aggregate expectations array is changed
     * 
     * @param listener_ The listener to add
     */
    public void addAggregateExpectationListener(ArrayObserver listener_)
    {
    	_aggExpNotifier.registerListener(listener_);
    }
    
    /**
	 * Given an on-grid individual or aggregate variable, interpolates it to the
	 * expected future values conditional current aggregates (controls only where specified) and 
	 * on the realisation of future aggregate
	 * shocks
	 *
	 * @param currentVar_ The variable to interpolate, which should be on-grid
	 * @param state_ The current processing state
	 */
	@Override
	public DoubleArray conditionalExpectation(DoubleArray currentVar_, final S state_)
	{
		return conditionalExpectation(currentVar_, state_, _config.isConstrained());
	}

	final public DoubleArray conditionalExpectation(DoubleArray currentVar_, S state_, final boolean constrained_)
	{ 
		// Create a new array with the appropriate dimensions to hold the result
		DoubleArray result;
		boolean indVar = false;
		
		if( currentVar_.numberOfDimensions() >= _individualVarDimensions.length ) 
		{
			indVar = true;
			result = createIndividualExpectationGrid(currentVar_.size()[currentVar_.numberOfDimensions()-1]);
		}
		else 
		{
			result = createAggregateExpectationGrid(currentVar_.size()[currentVar_.numberOfDimensions()-1]);
		}

		final boolean isIndividual = indVar;
		final int nAggShocks = _config.getAggregateExogenousStateCount();
		final int nAggFutureShocks = _config.getAggregateNormalisingStateCount();
		final int nAggStates = _config.getAggregateEndogenousStateCount();
		final int nAggControls = _config.getAggregateControlCount();

		DoubleArray expectedFutureAggregateStates = state_.getExpectedAggregateStates();
		final List> aggregateVars = new ArrayList>();

		aggregateVars.addAll(_config.getAggregateEndogenousStates());

		aggregateVars.addAll(_config.getAggregateControls());

		// Where to start interpolating depends on the type of variable -
		// individual or aggregate
		// Either way start at the first aggregate state dimension
		final int firstInterpDimension = isIndividual ? getAggDetStateStart() : 0;
				
		if(nAggStates > 0) {
			// Iterate across aggregate state transitions; ignore the highest
			// dimension, which is across different variables
			DoubleSubspaceSplitIterator futStateIter = expectedFutureAggregateStates.iteratorAcross(Utils.sequence(0,
					expectedFutureAggregateStates.numberOfDimensions() - 1));
	
			DoubleSubspaceSplitIterator futControlIter = null;
	
			// ditto for controls, if present
			if (state_ instanceof StateWithControls && nAggControls > 0)
			{
				final DoubleArray expectedControls = ((StateWithControls) state_).getExpectedAggregateControls();
				futControlIter = expectedControls.iteratorAcross(
						Utils.sequence(0, expectedControls.numberOfDimensions() - 1));
			}
	
			List callables = new ArrayList();
			
			while (futStateIter.hasNext())
			{
				futStateIter.nextDouble();
	
				if (nAggControls > 0)
				{
					futControlIter.nextDouble();
				}
	
				final DoubleSubspaceSplitIterator stateDimIter = futStateIter.getOrthogonalIterator();
				final DoubleSubspaceSplitIterator controlDimIter = futControlIter == null ? null : futControlIter.getOrthogonalIterator();
	
				callables.add(()->{
					Specification[] dimSpecsPerThread = new Specification[nAggStates + nAggControls];

					int i = 0;

					while (stateDimIter.hasNext())
					{
						// Get the value of the future state variable given the expectations
						double futureStateValue = stateDimIter.nextDouble();

						// Set it as the target along the appropriate
						// interpolation dimension
						dimSpecsPerThread[i] = spec(firstInterpDimension + i, 
								aggregateVars.get(i), futureStateValue);

						i++;
					}

					if (controlDimIter != null)
					{
						while (controlDimIter.hasNext())
						{
							// Get the value of the future control variable given expectations
							double futureControlValue = controlDimIter.nextDouble();

							// Set it as the target along the appropriate
							// interpolation dimension
							dimSpecsPerThread[i] = spec(firstInterpDimension + i, 
									aggregateVars.get(i), futureControlValue);

							i++;
						}
					}

					int[] index = stateDimIter.getOtherIndex();

					int[] subArrayIndex = Utils.repeatArray(-1, currentVar_.numberOfDimensions());

					System.arraycopy(index, nAggShocks, subArrayIndex, firstInterpDimension + nAggControls + nAggStates, nAggShocks
							+ (isIndividual ? nAggFutureShocks : 0));

					// Interpolate along the non-stochastic aggregate state
					// dimensions
					DoubleArray intermediate = interp(currentVar_.at(subArrayIndex), dimSpecsPerThread);

					// Fill into the results array
					result.fillAt(intermediate, index);
				});
			}
			getNumerics().getExecutor().executeAndWait(callables);
		}
		else
		{
			int[] subArrayIndex = Utils.repeatArray(-1, currentVar_.numberOfDimensions());
			
			/* Create an iterator over future exogenous states
			 */
			List dimList = new ArrayList();
			
			Utils.addLengthsToList(dimList, _config.getAggregateExogenousStates());
			Utils.addLengthsToList(dimList, _config.getAggregateNormalisingExogenousStates());
			
			Index futureExoStatesIndex = new Index(ArrayUtils.toPrimitive(dimList.toArray(new Integer[dimList.size()])));
			
			IndexIterator fesi = futureExoStatesIndex.iterator();
			
			while(fesi.hasNext()) 
			{
				fesi.nextInt();
				
				int[] index = fesi.getCurrentIndex();
				
				System.arraycopy(index, 0, subArrayIndex, firstInterpDimension, nAggShocks
						+ nAggFutureShocks);
				
				// No interpretation in this case!
				DoubleArray intermediate = currentVar_.at(subArrayIndex);
				
				// Prepare to fill at the correct index
				int[] fillIndex = Utils.repeatArray(-1, result.numberOfDimensions());

				// Copy the current agg stochastic state indices
//				System.arraycopy(index, 0, fillIndex, 0, nAggShocks);

				// Copy the future agg stochastic state indices
				System.arraycopy(index, 0, fillIndex, nAggShocks + firstInterpDimension, nAggShocks + nAggFutureShocks);

				// Record this for later use
				result.at(fillIndex).fillDimensions(intermediate, Utils.sequence(1,firstInterpDimension+1));
			}
		}
		
		return result;
	}
}