com.meliorbis.economics.infrastructure.Solver Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ModelSolver Show documentation
Show all versions of ModelSolver Show documentation
A library for solving economic models, particularly
macroeconomic models with heterogeneous agents who have model-consistent
expectations
/**
*
*/
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,S> 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, Criterion> 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,S> 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_);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy