broadwick.montecarlo.MonteCarlo.java Source code

Java tutorial

Introduction

Here is the source code for broadwick.montecarlo.MonteCarlo.java

Source

/*
 * Copyright 2014 University of Glasgow.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package broadwick.montecarlo;

import broadwick.rng.RNG;
import broadwick.statistics.Samples;
import broadwick.utils.CloneUtils;
import com.google.common.base.Throwables;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.time.StopWatch;

/**
 * An implementation of the Monte Carlo method, it runs many MonteCarloSimulations and gathers the statistics of their
 * outputs. Note, each simulation object is cloned and each of these clones are run independently
 * <code>numSimulations</code> times.
 */
@Slf4j
public class MonteCarlo {

    /**
     * Create a Monte Carlo object that is capable of running <code>numSimulation</code> MonteCarloScenarios.
     * @param simulation     the simulation or scenario to be run.
     * @param numSimulations the number of times the simulation should be run.
     */
    public MonteCarlo(final MonteCarloScenario simulation, final int numSimulations) {
        this.simulation = simulation;
        this.numSimulations = numSimulations;
        this.resultsConsumer = new MonteCarloDefaultResults();
    }

    /**
     * Run the Monte Carlo simulations. Two threads (a producer and consumer) are created to asynchronously run the
     * simulations (the producer) and to handle the results from each simulation as they are calculated (the consumer).
     * The producer thread uses an execution pool to manage running each simulation and places the results on a queue
     * which is monitored by a consumer thread to calculate the posterior distributions for the Monte Carlo run.
     */
    public final void run() {

        final ArrayBlockingQueue<MonteCarloResults> queue = new ArrayBlockingQueue<>(numSimulations + 1);

        //Creating Producer and Consumer Thread
        if (log.isTraceEnabled()) {
            log.trace("Creating Monte Carlo producer and consumer");
        }
        final Thread producer = new Thread(new Producer(queue, simulation, numSimulations));
        final Thread consumer = new Thread(new Consumer(queue, resultsConsumer));

        producer.start();
        consumer.start();

        try {
            producer.join();
            consumer.join();
        } catch (Exception e) {
            log.error("Error joining Monte Carlo results {}", Throwables.getStackTraceAsString(e));
        }
    }

    /**
     * Get a copy of the results from the simulation. This method MUST return a copy of the results NOT a reference.
     * @return the MonteCarloResults object that contains the results of all the simulations.
     */
    public final MonteCarloResults getResults() {
        return CloneUtils.deepClone(resultsConsumer);
    }

    /**
     * Set the consumer object for this Monte Carlo simulation. A consumer is simply a MonteCarloResults class that
     * 'joins' each result that is consumes so that statistics can be calculated at the end of the simulation.
     * @param consumer the results object to use as a consumer.
     */
    public final void setResultsConsumer(final MonteCarloResults consumer) {
        resultsConsumer = consumer;
        resultsConsumer.reset();
    }

    private final MonteCarloScenario simulation;
    private MonteCarloResults resultsConsumer;
    private final int numSimulations;
}

/**
 * This class takes (or consumes) the results of a single simulation of the model and saves the results in a manner to
 * allow statistics to be generated from the Monte Carlo simulation.
 */
@Slf4j
class Consumer implements Runnable {

    /**
     * Create the consumer object.
     * @param queue         the queue that the consumer should check for values to consume.
     * @param joinedResults the MonteCarloResults object to which the consumed results will be added.
     */
    public Consumer(final ArrayBlockingQueue<MonteCarloResults> queue, final MonteCarloResults joinedResults) {
        this.queue = queue;
        this.joinedResults = joinedResults;
        joinedResults.reset();
    }

