AbstractExtremaFinder.java

package org.aavso.tools.vstar.util;

import java.util.List;

import org.aavso.tools.vstar.data.ValidObservation;
import org.aavso.tools.vstar.exception.AlgorithmError;
import org.aavso.tools.vstar.ui.mediator.AnalysisType;
import org.aavso.tools.vstar.ui.mediator.Mediator;
import org.aavso.tools.vstar.ui.model.plot.ICoordSource;
import org.aavso.tools.vstar.util.locale.LocaleProps;
import org.aavso.tools.vstar.util.prefs.NumericPrecisionPrefs;
import org.apache.commons.math.analysis.UnivariateRealFunction;
import org.apache.commons.math.optimization.GoalType;

abstract public class AbstractExtremaFinder implements IInteruptible {

	protected List<ValidObservation> obs;
	protected ICoordSource timeCoordSource;
	protected double zeroPoint;
	protected UnivariateRealFunction function;
	protected int numericallyMinMagIndex;
	protected int numericallyMaxMagIndex;
	protected Double extremeMag;
	protected Double extremeTime;
	protected boolean interrupt;

	/**
	 * Constructor
	 * 
	 * @param obs
	 *            The list of observations modeled by the function.
	 * @param function
	 *            An Apache Commons Math Univariate function.
	 * @param timeCoordSource
	 *            Time coordinate source.
	 * @param zeroPoint
	 *            The zeroPoint to be added to the extreme time result.
	 */
	public AbstractExtremaFinder(List<ValidObservation> obs,
			UnivariateRealFunction function, ICoordSource timeCoordSource,
			double zeroPoint) {
		super();
		this.obs = obs;
		this.function = function;
		this.timeCoordSource = timeCoordSource;
		this.zeroPoint = zeroPoint;

		this.numericallyMinMagIndex = getNumericallyMinimumMagnitudeIndex();
		this.numericallyMaxMagIndex = getNumericallyMaximumMagnitudeIndex();

		extremeMag = null;
		extremeTime = null;

		interrupt = false;
	}

	/**
	 * Find the extremum according to the goal over a given time range.
	 * 
	 * @param goal
	 *            Minimum or maximum?
	 * @param bracketRange
	 *            The inclusive time range indices within which to look for the
	 *            extremum.
	 */
	abstract public void find(GoalType goal, int[] bracketRange)
			throws AlgorithmError;

	@Override
	public void interrupt() {
		interrupt = true;
	}

	protected Double getExtremeMag() {
		return extremeMag;
	}

	protected Double getExtremeTime() {
		return extremeTime;
	}

	/**
	 * Find the first numerically smaller values either side of the numerically
	 * largest (so, astronomical magnitude minimum) value.
	 * 
	 * @return an inclusive range that brackets the astronomical minimum
	 */
	protected int[] determineInitialSearchRangeForMinimum() {

		int[] range = new int[2];

		// To the left...
		range[0] = numericallyMaxMagIndex;
		for (int i = numericallyMaxMagIndex - 1; i >= 0; i--) {
			if (obs.get(i).getMag() < obs.get(numericallyMaxMagIndex).getMag()) {
				range[0] = i;
				break;
			}
		}

		// To the right...
		range[1] = numericallyMaxMagIndex;
		for (int i = numericallyMaxMagIndex + 1; i < obs.size(); i++) {
			if (obs.get(i).getMag() < obs.get(numericallyMaxMagIndex).getMag()) {
				range[1] = i;
				break;
			}
		}

		return range;
	}

