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

com.meliorbis.economics.infrastructure.Solver 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 java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;

import com.meliorbis.economics.aggregate.AggregateProblemSolver;
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.economics.utils.FileUtils;
import com.meliorbis.numerics.IntArrayFactories;
import com.meliorbis.numerics.NumericsException;
import com.meliorbis.numerics.convergence.Convergable;
import com.meliorbis.numerics.convergence.Converger;
import com.meliorbis.numerics.convergence.Criterion;
import com.meliorbis.numerics.convergence.ProgressDisplayingCallback;
import com.meliorbis.numerics.generic.IntegerArray;
import com.meliorbis.numerics.generic.MultiDimensionalArray;
import com.meliorbis.numerics.generic.primitives.DoubleArray;
import com.meliorbis.numerics.io.NumericsReader;
import com.meliorbis.numerics.io.NumericsWriter;
import com.meliorbis.numerics.io.NumericsWriterFactory;
import com.meliorbis.utils.Pair;
import com.meliorbis.utils.Timer;
import com.meliorbis.utils.Timer.Stoppable;

/**
 * A generic class to solve optimisation problems
 *
 * @author Tobias Grasl
 */
public final class Solver extends Base
{
    private static final int MAX_DISTANCE_FROM_MIN = Integer.getInteger(
    		"com.meliorbis.economics.maxmindist", 300);
	private static final Logger LOG = Logger.getLogger(Solver.class.getName());
    private static final File OUTDIR = new File(System.getProperty("com.meliorbis.economics.out", "Solutions"));

    public Solver(NumericsWriterFactory outputFactory_)
    {
        super(outputFactory_);
    }
   
    /**
     * Solves a single transition for a single age (where applicable)
     *
     * @param model_ The model being solved.
     * @param state_ The current calculation state.
     * 
     * @throws ModelException
     */
    > void performIndividualIteration(final Model model_, final S state_) throws ModelException
    {
    	LOG.fine("Starting individual solution iteration");
    	
        model_.getIndividualSolverInstance().performIteration(state_);
    	
        LOG.fine("Finished individual solution iteration");
    }

    /**
     * Determines the solution to the provided model, using the provided state object to store
     * information as the calculation progresses
     *
     * @param model_      The model to find the solve
     * @param writeState_ Indicates whether state should be written to persistent storage
     * 
     * @throws SolverException In the event of failure
     * 
     * @param  The type of the model being solved
     * @param  The type used to configure the model
     * @param  The type used to hold state during the calculation
     */
    public , 
    		C extends ModelConfig, S extends State> 
    	void solveModel(M model_, boolean writeState_) throws SolverException
    {
    	solveModel(model_, model_.initialState(), writeState_);
    }
    
    /**
     * Determines the solution to the provided model, using the provided state object to store
     * information as the calculation progresses
     *
     * @param model_      The model to find the solve
     * @param state_      The starting state, which will be updated as the calculation proceeds
     * @param writeState_ Indicates whether state should be written to persistent storage
     * 
     * @throws SolverException In the event of failure
     * 
     * @param  The type of the model being solved
     * @param  The type used to hold state during the calculation
     */
    public , S extends State> void solveModel(M model_, S state_, boolean writeState_) throws SolverException
    {
    	Stoppable solutionTimer = new Timer().start("Solve");
    	// Initialise the Solver(s)
        model_.getIndividualSolverInstance().initialise(state_);

        if(model_.getConfig().hasAggUncertainty()) {
        	AggregateProblemSolver aggregateSolverInstance = model_.getAggregateSolverInstance();
        	
        	// Even with aggregate risk it may be the case that the model is being solved with an exogenous forecasting rule,
        	// so there is no aggregate solver - check!
        	if(aggregateSolverInstance != null){
        		aggregateSolverInstance.initialise(state_);
        	}
        }
        
        System.out.println("Determining Transition Rules");

        Converger converger = new Converger();
        
        converger.addCallback(new ProgressDisplayingCallback());
        
        converger.addCallback(new Converger.Callback()
		{
			double _min = Double.MAX_VALUE;
			int _minPeriod = 0;
			
			@SuppressWarnings("unchecked")
			@Override
			public void notify(Pair state_, int period_)
			{
				if(state_.getRight().getValue() < _min) {
					_minPeriod = period_;
					_min = state_.getRight().getValue();
				}
				
				if(period_ - _minPeriod > MAX_DISTANCE_FROM_MIN) {
					model_.convergenceFailed((S) state_.getLeft());
					throw new RuntimeException("Not Converging");
				}
			}
		});
        Convergable convergable = s -> {
        	performIteration(model_,s);
        	return new Pair(s,s.getConvergenceCriterion());
        };
        
        converger.converge(convergable, state_, 1e-6);
        
        System.out.println("Found consistent transition rules");

        solutionTimer.stop();
        
        model_.solutionFound(state_);

        if (writeState_) writeState(model_, state_);
    }