    @Override
    public void run() {
        log.trace("Starting Monte Carlo results consumer thread");

        // it might seem that we should reset (or initialise) the consumer results object but
        // the consumer object is created for each Monte Carlo step so is really not strictly necessary.
        joinedResults.reset();
        final StopWatch sw = new StopWatch();
        sw.start();

        try {
            MonteCarloResults results = null;
            while (!(results instanceof Poison)) {
                results = queue.take();

                if (!(results instanceof Poison)) {
                    numSimulationsFound++;

                    // get the MonteCarloResults from the q and calculate the statistics on it.
                    joinedResults.join(results);

                    if (log.isTraceEnabled()) {
                        log.trace("Monte Carlo consumer: consumed {}", results.getSamples().getSummary());
                        log.trace("Monte Carlo consumer: consumed {} results, expected value={}",
                                numSimulationsFound, joinedResults.getExpectedValue());
                    }

                    // we no longer require the results so allow the memory to be freed.
                    results = null;
                }
            }
        } catch (java.lang.InterruptedException e) {
            log.error("Error consuming Monte Carlo Results {}", Throwables.getStackTraceAsString(e));
        }
        sw.stop();
        log.debug("Analysed {} simulation results in {}.", numSimulationsFound, sw);
    }

    private final ArrayBlockingQueue<MonteCarloResults> queue;
    private final MonteCarloResults joinedResults;
    private int numSimulationsFound = 0;
}

/**
 * This class runs a single simulation of the model to 'produce' results (hence the name). This runnable class is run
 * many times to create a Monte Carlo simulation.
 */
@Slf4j
class Producer implements Runnable {

    /**
     * Create the producer object.
     * @param queue          the queue on which the producers results are added.
     * @param simulation     the model that will be run to produce results.
     * @param numSimulations the number of simulations that are to be run and placed in the quque.
     */
    public Producer(final ArrayBlockingQueue<MonteCarloResults> queue, final MonteCarloScenario simulation,
            final int numSimulations) {
        this.queue = queue;
        this.simulation = simulation;
        this.numSimulations = numSimulations;
    }

    @Override
    public void run() {
        log.trace("Starting Monte Carlo results producer thread");
        try {
            final int poolSize = Runtime.getRuntime().availableProcessors();
            final ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("MCScenarioProducer-%d")
                    .setDaemon(true).build();
            final ExecutorService es = Executors.newFixedThreadPool(poolSize, threadFactory);
            final RNG generator = new RNG(RNG.Generator.Well44497b);

            final StopWatch sw = new StopWatch();
            sw.start();
            for (int i = 0; i < numSimulations; i++) {
                es.submit(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            log.trace("Monte Carlo producer: creating scenario object");
                            final MonteCarloScenario scenario = simulation.copyOf();
                            final MonteCarloResults results = scenario
                                    .run(generator.getInteger(0, Integer.MAX_VALUE - 1));
                            log.trace("Monte Carlo producer: generated results {}", results.getExpectedValue());
                            queue.put(results);
                        } catch (Exception e) {
                            log.error("Error running Monte Carlo simulation {}",
                                    Throwables.getStackTraceAsString(e));
                        }
                    }
                });
            }
            es.shutdown();
            while (!es.isTerminated()) {
                es.awaitTermination(1, TimeUnit.SECONDS);
            }
            queue.put(new Poison());

            sw.stop();
            log.info("Finished {} simulations in {}.", numSimulations, sw);
        } catch (Exception ex) {
            log.error("Monte Carlo simulation error: {}", Throwables.getStackTraceAsString(ex));
        }
    }

    private final ArrayBlockingQueue<MonteCarloResults> queue;
    private final MonteCarloScenario simulation;
    private final int numSimulations;
}

/**
 * Poison object that causes the consumer of the MonteCarlo results to stop looking for objects on the queue.
 */
class Poison implements MonteCarloResults {

    @Override
    public double getExpectedValue() {
        return Double.NEGATIVE_INFINITY;
    }

    @Override
    public Samples getSamples() {
        return new Samples();
    }

    @Override
    public String toCsv() {
        return "POISON";
    }

    @Override
    public final MonteCarloResults join(final MonteCarloResults results) {
        return results;
    }

    @Override
    public void reset() {
        // do nothing - this is a poison pill
    }
}