ApproxBayesianComp.java

/*
 * Copyright 2013 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.abc;

import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.Map;
import lombok.Getter;
import lombok.Setter;

/**
 * This class runs an approximate Bayesian computation, sampling from the posterior.
 */
public class ApproxBayesianComp {

    /**
     * Create an ABC instance. The created instance will be ready to run a calculation using the default controller and
     * distance measure, these will need to be set if custom ones are required.
     * @param observedData the observed data from which the distance will be calculated.
     * @param model        the model that generates the data.
     * @param priors       the sampler objec that samples from the priors.
     * @param sensitivity  the distance cutoff, distance greater than this value will be ignored.
     */
    public ApproxBayesianComp(final AbcNamedQuantity observedData, final AbcModel model,
                              final AbcPriorsSampler priors, final double sensitivity) {
        this.posteriors = new LinkedHashMap<>();
        this.observedData = observedData;
        this.model = model;
        this.priors = priors;
        this.epsilon = sensitivity;
        numSamplesTaken = 0;
    }

    /**
     * Run the ABC algorithm.
     */
    public final void run() {

        while (controller.goOn(this)) {

            final AbcNamedQuantity parameters = priors.sample();
            numSamplesTaken++;
            final AbcNamedQuantity generatedData = model.run(parameters);

            if (distance.calculate(generatedData, observedData) < epsilon) {
                save(parameters);
            }
        }
    }

    /**
     * Save the sample taken from the prior as it meets the criteria set for being a posterior sample.
     * @param prior the sampled values from the prior.
     */
    private void save(final AbcNamedQuantity prior) {

        if (posteriors.isEmpty()) {
            for (Map.Entry<String, Double> entry : prior.getParameters().entrySet()) {
                final LinkedList<Double> vals = new LinkedList<>();
                vals.add(entry.getValue());
                posteriors.put(entry.getKey(), vals);
            }
        } else {
            for (Map.Entry<String, Double> entry : prior.getParameters().entrySet()) {
                posteriors.get(entry.getKey()).add(entry.getValue());
            }
        }
    }
    @Setter
    private AbcController controller = new AbcMaxNumStepController();
    @Setter
    private AbcDistance distance = new AbcAbsDistance();
    private AbcModel model;
    private AbcPriorsSampler priors;
    @Getter
    private Map<String, LinkedList<Double>> posteriors;
    private AbcNamedQuantity observedData;
    private double epsilon;
    @Getter
    @SuppressWarnings("PMD.UnusedPrivateField")
    private int numSamplesTaken;
}