    /**
     * Performs a single iteration
     */
    , S extends State> void performIteration(M model_, S state_) throws ModelException,
			SolverException
	{
		// Update the period of the calculation
		state_.incrementPeriod();
		
		Timer timer = new Timer();
		
		Stoppable stoppable = timer.start("solve individual");

		// Solve the individual problem for a single period
		performIndividualIteration(model_, state_);

		stoppable.stop();
		
		// When there is no individual uncertainty, the individual is the aggregate - so
		// Need to adjust the expected aggregate states as appropriate. Let the model do that.
		if(!model_.getConfig().hasIndUncertainty()) {
			
			model_.adjustExpectedAggregates(state_);
			
		}
		// Does the model have aggregate uncertainty?
		else if(model_.getConfig().hasAggUncertainty() && model_.shouldUpdateAggregates(state_))
		{
			stoppable = timer.start("solve aggregate");
			updateAggTransRules(model_, state_);
			stoppable.stop();
		}
	}
    
    /**
     * Writes the provided calcState to a dated state directory
     *
     * @param model_ The model being solved
     * @param state_ The state to write
     * 
     * @return The path of the directory which the data was written to
     * 
     * @param  The type of the model being solved
     * @param  The type used to hold state during the calculation
     */
    public  , S extends State> String writeState(M model_, S state_)
    {
        File directoryToWrite = createSolutionDirectory();
        
        writeState(model_, state_, directoryToWrite);

        return directoryToWrite.getAbsolutePath();
    }

    public File createSolutionDirectory()
    {
        File outdir = OUTDIR;
        String prefix = "state";

        return FileUtils.createDatedDirectory(outdir, prefix);
    }

    /**
     * Writes the provided state to the directory specified
     *
     * @param model_    The model the state is for
     * @param state_ 	The state to write
     * @param dir_		The directory to write it in
     * 
     * @param  The type of the model being solved
     * @param  The type used to hold state during the calculation
     */
    public  , S extends State> void writeState(M model_, S state_, File dir_)
    {
    	
        NumericsWriter writer = getNumericsWriter(new File(dir_, "state"));
        ((AbstractStateBase)state_).write(writer);
        
        try
		{
			model_.writeAdditional(state_, writer);
			
			writeTimes(writer);
		} catch (IOException e)
		{
			throw new NumericsException("Error writing state", e);
		}
        finally
        {
        	try
        	{
        		writer.close();
        	}
        	catch(Exception e)
        	{
        	}
        }
    }

	/**
	 * Writes the timings gathered with Timer to a structure called 'timing' in the
	 * writer
	 * 
	 * @param writer The numerics writer to write to
	 * 
	 * @throws IOException If problems occur
	 */
	private void writeTimes(NumericsWriter writer) throws IOException
	{
		Map timesMap = Timer.getTimesMap();
		
		Map> outputTimes = new HashMap<>();
		
		// Transform the plain long[] to IntegerArrays
		timesMap.forEach((name, times) -> {
			// Convert to milliseconds so it fits in an int
			int[] values_ =
			{ (int)(times[0]/1000000), (int)times[1] };
			outputTimes.put(name, (IntegerArray) IntArrayFactories.createIntArray(values_));
		});
		
		writer.writeStructure("timing", outputTimes);
	}

    /**
     * Reads the state of the model provided from directory dir_
     *
     * @param model_ The model for which to read the state.
     * @param dir_   The directory in which the state to be read is stored
     * 
     * @return An appropriate model state
     * 
     * @param  The type used to configure the model
     * @param  The type used to hold state during the calculation
     */
    public > S readState(Model model_, File dir_)
    {
        NumericsReader reader = getNumericsReader(dir_);

        try
        {
            model_.getConfig().readParameters(reader);

            // Create the state to populate
            S state = model_.initialState();
            // Model Inputs ARE NOT READ; assumes they are already configured appropriately

            // Actual calculation state
            state.setAggregateTransition((DoubleArray) reader.getArray("aggTransition"));
            state.setIndividualPolicy((DoubleArray) reader.getArray("indTransition"));

            if (state instanceof StateWithControls)
            {
                ((StateWithControls) state).setExpectedAggregateControls((DoubleArray) reader.getArray("aggExpControls"));
            }

            // Finally, allow the state object to write additional data if necessary
            model_.readAdditional(state, reader);

            return state;
        } catch (IOException e)
        {
            throw new NumericsException("Error reading state", e);
        }
    }

	private > void updateAggTransRules(Model model_, S state_) throws SolverException
    {
    	LOG.fine("Starting aggregate transition update");
    	
        model_.getAggregateSolverInstance().updateAggregateTransition(state_);
        
        LOG.fine("Finished aggregate transition update");
        
        // Then the aggregate expectations also need to be updated
        model_.adjustExpectedAggregates(state_);
    	
    }
}