AbstractModel.java
/**
* VStar: a statistical analysis tool for variable star data.
* Copyright (C) 2010 AAVSO (http://www.aavso.org/)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.aavso.tools.vstar.util.model;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.aavso.tools.vstar.data.DateInfo;
import org.aavso.tools.vstar.data.Magnitude;
import org.aavso.tools.vstar.data.SeriesType;
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.ContinuousModelFunction;
import org.aavso.tools.vstar.ui.model.plot.ICoordSource;
import org.aavso.tools.vstar.ui.model.plot.JDCoordSource;
import org.aavso.tools.vstar.ui.model.plot.JDTimeElementEntity;
import org.aavso.tools.vstar.ui.model.plot.StandardPhaseCoordSource;
import org.aavso.tools.vstar.util.comparator.JDComparator;
import org.aavso.tools.vstar.util.comparator.StandardPhaseComparator;
import org.aavso.tools.vstar.util.locale.LocaleProps;
import org.aavso.tools.vstar.util.prefs.NumericPrecisionPrefs;
import org.aavso.tools.vstar.util.stats.DescStats;
/**
* Model classes can use this abstract base class to implement common code
* instead of implementing the IModel interface.
*/
public abstract class AbstractModel implements IModel {
protected boolean interrupted;
protected List<ValidObservation> obs;
protected List<ValidObservation> fit;
protected List<ValidObservation> residuals;
protected Map<String, String> functionStrMap;
protected ICoordSource timeCoordSource;
protected Comparator<ValidObservation> timeComparator;
protected double zeroPoint;
protected double sumSqResiduals = 0;
protected double aic = Double.NaN;
protected double bic = Double.NaN;
protected double rms = Double.NaN;
public AbstractModel(List<ValidObservation> obs) {
this.obs = obs;
fit = new ArrayList<ValidObservation>();
residuals = new ArrayList<ValidObservation>();
functionStrMap = new TreeMap<String, String>();
interrupted = false;
// Select time mode (JD or phase).
switch (Mediator.getInstance().getAnalysisType()) {
case RAW_DATA:
timeCoordSource = JDCoordSource.instance;
timeComparator = JDComparator.instance;
this.obs = obs;
zeroPoint = DescStats.calcTimeElementMean(obs, JDTimeElementEntity.instance);
break;
case PHASE_PLOT:
timeCoordSource = StandardPhaseCoordSource.instance;
timeComparator = StandardPhaseComparator.instance;
this.obs = new ArrayList<ValidObservation>(obs);
Collections.sort(this.obs, timeComparator);
zeroPoint = 0;
break;
}
}
/**
* Default behaviour for model run interrupt: set flag.
*/
@Override
public void interrupt() {
interrupted = true;
}
/**
* Collect fit and residual observations and incrementally compute the sum of
* squared residuals for use in fit metrics.
*
* @param modelValue A model-computed value
* @param ob The observation (at time t) being modeled
*/
public void collectObs(double modelValue, ValidObservation ob, String comment) {
ValidObservation fitOb = new ValidObservation();
fitOb.setDateInfo(new DateInfo(ob.getJD()));
if (Mediator.getInstance().getAnalysisType() == AnalysisType.PHASE_PLOT) {
fitOb.setPreviousCyclePhase(ob.getPreviousCyclePhase());
fitOb.setStandardPhase(ob.getStandardPhase());
}
fitOb.setMagnitude(new Magnitude(modelValue, 0));
fitOb.setBand(SeriesType.Model);
fitOb.setComments(comment);
fit.add(fitOb);
ValidObservation resOb = new ValidObservation();
resOb.setDateInfo(new DateInfo(ob.getJD()));
if (Mediator.getInstance().getAnalysisType() == AnalysisType.PHASE_PLOT) {
resOb.setPreviousCyclePhase(ob.getPreviousCyclePhase());
resOb.setStandardPhase(ob.getStandardPhase());
}
double residual = ob.getMag() - modelValue;
resOb.setMagnitude(new Magnitude(residual, 0));
resOb.setBand(SeriesType.Residuals);
resOb.setComments(comment);
residuals.add(resOb);
sumSqResiduals += (residual * residual);
}
/**
* Return the fitted observations, after having executed the algorithm.
*
* @return A list of observations that represent the fit.
*/
public List<ValidObservation> getFit() {
return fit;
}
/**
* Return the residuals as observations, after having executed the algorithm.
*
* @return A list of observations that represent the residuals.
*/
public List<ValidObservation> getResiduals() {
return residuals;
}
/**
* Return the list of coefficients that gives rise to the model. May return
* null.
*
* @return A list of fit coefficients or null if none available.
*/
public List<PeriodFitParameters> getParameters() {
return null;
}
/**
* Does this model have a function-based description?
*
* @return True or false.
*/
public boolean hasFuncDesc() {
return false;
}
/**
* Return a mapping from names to strings representing model functions.
*
* @return The model function string map.
*/
public Map<String, String> getFunctionStrings() {
return functionStrMap;
}
/**
* Returns the model function and context. This is required for creating a line
* plot to show the model as a continuous function. If a model creator cannot
* sensibly return such a function, it may return null and no such plot will be
* possible. It could also be useful for analysis purposes, e.g. analytic
* extrema finding.<br/>
*
* @return The function object.
*/
public ContinuousModelFunction getModelFunction() {
return null;
}
/**
* Compute the root mean square.
*
* pre-condition: assumes sum of squared residuals has been computed
*/
public void rootMeanSquare() {
rms = Math.sqrt(sumSqResiduals / residuals.size());
}
/**
* Compute the Bayesian and Aikake Information Criteria (BIC and AIC) fit
* metrics
*
* pre-condition: assumes sum of squared residuals has been computed
*
* @param numberOfEstimatedParams The number of estimated parameters, e.g.
* polynomial degree
*/
public void informationCriteria(double numberOfEstimatedParams) {
int n = residuals.size();
if (n != 0 && sumSqResiduals / n != 0) {
double commonIC = n * Math.log(sumSqResiduals / n);
aic = commonIC + 2 * numberOfEstimatedParams;
bic = commonIC + numberOfEstimatedParams * Math.log(n);
}
}
/**
* Gather fit metrics string.
*
* @throws AlgorithmError
*/
public void fitMetrics() throws AlgorithmError {
String strRepr = functionStrMap.get(LocaleProps.get("MODEL_INFO_FIT_METRICS_TITLE"));
if (strRepr == null) {
// Goodness of fit
if (rms != Double.NaN) {
strRepr = "RMS: " + NumericPrecisionPrefs.formatOther(rms);
}
// Akaike and Bayesean Information Criteria
if (aic != Double.NaN && bic != Double.NaN) {
strRepr += "\nAIC: " + NumericPrecisionPrefs.formatOther(aic);
strRepr += "\nBIC: " + NumericPrecisionPrefs.formatOther(bic);
}
}
functionStrMap.put(LocaleProps.get("MODEL_INFO_FIT_METRICS_TITLE"), strRepr);
}
/**
* @return a string representing the model as a VeLa function
*/
abstract public String toVeLaString();
/**
* Put function strings into the map
*/
public void functionStrings() {
functionStrMap.put(LocaleProps.get("MODEL_INFO_FUNCTION_TITLE"), toVeLaString());
}
/**
* @return Aikake Information Criteria
*/
public double getAIC() {
return aic;
}
/**
* @return Bayesian Information Criteria
*/
public double getBIC() {
return bic;
}
/**
* @return Root mean square
*/
public double getRMS() {
return rms;
}
/**
* Return a human-readable description of this model.
*
* @return The model description.
*/
abstract public String getDescription();
/**
* Return a human-readable 'kind' string (e.g. "Model", "Polynomial Fit")
* indicating what kind of a model this is.
*
* @return The 'kind' string.
*/
abstract public String getKind();
}