	/**
	 * Find the first numerically larger values either side of the numerically
	 * smallest (so, astronomical magnitude maximum) value.
	 * 
	 * @return an inclusive range that brackets the astronomical maximum
	 */
	protected int[] determineInitialSearchRangeForMaximum() {
		int[] range = new int[2];

		// To the left...
		range[0] = numericallyMinMagIndex;
		for (int i = numericallyMinMagIndex - 1; i >= 0; i--) {
			if (obs.get(i).getMag() > obs.get(numericallyMinMagIndex).getMag()) {
				range[0] = i;
				break;
			}
		}

		// To the right...
		range[1] = numericallyMinMagIndex;
		for (int i = numericallyMinMagIndex + 1; i < obs.size(); i++) {
			if (obs.get(i).getMag() > obs.get(numericallyMinMagIndex).getMag()) {
				range[1] = i;
				break;
			}
		}

		return range;
	}

	protected int getNumericallyMinimumMagnitudeIndex() {
		int minIndex = 0;

		for (int i = 0; i < obs.size(); i++) {
			if (obs.get(i).getMag() < obs.get(minIndex).getMag()) {
				minIndex = i;
			}
		}

		return minIndex;
	}

	protected int getNumericallyMaximumMagnitudeIndex() {
		int maxIndex = 0;

		for (int i = 0; i < obs.size(); i++) {
			if (obs.get(i).getMag() > obs.get(maxIndex).getMag()) {
				maxIndex = i;
			}
		}

		return maxIndex;
	}

	/**
	 * Return a string representation of the minimum magnitude (numerical
	 * maximum) generated by the function.
	 * 
	 * @return The minimum string.
	 * @throws AlgorithmError
	 *             if there is an error in minima determination.
	 */
	public String toMinimaString() throws AlgorithmError {
		int[] range = determineInitialSearchRangeForMinimum();

		return toExtremumString("MODEL_INFO_MINIMA_TITLE", GoalType.MAXIMIZE,
				function, range);
	}

	/**
	 * Return a string representation of the maximum magnitude (numerical
	 * minimum) generated by the function.
	 * 
	 * @return The maximum string.
	 * @throws AlgorithmError
	 *             if there is an error in maxima determination.
	 */
	public String toMaximaString() throws AlgorithmError {
		int[] range = determineInitialSearchRangeForMaximum();

		return toExtremumString("MODEL_INFO_MAXIMA_TITLE", GoalType.MINIMIZE,
				function, range);
	}

	/**
	 * Return a string representation of the extreme magnitude (numerical
	 * opposite of magnitude) generated by the function.
	 * 
	 * @param goal
	 *            The goal (numericallyMinMagIndex, numericallyMaxMagIndex).
	 * @param function
	 *            The function for which the extremum is required.
	 * @param bracketRange
	 *            The inclusive range within which to look for the extremum.
	 * @return The extremum string.
	 * @throws AlgorithmError
	 *             if there is an error in extrema determination.
	 */
	protected String toExtremumString(String titleKey, GoalType goal,
			UnivariateRealFunction function, int[] bracketRange)
			throws AlgorithmError {

		String strRepr = null;

		// TODO: at some stage, allow this (JD vs phase)
		if (Mediator.getInstance().getAnalysisType() == AnalysisType.RAW_DATA) {
			// Note: won't extremeMag/Time be overwritten? probably doesn't
			// matter
			// because of the way we sequentially construct a string here...
			find(goal, bracketRange);

			double extremeMag = getExtremeMag();

			strRepr = String.format("%s: %s, Mag: %s [%s]",
					timeCoordSource.getUnit(),
					NumericPrecisionPrefs.formatTime(getExtremeTime()),
					NumericPrecisionPrefs.formatMag(extremeMag),
					LocaleProps.get(titleKey));

			// Is the extremum within a reasonable range? If not,
			// set it to null.
			if (strRepr != null && extremeTime == Double.POSITIVE_INFINITY) {
				strRepr = null;
			}
		}

		return strRepr;
	}

	@Override
	public String toString() {
		String str = "";

		try {
			String maxStr = toMaximaString();
			String minStr = toMinimaString();

			if (minStr != null) {
				str += minStr;
				str += "\n";
			}

			if (maxStr != null) {
				str += maxStr;
			}
		} catch (AlgorithmError e) {
		}

		return "".equals(str) ? null : str;
	}

}