diff --git a/plugin/build.xml b/plugin/build.xml
index 52ef137e9..8a383c53c 100644
--- a/plugin/build.xml
+++ b/plugin/build.xml
@@ -39,8 +39,8 @@
-
-
+
+
@@ -79,11 +79,12 @@
+
-
+
@@ -92,91 +93,93 @@
-
-
+
+
+
+
+
+ -->
+
+
+
+
-
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
+
-
+
-
-
+
+
-
+
-
+
-
+
-
-
-
-
-
+
+
+
+
+
-
-
+
+
-
+
-
-
+
+
Test Summary
============
-
-
-
-
-
-
-
-
-
-
-
-
+
-
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
@@ -187,10 +190,11 @@
+
-
+
-
+
@@ -225,6 +229,7 @@
+
@@ -251,7 +256,7 @@
-
+
@@ -277,6 +282,7 @@
+
@@ -295,7 +301,7 @@
-->
-
+
@@ -307,11 +313,11 @@
-
+
-
-
-
+
+
+
diff --git a/plugin/src/org/aavso/tools/vstar/external/lib/PiecewiseLinearModel.java b/plugin/src/org/aavso/tools/vstar/external/lib/PiecewiseLinearModel.java
new file mode 100644
index 000000000..ab97e6aac
--- /dev/null
+++ b/plugin/src/org/aavso/tools/vstar/external/lib/PiecewiseLinearModel.java
@@ -0,0 +1,317 @@
+/**
+ * VStar: a statistical analysis tool for variable star data.
+ * Copyright (C) 2009 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 .
+ */
+package org.aavso.tools.vstar.external.lib;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.aavso.tools.vstar.data.ValidObservation;
+import org.aavso.tools.vstar.exception.AlgorithmError;
+import org.aavso.tools.vstar.ui.model.plot.ContinuousModelFunction;
+import org.aavso.tools.vstar.ui.model.plot.ICoordSource;
+import org.aavso.tools.vstar.util.AbstractExtremaFinder;
+import org.aavso.tools.vstar.util.locale.LocaleProps;
+import org.aavso.tools.vstar.util.model.AbstractModel;
+import org.aavso.tools.vstar.util.prefs.NumericPrecisionPrefs;
+import org.apache.commons.math.analysis.UnivariateRealFunction;
+import org.apache.commons.math.optimization.GoalType;
+
+/**
+ * This class represents a piecewise linear model.
+ */
+public class PiecewiseLinearModel extends AbstractModel {
+
+ private static final String DESC = "Piecewise linear model";
+
+ private PiecewiseLinearFunction piecewiseFunction;
+
+ public PiecewiseLinearModel(List obs, List meanObs) {
+ super(obs);
+
+ // Create piecewise linear model
+ piecewiseFunction = new PiecewiseLinearFunction(meanObs, timeCoordSource);
+ }
+
+ @Override
+ public void execute() throws AlgorithmError {
+
+ fit = new ArrayList();
+ residuals = new ArrayList();
+
+ String comment = DESC;
+
+ for (int i = 0; i < obs.size() && !interrupted; i++) {
+ ValidObservation ob = obs.get(i);
+
+ double x = timeCoordSource.getXCoord(i, obs);
+ double y = piecewiseFunction.value(x);
+
+ collectObs(y, ob, comment);
+ }
+
+ rootMeanSquare();
+ informationCriteria(piecewiseFunction.numberOfFunctions());
+ fitMetrics();
+
+ PiecewiseLinearFunctionExtremaFinder finder = new PiecewiseLinearFunctionExtremaFinder(fit, piecewiseFunction,
+ timeCoordSource);
+
+ String extremaStr = finder.toString();
+
+ if (extremaStr != null) {
+ String title = LocaleProps.get("MODEL_INFO_EXTREMA_TITLE");
+
+ functionStrMap.put(title, extremaStr);
+ }
+
+ functionStrings();
+ }
+
+ @Override
+ public boolean hasFuncDesc() {
+ return true;
+ }
+
+ public String toVeLaString() {
+ String strRepr = functionStrMap.get(LocaleProps.get("MODEL_INFO_FUNCTION_TITLE"));
+
+ if (strRepr == null) {
+ strRepr = piecewiseFunction.toVeLaString();
+ }
+
+ return strRepr;
+ }
+
+ @Override
+ public String getDescription() {
+ return DESC;
+ }
+
+ @Override
+ public String getKind() {
+ return "Piecewise linear model";
+ }
+
+ @Override
+ public ContinuousModelFunction getModelFunction() {
+ return new ContinuousModelFunction(piecewiseFunction, fit, 0);
+ }
+
+ public static class PiecewiseLinearFunction implements UnivariateRealFunction {
+ private List functions;
+ private LinearFunction currFunc;
+ private int funIndex;
+
+ public PiecewiseLinearFunction(List obs, ICoordSource timeCoordSource) {
+ functions = new ArrayList();
+
+ for (int i = 0; i < obs.size() - 1; i++) {
+ ValidObservation ob1 = obs.get(i);
+ ValidObservation ob2 = obs.get(i + 1);
+ double t1 = timeCoordSource.getXCoord(i, obs);
+ double t2 = timeCoordSource.getXCoord(i + 1, obs);
+ functions.add(new LinearFunction(t1, t2, ob1.getMag(), ob2.getMag()));
+ }
+
+ currFunc = functions.get(0);
+ funIndex = 0;
+ }
+
+ @Override
+ public double value(double t) {
+ if (t > currFunc.endTime() && funIndex < functions.size() - 1) {
+ funIndex++;
+ currFunc = functions.get(funIndex);
+ }
+
+ return currFunc.value(t);
+ }
+
+ /**
+ * @return The list of functions.
+ */
+ public List getFunctions() {
+ return functions;
+ }
+
+ /**
+ * Find the index of the function to which the target time corresponds.
+ *
+ * @param t The target time.
+ * @return The index of the function or -1 if t does not correspond to the time
+ * range of any linear function.
+ */
+ public int seekFunction(double t) {
+ int index = -1;
+
+ for (int i = 0; i < functions.size() - 1; i++) {
+ LinearFunction linearFunc = functions.get(i);
+ if (t >= linearFunc.t1 && t < linearFunc.t2) {
+ index = i;
+ break;
+ }
+ }
+
+ return index;
+ }
+
+ public int numberOfFunctions() {
+ return functions.size();
+ }
+
+ public String toVeLaString() {
+ String strRepr = "";
+
+ strRepr += "f(t:real) : real {\n";
+ strRepr += " when\n";
+ for (int i = 0; i < functions.size(); i++) {
+ LinearFunction function = functions.get(i);
+ boolean first = i == 0;
+ boolean last = i == functions.size() - 1;
+ strRepr += " " + function.toVeLaString(first, last);
+ }
+ strRepr += "\n}";
+
+ return strRepr;
+ }
+ }
+
+ /**
+ * Represents the function for a line segment
+ */
+ public static class LinearFunction implements UnivariateRealFunction {
+
+ private double t1;
+ private double t2;
+ private double mag1;
+ private double mag2;
+ private double m;
+ private double c;
+
+ public LinearFunction(double t1, double t2, double mag1, double mag2) {
+ this.t1 = t1;
+ this.t2 = t2;
+ this.mag1 = mag1;
+ this.mag2 = mag2;
+
+ // y = mx + c
+ m = slope();
+ c = mag1 - m * t1;
+ }
+
+ public double startTime() {
+ return t1;
+ }
+
+ public double endTime() {
+ return t2;
+ }
+
+ public double startMag() {
+ return mag1;
+ }
+
+ public double endMag() {
+ return mag2;
+ }
+
+ public double slope() {
+ return (mag2 - mag1) / (t2 - t1);
+ }
+
+ public double yIntercept() {
+ return c;
+ }
+
+ @Override
+ public double value(double t) {
+ return m * t + c;
+ }
+
+ public String toVeLaString(boolean first, boolean last) {
+ // For the first line segment, we only need to check
+ // the second bound. For the last line segment, we don't
+ // need to check either bound.
+ StringBuffer buf = new StringBuffer();
+
+ if (!first && !last) {
+ buf.append("t >= ");
+ buf.append(NumericPrecisionPrefs.formatTimeLocaleIndependent(t1));
+ buf.append(" and ");
+ }
+
+ if (!last) {
+ buf.append("t < ");
+ buf.append(NumericPrecisionPrefs.formatTimeLocaleIndependent(t2));
+ } else {
+ buf.append("true ");
+ }
+
+ buf.append(" -> ");
+
+ buf.append(NumericPrecisionPrefs.formatOtherLocaleIndependent(m));
+ buf.append("*t + ");
+ buf.append(NumericPrecisionPrefs.formatOtherLocaleIndependent(c));
+
+ if (!last)
+ buf.append("\n");
+
+ return buf.toString();
+ }
+ }
+
+ public static class PiecewiseLinearFunctionExtremaFinder extends AbstractExtremaFinder {
+ PiecewiseLinearFunction plf;
+
+ public PiecewiseLinearFunctionExtremaFinder(List obs, PiecewiseLinearFunction function,
+ ICoordSource timeCoordSource) {
+ super(obs, function, timeCoordSource, 0);
+ plf = function;
+ }
+
+ @Override
+ public void find(GoalType goal, int[] bracketRange) throws AlgorithmError {
+ extremeTime = Double.POSITIVE_INFINITY;
+ extremeMag = Double.POSITIVE_INFINITY;
+
+ double firstJD = obs.get(bracketRange[0]).getJD();
+ double lastJD = obs.get(bracketRange[1]).getJD();
+
+ int firstIndex = plf.seekFunction(firstJD);
+ int lastIndex = plf.seekFunction(lastJD);
+
+ // extrema should be at the meeting point of two linear functions
+ boolean found = false;
+
+ if (lastIndex == firstIndex + 1) {
+ if (goal == GoalType.MINIMIZE && plf.functions.get(firstIndex).slope() < 0
+ && plf.functions.get(lastIndex).slope() > 0) {
+ found = true;
+ } else if (goal == GoalType.MAXIMIZE && plf.functions.get(firstIndex).slope() > 0
+ && plf.functions.get(lastIndex).slope() < 0) {
+ found = true;
+ }
+ }
+
+ if (found) {
+ extremeTime = plf.functions.get(lastIndex).t1;
+ extremeMag = plf.functions.get(lastIndex).value(extremeTime);
+ }
+ }
+ }
+}
diff --git a/plugin/src/org/aavso/tools/vstar/external/plugin/AoVPeriodSearch.java b/plugin/src/org/aavso/tools/vstar/external/plugin/AoVPeriodSearch.java
index 4bfa4ee41..88baff19d 100644
--- a/plugin/src/org/aavso/tools/vstar/external/plugin/AoVPeriodSearch.java
+++ b/plugin/src/org/aavso/tools/vstar/external/plugin/AoVPeriodSearch.java
@@ -33,6 +33,7 @@
import org.aavso.tools.vstar.data.ValidObservation;
import org.aavso.tools.vstar.exception.AlgorithmError;
import org.aavso.tools.vstar.exception.CancellationException;
+import org.aavso.tools.vstar.external.lib.PiecewiseLinearModel;
import org.aavso.tools.vstar.plugin.PluginComponentFactory;
import org.aavso.tools.vstar.plugin.period.PeriodAnalysisComponentFactory;
import org.aavso.tools.vstar.plugin.period.PeriodAnalysisDialogBase;
@@ -50,6 +51,7 @@
import org.aavso.tools.vstar.ui.mediator.message.NewStarMessage;
import org.aavso.tools.vstar.ui.mediator.message.PeriodAnalysisSelectionMessage;
import org.aavso.tools.vstar.ui.model.list.PeriodAnalysisDataTableModel;
+import org.aavso.tools.vstar.ui.model.plot.ObservationAndMeanPlotModel;
import org.aavso.tools.vstar.ui.model.plot.PeriodAnalysis2DPlotModel;
import org.aavso.tools.vstar.ui.model.plot.PhaseTimeElementEntity;
import org.aavso.tools.vstar.util.comparator.StandardPhaseComparator;
@@ -85,564 +87,563 @@
*/
public class AoVPeriodSearch extends PeriodAnalysisPluginBase {
- private final static int MAX_TOP_HITS = 20;
+ private final static int MAX_TOP_HITS = 20;
- private boolean firstInvocation;
- private boolean interrupted;
- private boolean cancelled;
- private boolean legalParams;
+ private boolean firstInvocation;
+ private boolean interrupted;
+ private boolean cancelled;
+ private boolean legalParams;
- private int bins;
- private Double minPeriod, maxPeriod, resolution;
+ private List obs;
- private IPeriodAnalysisAlgorithm algorithm;
+ private int bins;
+ private Double minPeriod, maxPeriod, resolution;
- private PeriodAnalysisCoordinateType F_STATISTIC;
- private PeriodAnalysisCoordinateType P_VALUE;
+ private IPeriodAnalysisAlgorithm algorithm;
- /**
- * Constructor
- */
- public AoVPeriodSearch() {
- super();
- firstInvocation = true;
- reset();
- }
-
- @Override
- public String getDescription() {
- return "AoV period search";
- }
-
- @Override
- public String getDisplayName() {
- return "AoV with Period Range";
- }
-
- /**
- * @see org.aavso.tools.vstar.plugin.IPlugin#getDocName()
- */
- @Override
- public String getDocName() {
- return "AoV Period Analysis Plug-In.pdf";
- }
-
- @Override
- public void executeAlgorithm(List obs)
- throws AlgorithmError, CancellationException {
-
- if (firstInvocation) {
- Mediator.getInstance().getNewStarNotifier()
- .addListener(getNewStarListener());
-
- F_STATISTIC = PeriodAnalysisCoordinateType.create("F-statistic");
- P_VALUE = PeriodAnalysisCoordinateType.create("p-value");
-
- firstInvocation = false;
- }
-
- algorithm = new AoVAlgorithm(obs);
- algorithm.execute();
- }
-
- @Override
- public JDialog getDialog(SeriesType sourceSeriesType) {
- return interrupted || cancelled ? null : new PeriodAnalysisDialog(
- sourceSeriesType);
- }
-
- @SuppressWarnings("serial")
- class PeriodAnalysisDialog extends PeriodAnalysisDialogBase implements
- Listener {
-
- private double period;
- private SeriesType sourceSeriesType;
- private IPeriodAnalysisDatum selectedDataPoint;
-
- private PeriodAnalysisDataTablePane resultsTablePane;
- private PeriodAnalysisTopHitsTablePane topHitsTablePane;
- private PeriodAnalysis2DChartPane plotPane;
- private PeriodAnalysis2DChartPane topHitsPlotPane;
-
- public PeriodAnalysisDialog(SeriesType sourceSeriesType) {
- super("AoV", false, true, false);
-
- this.sourceSeriesType = sourceSeriesType;
-
- prepareDialog();
-
- this.setNewPhasePlotButtonState(false);
-
- startup(); // Note: why does base class not call this in
- // prepareDialog()?
- }
-
- @Override
- protected Component createContent() {
- String title = "AoV Periodogram";
-
- PeriodAnalysis2DPlotModel dataPlotModel = new PeriodAnalysis2DPlotModel(
- algorithm.getResultSeries(),
- PeriodAnalysisCoordinateType.PERIOD, F_STATISTIC, false);
-
- plotPane = PeriodAnalysisComponentFactory.createLinePlot(title,
- sourceSeriesType.getDescription(), dataPlotModel, false);
-
- PeriodAnalysis2DPlotModel topHitsPlotModel = new PeriodAnalysis2DPlotModel(
- algorithm.getTopHits(),
- PeriodAnalysisCoordinateType.PERIOD, F_STATISTIC, false);
-
- topHitsPlotPane = PeriodAnalysisComponentFactory.createScatterPlot(
- title, sourceSeriesType.getDescription(), topHitsPlotModel,
- false);
-
- // Add the above line plot's model to the scatter plot.
- // Render the scatter plot last so the "handles" will be
- // the first items selected by the mouse.
- JFreeChart chart = topHitsPlotPane.getChart();
- chart.getXYPlot().setDataset(PeriodAnalysis2DChartPane.DATA_SERIES,
- dataPlotModel);
- chart.getXYPlot().setDataset(
- PeriodAnalysis2DChartPane.TOP_HIT_SERIES, topHitsPlotModel);
- chart.getXYPlot().setRenderer(
- PeriodAnalysis2DChartPane.DATA_SERIES,
- plotPane.getChart().getXYPlot().getRenderer());
- chart.getXYPlot().setDatasetRenderingOrder(
- DatasetRenderingOrder.REVERSE);
-
- plotPane = topHitsPlotPane;
-
- // Full results table
- PeriodAnalysisCoordinateType[] columns = {
- PeriodAnalysisCoordinateType.FREQUENCY,
- PeriodAnalysisCoordinateType.PERIOD, F_STATISTIC, P_VALUE };
-
- PeriodAnalysisDataTableModel dataTableModel = new PeriodAnalysisDataTableModel(
- columns, algorithm.getResultSeries());
- resultsTablePane = new NoModelPeriodAnalysisDataTablePane(
- dataTableModel, algorithm);
-
- PeriodAnalysisDataTableModel topHitsModel = new PeriodAnalysisDataTableModel(
- columns, algorithm.getTopHits());
- topHitsTablePane = new NoModelPeriodAnalysisTopHitsTablePane(
- topHitsModel, dataTableModel, algorithm);
-
- // Return tabbed pane of plot and period display component.
- return PluginComponentFactory.createTabs(new NamedComponent(
- "Periodogram", plotPane), new NamedComponent("Results",
- resultsTablePane), new NamedComponent("Top Hits",
- topHitsTablePane));
- }
-
- // Send a period change message when the new-phase-plot button is
- // clicked.
- @Override
- protected void newPhasePlotButtonAction() {
- sendPeriodChangeMessage(period);
- }
-
- @Override
- public void startup() {
- Mediator.getInstance().getPeriodAnalysisSelectionNotifier()
- .addListener(this);
-
- resultsTablePane.startup();
- topHitsTablePane.startup();
- plotPane.startup();
- }
-
- @Override
- public void cleanup() {
- Mediator.getInstance().getPeriodAnalysisSelectionNotifier()
- .removeListenerIfWilling(this);
-
- resultsTablePane.cleanup();
- topHitsTablePane.cleanup();
- plotPane.cleanup();
- }
-
- // Next two methods are for Listener
-
- @Override
- public boolean canBeRemoved() {
- return false;
- }
-
- @Override
- public void update(PeriodAnalysisSelectionMessage info) {
- period = info.getDataPoint().getPeriod();
- selectedDataPoint = info.getDataPoint();
- setNewPhasePlotButtonState(true);
- }
-
- // ** No model result and top-hit panes **
-
- class NoModelPeriodAnalysisDataTablePane extends
- PeriodAnalysisDataTablePane {
-
- public NoModelPeriodAnalysisDataTablePane(
- PeriodAnalysisDataTableModel model,
- IPeriodAnalysisAlgorithm algorithm) {
- super(model, algorithm);
- }
-
- @Override
- protected JPanel createButtonPanel() {
- return new JPanel();
- }
-
- @Override
- protected void enableButtons() {
- // Do nothing
- }
- }
-
- class NoModelPeriodAnalysisTopHitsTablePane extends
- PeriodAnalysisTopHitsTablePane {
-
- public NoModelPeriodAnalysisTopHitsTablePane(
- PeriodAnalysisDataTableModel topHitsModel,
- PeriodAnalysisDataTableModel fullDataModel,
- IPeriodAnalysisAlgorithm algorithm) {
- super(topHitsModel, fullDataModel, algorithm);
- }
-
- @Override
- protected JPanel createButtonPanel() {
- return new JPanel();
- }
-
- @Override
- protected void enableButtons() {
- // Do nothing
- }
- }
-
- @Override
- protected void findHarmonicsButtonAction() {
- // Do nothing since we don't include a find-harmonics button for
- // AoV.
- }
- }
-
- // The AoV algorithm implementation.
- class AoVAlgorithm implements IPeriodAnalysisAlgorithm {
-
- private List obs;
-
- private List frequencies;
- private ArrayList orderedFrequencies;
-
- private List periods;
- private ArrayList orderedPeriods;
-
- private List fValues;
- private ArrayList orderedFValues;
-
- private List pValues;
- private ArrayList orderedPValues;
-
- // private double smallestFValue;
- // private int smallestValueIndex;
-
- public AoVAlgorithm(List obs) {
- this.obs = obs;
-
- frequencies = new ArrayList();
- orderedFrequencies = new ArrayList();
-
- periods = new ArrayList();
- orderedPeriods = new ArrayList();
-
- fValues = new ArrayList();
- orderedFValues = new ArrayList();
-
- pValues = new ArrayList();
- orderedPValues = new ArrayList();
-
- // smallestFValue = Double.MAX_VALUE;
- // smallestValueIndex = 0;
- }
-
- @Override
- public String getRefineByFrequencyName() {
- return "None";
- }
-
- @Override
- public Map> getResultSeries() {
- Map> results = new LinkedHashMap>();
-
- results.put(PeriodAnalysisCoordinateType.FREQUENCY, frequencies);
- results.put(PeriodAnalysisCoordinateType.PERIOD, periods);
- results.put(F_STATISTIC, fValues);
- results.put(P_VALUE, pValues);
-
- return results;
- }
-
- @Override
- public Map> getTopHits() {
- // TODO: create top hits by sorting doubles in descending order
- // pairs of doubles;
- // limit to MAX_TOP_HITS = 100
-
- Map> topHits = new LinkedHashMap>();
-
- topHits.put(PeriodAnalysisCoordinateType.FREQUENCY,
- orderedFrequencies);
- topHits.put(PeriodAnalysisCoordinateType.PERIOD, orderedPeriods);
- topHits.put(F_STATISTIC, orderedFValues);
- topHits.put(P_VALUE, orderedPValues);
-
- return topHits;
- }
-
- @Override
- public void multiPeriodicFit(List harmonics,
- PeriodAnalysisDerivedMultiPeriodicModel model)
- throws AlgorithmError {
- }
-
- @Override
- public List refineByFrequency(
- List freqs, List variablePeriods,
- List lockedPeriod) throws AlgorithmError {
- return null;
- }
-
- @Override
- public void execute() throws AlgorithmError {
- // Request parameters
- // TODO: move this to top-level execute method and just pass actual
- // parameters to this class?
- while (!areParametersLegal(obs) && !cancelled)
- ;
-
- if (!cancelled) {
- // Duplicate the obs (just JD and mag) so we can set phases
- // without disturbing the original observation object.
- List phObs = new ArrayList();
-
- // TODO: cache these by JD range between new star resets...
-
- interrupted = false;
-
- // TODO: for a multi-threaded range-subset approach, we would
- // need to do this once for each thread
- for (ValidObservation ob : obs) {
- if (interrupted)
- break;
-
- ValidObservation phOb = new ValidObservation();
-
- double jd = ob.getDateInfo().getJulianDay();
- phOb.setDateInfo(new DateInfo(jd));
-
- Magnitude mag = new Magnitude(ob.getMagnitude()
- .getMagValue(), ob.getMagnitude().getUncertainty());
- phOb.setMagnitude(mag);
-
- phObs.add(phOb);
- }
-
- // Choose an epoch value.
- double epoch = PhaseCalcs.epochStrategyMap.get("alpha")
- .determineEpoch(phObs);
-
- // Iterate over the periods in the range at the specified
- // resolution.
-
- // TODO: multi-core approach => iterate over a subset of the
- // period range but over all observations, where the full set
- // is copied for each core (set phases, sort mutate obs and
- // list...); top-hits will have to be combined and ordered once
- // at end as part of or before prune operation; instead, could
- // just iterate over a subset of observations; this would only
- // give a large speedup if many observations; such a for-loop
- // unrolling would be simpler and less memory intensive though;
- // may be worth trying first
-
- for (double period = minPeriod; period <= maxPeriod; period += resolution) {
- if (interrupted)
- break;
-
- PhaseCalcs.setPhases(phObs, epoch, period);
-
- Collections.sort(phObs, StandardPhaseComparator.instance);
-
- // Note: 1 / bins = 1 cycle divided into N bins
- BinningResult binningResult = DescStats
- .createSymmetricBinnedObservations(phObs,
- PhaseTimeElementEntity.instance, 1.0 / bins);
-
- // Collect results
- // PMAK, Issue #152:
- // Use fixInf() to prevent
- // 'java.lang.IllegalArgumentException: Must be finite'
- // error in AoV chart when period = 0
- frequencies.add(fixInf(1.0 / period));
- periods.add(period);
- fValues.add(fixInf(binningResult.getFValue()));
- pValues.add(fixInf(binningResult.getPValue()));
-
- updateOrderedValues();
- }
-
- pruneTopHits();
- }
- }
-
- // replace +-Infinity by NaN
- private double fixInf(double v) {
- if (Double.isInfinite(v))
- return Double.NaN;
- else
- return v;
- }
-
- private void updateOrderedValues() {
- if (orderedFrequencies.isEmpty()) {
- orderedFrequencies.add(frequencies.get(0));
- orderedPeriods.add(periods.get(0));
- orderedFValues.add(fValues.get(0));
- orderedPValues.add(pValues.get(0));
- } else {
- int i = periods.size() - 1;
-
- double frequency = frequencies.get(i);
- double period = periods.get(i);
- double fValue = fValues.get(i);
- double pValue = pValues.get(i);
-
- // Starting from highest fValue, find index to insert value
- // and...
- int index = 0;
- for (int j = 0; j < orderedFValues.size(); j++) {
- if (fValue < orderedFValues.get(j)) {
- // Insertion index is one after the matched element's
- // index since the list's elements are in descending
- // order.
- index++;
- }
- }
-
- // ...apply to all ordered collections.
- if (index >= 0) {
- orderedFrequencies.add(index, frequency);
- orderedPeriods.add(index, period);
- orderedFValues.add(index, fValue);
- orderedPValues.add(index, pValue);
- } else {
- orderedFrequencies.add(0, frequency);
- orderedPeriods.add(0, period);
- orderedFValues.add(0, fValue);
- orderedPValues.add(0, pValue);
- }
- }
- }
-
- private void pruneTopHits() {
- if (periods.size() > MAX_TOP_HITS) {
- orderedFrequencies = new ArrayList(
- orderedFrequencies.subList(0, MAX_TOP_HITS));
-
- orderedPeriods = new ArrayList(orderedPeriods.subList(
- 0, MAX_TOP_HITS));
-
- orderedFValues = new ArrayList(orderedFValues.subList(
- 0, MAX_TOP_HITS));
-
- orderedPValues = new ArrayList(orderedPValues.subList(
- 0, MAX_TOP_HITS));
- }
- }
-
- @Override
- public void interrupt() {
- interrupted = true;
- }
- }
-
- // Ask user for period min, max, resolution and number of bins.
- private boolean areParametersLegal(List obs) {
- legalParams = true;
-
- List> fields = new ArrayList>();
-
- // / double days = obs.get(obs.size() - 1).getJD() - obs.get(0).getJD();
- DoubleField minPeriodField = new DoubleField("Minimum Period", 0.0,
- null, minPeriod);
- fields.add(minPeriodField);
-
- DoubleField maxPeriodField = new DoubleField("Maximum Period", 0.0,
- null, maxPeriod);
- fields.add(maxPeriodField);
-
- DoubleField resolutionField = new DoubleField("Resolution", 0.0, 1.0,
- resolution);
- fields.add(resolutionField);
-
- IntegerField binsField = new IntegerField("Bins", 0, 50, bins);
- fields.add(binsField);
-
- MultiEntryComponentDialog dlg = new MultiEntryComponentDialog(
- "AoV Parameters", fields);
-
- cancelled = dlg.isCancelled();
-
- if (!cancelled) {
-
- try {
- bins = binsField.getValue();
- if (bins <= 0) {
- MessageBox.showErrorDialog("AoV Parameters",
- "Number of bins must be greater than zero");
- legalParams = false;
- }
- } catch (Exception e) {
- legalParams = false;
- }
-
- minPeriod = minPeriodField.getValue();
- maxPeriod = maxPeriodField.getValue();
- resolution = resolutionField.getValue();
-
- if (minPeriod >= maxPeriod) {
- MessageBox
- .showErrorDialog("AoV Parameters",
- "Minimum period must be less than or equal to maximum period");
- legalParams = false;
- }
-
- if (resolution <= 0.0) {
- MessageBox.showErrorDialog("AoV Parameters",
- "Resolution must be between 0 and 1");
- legalParams = false;
- }
- }
-
- return legalParams;
- }
-
- @Override
- public void interrupt() {
- interrupted = true;
- }
-
- @Override
- protected void newStarAction(NewStarMessage message) {
- reset();
- }
-
- @Override
- public void reset() {
- cancelled = false;
- legalParams = false;
- interrupted = false;
- minPeriod = 0.0;
- maxPeriod = 0.0;
- resolution = 0.1;
- bins = 10;
- }
+ private PeriodAnalysisCoordinateType F_STATISTIC;
+ private PeriodAnalysisCoordinateType P_VALUE;
+
+ /**
+ * Constructor
+ */
+ public AoVPeriodSearch() {
+ super();
+ firstInvocation = true;
+ reset();
+ }
+
+ @Override
+ public String getDescription() {
+ return "AoV period search";
+ }
+
+ @Override
+ public String getDisplayName() {
+ return "AoV with Period Range";
+ }
+
+ /**
+ * @see org.aavso.tools.vstar.plugin.IPlugin#getDocName()
+ */
+ @Override
+ public String getDocName() {
+ return "AoV Period Analysis Plug-In.pdf";
+ }
+
+ @Override
+ public void executeAlgorithm(List obs) throws AlgorithmError, CancellationException {
+
+ this.obs = obs;
+
+ if (firstInvocation) {
+ Mediator.getInstance().getNewStarNotifier().addListener(getNewStarListener());
+
+ F_STATISTIC = PeriodAnalysisCoordinateType.create("F-statistic");
+ P_VALUE = PeriodAnalysisCoordinateType.create("p-value");
+
+ firstInvocation = false;
+ }
+
+ algorithm = new AoVAlgorithm(obs);
+ algorithm.execute();
+ }
+
+ @Override
+ public JDialog getDialog(SeriesType sourceSeriesType) {
+ return interrupted || cancelled ? null : new PeriodAnalysisDialog(sourceSeriesType);
+ }
+
+ @SuppressWarnings("serial")
+ class PeriodAnalysisDialog extends PeriodAnalysisDialogBase implements Listener {
+
+ private double period;
+ private SeriesType sourceSeriesType;
+ private IPeriodAnalysisDatum selectedDataPoint;
+
+ private PeriodAnalysisDataTablePane resultsTablePane;
+ private AoVPeriodAnalysisTopHitsTablePane topHitsTablePane;
+ private PeriodAnalysis2DChartPane plotPane;
+ private PeriodAnalysis2DChartPane topHitsPlotPane;
+
+ public PeriodAnalysisDialog(SeriesType sourceSeriesType) {
+ super("AoV", false, true, false);
+
+ this.sourceSeriesType = sourceSeriesType;
+
+ prepareDialog();
+
+ this.setNewPhasePlotButtonState(false);
+
+ startup(); // Note: why does base class not call this in
+ // prepareDialog()?
+ }
+
+ @Override
+ protected Component createContent() {
+ String title = "AoV Periodogram";
+
+ PeriodAnalysis2DPlotModel dataPlotModel = new PeriodAnalysis2DPlotModel(algorithm.getResultSeries(),
+ PeriodAnalysisCoordinateType.PERIOD, F_STATISTIC, false);
+
+ plotPane = PeriodAnalysisComponentFactory.createLinePlot(title, sourceSeriesType.getDescription(),
+ dataPlotModel, false);
+
+ PeriodAnalysis2DPlotModel topHitsPlotModel = new PeriodAnalysis2DPlotModel(algorithm.getTopHits(),
+ PeriodAnalysisCoordinateType.PERIOD, F_STATISTIC, false);
+
+ topHitsPlotPane = PeriodAnalysisComponentFactory.createScatterPlot(title, sourceSeriesType.getDescription(),
+ topHitsPlotModel, false);
+
+ // Add the above line plot's model to the scatter plot.
+ // Render the scatter plot last so the "handles" will be
+ // the first items selected by the mouse.
+ JFreeChart chart = topHitsPlotPane.getChart();
+ chart.getXYPlot().setDataset(PeriodAnalysis2DChartPane.DATA_SERIES, dataPlotModel);
+ chart.getXYPlot().setDataset(PeriodAnalysis2DChartPane.TOP_HIT_SERIES, topHitsPlotModel);
+ chart.getXYPlot().setRenderer(PeriodAnalysis2DChartPane.DATA_SERIES,
+ plotPane.getChart().getXYPlot().getRenderer());
+ chart.getXYPlot().setDatasetRenderingOrder(DatasetRenderingOrder.REVERSE);
+
+ plotPane = topHitsPlotPane;
+
+ // Full results table
+ PeriodAnalysisCoordinateType[] columns = { PeriodAnalysisCoordinateType.FREQUENCY,
+ PeriodAnalysisCoordinateType.PERIOD, F_STATISTIC, P_VALUE };
+
+ PeriodAnalysisDataTableModel dataTableModel = new PeriodAnalysisDataTableModel(columns,
+ algorithm.getResultSeries());
+ resultsTablePane = new NoModelPeriodAnalysisDataTablePane(dataTableModel, algorithm);
+
+ PeriodAnalysisDataTableModel topHitsModel = new PeriodAnalysisDataTableModel(columns,
+ algorithm.getTopHits());
+ topHitsTablePane = new AoVPeriodAnalysisTopHitsTablePane(obs, topHitsModel, dataTableModel, algorithm);
+
+ // Return tabbed pane of plot and table components.
+ return PluginComponentFactory.createTabs(new NamedComponent("Periodogram", plotPane),
+ new NamedComponent("Results", resultsTablePane), new NamedComponent("Top Hits", topHitsTablePane));
+ }
+
+ // Send a period change message when the new-phase-plot button is
+ // clicked and enable the model button so a model can be created.
+ @Override
+ protected void newPhasePlotButtonAction() {
+ sendPeriodChangeMessage(period);
+ topHitsTablePane.setModelButtonState(true);
+ }
+
+ // TODO: need the next two or use base class versipns?
+
+ @Override
+ public void startup() {
+ Mediator.getInstance().getPeriodAnalysisSelectionNotifier().addListener(this);
+
+ resultsTablePane.startup();
+ topHitsTablePane.startup();
+ plotPane.startup();
+ }
+
+ @Override
+ public void cleanup() {
+ Mediator.getInstance().getPeriodAnalysisSelectionNotifier().removeListenerIfWilling(this);
+
+ resultsTablePane.cleanup();
+ topHitsTablePane.cleanup();
+ plotPane.cleanup();
+ }
+
+ // Next two methods are for Listener
+
+ @Override
+ public boolean canBeRemoved() {
+ return false;
+ }
+
+ @Override
+ public void update(PeriodAnalysisSelectionMessage info) {
+ period = info.getDataPoint().getPeriod();
+ selectedDataPoint = info.getDataPoint();
+ setNewPhasePlotButtonState(true);
+ }
+
+ // ** No model result and top-hit panes **
+
+ class NoModelPeriodAnalysisDataTablePane extends PeriodAnalysisDataTablePane {
+
+ public NoModelPeriodAnalysisDataTablePane(PeriodAnalysisDataTableModel model,
+ IPeriodAnalysisAlgorithm algorithm) {
+ super(model, algorithm);
+ }
+
+ @Override
+ protected JPanel createButtonPanel() {
+ return new JPanel();
+ }
+
+ @Override
+ protected void enableButtons() {
+ // Do nothing
+ }
+ }
+
+ class AoVPeriodAnalysisTopHitsTablePane extends PeriodAnalysisTopHitsTablePane {
+
+ private List obs;
+
+ public AoVPeriodAnalysisTopHitsTablePane(List obs,
+ PeriodAnalysisDataTableModel topHitsModel, PeriodAnalysisDataTableModel fullDataModel,
+ IPeriodAnalysisAlgorithm algorithm) {
+ super(topHitsModel, fullDataModel, algorithm);
+ this.obs = obs;
+ setModelButtonState(false);
+ }
+
+ @Override
+ protected void enableButtons() {
+ // Override base class to not enable model button.
+ // The intent is to allow the dialog to which an instance
+ // of this class will exist to control whether the model
+ // button is enabled.
+ }
+
+ public void setModelButtonState(boolean state) {
+ modelButton.setEnabled(state);
+ }
+
+ @Override
+ protected void modelAction(List dataPoints) {
+ final JPanel parent = this;
+
+ try {
+ // This will only be invoked when a phase plot has been created (see
+ // setModelButtonState()).
+ Mediator mediator = Mediator.getInstance();
+ ObservationAndMeanPlotModel plotModel = mediator
+ .getObservationPlotModel(mediator.getAnalysisType());
+ PiecewiseLinearModel model = new PiecewiseLinearModel(obs, plotModel.getMeanObsList());
+ mediator.performModellingOperation(model);
+ } catch (Exception ex) {
+ MessageBox.showErrorDialog(parent, "Modelling", ex.getLocalizedMessage());
+ }
+ }
+ }
+
+ @Override
+ protected void findHarmonicsButtonAction() {
+ // Do nothing since we don't include a find-harmonics button for
+ // AoV.
+ }
+ }
+
+ // The AoV algorithm implementation.
+ class AoVAlgorithm implements IPeriodAnalysisAlgorithm {
+
+ private List obs;
+
+ private List frequencies;
+ private ArrayList orderedFrequencies;
+
+ private List periods;
+ private ArrayList orderedPeriods;
+
+ private List fValues;
+ private ArrayList orderedFValues;
+
+ private List pValues;
+ private ArrayList orderedPValues;
+
+ // private double smallestFValue;
+ // private int smallestValueIndex;
+
+ public AoVAlgorithm(List obs) {
+ this.obs = obs;
+
+ frequencies = new ArrayList();
+ orderedFrequencies = new ArrayList();
+
+ periods = new ArrayList();
+ orderedPeriods = new ArrayList();
+
+ fValues = new ArrayList();
+ orderedFValues = new ArrayList();
+
+ pValues = new ArrayList();
+ orderedPValues = new ArrayList();
+
+ // smallestFValue = Double.MAX_VALUE;
+ // smallestValueIndex = 0;
+ }
+
+ @Override
+ public String getRefineByFrequencyName() {
+ return null;
+ }
+
+ @Override
+ public Map> getResultSeries() {
+ Map> results = new LinkedHashMap>();
+
+ results.put(PeriodAnalysisCoordinateType.FREQUENCY, frequencies);
+ results.put(PeriodAnalysisCoordinateType.PERIOD, periods);
+ results.put(F_STATISTIC, fValues);
+ results.put(P_VALUE, pValues);
+
+ return results;
+ }
+
+ @Override
+ public Map> getTopHits() {
+ // TODO: create top hits by sorting doubles in descending order
+ // pairs of doubles;
+ // limit to MAX_TOP_HITS = 100
+
+ Map> topHits = new LinkedHashMap>();
+
+ topHits.put(PeriodAnalysisCoordinateType.FREQUENCY, orderedFrequencies);
+ topHits.put(PeriodAnalysisCoordinateType.PERIOD, orderedPeriods);
+ topHits.put(F_STATISTIC, orderedFValues);
+ topHits.put(P_VALUE, orderedPValues);
+
+ return topHits;
+ }
+
+ @Override
+ public void multiPeriodicFit(List harmonics, PeriodAnalysisDerivedMultiPeriodicModel model)
+ throws AlgorithmError {
+ // TODO: msg box: unsupported
+ }
+
+ @Override
+ public List refineByFrequency(List freqs, List variablePeriods,
+ List lockedPeriod) throws AlgorithmError {
+ return null;
+ }
+
+ @Override
+ public void execute() throws AlgorithmError {
+ // Request parameters
+ // TODO: move this to top-level execute method and just pass actual
+ // parameters to this class?
+ while (!areParametersLegal(obs) && !cancelled)
+ ;
+
+ if (!cancelled) {
+ // TODO: cache these by JD range between new star resets...
+
+ interrupted = false;
+
+ // Duplicate the obs (just JD and mag) so we can set phases
+ // without disturbing the original observation object.
+ // TODO: for a multi-threaded range-subset approach, we would
+ // need to do this once for each thread
+ List phObs = copyObs(obs);
+
+ // Choose an epoch value.
+ double epoch = PhaseCalcs.epochStrategyMap.get("alpha").determineEpoch(phObs);
+
+ // Iterate over the periods in the range at the specified
+ // resolution.
+
+ // TODO: multi-core approach => iterate over a subset of the
+ // period range but over all observations, where the full set
+ // is copied for each core (set phases, sort mutate obs and
+ // list...); top-hits will have to be combined and ordered once
+ // at end as part of or before prune operation; instead, could
+ // just iterate over a subset of observations; this would only
+ // give a large speedup if many observations; such a for-loop
+ // unrolling would be simpler and less memory intensive though;
+ // may be worth trying first
+
+ for (double period = minPeriod; period <= maxPeriod; period += resolution) {
+ if (interrupted)
+ break;
+
+ PhaseCalcs.setPhases(phObs, epoch, period);
+
+ Collections.sort(phObs, StandardPhaseComparator.instance);
+
+ // Note: 1 / bins = 1 cycle divided into N bins
+ BinningResult binningResult = DescStats.createSymmetricBinnedObservations(phObs,
+ PhaseTimeElementEntity.instance, 1.0 / bins);
+
+ // Collect results
+ // PMAK, Issue #152:
+ // Use fixInf() to prevent
+ // 'java.lang.IllegalArgumentException: Must be finite'
+ // error in AoV chart when period = 0
+ frequencies.add(fixInf(1.0 / period));
+ periods.add(period);
+ fValues.add(fixInf(binningResult.getFValue()));
+ pValues.add(fixInf(binningResult.getPValue()));
+
+ updateOrderedValues();
+ }
+
+ pruneTopHits();
+ }
+ }
+
+ // replace +-Infinity by NaN
+ private double fixInf(double v) {
+ if (Double.isInfinite(v))
+ return Double.NaN;
+ else
+ return v;
+ }
+
+ private void updateOrderedValues() {
+ if (orderedFrequencies.isEmpty()) {
+ orderedFrequencies.add(frequencies.get(0));
+ orderedPeriods.add(periods.get(0));
+ orderedFValues.add(fValues.get(0));
+ orderedPValues.add(pValues.get(0));
+ } else {
+ int i = periods.size() - 1;
+
+ double frequency = frequencies.get(i);
+ double period = periods.get(i);
+ double fValue = fValues.get(i);
+ double pValue = pValues.get(i);
+
+ // Starting from highest fValue, find index to insert value
+ // and...
+ int index = 0;
+ for (int j = 0; j < orderedFValues.size(); j++) {
+ if (fValue < orderedFValues.get(j)) {
+ // Insertion index is one after the matched element's
+ // index since the list's elements are in descending
+ // order.
+ index++;
+ }
+ }
+
+ // ...apply to all ordered collections.
+ if (index >= 0) {
+ orderedFrequencies.add(index, frequency);
+ orderedPeriods.add(index, period);
+ orderedFValues.add(index, fValue);
+ orderedPValues.add(index, pValue);
+ } else {
+ orderedFrequencies.add(0, frequency);
+ orderedPeriods.add(0, period);
+ orderedFValues.add(0, fValue);
+ orderedPValues.add(0, pValue);
+ }
+ }
+ }
+
+ private void pruneTopHits() {
+ if (periods.size() > MAX_TOP_HITS) {
+ orderedFrequencies = new ArrayList(orderedFrequencies.subList(0, MAX_TOP_HITS));
+
+ orderedPeriods = new ArrayList(orderedPeriods.subList(0, MAX_TOP_HITS));
+
+ orderedFValues = new ArrayList(orderedFValues.subList(0, MAX_TOP_HITS));
+
+ orderedPValues = new ArrayList(orderedPValues.subList(0, MAX_TOP_HITS));
+ }
+ }
+
+ @Override
+ public void interrupt() {
+ interrupted = true;
+ }
+ }
+
+ // Return a copy of the specified observation list
+ private List copyObs(List obs) {
+ List copiedObs = new ArrayList();
+
+ for (ValidObservation ob : obs) {
+ if (interrupted)
+ break;
+
+ ValidObservation copiedOb = new ValidObservation();
+
+ double jd = ob.getDateInfo().getJulianDay();
+ copiedOb.setDateInfo(new DateInfo(jd));
+
+ Magnitude mag = new Magnitude(ob.getMagnitude().getMagValue(), ob.getMagnitude().getUncertainty());
+ copiedOb.setMagnitude(mag);
+
+ copiedObs.add(copiedOb);
+ }
+
+ return copiedObs;
+ }
+
+ // Ask user for period min, max, resolution and number of bins.
+ private boolean areParametersLegal(List obs) {
+ legalParams = true;
+
+ List> fields = new ArrayList>();
+
+ // / double days = obs.get(obs.size() - 1).getJD() - obs.get(0).getJD();
+ DoubleField minPeriodField = new DoubleField("Minimum Period", 0.0, null, minPeriod);
+ fields.add(minPeriodField);
+
+ DoubleField maxPeriodField = new DoubleField("Maximum Period", 0.0, null, maxPeriod);
+ fields.add(maxPeriodField);
+
+ DoubleField resolutionField = new DoubleField("Resolution", 0.0, 1.0, resolution);
+ fields.add(resolutionField);
+
+ IntegerField binsField = new IntegerField("Bins", 0, 50, bins);
+ fields.add(binsField);
+
+ MultiEntryComponentDialog dlg = new MultiEntryComponentDialog("AoV Parameters", fields);
+
+ cancelled = dlg.isCancelled();
+
+ if (!cancelled) {
+
+ try {
+ bins = binsField.getValue();
+ if (bins <= 0) {
+ MessageBox.showErrorDialog("AoV Parameters", "Number of bins must be greater than zero");
+ legalParams = false;
+ }
+ } catch (Exception e) {
+ legalParams = false;
+ }
+
+ minPeriod = minPeriodField.getValue();
+ maxPeriod = maxPeriodField.getValue();
+ resolution = resolutionField.getValue();
+
+ if (minPeriod >= maxPeriod) {
+ MessageBox.showErrorDialog("AoV Parameters",
+ "Minimum period must be less than or equal to maximum period");
+ legalParams = false;
+ }
+
+ if (resolution <= 0.0) {
+ MessageBox.showErrorDialog("AoV Parameters", "Resolution must be between 0 and 1");
+ legalParams = false;
+ }
+ }
+
+ return legalParams;
+ }
+
+ @Override
+ public void interrupt() {
+ interrupted = true;
+ }
+
+ @Override
+ protected void newStarAction(NewStarMessage message) {
+ reset();
+ }
+
+ @Override
+ public void reset() {
+ cancelled = false;
+ legalParams = false;
+ interrupted = false;
+ minPeriod = 0.0;
+ maxPeriod = 0.0;
+ resolution = 0.1;
+ bins = 10;
+ }
}
diff --git a/plugin/src/org/aavso/tools/vstar/external/plugin/ApacheCommonsLoessFitter.java b/plugin/src/org/aavso/tools/vstar/external/plugin/ApacheCommonsLoessFitter.java
index ce576cf50..8d2c14994 100644
--- a/plugin/src/org/aavso/tools/vstar/external/plugin/ApacheCommonsLoessFitter.java
+++ b/plugin/src/org/aavso/tools/vstar/external/plugin/ApacheCommonsLoessFitter.java
@@ -18,7 +18,6 @@
package org.aavso.tools.vstar.external.plugin;
import java.util.ArrayList;
-import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
@@ -35,9 +34,7 @@
import org.aavso.tools.vstar.ui.model.plot.JDCoordSource;
import org.aavso.tools.vstar.ui.model.plot.StandardPhaseCoordSource;
import org.aavso.tools.vstar.util.locale.LocaleProps;
-import org.aavso.tools.vstar.util.model.IModel;
-import org.aavso.tools.vstar.util.model.PeriodFitParameters;
-import org.aavso.tools.vstar.util.prefs.NumericPrecisionPrefs;
+import org.aavso.tools.vstar.util.model.AbstractModel;
import org.apache.commons.math.MathException;
import org.apache.commons.math.analysis.interpolation.LoessInterpolator;
import org.apache.commons.math.analysis.polynomials.PolynomialFunction;
@@ -52,311 +49,252 @@
*/
public class ApacheCommonsLoessFitter extends ModelCreatorPluginBase {
- public ApacheCommonsLoessFitter() {
- super();
- }
-
- @Override
- public String getDescription() {
- return "Loess Fit";
- }
-
- @Override
- public String getDisplayName() {
- return getDescription();
- }
-
- /**
- * @see org.aavso.tools.vstar.plugin.IPlugin#getDocName()
- */
- @Override
- public String getDocName() {
- return "ApacheCommonsLoessFilter.pdf";
- }
-
- @Override
- public IModel getModel(List obs) {
- LoessFitCreator fitCreator = new LoessFitCreator(obs);
- return fitCreator.createModel();
- }
-
- class LoessFitCreator {
- private List obs;
-
- LoessFitCreator(List obs) {
- this.obs = obs;
- }
-
- // Create a model representing a polynomial fit of the requested degree.
- IModel createModel() {
-
- // TODO: create a dialog to permit entry of params for other
- // forms of ctor (Loess algorithm variants).
-
- return new IModel() {
- boolean interrupted = false;
- List fit;
- List residuals;
- PolynomialSplineFunction function;
- Map functionStrMap = new LinkedHashMap();
- double aic = Double.NaN;
- double bic = Double.NaN;
-
- // TODO: why am I not using this in model creation method!? (as
- // I do for polyfit)
- // final double zeroPoint = DescStats.calcTimeElementMean(obs,
- // JDTimeElementEntity.instance);
-
- @Override
- public String getDescription() {
- return getKind() + " for " + obs.get(0).getBand()
- + " series";
- }
-
- @Override
- public List getFit() {
- return fit;
- }
-
- @Override
- public String getKind() {
- return "Loess Fit";
- }
-
- @Override
- public List getParameters() {
- // None for a Loess fit.
- return null;
- }
-
- @Override
- public List getResiduals() {
- return residuals;
- }
-
- @Override
- public boolean hasFuncDesc() {
- return true;
- }
-
- public String toString() {
- String strRepr = functionStrMap.get(LocaleProps
- .get("MODEL_INFO_FUNCTION_TITLE"));
-
- if (strRepr == null) {
- /*
- strRepr = "f(t:real) : real {\n";
-
- double constCoeff = 0;
-
- for (PolynomialFunction f : function.getPolynomials()) {
- double[] coeffs = f.getCoefficients();
- for (int i = coeffs.length - 1; i >= 1; i--) {
- strRepr += " " + NumericPrecisionPrefs.formatPolyCoef(coeffs[i]);
- strRepr += "*t^" + i + "+\n";
- }
- constCoeff += coeffs[0];
- }
- strRepr += " " + NumericPrecisionPrefs.formatPolyCoef(constCoeff);
- strRepr += "\n}";
- */
- strRepr = Mediator.NOT_IMPLEMENTED_YET;
- }
-
- return strRepr;
- }
-
- public String toExcelString() {
- String strRepr = functionStrMap.get(LocaleProps
- .get("MODEL_INFO_EXCEL_TITLE"));
-
- if (strRepr == null) {
- /*
- strRepr = "=SUM(";
-
- double constCoeff = 0;
-
- for (PolynomialFunction f : function.getPolynomials()) {
- double[] coeffs = f.getCoefficients();
- for (int i = coeffs.length - 1; i >= 1; i--) {
- strRepr += NumericPrecisionPrefs.formatPolyCoef(coeffs[i]);
- strRepr += "*A1^" + i + NumericPrecisionPrefs.getExcelFormulaSeparator() + "\n";
- }
- constCoeff += coeffs[0];
- }
-
- strRepr += NumericPrecisionPrefs.formatPolyCoef(constCoeff) + ")";
- */
- strRepr = Mediator.NOT_IMPLEMENTED_YET;
- }
-
- return strRepr;
- }
-
- // Note: There is already a Loess fit function in R, so it
- // would be interesting to compare the results of that and this
- // plugin.
- // toRString must be locale-independent!
- public String toRString() {
- String strRepr = functionStrMap.get(LocaleProps
- .get("MODEL_INFO_R_TITLE"));
-
- if (strRepr == null) {
- /*
- strRepr = "model <- function(t)\n";
-
- double constCoeff = 0;
-
- for (PolynomialFunction f : function.getPolynomials()) {
- double[] coeffs = f.getCoefficients();
- for (int i = coeffs.length - 1; i >= 1; i--) {
- strRepr += NumericPrecisionPrefs.formatPolyCoefLocaleIndependent(coeffs[i]);
- strRepr += "*t^" + i + "+\n";
- }
- constCoeff += coeffs[0];
- }
-
- strRepr += NumericPrecisionPrefs.formatPolyCoefLocaleIndependent(constCoeff);
- */
- strRepr = Mediator.NOT_IMPLEMENTED_YET;
- }
-
- return strRepr;
- }
-
- @Override
- public ContinuousModelFunction getModelFunction() {
- return new ContinuousModelFunction(function, fit);
- }
-
- @Override
- public void execute() throws AlgorithmError {
-
- // The Loess fitter requires a strictly increasing sequence
- // on the domain (i.e. JD values), i.e. no duplicates.
- Map jdToMagMap = new TreeMap();
-
- for (int i = 0; i < obs.size(); i++) {
- ValidObservation ob = obs.get(i);
- // This means that the last magnitude for a JD wins!
- jdToMagMap.put(ob.getJD(), ob.getMag());
- }
-
- double[] xvals = new double[jdToMagMap.size()];
- double[] yvals = new double[jdToMagMap.size()];
-
- int index = 0;
- for (Double jd : jdToMagMap.keySet()) {
- xvals[index] = jd;
- yvals[index++] = jdToMagMap.get(jd);
- }
-
- try {
- final LoessInterpolator interpolator = new LoessInterpolator();
- function = interpolator.interpolate(xvals, yvals);
-
- fit = new ArrayList();
- residuals = new ArrayList();
- double sumSqResiduals = 0;
-
- String comment = "From Loess fit";
-
- // Create fit and residual observations and
- // compute the sum of squares of residuals for
- // Akaike and Bayesean Information Criteria.
- for (int i = 0; i < xvals.length && !interrupted; i++) {
- double jd = xvals[i];
- double mag = yvals[i];
-
- double y = function.value(jd);
-
- ValidObservation fitOb = new ValidObservation();
- fitOb.setDateInfo(new DateInfo(jd));
- fitOb.setMagnitude(new Magnitude(y, 0));
- fitOb.setBand(SeriesType.Model);
- fitOb.setComments(comment);
- fit.add(fitOb);
-
- ValidObservation resOb = new ValidObservation();
- resOb.setDateInfo(new DateInfo(jd));
- double residual = mag - y;
- resOb.setMagnitude(new Magnitude(residual, 0));
- resOb.setBand(SeriesType.Residuals);
- resOb.setComments(comment);
- residuals.add(resOb);
-
- sumSqResiduals += (residual * residual);
- }
-
- // TODO: what to use for degree (or N) here?
- double degree = 0;
-
- for (PolynomialFunction f : function.getPolynomials()) {
- degree += f.getCoefficients().length;
- }
-
- // Fit metrics (AIC, BIC).
- int n = residuals.size();
- if (n != 0 && sumSqResiduals / n != 0) {
- double commonIC = n * Math.log(sumSqResiduals / n);
- aic = commonIC + 2 * degree;
- bic = commonIC + degree * Math.log(n);
- }
-
- ICoordSource timeCoordSource = null;
- switch (Mediator.getInstance().getAnalysisType()) {
- case RAW_DATA:
- timeCoordSource = JDCoordSource.instance;
- break;
-
- case PHASE_PLOT:
- timeCoordSource = StandardPhaseCoordSource.instance;
- break;
- }
-
- // Minimum/maximum.
- // TODO: use derivative approach
- // ApacheCommonsBrentOptimiserExtremaFinder finder = new
- // ApacheCommonsBrentOptimiserExtremaFinder(
- // fit, function, timeCoordSource, 0);
- //
- // String extremaStr = finder.toString();
- //
- // if (extremaStr != null) {
- // String title = LocaleProps
- // .get("MODEL_INFO_EXTREMA_TITLE");
- //
- // functionStrMap.put(title, extremaStr);
- // }
-
- // Excel, R equations.
- // TODO: consider Python, e.g. for use with matplotlib.
- // functionStrMap.put("Function", toString());
- functionStrMap.put(
- LocaleProps.get("MODEL_INFO_FUNCTION_TITLE"),
- toString());
- functionStrMap.put(
- LocaleProps.get("MODEL_INFO_EXCEL_TITLE"),
- toExcelString());
- functionStrMap.put(
- LocaleProps.get("MODEL_INFO_R_TITLE"),
- toRString());
-
- } catch (MathException e) {
- throw new AlgorithmError(e.getLocalizedMessage());
- }
- }
-
- @Override
- public void interrupt() {
- interrupted = true;
- }
-
- @Override
- public Map getFunctionStrings() {
- return functionStrMap;
- }
- };
- }
- }
+ public ApacheCommonsLoessFitter() {
+ super();
+ }
+
+ @Override
+ public String getDescription() {
+ return "Loess Fit";
+ }
+
+ @Override
+ public String getDisplayName() {
+ return getDescription();
+ }
+
+ /**
+ * @see org.aavso.tools.vstar.plugin.IPlugin#getDocName()
+ */
+ @Override
+ public String getDocName() {
+ return "ApacheCommonsLoessFilter.pdf";
+ }
+
+ @Override
+ public AbstractModel getModel(List obs) {
+ return new LoessFitCreator(obs);
+ }
+
+ class LoessFitCreator extends AbstractModel {
+
+ LoessFitCreator(List obs) {
+ super(obs);
+ }
+
+ // TODO: create a dialog to permit entry of params for other
+ // forms of ctor (Loess algorithm variants).
+
+ PolynomialSplineFunction function;
+ double aic = Double.NaN;
+ double bic = Double.NaN;
+
+ @Override
+ public String getDescription() {
+ return getKind() + " for " + obs.get(0).getBand() + " series";
+ }
+
+ @Override
+ public String getKind() {
+ return "Loess Fit";
+ }
+
+ @Override
+ public boolean hasFuncDesc() {
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return toVeLaString();
+ }
+
+ @Override
+ public String toVeLaString() {
+ String strRepr = functionStrMap.get(LocaleProps.get("MODEL_INFO_FUNCTION_TITLE"));
+
+ if (strRepr == null) {
+ /*
+ * strRepr = "f(t:real) : real {\n";
+ *
+ * double constCoeff = 0;
+ *
+ * for (PolynomialFunction f : function.getPolynomials()) { double[] coeffs =
+ * f.getCoefficients(); for (int i = coeffs.length - 1; i >= 1; i--) { strRepr
+ * += " " + NumericPrecisionPrefs.formatPolyCoef(coeffs[i]); strRepr += "*t^"
+ * + i + "+\n"; } constCoeff += coeffs[0]; } strRepr += " " +
+ * NumericPrecisionPrefs.formatPolyCoef(constCoeff); strRepr += "\n}";
+ */
+ strRepr = Mediator.NOT_IMPLEMENTED_YET;
+ }
+
+ return strRepr;
+ }
+
+ public String toExcelString() {
+ String strRepr = functionStrMap.get(LocaleProps.get("MODEL_INFO_EXCEL_TITLE"));
+
+ if (strRepr == null) {
+ /*
+ * strRepr = "=SUM(";
+ *
+ * double constCoeff = 0;
+ *
+ * for (PolynomialFunction f : function.getPolynomials()) { double[] coeffs =
+ * f.getCoefficients(); for (int i = coeffs.length - 1; i >= 1; i--) { strRepr
+ * += NumericPrecisionPrefs.formatPolyCoef(coeffs[i]); strRepr += "*A1^" + i +
+ * NumericPrecisionPrefs.getExcelFormulaSeparator() + "\n"; } constCoeff +=
+ * coeffs[0]; }
+ *
+ * strRepr += NumericPrecisionPrefs.formatPolyCoef(constCoeff) + ")";
+ */
+ strRepr = Mediator.NOT_IMPLEMENTED_YET;
+ }
+
+ return strRepr;
+ }
+
+ // Note: There is already a Loess fit function in R, so it
+ // would be interesting to compare the results of that and this
+ // plugin.
+ // toRString must be locale-independent!
+ public String toRString() {
+ String strRepr = functionStrMap.get(LocaleProps.get("MODEL_INFO_R_TITLE"));
+
+ if (strRepr == null) {
+ /*
+ * strRepr = "model <- function(t)\n";
+ *
+ * double constCoeff = 0;
+ *
+ * for (PolynomialFunction f : function.getPolynomials()) { double[] coeffs =
+ * f.getCoefficients(); for (int i = coeffs.length - 1; i >= 1; i--) { strRepr
+ * += NumericPrecisionPrefs.formatPolyCoefLocaleIndependent(coeffs[i]); strRepr
+ * += "*t^" + i + "+\n"; } constCoeff += coeffs[0]; }
+ *
+ * strRepr += NumericPrecisionPrefs.formatPolyCoefLocaleIndependent(constCoeff);
+ */
+ strRepr = Mediator.NOT_IMPLEMENTED_YET;
+ }
+
+ return strRepr;
+ }
+
+ @Override
+ public ContinuousModelFunction getModelFunction() {
+ return new ContinuousModelFunction(function, fit);
+ }
+
+ @Override
+ public void execute() throws AlgorithmError {
+
+ // The Loess fitter requires a strictly increasing sequence
+ // on the domain (i.e. JD values), i.e. no duplicates.
+ Map jdToMagMap = new TreeMap();
+
+ for (int i = 0; i < obs.size(); i++) {
+ ValidObservation ob = obs.get(i);
+ // This means that the last magnitude for a JD wins!
+ jdToMagMap.put(ob.getJD(), ob.getMag());
+ }
+
+ double[] xvals = new double[jdToMagMap.size()];
+ double[] yvals = new double[jdToMagMap.size()];
+
+ int index = 0;
+ for (Double jd : jdToMagMap.keySet()) {
+ xvals[index] = jd;
+ yvals[index++] = jdToMagMap.get(jd);
+ }
+
+ try {
+ final LoessInterpolator interpolator = new LoessInterpolator();
+ function = interpolator.interpolate(xvals, yvals);
+
+ fit = new ArrayList();
+ residuals = new ArrayList();
+ double sumSqResiduals = 0;
+
+ String comment = "From Loess fit";
+
+ // Create fit and residual observations and
+ // compute the sum of squares of residuals for
+ // Akaike and Bayesean Information Criteria.
+ for (int i = 0; i < xvals.length && !interrupted; i++) {
+ double jd = xvals[i];
+ double mag = yvals[i];
+
+ double y = function.value(jd);
+
+ ValidObservation fitOb = new ValidObservation();
+ fitOb.setDateInfo(new DateInfo(jd));
+ fitOb.setMagnitude(new Magnitude(y, 0));
+ fitOb.setBand(SeriesType.Model);
+ fitOb.setComments(comment);
+ fit.add(fitOb);
+
+ ValidObservation resOb = new ValidObservation();
+ resOb.setDateInfo(new DateInfo(jd));
+ double residual = mag - y;
+ resOb.setMagnitude(new Magnitude(residual, 0));
+ resOb.setBand(SeriesType.Residuals);
+ resOb.setComments(comment);
+ residuals.add(resOb);
+
+ sumSqResiduals += (residual * residual);
+ }
+
+ // TODO: what to use for degree (or N) here?
+ double degree = 0;
+
+ for (PolynomialFunction f : function.getPolynomials()) {
+ degree += f.getCoefficients().length;
+ }
+
+ // Fit metrics (AIC, BIC).
+ int n = residuals.size();
+ if (n != 0 && sumSqResiduals / n != 0) {
+ double commonIC = n * Math.log(sumSqResiduals / n);
+ aic = commonIC + 2 * degree;
+ bic = commonIC + degree * Math.log(n);
+ }
+
+ ICoordSource timeCoordSource = null;
+ switch (Mediator.getInstance().getAnalysisType()) {
+ case RAW_DATA:
+ timeCoordSource = JDCoordSource.instance;
+ break;
+
+ case PHASE_PLOT:
+ timeCoordSource = StandardPhaseCoordSource.instance;
+ break;
+ }
+
+ // Minimum/maximum.
+ // TODO: use derivative approach
+ // ApacheCommonsBrentOptimiserExtremaFinder finder = new
+ // ApacheCommonsBrentOptimiserExtremaFinder(
+ // fit, function, timeCoordSource, 0);
+ //
+ // String extremaStr = finder.toString();
+ //
+ // if (extremaStr != null) {
+ // String title = LocaleProps
+ // .get("MODEL_INFO_EXTREMA_TITLE");
+ //
+ // functionStrMap.put(title, extremaStr);
+ // }
+
+ // Excel, R equations.
+ // TODO: consider Python, e.g. for use with matplotlib.
+ // functionStrMap.put("Function", toString());
+ functionStrMap.put(LocaleProps.get("MODEL_INFO_FUNCTION_TITLE"), toString());
+ functionStrMap.put(LocaleProps.get("MODEL_INFO_EXCEL_TITLE"), toExcelString());
+ functionStrMap.put(LocaleProps.get("MODEL_INFO_R_TITLE"), toRString());
+
+ } catch (MathException e) {
+ throw new AlgorithmError(e.getLocalizedMessage());
+ }
+ }
+ }
}
diff --git a/plugin/src/org/aavso/tools/vstar/external/plugin/FourierModelCreator.java b/plugin/src/org/aavso/tools/vstar/external/plugin/FourierModelCreator.java
index f3263a5c7..3b2fbf3f1 100644
--- a/plugin/src/org/aavso/tools/vstar/external/plugin/FourierModelCreator.java
+++ b/plugin/src/org/aavso/tools/vstar/external/plugin/FourierModelCreator.java
@@ -21,7 +21,6 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.Optional;
import org.aavso.tools.vstar.data.ValidObservation;
import org.aavso.tools.vstar.plugin.ModelCreatorPluginBase;
diff --git a/plugin/src/org/aavso/tools/vstar/external/plugin/PiecewiseLinearMeanSeriesModel.java b/plugin/src/org/aavso/tools/vstar/external/plugin/PiecewiseLinearMeanSeriesModel.java
new file mode 100644
index 000000000..a8d76b901
--- /dev/null
+++ b/plugin/src/org/aavso/tools/vstar/external/plugin/PiecewiseLinearMeanSeriesModel.java
@@ -0,0 +1,58 @@
+/**
+ * VStar: a statistical analysis tool for variable star data.
+ * Copyright (C) 2009 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 .
+ */
+package org.aavso.tools.vstar.external.plugin;
+
+import java.util.List;
+
+import org.aavso.tools.vstar.data.ValidObservation;
+import org.aavso.tools.vstar.external.lib.PiecewiseLinearModel;
+import org.aavso.tools.vstar.plugin.ModelCreatorPluginBase;
+import org.aavso.tools.vstar.ui.mediator.Mediator;
+import org.aavso.tools.vstar.ui.model.plot.ObservationAndMeanPlotModel;
+import org.aavso.tools.vstar.util.model.AbstractModel;
+
+/**
+ * This plug-in creates a piecewise linear model from the current means series.
+ */
+public class PiecewiseLinearMeanSeriesModel extends ModelCreatorPluginBase {
+
+ private final String DESC = "Piecewise linear model from Means";
+
+ public PiecewiseLinearMeanSeriesModel() {
+ super();
+ }
+
+ @Override
+ public AbstractModel getModel(List obs) {
+ // Get the mean observation list for the current mode
+ Mediator mediator = Mediator.getInstance();
+ ObservationAndMeanPlotModel plotModel = mediator.getObservationPlotModel(mediator.getAnalysisType());
+
+ return new PiecewiseLinearModel(obs, plotModel.getMeanObsList());
+ }
+
+ @Override
+ public String getDescription() {
+ return DESC;
+ }
+
+ @Override
+ public String getDisplayName() {
+ return DESC;
+ }
+}
\ No newline at end of file
diff --git a/plugin/src/org/aavso/tools/vstar/external/plugin/VeLaModelCreator.java b/plugin/src/org/aavso/tools/vstar/external/plugin/VeLaModelCreator.java
index 1f055f7a0..ac0ed5432 100644
--- a/plugin/src/org/aavso/tools/vstar/external/plugin/VeLaModelCreator.java
+++ b/plugin/src/org/aavso/tools/vstar/external/plugin/VeLaModelCreator.java
@@ -2,10 +2,7 @@
import java.util.ArrayList;
import java.util.Collections;
-import java.util.Comparator;
-import java.util.LinkedHashMap;
import java.util.List;
-import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
@@ -19,17 +16,11 @@
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.StandardPhaseCoordSource;
import org.aavso.tools.vstar.ui.vela.VeLaDialog;
import org.aavso.tools.vstar.util.ApacheCommonsDerivativeBasedExtremaFinder;
import org.aavso.tools.vstar.util.Tolerance;
-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.model.IModel;
-import org.aavso.tools.vstar.util.model.PeriodFitParameters;
+import org.aavso.tools.vstar.util.model.AbstractModel;
import org.aavso.tools.vstar.util.prefs.NumericPrecisionPrefs;
import org.aavso.tools.vstar.vela.Operand;
import org.aavso.tools.vstar.vela.Type;
@@ -44,375 +35,286 @@
*/
public class VeLaModelCreator extends ModelCreatorPluginBase {
- private static final String FUNC_NAME = "F";
- private static final String DERIV_FUNC_NAME = "DF";
- private static final String RESOLUTION_VAR = "RESOLUTION";
-
- private static VeLaDialog velaDialog;
-
- private ICoordSource timeCoordSource;
- private Comparator timeComparator;
-
- public VeLaModelCreator() {
- super();
- }
-
- @Override
- public String getDescription() {
- return "VeLa model creator";
- }
-
- @Override
- public String getDisplayName() {
- return "VeLa Model";
- }
-
- /**
- * @see org.aavso.tools.vstar.plugin.IPlugin#getDocName()
- */
- @Override
- public String getDocName() {
- return "VeLa Model Creator Plug-In.pdf";
- }
-
- @Override
- public IModel getModel(List obs) {
- VeLaModel velaModel = new VeLaModel(obs);
- return velaModel.createModel();
- }
-
- class VeLaUnivariateRealFunction implements
- DifferentiableUnivariateRealFunction {
-
- private VeLaInterpreter vela;
- private String funcName;
-
- public VeLaUnivariateRealFunction(VeLaInterpreter vela, String funcName) {
- this.vela = vela;
- this.funcName = funcName;
- }
-
- /**
- * Return the value of the model function or its derivative.
- *
- * @param t
- * The time value.
- * @return The model value at time t.
- * @throws FunctionEvaluationException
- * If there is an error during function evaluation.
- */
- @Override
- public double value(double t) throws FunctionEvaluationException {
- String funCall = funcName + "(" + NumericPrecisionPrefs.formatTime(t) + ")";
- Optional result = vela.program(funCall);
- if (result.isPresent()) {
- return result.get().doubleVal();
- } else {
- throw new FunctionEvaluationException(t);
- }
- }
-
- /**
- * If the derivative (df) function doesn't exist, this will never be
- * called since we will bypass extrema determination.
- */
- @Override
- public UnivariateRealFunction derivative() {
- return new VeLaUnivariateRealFunction(vela, DERIV_FUNC_NAME);
- }
- }
-
- class VeLaModel {
- private List obs;
- private double zeroPoint;
- private VeLaInterpreter vela;
-
- VeLaModel(List obs) {
- // Create a VeLa interpreter instance.
- vela = new VeLaInterpreter();
-
- // 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);
- zeroPoint = 0;
- List jdList = obs.stream()
- .map(ob -> new Operand(Type.REAL, ob.getJD()))
- .collect(Collectors.toList());
- vela.bind("TIMES", new Operand(Type.LIST, jdList), true);
- break;
-
- case PHASE_PLOT:
- timeCoordSource = StandardPhaseCoordSource.instance;
- timeComparator = StandardPhaseComparator.instance;
- this.obs = new ArrayList(obs);
- Collections.sort(this.obs, timeComparator);
- zeroPoint = 0;
- List phaseList = this.obs
- .stream()
- .map(ob -> new Operand(Type.REAL, ob.getStandardPhase()))
- .collect(Collectors.toList());
- vela.bind("TIMES", new Operand(Type.LIST, phaseList), true);
- break;
- }
-
- List magList = this.obs.stream()
- .map(ob -> new Operand(Type.REAL, ob.getMag()))
- .collect(Collectors.toList());
- Operand mags = new Operand(Type.LIST, magList);
- vela.bind("MAGS", mags, true);
- }
-
- // Create a VeLa model.
- IModel createModel() {
- IModel model = null;
- String modelFuncStr = null;
- String modelNameStr = null;
- boolean ok = false;
-
- if (inTestMode()) {
- modelFuncStr = getModelFunc();
- modelNameStr = getModelName();
- ok = true;
- } else {
- if (velaDialog == null) {
- velaDialog = new VeLaDialog(
- "Function Code [model: f(t), optional derivative: df(t)]");
- } else {
- velaDialog.showDialog();
- }
-
- if (!velaDialog.isCancelled()) {
- modelFuncStr = velaDialog.getCode();
- modelNameStr = velaDialog.getPath();
- ok = true;
- }
- }
-
- if (ok) {
- String velaModelFunctionStr = modelFuncStr;
- String modelName = modelNameStr;
- // double resolution = resolutionField.getValue();
-
- model = new IModel() {
- boolean interrupted = false;
- List fit;
- List residuals;
- UnivariateRealFunction function;
- Map functionStrMap = new LinkedHashMap();
-
- @Override
- public String getDescription() {
- return modelName + " applied to "
- + obs.get(0).getBand() + " series";
- }
-
- @Override
- public List getFit() {
- return fit;
- }
-
- @Override
- public List getResiduals() {
- return residuals;
- }
-
- @Override
- public String getKind() {
- return "VeLa Model";
- }
-
- @Override
- public List getParameters() {
- // None for a VeLa model.
- return null;
- }
-
- @Override
- public boolean hasFuncDesc() {
- return true;
- }
-
- @Override
- public String toString() {
- return velaModelFunctionStr;
- }
-
- @Override
- public ContinuousModelFunction getModelFunction() {
- return new ContinuousModelFunction(function, fit,
- zeroPoint);
- }
-
- @Override
- public void execute() throws AlgorithmError {
- if (!interrupted) {
- try {
- // Evaluate the VeLa model code.
- // A univariate function f(t:real):real is
- // assumed to exist after this completes.
- vela.program(velaModelFunctionStr);
-
- String funcName = FUNC_NAME;
-
- // Has a model function been defined?
- if (!vela.lookupFunctions(FUNC_NAME)
- .isPresent()) {
- MessageBox.showErrorDialog(
- "VeLa Model Error",
- "f(t:real):real undefined");
- } else {
- function = new VeLaUnivariateRealFunction(
- vela, funcName);
-
- fit = new ArrayList();
- residuals = new ArrayList();
-
- String comment = "\n"
- + velaModelFunctionStr;
-
- // Create fit and residual observations.
- for (int i = 0; i < obs.size()
- && !interrupted; i++) {
- ValidObservation ob = obs.get(i);
-
- // Push an environment that makes the
- // observation available to VeLa code.
- vela.pushEnvironment(new VeLaValidObservationEnvironment(
- ob));
-
- double x = timeCoordSource.getXCoord(i,
- obs);
-
- // double zeroedX = x - zeroPoint;
- double y = function.value(x);
-
- 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(y, 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() - y;
- resOb.setMagnitude(new Magnitude(
- residual, 0));
- resOb.setBand(SeriesType.Residuals);
- resOb.setComments(comment);
- residuals.add(resOb);
-
- // Pop the observation environment.
- vela.popEnvironment();
- }
-
- functionStrMap.put(LocaleProps
- .get("MODEL_INFO_FUNCTION_TITLE"),
- toString());
-
- // Has a derivative function been defined?
- // If so, carry out extrema determination.
- if (vela.lookupFunctions(DERIV_FUNC_NAME)
- .isPresent()) {
- // Use a real VeLa resolution variable
- // if it exists, else use a value of
- // 0.1.
- double resolution = 0.1;
- Optional resVar = vela
- .lookupBinding(RESOLUTION_VAR);
- if (resVar.isPresent()) {
- switch (resVar.get().getType()) {
- case REAL:
- resolution = resVar.get()
- .doubleVal();
- break;
- case INTEGER:
- resolution = resVar.get()
- .intVal();
- break;
- default:
- MessageBox
- .showErrorDialog(
- "VeLa Model Error",
- "Resolution must be numeric");
- break;
- }
- }
-
- ApacheCommonsDerivativeBasedExtremaFinder finder = new ApacheCommonsDerivativeBasedExtremaFinder(
- fit,
- (DifferentiableUnivariateRealFunction) function,
- timeCoordSource, zeroPoint,
- resolution);
-
- String extremaStr = finder.toString();
-
- if (extremaStr != null) {
- String title = LocaleProps
- .get("MODEL_INFO_EXTREMA_TITLE");
-
- functionStrMap.put(title,
- extremaStr);
- }
- }
- }
- } catch (FunctionEvaluationException e) {
- throw new AlgorithmError(
- e.getLocalizedMessage());
- }
- }
- }
-
- @Override
- public void interrupt() {
- interrupted = true;
- }
-
- @Override
- public Map getFunctionStrings() {
- return functionStrMap;
- }
- };
- }
-
- return model;
- }
- }
-
- // Plug-in test
+ private static final String FUNC_NAME = "F";
+ private static final String DERIV_FUNC_NAME = "DF";
+ private static final String RESOLUTION_VAR = "RESOLUTION";
+
+ private static VeLaDialog velaDialog;
+
+ public VeLaModelCreator() {
+ super();
+ }
+
+ @Override
+ public String getDescription() {
+ return "VeLa model creator";
+ }
+
+ @Override
+ public String getDisplayName() {
+ return "VeLa Model";
+ }
+
+ /**
+ * @see org.aavso.tools.vstar.plugin.IPlugin#getDocName()
+ */
+ @Override
+ public String getDocName() {
+ return "VeLa Model Creator Plug-In.pdf";
+ }
+
+ @Override
+ public AbstractModel getModel(List obs) {
+ return new VeLaModel(obs);
+ }
+
+ class VeLaUnivariateRealFunction implements DifferentiableUnivariateRealFunction {
+
+ private VeLaInterpreter vela;
+ private String funcName;
+
+ public VeLaUnivariateRealFunction(VeLaInterpreter vela, String funcName) {
+ this.vela = vela;
+ this.funcName = funcName;
+ }
+
+ /**
+ * Return the value of the model function or its derivative.
+ *
+ * @param t The time value.
+ * @return The model value at time t.
+ * @throws FunctionEvaluationException If there is an error during function
+ * evaluation.
+ */
+ @Override
+ public double value(double t) throws FunctionEvaluationException {
+ String funCall = funcName + "(" + NumericPrecisionPrefs.formatTime(t) + ")";
+ Optional result = vela.program(funCall);
+ if (result.isPresent()) {
+ return result.get().doubleVal();
+ } else {
+ throw new FunctionEvaluationException(t);
+ }
+ }
+
+ /**
+ * If the derivative (df) function doesn't exist, this will never be called
+ * since we will bypass extrema determination.
+ */
+ @Override
+ public UnivariateRealFunction derivative() {
+ return new VeLaUnivariateRealFunction(vela, DERIV_FUNC_NAME);
+ }
+ }
+
+ class VeLaModel extends AbstractModel {
+ double zeroPoint;
+ UnivariateRealFunction function;
+ VeLaInterpreter vela;
+ String velaModelFunctionStr;
+ String modelName;
+
+ VeLaModel(List obs) {
+ super(obs);
+
+ // Create a VeLa interpreter instance.
+ vela = new VeLaInterpreter();
+
+ // Select time mode (JD or phase).
+ switch (Mediator.getInstance().getAnalysisType()) {
+ case RAW_DATA:
+ zeroPoint = 0;
+ List jdList = obs.stream().map(ob -> new Operand(Type.REAL, ob.getJD()))
+ .collect(Collectors.toList());
+ vela.bind("TIMES", new Operand(Type.LIST, jdList), true);
+ break;
+
+ case PHASE_PLOT:
+ Collections.sort(this.obs, timeComparator);
+ zeroPoint = 0;
+ List phaseList = this.obs.stream().map(ob -> new Operand(Type.REAL, ob.getStandardPhase()))
+ .collect(Collectors.toList());
+ vela.bind("TIMES", new Operand(Type.LIST, phaseList), true);
+ break;
+ }
+
+ List magList = this.obs.stream().map(ob -> new Operand(Type.REAL, ob.getMag()))
+ .collect(Collectors.toList());
+ Operand mags = new Operand(Type.LIST, magList);
+ vela.bind("MAGS", mags, true);
+
+ String modelFuncStr = null;
+ String modelNameStr = null;
+
+ if (inTestMode()) {
+ modelFuncStr = getTestModelFunc();
+ modelNameStr = getTestModelName();
+ } else {
+ if (velaDialog == null) {
+ velaDialog = new VeLaDialog("Function Code [model: f(t), optional derivative: df(t)]");
+ } else {
+ velaDialog.showDialog();
+ }
+
+ if (!velaDialog.isCancelled()) {
+ modelFuncStr = velaDialog.getCode();
+ modelNameStr = velaDialog.getPath();
+ }
+ }
+
+ velaModelFunctionStr = modelFuncStr;
+ modelName = modelNameStr;
+ }
+
+ @Override
+ public String getDescription() {
+ return modelName + " applied to " + obs.get(0).getBand() + " series";
+ }
+
+ @Override
+ public String getKind() {
+ return "VeLa Model";
+ }
+
+ @Override
+ public boolean hasFuncDesc() {
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return toVeLaString();
+ }
+
+ @Override
+ public String toVeLaString() {
+ return velaModelFunctionStr;
+ }
+
+ @Override
+ public ContinuousModelFunction getModelFunction() {
+ return new ContinuousModelFunction(function, fit, zeroPoint);
+ }
+
+ @Override
+ public void execute() throws AlgorithmError {
+ if (!interrupted) {
+ try {
+ // Evaluate the VeLa model code.
+ // A univariate function f(t:real):real is
+ // assumed to exist after this completes.
+ vela.program(velaModelFunctionStr);
+
+ String funcName = FUNC_NAME;
+
+ // Has a model function been defined?
+ if (!vela.lookupFunctions(FUNC_NAME).isPresent()) {
+ MessageBox.showErrorDialog("VeLa Model Error", "f(t:real):real undefined");
+ } else {
+ function = new VeLaUnivariateRealFunction(vela, funcName);
+
+ fit = new ArrayList();
+ residuals = new ArrayList();
+
+ String comment = "\n" + velaModelFunctionStr;
+
+ // Create fit and residual observations.
+ for (int i = 0; i < obs.size() && !interrupted; i++) {
+ ValidObservation ob = obs.get(i);
+
+ // Push an environment that makes the
+ // observation available to VeLa code.
+ vela.pushEnvironment(new VeLaValidObservationEnvironment(ob));
+
+ double x = timeCoordSource.getXCoord(i, obs);
+
+ // double zeroedX = x - zeroPoint;
+ double y = function.value(x);
+
+ 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(y, 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() - y;
+ resOb.setMagnitude(new Magnitude(residual, 0));
+ resOb.setBand(SeriesType.Residuals);
+ resOb.setComments(comment);
+ residuals.add(resOb);
+
+ // Pop the observation environment.
+ vela.popEnvironment();
+ }
+
+ functionStrMap.put(LocaleProps.get("MODEL_INFO_FUNCTION_TITLE"), toString());
+
+ // Has a derivative function been defined?
+ // If so, carry out extrema determination.
+ if (vela.lookupFunctions(DERIV_FUNC_NAME).isPresent()) {
+ // Use a real VeLa resolution variable
+ // if it exists, else use a value of
+ // 0.1.
+ double resolution = 0.1;
+ Optional resVar = vela.lookupBinding(RESOLUTION_VAR);
+ if (resVar.isPresent()) {
+ switch (resVar.get().getType()) {
+ case REAL:
+ resolution = resVar.get().doubleVal();
+ break;
+ case INTEGER:
+ resolution = resVar.get().intVal();
+ break;
+ default:
+ MessageBox.showErrorDialog("VeLa Model Error", "Resolution must be numeric");
+ break;
+ }
+ }
+
+ ApacheCommonsDerivativeBasedExtremaFinder finder = new ApacheCommonsDerivativeBasedExtremaFinder(
+ fit, (DifferentiableUnivariateRealFunction) function, timeCoordSource, zeroPoint,
+ resolution);
+
+ String extremaStr = finder.toString();
+
+ if (extremaStr != null) {
+ String title = LocaleProps.get("MODEL_INFO_EXTREMA_TITLE");
+
+ functionStrMap.put(title, extremaStr);
+ }
+ }
+ }
+ } catch (FunctionEvaluationException e) {
+ throw new AlgorithmError(e.getLocalizedMessage());
+ }
+ }
+ }
+ }
+
+ // Plug-in test
@Override
public Boolean test() {
boolean success = true;
-
+
setTestMode(true);
-
+
try {
- IModel model = getModel(createObs());
+ AbstractModel model = getModel(createObs());
model.execute();
success &= model.hasFuncDesc();
- String desc = getModelName() + " applied to Visual series";
+ String desc = getTestModelName() + " applied to Visual series";
success &= model.getDescription().equals(desc);
success &= !model.getFit().isEmpty();
success &= !model.getResiduals().isEmpty();
@@ -426,8 +328,8 @@ public Boolean test() {
return success;
}
-
- private String getModelFunc() {
+
+ private String getTestModelFunc() {
String func = "";
func += "f(t:real) : real {\n";
@@ -435,14 +337,14 @@ private String getModelFunc() {
func += " -0.6588158 * cos(2*PI*0.0017177*(t-2451700))\n";
func += " +1.3908874 * sin(2*PI*0.0017177*(t-2451700))";
func += "}\n";
-
+
return func;
}
-
- private String getModelName() {
+
+ private String getTestModelName() {
return "test model";
}
-
+
private List createObs() {
List obs = new ArrayList();
diff --git a/plugin/test/org/aavso/tools/vstar/external/plugin/AllTests.java b/plugin/test/org/aavso/tools/vstar/external/plugin/AllTests.java
index 631b02374..b614cd4e8 100644
--- a/plugin/test/org/aavso/tools/vstar/external/plugin/AllTests.java
+++ b/plugin/test/org/aavso/tools/vstar/external/plugin/AllTests.java
@@ -33,6 +33,7 @@ public static Test suite() {
// $JUnit-BEGIN$
suite.addTestSuite(PluginTest.class);
+ suite.addTestSuite(PiecewiseLinearModelTest.class);
// $JUnit-END$
return suite;
diff --git a/plugin/test/org/aavso/tools/vstar/external/plugin/PiecewiseLinearModelTest.java b/plugin/test/org/aavso/tools/vstar/external/plugin/PiecewiseLinearModelTest.java
new file mode 100644
index 000000000..c9f25d9c9
--- /dev/null
+++ b/plugin/test/org/aavso/tools/vstar/external/plugin/PiecewiseLinearModelTest.java
@@ -0,0 +1,113 @@
+/**
+ * VStar: a statistical analysis tool for variable star data.
+ * Copyright (C) 2009 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 .
+ */
+package org.aavso.tools.vstar.external.plugin;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.aavso.tools.vstar.data.DateInfo;
+import org.aavso.tools.vstar.data.Magnitude;
+import org.aavso.tools.vstar.data.ValidObservation;
+import org.aavso.tools.vstar.external.lib.PiecewiseLinearModel.LinearFunction;
+import org.aavso.tools.vstar.external.lib.PiecewiseLinearModel.PiecewiseLinearFunction;
+import org.aavso.tools.vstar.ui.model.plot.JDCoordSource;
+import org.aavso.tools.vstar.util.Tolerance;
+
+import junit.framework.TestCase;
+
+/**
+ * Unit tests for piecewise linear model plug-in library.
+ */
+public class PiecewiseLinearModelTest extends TestCase {
+
+ public PiecewiseLinearModelTest(String name) {
+ super(name);
+ }
+
+ public void testLinearFunction() {
+ double DELTA = 1e-6;
+
+ LinearFunction function = new LinearFunction(2459645, 2459640, 10, 12.5);
+
+ double m = -0.5;
+ assertTrue(Tolerance.areClose(m, function.slope(), DELTA, true));
+ assertTrue(Tolerance.areClose(10 - (m * 2459645), function.yIntercept(), DELTA, true));
+ assertTrue(Tolerance.areClose(m * 2459642 + function.yIntercept(), function.value(2459642), DELTA, true));
+ }
+
+ public void testPiecewiseLinearFunction() {
+ double DELTA = 1e-6;
+
+ List meanObs = getTestMeanObs();
+ PiecewiseLinearFunction plf = new PiecewiseLinearFunction(meanObs, JDCoordSource.instance);
+
+ List obs = getTestObs();
+
+ double t1 = obs.get(0).getJD();
+ LinearFunction function1 = plf.getFunctions().get(0);
+ assertTrue(Tolerance.areClose(function1.slope() * t1 + function1.yIntercept(), plf.value(t1), DELTA, true));
+
+ double t2 = obs.get(1).getJD();
+ LinearFunction function2 = plf.getFunctions().get(1);
+ assertTrue(Tolerance.areClose(function2.slope() * t2 + function2.yIntercept(), plf.value(t2), DELTA, true));
+ }
+
+ // Helpers
+
+ private List getTestMeanObs() {
+ List obs = new ArrayList();
+
+ ValidObservation ob1 = new ValidObservation();
+ ob1.setDateInfo(new DateInfo(2459644));
+ ob1.setMagnitude(new Magnitude(4.5, 0));
+ obs.add(ob1);
+
+ ValidObservation ob2 = new ValidObservation();
+ ob2.setDateInfo(new DateInfo(2459645.5));
+ ob2.setMagnitude(new Magnitude(5.5, 0));
+ obs.add(ob2);
+
+ ValidObservation ob3 = new ValidObservation();
+ ob3.setDateInfo(new DateInfo(22459645.5));
+ ob3.setMagnitude(new Magnitude(5.5, 0));
+ obs.add(ob3);
+
+ ValidObservation ob4 = new ValidObservation();
+ ob4.setDateInfo(new DateInfo(2459647));
+ ob4.setMagnitude(new Magnitude(7, 0));
+ obs.add(ob4);
+
+ return obs;
+ }
+
+ private List getTestObs() {
+ List obs = new ArrayList();
+
+ ValidObservation ob1 = new ValidObservation();
+ ob1.setDateInfo(new DateInfo(2459645.1134785));
+ ob1.setMagnitude(new Magnitude(5, 0));
+ obs.add(ob1);
+
+ ValidObservation ob2 = new ValidObservation();
+ ob2.setDateInfo(new DateInfo(2459646.2));
+ ob2.setMagnitude(new Magnitude(6, 0));
+ obs.add(ob2);
+
+ return obs;
+ }
+}
diff --git a/script/VeLa/Y/Y.vela b/script/VeLa/Y/Y.vela
index bcd3b32b6..62eefe837 100644
--- a/script/VeLa/Y/Y.vela
+++ b/script/VeLa/Y/Y.vela
@@ -3,7 +3,7 @@
# nor are function parameter and return types fully
# specified (just "function") yet
-Y is λ(h : function) : function {
+Y is λ(h : λ) : function {
λ(f : function) : function {
f(f)
} (λ(f : function) : function {
diff --git a/script/VeLa/piecewise_linear_model.vl b/script/VeLa/piecewise_linear_model.vl
index ddd0de861..55432a9a9 100644
--- a/script/VeLa/piecewise_linear_model.vl
+++ b/script/VeLa/piecewise_linear_model.vl
@@ -4,23 +4,23 @@ slope(x1:real y1:real x2:real y2:real) : real {
(y2-y1) / (x2-x1)
}
--- piecewise linear model
+# piecewise linear model
genf() : function {
i <- 0
f(t:real) : real {
- -- is time coordinate beyond end of current line segment?
+ # is time coordinate beyond end of current line segment?
if t > nth(times i+1) and i < length(times)-1 then {
i <- i+1
}
- -- obtain coordinates of point at ends of line segment
+ # obtain coordinates of point at ends of line segment
t0 <- nth(times i)
mag0 <- nth(mags i)
t1 <- nth(times i+1)
mag1 <- nth(mags i+1)
- -- create linear model for segment and compute y value
+ # create linear model for segment and compute y value
m <- slope(t1 t0 mag1 mag0)
c <- mag1 - m*t1
y <- m*t + c
@@ -31,23 +31,23 @@ genf() : function {
f
}
--- derivative of model
+# derivative of model
gendf() : function {
i <- 0
df(t:real) : real {
- -- is time coordinate beyond end of current line segment?
+ # is time coordinate beyond end of current line segment?
if t > nth(times i+1) and i < length(times)-1 then {
i <- i+1
}
- -- obtain coordinates of point at ends of line segment
+ # obtain coordinates of point at ends of line segment
t0 <- nth(times i)
mag0 <- nth(mags i)
t1 <- nth(times i+1)
mag1 <- nth(mags i+1)
- -- return slope at the coordinate
+ # return slope at the coordinate
slope(t1 t0 mag1 mag0)
}
@@ -57,16 +57,20 @@ gendf() : function {
f <- genf()
df <- gendf()
---println("slope: " slope(nth(times 0) nth(mags 0) nth(times 1) nth(mags 1)))
+#println("slope: " slope(nth(times 0) nth(mags 0) nth(times 1) nth(mags 1)))
---series <- "Unspecified"
+series <- "Unspecified"
---times <- [0.045 0.141 0.239 0.343 0.44 0.543 0.641 0.741 0.842 0.941 1]
---times <- getPhases(series)
---times <- getTimes(series)
+times <- [0.045 0.141 0.239 0.343 0.44 0.543 0.641 0.741 0.842 0.941 1.0]
+#times <- getPhases(series)
+#times <- getTimes(series)
---mags <- [3.678 3.776 3.866 3.943 4 4.062 4.117 4.089 3.883 3.651 3.653]
---mags <- getMags(series)
+mags <- [3.678 3.776 3.866 3.943 4.0 4.062 4.117 4.089 3.883 3.651 3.653]
+#mags <- getMags(series)
---println(map(function(n:real):real{n*n} mags))
---println(map(f times))
+#println(map(function(n:real):real{n*n} mags))
+#println(map(f times))
+
+model is map(f times)
+println(model)
+scatter("Mean Model" "t" "mag" times mags)
diff --git a/src/org/aavso/tools/vstar/plugin/model/impl/ApacheCommonsPolynomialFitCreatorPlugin.java b/src/org/aavso/tools/vstar/plugin/model/impl/ApacheCommonsPolynomialFitCreatorPlugin.java
index 83a2bc43e..0c7484046 100644
--- a/src/org/aavso/tools/vstar/plugin/model/impl/ApacheCommonsPolynomialFitCreatorPlugin.java
+++ b/src/org/aavso/tools/vstar/plugin/model/impl/ApacheCommonsPolynomialFitCreatorPlugin.java
@@ -18,34 +18,22 @@
package org.aavso.tools.vstar.plugin.model.impl;
import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
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.plugin.ModelCreatorPluginBase;
import org.aavso.tools.vstar.ui.dialog.PolynomialDegreeDialog;
-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.ApacheCommonsDerivativeBasedExtremaFinder;
-import org.aavso.tools.vstar.util.comparator.JDComparator;
-import org.aavso.tools.vstar.util.comparator.StandardPhaseComparator;
+import org.aavso.tools.vstar.util.Tolerance;
import org.aavso.tools.vstar.util.locale.LocaleProps;
-import org.aavso.tools.vstar.util.model.IModel;
+import org.aavso.tools.vstar.util.model.AbstractModel;
import org.aavso.tools.vstar.util.model.PeriodFitParameters;
import org.aavso.tools.vstar.util.prefs.NumericPrecisionPrefs;
-import org.aavso.tools.vstar.util.stats.DescStats;
import org.apache.commons.math.ConvergenceException;
import org.apache.commons.math.FunctionEvaluationException;
import org.apache.commons.math.analysis.DifferentiableUnivariateRealFunction;
@@ -59,442 +47,346 @@
* A polynomial model creator plugin that uses an Apache Commons polynomial
* fitter.
*/
-public class ApacheCommonsPolynomialFitCreatorPlugin extends
- ModelCreatorPluginBase {
-
- private boolean needGUI = true;
-
- private int degree;
-
- private ICoordSource timeCoordSource;
- private Comparator timeComparator;
-
- private PolynomialFitCreator fitCreator;
-
- public ApacheCommonsPolynomialFitCreatorPlugin() {
- super();
- }
-
- @Override
- public String getDescription() {
- return LocaleProps.get("ANALYSIS_MENU_POLYNOMIAL_FIT");
- }
-
- @Override
- public String getDisplayName() {
- return LocaleProps.get("ANALYSIS_MENU_POLYNOMIAL_FIT");
- }
-
- @Override
- public IModel getModel(List obs) {
- fitCreator = new PolynomialFitCreator(obs);
- return fitCreator.createModel();
- }
-
- /**
- * This is intended for setting parameters from the scripting API.
- */
- @Override
- public void setParams(Object[] params) {
- assert (params.length == 1);
- double degree = (double) params[0];
- setDegree((int) degree);
- needGUI = false;
- }
-
- private void setDegree(int degree) {
- this.degree = degree;
- }
-
- public int getDegree() {
- return degree;
- }
-
- private int getMinDegree() {
- return 0;
- }
-
- private int getMaxDegree() {
- // TODO: make this a preference
- return 30;
- }
-
- class PolynomialFitCreator {
- private List obs;
- private double zeroPoint;
-
- PolynomialFitCreator(List obs) {
- // TODO: the code in this block should be refactored into the model
- // creator base class or elsewhere
-
- // 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(obs);
- Collections.sort(this.obs, timeComparator);
- zeroPoint = 0;
- break;
- }
- }
-
- // Create a model representing a polynomial fit of the requested degree.
- IModel createModel() {
- IModel model = null;
-
- int minDegree = getMinDegree();
- int maxDegree = getMaxDegree();
-
- boolean cancelled = false;
-
- if (needGUI) {
- PolynomialDegreeDialog polyDegreeDialog = new PolynomialDegreeDialog(
- minDegree, maxDegree);
-
- setDegree(polyDegreeDialog.getDegree());
-
- cancelled = polyDegreeDialog.isCancelled();
- }
-
- if (!cancelled) {
- final AbstractLeastSquaresOptimizer optimizer = new LevenbergMarquardtOptimizer();
-
- final PolynomialFitter fitter = new PolynomialFitter(
- getDegree(), optimizer);
-
- model = new IModel() {
- boolean interrupted = false;
- List fit;
- List residuals;
- PolynomialFunction function;
- Map functionStrMap = new LinkedHashMap();
- double aic = Double.NaN;
- double bic = Double.NaN;
-
- @Override
- public String getDescription() {
- return LocaleProps
- .get("MODEL_INFO_POLYNOMIAL_DEGREE_DESC")
- + degree
- + " for "
- + obs.get(0).getBand()
- + " series";
- }
-
- @Override
- public List getFit() {
- return fit;
- }
-
- @Override
- public List getResiduals() {
- return residuals;
- }
-
- @Override
- public String getKind() {
- return LocaleProps.get("ANALYSIS_MENU_POLYNOMIAL_FIT");
- }
-
- // TODO: if this is not generalisable, it should be removed
- // as a requirement from base class
- @Override
- public List getParameters() {
- // None for a polynomial fit.
- return null;
- }
-
- @Override
- public boolean hasFuncDesc() {
- return true;
- }
-
- public String toFitMetricsString() throws AlgorithmError {
- String strRepr = functionStrMap
- .get(LocaleProps.get("MODEL_INFO_FIT_METRICS_TITLE"));
-
- if (strRepr == null) {
- // Goodness of fit
- strRepr = "RMS: "
- + NumericPrecisionPrefs
- .formatOther(optimizer.getRMS());
-
- // Akaike and Bayesean Information Criteria
- if (aic != Double.NaN && bic != Double.NaN) {
- strRepr += "\nAIC: "
- + NumericPrecisionPrefs
- .formatOther(aic);
- strRepr += "\nBIC: "
- + NumericPrecisionPrefs
- .formatOther(bic);
- }
- }
-
- return strRepr;
- }
-
- @Override
- public String toString() {
- String strRepr = functionStrMap.get(LocaleProps
- .get("MODEL_INFO_FUNCTION_TITLE"));
-
- if (strRepr == null) {
- strRepr = "zeroPoint is "
- + NumericPrecisionPrefs
- .formatTime(zeroPoint) + "\n\n";
-
- strRepr += "f(t:real) : real {\n";
-
- double[] coeffs = function.getCoefficients();
- for (int i = coeffs.length - 1; i >= 1; i--) {
- strRepr += " " + NumericPrecisionPrefs.formatPolyCoef(coeffs[i]);
- strRepr += "*(t-zeroPoint)^" + i + " +\n";
- }
- strRepr += " " + NumericPrecisionPrefs.formatPolyCoef(coeffs[0]);
- strRepr += "\n}";
- }
-
- return strRepr;
- }
-
- public String toExcelString() {
- String strRepr = functionStrMap.get(LocaleProps
- .get("MODEL_INFO_EXCEL_TITLE"));
-
- if (strRepr == null) {
- strRepr = "=";
-
- double[] coeffs = function.getCoefficients();
- for (int i = coeffs.length - 1; i >= 1; i--) {
- strRepr += NumericPrecisionPrefs.formatPolyCoef(coeffs[i]);
- strRepr += "*(A1-"
- + NumericPrecisionPrefs
- .formatTime(zeroPoint) + ")^"
- + i + "+\n";
- }
- strRepr += NumericPrecisionPrefs.formatPolyCoef(coeffs[0]);
- }
-
- return strRepr;
- }
-
- // toRString must be locale-independent!
- public String toRString() {
- String strRepr = functionStrMap.get(LocaleProps
- .get("MODEL_INFO_R_TITLE"));
-
- if (strRepr == null) {
- strRepr = "zeroPoint <- "
- + NumericPrecisionPrefs
- .formatTimeLocaleIndependent(zeroPoint) + "\n\n";
-
- strRepr += "model <- function(t)\n";
-
- double[] coeffs = function.getCoefficients();
- for (int i = coeffs.length - 1; i >= 1; i--) {
- strRepr += NumericPrecisionPrefs.formatPolyCoefLocaleIndependent(coeffs[i]);
- strRepr += "*(t-zeroPoint)^" + i + " +\n";
- }
- strRepr += NumericPrecisionPrefs.formatPolyCoefLocaleIndependent(coeffs[0]);
- }
-
- return strRepr;
- }
-
- @Override
- public ContinuousModelFunction getModelFunction() {
- // UnivariateRealFunction func = new
- // UnivariateRealFunction() {
- // @Override
- // public double value(double x)
- // throws FunctionEvaluationException {
- // double y = 0;
- // double[] coeffs = function.getCoefficients();
- // for (int i = coeffs.length - 1; i >= 1; i--) {
- // y += coeffs[i] * Math.pow(x, i);
- // }
- // y += coeffs[0];
- // return y;
- // }
- // };
-
- return new ContinuousModelFunction(function, fit,
- zeroPoint);
- }
-
- // An alternative implementation for getModelFunction() that
- // uses Horner's method to avoid exponentiation.
- public UnivariateRealFunction getModelFunctionHorner() {
- UnivariateRealFunction func = new UnivariateRealFunction() {
- @Override
- public double value(double x)
- throws FunctionEvaluationException {
- // Compute the value of the polynomial for x via
- // Horner's method.
- double y = 0;
- double[] coeffs = function.getCoefficients();
- for (double coeff : coeffs) {
- y = y * x + coeff;
- }
- return y;
- }
- };
-
- return func;
- }
-
- @Override
- public void execute() throws AlgorithmError {
-
- for (int i = 0; i < obs.size() && !interrupted; i++) {
- fitter.addObservedPoint(1.0,
- timeCoordSource.getXCoord(i, obs)
- - zeroPoint, obs.get(i).getMag());
- }
-
- if (!interrupted) {
- try {
- function = fitter.fit();
-
- fit = new ArrayList();
- residuals = new ArrayList();
- double sumSqResiduals = 0;
-
- String comment = LocaleProps
- .get("MODEL_INFO_POLYNOMIAL_DEGREE_DESC")
- + degree;
-
- // Create fit and residual observations and
- // compute the sum of squares of residuals for
- // Akaike and Bayesean Information Criteria.
- for (int i = 0; i < obs.size() && !interrupted; i++) {
- ValidObservation ob = obs.get(i);
-
- double x = timeCoordSource
- .getXCoord(i, obs);
- double zeroedX = x - zeroPoint;
- double y = function.value(zeroedX);
-
- 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(y, 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() - y;
- resOb.setMagnitude(new Magnitude(residual,
- 0));
- resOb.setBand(SeriesType.Residuals);
- resOb.setComments(comment);
- residuals.add(resOb);
-
- sumSqResiduals += (residual * residual);
- }
-
- // Fit metrics (AIC, BIC).
- int n = residuals.size();
- if (n != 0 && sumSqResiduals / n != 0) {
- double commonIC = n
- * Math.log(sumSqResiduals / n);
- aic = commonIC + 2 * degree;
- bic = commonIC + degree * Math.log(n);
- }
-
- functionStrMap.put(LocaleProps
- .get("MODEL_INFO_FIT_METRICS_TITLE"),
- toFitMetricsString());
-
- ApacheCommonsDerivativeBasedExtremaFinder finder = new ApacheCommonsDerivativeBasedExtremaFinder(
- fit,
- (DifferentiableUnivariateRealFunction) function,
- timeCoordSource, zeroPoint);
-
- String extremaStr = finder.toString();
-
- if (extremaStr != null) {
- String title = LocaleProps
- .get("MODEL_INFO_EXTREMA_TITLE");
-
- functionStrMap.put(title, extremaStr);
- }
-
- // Minimum/maximum.
- // ApacheCommonsBrentOptimiserExtremaFinder
- // finder = new
- // ApacheCommonsBrentOptimiserExtremaFinder(
- // fit, function, timeCoordSource,
- // zeroPoint);
- //
- // String extremaStr = finder.toString();
- //
- // if (extremaStr != null) {
- // String title = LocaleProps
- // .get("MODEL_INFO_EXTREMA_TITLE");
- //
- // functionStrMap.put(title, extremaStr);
- // }
-
- // VeLa, Excel, R equations.
- // TODO: consider Python, e.g. for use with
- // matplotlib.
- functionStrMap.put(LocaleProps
- .get("MODEL_INFO_FUNCTION_TITLE"),
- toString());
-
- functionStrMap.put(LocaleProps
- .get("MODEL_INFO_EXCEL_TITLE"),
- toExcelString());
-
- functionStrMap.put(
- LocaleProps.get("MODEL_INFO_R_TITLE"),
- toRString());
-
- } catch (ConvergenceException e) {
- throw new AlgorithmError(
- e.getLocalizedMessage());
- }
- }
- }
-
- @Override
- public void interrupt() {
- interrupted = true;
- }
-
- @Override
- public Map getFunctionStrings() {
- return functionStrMap;
- }
- };
- }
-
- return model;
- }
- }
+public class ApacheCommonsPolynomialFitCreatorPlugin extends ModelCreatorPluginBase {
+
+ private boolean needGUI = true;
+
+ private int degree;
+
+ public ApacheCommonsPolynomialFitCreatorPlugin() {
+ super();
+ }
+
+ @Override
+ public String getDescription() {
+ return LocaleProps.get("ANALYSIS_MENU_POLYNOMIAL_FIT");
+ }
+
+ @Override
+ public String getDisplayName() {
+ return LocaleProps.get("ANALYSIS_MENU_POLYNOMIAL_FIT");
+ }
+
+ @Override
+ public AbstractModel getModel(List obs) {
+ return new PolynomialFitModel(obs);
+ }
+
+ /**
+ * This is intended for setting parameters from the scripting API or plug-in
+ * test.
+ */
+ @Override
+ public void setParams(Object[] params) {
+ assert (params.length == 1);
+ double degree = (double) params[0];
+ setDegree((int) degree);
+ needGUI = false;
+ }
+
+ private void setDegree(int degree) {
+ this.degree = degree;
+ }
+
+ public int getDegree() {
+ return degree;
+ }
+
+ private int getMinDegree() {
+ return 0;
+ }
+
+ private int getMaxDegree() {
+ // TODO: make this a preference
+ return 30;
+ }
+
+ class PolynomialFitModel extends AbstractModel {
+ PolynomialFunction function;
+ PolynomialFitter fitter;
+ AbstractLeastSquaresOptimizer optimizer;
+
+ PolynomialFitModel(List obs) {
+ super(obs);
+
+ int minDegree = getMinDegree();
+ int maxDegree = getMaxDegree();
+
+ boolean cancelled = false;
+
+ if (needGUI) {
+ PolynomialDegreeDialog polyDegreeDialog = new PolynomialDegreeDialog(minDegree, maxDegree);
+
+ setDegree(polyDegreeDialog.getDegree());
+
+ cancelled = polyDegreeDialog.isCancelled();
+ }
+
+ if (!cancelled) {
+ optimizer = new LevenbergMarquardtOptimizer();
+ fitter = new PolynomialFitter(getDegree(), optimizer);
+ }
+ }
+
+ @Override
+ public String getDescription() {
+ return LocaleProps.get("MODEL_INFO_POLYNOMIAL_DEGREE_DESC") + degree + " for " + obs.get(0).getBand()
+ + " series";
+ }
+
+ @Override
+ public String getKind() {
+ return LocaleProps.get("ANALYSIS_MENU_POLYNOMIAL_FIT");
+ }
+
+ // TODO: if this is not generalisable, it should be removed
+ // as a requirement from base class or the name changed to
+ // getPeriodFitParameters()
+ @Override
+ public List getParameters() {
+ // None for a polynomial fit.
+ return null;
+ }
+
+ @Override
+ public boolean hasFuncDesc() {
+ return true;
+ }
+
+ @Override
+ public void rootMeanSquare() {
+ rms = optimizer.getRMS();
+ }
+
+ @Override
+ public Map getFunctionStrings() {
+ return functionStrMap;
+ }
+
+ @Override
+ public String toString() {
+ return toVeLaString();
+ }
+
+ @Override
+ public String toVeLaString() {
+ String strRepr = functionStrMap.get(LocaleProps.get("MODEL_INFO_FUNCTION_TITLE"));
+
+ if (strRepr == null) {
+ strRepr = "zeroPoint is " + NumericPrecisionPrefs.formatTime(zeroPoint) + "\n\n";
+
+ strRepr += "f(t:real) : real {\n";
+
+ double[] coeffs = function.getCoefficients();
+ for (int i = coeffs.length - 1; i >= 1; i--) {
+ strRepr += " " + NumericPrecisionPrefs.formatCoef(coeffs[i]);
+ strRepr += "*(t-zeroPoint)^" + i + " +\n";
+ }
+ strRepr += " " + NumericPrecisionPrefs.formatCoef(coeffs[0]);
+ strRepr += "\n}";
+ }
+
+ return strRepr;
+ }
+
+ public String toExcelString() {
+ String strRepr = functionStrMap.get(LocaleProps.get("MODEL_INFO_EXCEL_TITLE"));
+
+ if (strRepr == null) {
+ strRepr = "=";
+
+ double[] coeffs = function.getCoefficients();
+ for (int i = coeffs.length - 1; i >= 1; i--) {
+ strRepr += NumericPrecisionPrefs.formatCoef(coeffs[i]);
+ strRepr += "*(A1-" + NumericPrecisionPrefs.formatTime(zeroPoint) + ")^" + i + "+\n";
+ }
+ strRepr += NumericPrecisionPrefs.formatCoef(coeffs[0]);
+ }
+
+ return strRepr;
+ }
+
+ // toRString must be locale-independent!
+ public String toRString() {
+ String strRepr = functionStrMap.get(LocaleProps.get("MODEL_INFO_R_TITLE"));
+
+ if (strRepr == null) {
+ strRepr = "zeroPoint <- " + NumericPrecisionPrefs.formatTimeLocaleIndependent(zeroPoint) + "\n\n";
+
+ strRepr += "model <- function(t)\n";
+
+ double[] coeffs = function.getCoefficients();
+ for (int i = coeffs.length - 1; i >= 1; i--) {
+ strRepr += NumericPrecisionPrefs.formatCoefLocaleIndependent(coeffs[i]);
+ strRepr += "*(t-zeroPoint)^" + i + " +\n";
+ }
+ strRepr += NumericPrecisionPrefs.formatCoefLocaleIndependent(coeffs[0]);
+ }
+
+ return strRepr;
+ }
+
+ @Override
+ public void functionStrings() {
+ super.functionStrings();
+ functionStrMap.put(LocaleProps.get("MODEL_INFO_EXCEL_TITLE"), toExcelString());
+ functionStrMap.put(LocaleProps.get("MODEL_INFO_R_TITLE"), toRString());
+ }
+
+ @Override
+ public ContinuousModelFunction getModelFunction() {
+ // UnivariateRealFunction func = new
+ // UnivariateRealFunction() {
+ // @Override
+ // public double value(double x)
+ // throws FunctionEvaluationException {
+ // double y = 0;
+ // double[] coeffs = function.getCoefficients();
+ // for (int i = coeffs.length - 1; i >= 1; i--) {
+ // y += coeffs[i] * Math.pow(x, i);
+ // }
+ // y += coeffs[0];
+ // return y;
+ // }
+ // };
+
+ return new ContinuousModelFunction(function, fit, zeroPoint);
+ }
+
+ // An alternative implementation for getModelFunction() that
+ // uses Horner's method to avoid exponentiation.
+ public UnivariateRealFunction getModelFunctionHorner() {
+ UnivariateRealFunction func = new UnivariateRealFunction() {
+ @Override
+ public double value(double x) throws FunctionEvaluationException {
+ // Compute the value of the polynomial for x via
+ // Horner's method.
+ double y = 0;
+ double[] coeffs = function.getCoefficients();
+ for (double coeff : coeffs) {
+ y = y * x + coeff;
+ }
+ return y;
+ }
+ };
+
+ return func;
+ }
+
+ @Override
+ public void execute() throws AlgorithmError {
+
+ for (int i = 0; i < obs.size() && !interrupted; i++) {
+ fitter.addObservedPoint(1.0, timeCoordSource.getXCoord(i, obs) - zeroPoint, obs.get(i).getMag());
+ }
+
+ if (!interrupted) {
+ try {
+ function = fitter.fit();
+
+ fit = new ArrayList();
+ residuals = new ArrayList();
+
+ String comment = LocaleProps.get("MODEL_INFO_POLYNOMIAL_DEGREE_DESC") + degree;
+
+ for (int i = 0; i < obs.size() && !interrupted; i++) {
+ ValidObservation ob = obs.get(i);
+
+ double x = timeCoordSource.getXCoord(i, obs);
+ double zeroedX = x - zeroPoint;
+ double y = function.value(zeroedX);
+
+ collectObs(y, ob, comment);
+ }
+
+ rootMeanSquare();
+ informationCriteria(degree);
+ fitMetrics();
+
+ ApacheCommonsDerivativeBasedExtremaFinder finder = new ApacheCommonsDerivativeBasedExtremaFinder(
+ fit, (DifferentiableUnivariateRealFunction) function, timeCoordSource, zeroPoint);
+
+ String extremaStr = finder.toString();
+
+ if (extremaStr != null) {
+ String title = LocaleProps.get("MODEL_INFO_EXTREMA_TITLE");
+
+ functionStrMap.put(title, extremaStr);
+ }
+
+ functionStrings();
+
+ } catch (ConvergenceException e) {
+ throw new AlgorithmError(e.getLocalizedMessage());
+ }
+ }
+ }
+ }
+
+ // Test
+ // see also TSPolynomialFitterTest (under test directory)
+
+ @Override
+ public Boolean test() {
+ boolean result = true;
+
+ setTestMode(true);
+ needGUI = false;
+
+ result &= testPolynomialFit();
+
+ setTestMode(false);
+
+ return result;
+ }
+
+ private boolean testPolynomialFit() {
+ boolean result = true;
+
+ List obs = getTestObs();
+
+ setDegree(9);
+
+ AbstractModel model = getModel(obs);
+
+ try {
+ model.execute();
+
+ double DELTA = 1e-6;
+
+ List fit = model.getFit();
+ ValidObservation fitOb = fit.get(0);
+ result &= fitOb.getJD() == 2459301.0;
+ result &= Tolerance.areClose(0.629248, fitOb.getMag(), DELTA, true);
+
+ List residuals = model.getResiduals();
+ ValidObservation resOb = residuals.get(0);
+ result &= resOb.getJD() == 2459301.0;
+ result &= Tolerance.areClose(0.000073, resOb.getMag(), DELTA, true);
+
+ result &= Tolerance.areClose(0.0000162266724849, model.getRMS(), DELTA, true);
+ result &= Tolerance.areClose(-7923.218889035116, model.getAIC(), DELTA, true);
+ result &= Tolerance.areClose(-7888.243952752065, model.getBIC(), DELTA, true);
+
+ } catch (AlgorithmError e) {
+ result = false;
+ }
+
+ return result;
+ }
+
+ private List getTestObs() {
+ List obs = new ArrayList();
+
+ for (int t = 1; t <= 360; t++) {
+ double time = 2459300 + t;
+ double mag = Math.sin(Math.toRadians(time));
+ ValidObservation ob = new ValidObservation();
+ ob.setDateInfo(new DateInfo(time));
+ ob.setMagnitude(new Magnitude(mag, 0));
+ obs.add(ob);
+ }
+
+ return obs;
+ }
}
diff --git a/src/org/aavso/tools/vstar/ui/dialog/period/PeriodAnalysisDataTablePane.java b/src/org/aavso/tools/vstar/ui/dialog/period/PeriodAnalysisDataTablePane.java
index 19718e147..dc83a55cf 100644
--- a/src/org/aavso/tools/vstar/ui/dialog/period/PeriodAnalysisDataTablePane.java
+++ b/src/org/aavso/tools/vstar/ui/dialog/period/PeriodAnalysisDataTablePane.java
@@ -27,6 +27,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.stream.Collectors;
import javax.swing.BoxLayout;
import javax.swing.JButton;
@@ -59,296 +60,311 @@
@SuppressWarnings("serial")
public class PeriodAnalysisDataTablePane extends JPanel implements ListSelectionListener, IStartAndCleanup {
- protected JTable table;
- protected PeriodAnalysisDataTableModel model;
- protected TableRowSorter rowSorter;
+ protected JTable table;
+ protected PeriodAnalysisDataTableModel model;
+ protected TableRowSorter rowSorter;
- protected JButton modelButton;
+ protected JButton modelButton;
- protected IPeriodAnalysisAlgorithm algorithm;
+ protected IPeriodAnalysisAlgorithm algorithm;
- protected boolean wantModelButton;
+ protected boolean wantModelButton;
- protected Map> freqToHarmonicsMap;
+ protected Map> freqToHarmonicsMap;
+
+ protected Listener harmonicSearchResultListener;
+ protected Listener periodAnalysisSelectionListener;
+
+ private String tablePaneID = null;
+
+ private boolean valueChangedDisabledState = false;
+
+ /**
+ * Constructor
+ *
+ * @param model The period analysis table model.
+ * @param algorithm The period analysis algorithm.
+ * @param wantModelButton Add a model button?
+ */
+ public PeriodAnalysisDataTablePane(PeriodAnalysisDataTableModel model, IPeriodAnalysisAlgorithm algorithm,
+ boolean wantModelButton) {
+ super(new GridLayout(1, 1));
+
+ this.model = model;
+ this.algorithm = algorithm;
+ this.wantModelButton = wantModelButton;
+
+ freqToHarmonicsMap = new HashMap>();
+
+ table = new JTable(model);
+ JScrollPane scrollPane = new JScrollPane(table);
+
+ this.add(scrollPane);
+
+ table.getSelectionModel().addListSelectionListener(this);
+
+ table.setColumnSelectionAllowed(false);
+ table.setRowSelectionAllowed(true);
+
+ table.setAutoCreateRowSorter(true);
+ FormattedDoubleComparator comparator = FormattedDoubleComparator.getInstance();
+ rowSorter = new TableRowSorter(model);
+ for (int i = 0; i < model.getColumnCount(); i++) {
+ rowSorter.setComparator(i, comparator);
+ }
+ table.setRowSorter(rowSorter);
+
+ setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
+ add(createButtonPanel());
+ }
+
+ /**
+ * Constructor for a period analysis data table pane with a model button.
+ *
+ * @param model The period analysis table model.
+ * @param algorithm The period analysis algorithm.
+ */
+ public PeriodAnalysisDataTablePane(PeriodAnalysisDataTableModel model, IPeriodAnalysisAlgorithm algorithm) {
+ this(model, algorithm, true);
+ }
+
+ protected JPanel createButtonPanel() {
+ JPanel buttonPane = new JPanel();
+
+ modelButton = new JButton(LocaleProps.get("CREATE_MODEL_BUTTON"));
+ modelButton.setEnabled(false);
+ modelButton.addActionListener(createModelButtonHandler());
+
+ if (!wantModelButton) {
+ modelButton.setVisible(false);
+ }
+
+ buttonPane.add(modelButton, BorderLayout.LINE_END);
+
+ return buttonPane;
+ }
+
+ /**
+ * We send a period analysis selection message when the table selection value
+ * has "settled". This event could be consumed by other views such as plots.
+ */
+ public void valueChanged(ListSelectionEvent e) {
+ if (isValueChangeDisabled())
+ return;
+
+ if (e.getSource() == table.getSelectionModel() && table.getRowSelectionAllowed() && !e.getValueIsAdjusting()) {
+ int row = table.getSelectedRow();
+
+ if (row >= 0) {
+ row = table.convertRowIndexToModel(row);
+
+ PeriodAnalysisSelectionMessage message = new PeriodAnalysisSelectionMessage(this,
+ model.getDataPointFromRow(row), row);
+ message.setTag(Mediator.getParentDialogName(this));
+ Mediator.getInstance().getPeriodAnalysisSelectionNotifier().notifyListeners(message);
+ }
+ }
+ }
+
+ // Model button listener.
+ protected ActionListener createModelButtonHandler() {
+ return new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ // TODO: add a user selected frequencies protected method
+ // that can be called from an abstract method that implements
+ // the modelling operation
+ // TODO: only this vcaries: userSelectedFreqs.add(dataPoint.getFrequency());
+
+ List dataPoints = new ArrayList();
+ int[] selectedTableRowIndices = table.getSelectedRows();
+ if (selectedTableRowIndices.length < 1) {
+ MessageBox.showMessageDialog(LocaleProps.get("CREATE_MODEL_BUTTON"), "Please select a row");
+ return;
+ }
+ for (int row : selectedTableRowIndices) {
+ int modelRow = table.convertRowIndexToModel(row);
+
+ PeriodAnalysisDataPoint dataPoint = model.getDataPointFromRow(modelRow);
+ dataPoints.add(dataPoint);
+ }
+
+ modelAction(dataPoints);
+ }
+ };
+ }
+
+ /**
+ * Modelling action. Subclasses can override.
+ * Note: should really make this class abstract on this method.
+ *
+ * @param dataPoints The selected data points.
+ */
+ protected void modelAction(List dataPoints) {
+ final JPanel parent = this;
+
+ List userSelectedFreqs = dataPoints.stream().map(point -> point.getFrequency()).collect(Collectors.toList());
+
+ HarmonicInputDialog dialog = new HarmonicInputDialog(parent, userSelectedFreqs, freqToHarmonicsMap);
+
+ if (!dialog.isCancelled()) {
+ List harmonics = dialog.getHarmonics();
+ if (!harmonics.isEmpty()) {
+ try {
+ PeriodAnalysisDerivedMultiPeriodicModel model = new PeriodAnalysisDerivedMultiPeriodicModel(
+ dataPoints.get(0), harmonics, algorithm);
+
+ Mediator.getInstance().performModellingOperation(model);
+ } catch (Exception ex) {
+ MessageBox.showErrorDialog(parent, "Modelling", ex.getLocalizedMessage());
+ }
+ } else {
+ MessageBox.showErrorDialog("Create Model", "Period list error");
+ }
+ }
+ }
+
+ /**
+ * A listener to store the latest harmonic search result in a mapping from
+ * (fundamental) frequency to harmonics.
+ */
+ protected Listener createHarmonicSearchResultListener() {
+ final PeriodAnalysisDataTablePane tablePane = this;
+ return new Listener() {
+ @Override
+ public void update(HarmonicSearchResultMessage info) {
+ if (!Mediator.isMsgForDialog(Mediator.getParentDialog(tablePane), info))
+ return;
+ freqToHarmonicsMap.put(info.getDataPoint().getFrequency(), info.getHarmonics());
+
+ String id = tablePane.getTablePaneID();
+ String currentID = info.getIDstring();
+ if (currentID != null && currentID.equals(id)) {
+ if (info.getHarmonics().size() > 0) {
+ new HarmonicInfoDialog(info, tablePane);
+ } else {
+ MessageBox.showMessageDialog("Harmonics", "No top hit for this frequency");
+ }
+ }
+ }
+
+ @Override
+ public boolean canBeRemoved() {
+ return true;
+ }
+ };
+ }
+
+ /**
+ * Select the row in the table corresponding to the period analysis selection.
+ * We also enable the "refine" button.
+ */
+ protected Listener createPeriodAnalysisListener() {
+ final Component parent = this;
+
+ return new Listener() {
+ @Override
+ public void update(PeriodAnalysisSelectionMessage info) {
+ if (!Mediator.isMsgForDialog(Mediator.getParentDialog(PeriodAnalysisDataTablePane.this), info))
+ return;
+ if (info.getSource() != parent) {
+ // Find data point in table.
+ int row = -1;
+ for (int i = 0; i < model.getRowCount(); i++) {
+ if (model.getDataPointFromRow(i).equals(info.getDataPoint())) {
+ row = i;
+ break;
+ }
+ }
+
+ // Note that the row may not correspond to anything in the
+ // data table, e.g. in the case of period analysis
+ // refinement.
+ if (row != -1) {
+ // Convert to view index!
+ row = table.convertRowIndexToView(row);
+
+ // Scroll to an arbitrary column (zeroth) within
+ // the selected row, then select that row.
+ // Assumption: we are specifying the zeroth cell
+ // within row i as an x,y coordinate relative to
+ // the top of the table pane.
+ // Note that we could call this on the scroll
+ // pane, which would then forward the request to
+ // the table pane anyway.
+ int colWidth = (int) table.getCellRect(row, 0, true).getWidth();
+ int rowHeight = table.getRowHeight(row);
+ table.scrollRectToVisible(new Rectangle(colWidth, rowHeight * row, colWidth, rowHeight));
+
+ boolean state = disableValueChangeEvent();
+ try {
+ table.setRowSelectionInterval(row, row);
+ } finally {
+ setValueChangedDisabledState(state);
+ }
+ enableButtons();
+ }
+ } else {
+ enableButtons();
+ }
+ }
+
+ @Override
+ public boolean canBeRemoved() {
+ return true;
+ }
+ };
+ }
+
+ /**
+ * Enable the buttons on this pane.
+ */
+ protected void enableButtons() {
+ modelButton.setEnabled(true);
+ }
+
+ @Override
+ public void startup() {
+ harmonicSearchResultListener = createHarmonicSearchResultListener();
+ Mediator.getInstance().getHarmonicSearchNotifier().addListener(harmonicSearchResultListener);
+
+ periodAnalysisSelectionListener = createPeriodAnalysisListener();
+ Mediator.getInstance().getPeriodAnalysisSelectionNotifier().addListener(periodAnalysisSelectionListener);
+ }
+
+ @Override
+ public void cleanup() {
+ Mediator.getInstance().getHarmonicSearchNotifier().removeListenerIfWilling(harmonicSearchResultListener);
+ Mediator.getInstance().getPeriodAnalysisSelectionNotifier()
+ .removeListenerIfWilling(periodAnalysisSelectionListener);
+ }
+
+ public void setTablePaneID(String tablePaneID) {
+ this.tablePaneID = tablePaneID;
+ }
+
+ public String getTablePaneID() {
+ return tablePaneID;
+ }
+
+ /**
+ * @return the table
+ */
+ public JTable getTable() {
+ return table;
+ }
+
+ public boolean disableValueChangeEvent() {
+ boolean state = valueChangedDisabledState;
+ valueChangedDisabledState = true;
+ return state;
+ }
+
+ public void setValueChangedDisabledState(boolean state) {
+ valueChangedDisabledState = state;
+ }
+
+ public boolean isValueChangeDisabled() {
+ return valueChangedDisabledState;
+ }
- protected Listener harmonicSearchResultListener;
- protected Listener periodAnalysisSelectionListener;
-
- private String tablePaneID = null;
-
- private boolean valueChangedDisabledState = false;
-
- /**
- * Constructor
- *
- * @param model The period analysis table model.
- * @param algorithm The period analysis algorithm.
- * @param wantModelButton Add a model button?
- */
- public PeriodAnalysisDataTablePane(PeriodAnalysisDataTableModel model, IPeriodAnalysisAlgorithm algorithm,
- boolean wantModelButton) {
- super(new GridLayout(1, 1));
-
- this.model = model;
- this.algorithm = algorithm;
- this.wantModelButton = wantModelButton;
-
- freqToHarmonicsMap = new HashMap>();
-
- table = new JTable(model);
- JScrollPane scrollPane = new JScrollPane(table);
-
- this.add(scrollPane);
-
- table.getSelectionModel().addListSelectionListener(this);
-
- table.setColumnSelectionAllowed(false);
- table.setRowSelectionAllowed(true);
-
- table.setAutoCreateRowSorter(true);
- FormattedDoubleComparator comparator = FormattedDoubleComparator.getInstance();
- rowSorter = new TableRowSorter(model);
- for (int i = 0; i < model.getColumnCount(); i++) {
- rowSorter.setComparator(i, comparator);
- }
- table.setRowSorter(rowSorter);
-
- setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
- add(createButtonPanel());
- }
-
- /**
- * Constructor for a period analysis data table pane with a model button.
- *
- * @param model The period analysis table model.
- * @param algorithm The period analysis algorithm.
- */
- public PeriodAnalysisDataTablePane(PeriodAnalysisDataTableModel model, IPeriodAnalysisAlgorithm algorithm) {
- this(model, algorithm, true);
- }
-
- protected JPanel createButtonPanel() {
- JPanel buttonPane = new JPanel();
-
- modelButton = new JButton(LocaleProps.get("CREATE_MODEL_BUTTON"));
- modelButton.setEnabled(false);
- modelButton.addActionListener(createModelButtonHandler());
-
- if (!wantModelButton) {
- modelButton.setVisible(false);
- }
-
- buttonPane.add(modelButton, BorderLayout.LINE_END);
-
- return buttonPane;
- }
-
- /**
- * We send a period analysis selection message when the table selection value
- * has "settled". This event could be consumed by other views such as plots.
- */
- public void valueChanged(ListSelectionEvent e) {
- if (isValueChangeDisabled())
- return;
-
- if (e.getSource() == table.getSelectionModel() && table.getRowSelectionAllowed() && !e.getValueIsAdjusting()) {
- int row = table.getSelectedRow();
-
- if (row >= 0) {
- row = table.convertRowIndexToModel(row);
-
- PeriodAnalysisSelectionMessage message = new PeriodAnalysisSelectionMessage(this,
- model.getDataPointFromRow(row), row);
- message.setTag(Mediator.getParentDialogName(this));
- Mediator.getInstance().getPeriodAnalysisSelectionNotifier().notifyListeners(message);
- }
- }
- }
-
- // Model button listener.
- protected ActionListener createModelButtonHandler() {
- final JPanel parent = this;
-
- return new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- List dataPoints = new ArrayList();
- List userSelectedFreqs = new ArrayList();
- int[] selectedTableRowIndices = table.getSelectedRows();
- if (selectedTableRowIndices.length < 1) {
- MessageBox.showMessageDialog(LocaleProps.get("CREATE_MODEL_BUTTON"), "Please select a row");
- return;
- }
- for (int row : selectedTableRowIndices) {
- int modelRow = table.convertRowIndexToModel(row);
-
- PeriodAnalysisDataPoint dataPoint = model.getDataPointFromRow(modelRow);
- dataPoints.add(dataPoint);
- userSelectedFreqs.add(dataPoint.getFrequency());
- }
-
- HarmonicInputDialog dialog = new HarmonicInputDialog(parent, userSelectedFreqs, freqToHarmonicsMap);
-
- if (!dialog.isCancelled()) {
- List harmonics = dialog.getHarmonics();
- if (!harmonics.isEmpty()) {
- try {
- PeriodAnalysisDerivedMultiPeriodicModel model = new PeriodAnalysisDerivedMultiPeriodicModel(
- dataPoints.get(0), harmonics, algorithm);
-
- Mediator.getInstance().performModellingOperation(model);
- } catch (Exception ex) {
- MessageBox.showErrorDialog(parent, "Modelling", ex.getLocalizedMessage());
- }
- } else {
- MessageBox.showErrorDialog("Create Model", "Period list error");
- }
- }
- }
- };
- }
-
- /**
- * A listener to store the latest harmonic search result in a mapping from
- * (fundamental) frequency to harmonics.
- */
- protected Listener createHarmonicSearchResultListener() {
- final PeriodAnalysisDataTablePane tablePane = this;
- return new Listener() {
- @Override
- public void update(HarmonicSearchResultMessage info) {
- if (!Mediator.isMsgForDialog(Mediator.getParentDialog(tablePane), info))
- return;
- freqToHarmonicsMap.put(info.getDataPoint().getFrequency(), info.getHarmonics());
-
- String id = tablePane.getTablePaneID();
- String currentID = info.getIDstring();
- if (currentID != null && currentID.equals(id)) {
- if (info.getHarmonics().size() > 0) {
- new HarmonicInfoDialog(info, tablePane);
- } else {
- MessageBox.showMessageDialog("Harmonics", "No top hit for this frequency");
- }
- }
- }
-
- @Override
- public boolean canBeRemoved() {
- return true;
- }
- };
- }
-
- /**
- * Select the row in the table corresponding to the period analysis selection.
- * We also enable the "refine" button.
- */
- protected Listener createPeriodAnalysisListener() {
- final Component parent = this;
-
- return new Listener() {
- @Override
- public void update(PeriodAnalysisSelectionMessage info) {
- if (!Mediator.isMsgForDialog(Mediator.getParentDialog(PeriodAnalysisDataTablePane.this), info))
- return;
- if (info.getSource() != parent) {
- // Find data point in table.
- int row = -1;
- for (int i = 0; i < model.getRowCount(); i++) {
- if (model.getDataPointFromRow(i).equals(info.getDataPoint())) {
- row = i;
- break;
- }
- }
-
- // Note that the row may not correspond to anything in the
- // data table, e.g. in the case of period analysis
- // refinement.
- if (row != -1) {
- // Convert to view index!
- row = table.convertRowIndexToView(row);
-
- // Scroll to an arbitrary column (zeroth) within
- // the selected row, then select that row.
- // Assumption: we are specifying the zeroth cell
- // within row i as an x,y coordinate relative to
- // the top of the table pane.
- // Note that we could call this on the scroll
- // pane, which would then forward the request to
- // the table pane anyway.
- int colWidth = (int) table.getCellRect(row, 0, true).getWidth();
- int rowHeight = table.getRowHeight(row);
- table.scrollRectToVisible(new Rectangle(colWidth, rowHeight * row, colWidth, rowHeight));
-
- boolean state = disableValueChangeEvent();
- try {
- table.setRowSelectionInterval(row, row);
- } finally {
- setValueChangedDisabledState(state);
- }
- enableButtons();
- }
- } else {
- enableButtons();
- }
- }
-
- @Override
- public boolean canBeRemoved() {
- return true;
- }
- };
- }
-
- /**
- * Enable the buttons on this pane.
- */
- protected void enableButtons() {
- modelButton.setEnabled(true);
- }
-
- @Override
- public void startup() {
- harmonicSearchResultListener = createHarmonicSearchResultListener();
- Mediator.getInstance().getHarmonicSearchNotifier().addListener(harmonicSearchResultListener);
-
- periodAnalysisSelectionListener = createPeriodAnalysisListener();
- Mediator.getInstance().getPeriodAnalysisSelectionNotifier().addListener(periodAnalysisSelectionListener);
- }
-
- @Override
- public void cleanup() {
- Mediator.getInstance().getHarmonicSearchNotifier().removeListenerIfWilling(harmonicSearchResultListener);
- Mediator.getInstance().getPeriodAnalysisSelectionNotifier()
- .removeListenerIfWilling(periodAnalysisSelectionListener);
- }
-
- public void setTablePaneID(String tablePaneID) {
- this.tablePaneID = tablePaneID;
- }
-
- public String getTablePaneID() {
- return tablePaneID;
- }
-
- /**
- * @return the table
- */
- public JTable getTable() {
- return table;
- }
-
- public boolean disableValueChangeEvent() {
- boolean state = valueChangedDisabledState;
- valueChangedDisabledState = true;
- return state;
- }
-
- public void setValueChangedDisabledState(boolean state) {
- valueChangedDisabledState = state;
- }
-
- public boolean isValueChangeDisabled() {
- return valueChangedDisabledState;
- }
-
}
diff --git a/src/org/aavso/tools/vstar/ui/dialog/period/PeriodAnalysisTopHitsTablePane.java b/src/org/aavso/tools/vstar/ui/dialog/period/PeriodAnalysisTopHitsTablePane.java
index 88c21e828..54bd4c68a 100644
--- a/src/org/aavso/tools/vstar/ui/dialog/period/PeriodAnalysisTopHitsTablePane.java
+++ b/src/org/aavso/tools/vstar/ui/dialog/period/PeriodAnalysisTopHitsTablePane.java
@@ -52,286 +52,262 @@
@SuppressWarnings("serial")
public class PeriodAnalysisTopHitsTablePane extends PeriodAnalysisDataTablePane {
- private Set refinedDataPoints;
- private Set resultantDataPoints;
-
- private JButton refineButton;
-
- private Listener periodAnalysisRefinementListener;
-
- /**
- * Constructor.
- *
- * @param topHitsModel
- * The top hits data model.
- * @param fullDataModel
- * The full data data model.
- * @param algorithm
- * The period analysis algorithm.
- */
- public PeriodAnalysisTopHitsTablePane(
- PeriodAnalysisDataTableModel topHitsModel,
- PeriodAnalysisDataTableModel fullDataModel,
- IPeriodAnalysisAlgorithm algorithm) {
- super(topHitsModel, algorithm);
-
- refinedDataPoints = new TreeSet(
- PeriodAnalysisDataPointComparator.instance);
-
- resultantDataPoints = new TreeSet(
- PeriodAnalysisDataPointComparator.instance);
- }
-
- protected JPanel createButtonPanel() {
- JPanel buttonPane = super.createButtonPanel();
-
- refineButton = new JButton(algorithm.getRefineByFrequencyName());
- refineButton.setEnabled(false);
- refineButton.addActionListener(createRefineButtonHandler());
- buttonPane.add(refineButton, BorderLayout.LINE_START);
-
- return buttonPane;
- }
-
- // Refine button listener.
- private ActionListener createRefineButtonHandler() {
- final JPanel parent = this;
- return new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- // Collect frequencies to be used in refinement, ensuring that
- // we don't try to use a frequency that has already been used
- // for refinement. We also do not want to use the result of a
- // previous refinement.
- List freqs = new ArrayList();
- int[] selectedTableRowIndices = table.getSelectedRows();
- List inputDataPoints = new ArrayList();
-
- for (int row : selectedTableRowIndices) {
- int modelRow = table.convertRowIndexToModel(row);
- PeriodAnalysisDataPoint dataPoint = model
- .getDataPointFromRow(modelRow);
-
- if (!refinedDataPoints.contains(dataPoint)) {
- inputDataPoints.add(dataPoint);
- freqs.add(dataPoint.getFrequency());
- } else {
- String msg = String.format("Top Hit with frequency %s"
- + " and power %s"
- + " has previously been used.",
- NumericPrecisionPrefs.formatOther(dataPoint
- .getFrequency()), NumericPrecisionPrefs
- .formatOther(dataPoint.getPower()));
- MessageBox.showErrorDialog(parent, algorithm
- .getRefineByFrequencyName(), msg);
- freqs.clear();
- break;
- }
-
- if (resultantDataPoints.contains(dataPoint)) {
- String msg = String.format("Top Hit with frequency %s"
- + " and power %s"
- + " was generated by %s so cannot be used.",
- dataPoint.getFrequency(), dataPoint.getPower(),
- algorithm.getRefineByFrequencyName());
- MessageBox.showErrorDialog(parent, algorithm
- .getRefineByFrequencyName(), msg);
- freqs.clear();
- break;
- }
- }
-
- if (!freqs.isEmpty()) {
- try {
- RefinementParameterDialog dialog = new RefinementParameterDialog(
- parent, freqs, 6);
- if (!dialog.isCancelled()) {
- List variablePeriods = dialog
- .getVariablePeriods();
- List lockedPeriods = dialog
- .getLockedPeriods();
-
- // Perform a refinement operation and get the new
- // top-hits resulting from the refinement.
- List newTopHits = algorithm
- .refineByFrequency(freqs, variablePeriods,
- lockedPeriods);
- // Mark input frequencies as refined so we don't
- // try to refine them again.
- refinedDataPoints.addAll(inputDataPoints);
-
- // Update the model and tell anyone else who might
- // be interested.
- Map> data = algorithm
- .getResultSeries();
- Map> topHits = algorithm
- .getTopHits();
-
- model.setData(topHits);
-
- PeriodAnalysisRefinementMessage msg = new PeriodAnalysisRefinementMessage(
- this, data, topHits, newTopHits);
- msg.setTag(Mediator.getParentDialogName(PeriodAnalysisTopHitsTablePane.this));
- Mediator.getInstance()
- .getPeriodAnalysisRefinementNotifier()
- .notifyListeners(msg);
- }
- } catch (AlgorithmError ex) {
- MessageBox.showErrorDialog(parent, algorithm
- .getRefineByFrequencyName(), ex
- .getLocalizedMessage());
- } catch (InterruptedException ex) {
- // Do nothing; just return.
- }
- }
- }
- };
- }
-
- /**
- * Select the row in the table corresponding to the period analysis
- * selection. We also enable the "refine" button.
- */
- protected Listener createPeriodAnalysisListener() {
- final Component parent = this;
-
- return new Listener() {
- @Override
- public void update(PeriodAnalysisSelectionMessage info) {
- if (!Mediator.isMsgForDialog(Mediator.getParentDialog(PeriodAnalysisTopHitsTablePane.this), info))
- return;
- if (info.getSource() != parent) {
- // Find data point in top hits table.
- int row = -1;
- for (int i = 0; i < model.getRowCount(); i++) {
- if (model.getDataPointFromRow(i).equals(
- info.getDataPoint())) {
- row = i;
- break;
- }
- }
-
- // Note that the row may not correspond to anything in the
- // top hits table since there's more data in the full
- // dataset than there is here!
- if (row != -1) {
- // Convert to view index!
- row = table.convertRowIndexToView(row);
-
- // Scroll to an arbitrary column (zeroth) within
- // the selected row, then select that row.
- // Assumption: we are specifying the zeroth cell
- // within row i as an x,y coordinate relative to
- // the top of the table pane.
- // Note that we could call this on the scroll
- // pane, which would then forward the request to
- // the table pane anyway.
- int colWidth = (int) table.getCellRect(row, 0, true)
- .getWidth();
- int rowHeight = table.getRowHeight(row);
- table.scrollRectToVisible(new Rectangle(colWidth,
- rowHeight * row, colWidth, rowHeight));
-
- boolean state = disableValueChangeEvent();
- try {
- table.setRowSelectionInterval(row, row);
- } finally {
- setValueChangedDisabledState(state);
- }
- enableButtons();
- } else {
- boolean state = disableValueChangeEvent();
- try {
- table.clearSelection();
- } finally {
- setValueChangedDisabledState(state);
- }
- }
- } else {
- enableButtons();
- }
- }
-
- @Override
- public boolean canBeRemoved() {
- return true;
- }
- };
- }
-
- /**
- * @see org.aavso.tools.vstar.ui.dialog.period.PeriodAnalysisDataTablePane#enableButtons()
- */
- @Override
- protected void enableButtons() {
- super.enableButtons();
- refineButton.setEnabled(true);
- }
-
- /**
- * We send a period analysis selection message when the table selection
- * value has "settled". This event could be consumed by other views such as
- * plots.
- */
- @Override
- public void valueChanged(ListSelectionEvent e) {
- if (isValueChangeDisabled())
- return;
-
- if (e.getSource() == table.getSelectionModel()
- && table.getRowSelectionAllowed() && !e.getValueIsAdjusting()) {
- // Which row in the top hits table was selected?
- int row = table.getSelectedRow();
-
- if (row >= 0) {
- row = table.convertRowIndexToModel(row);
- PeriodAnalysisSelectionMessage message = new PeriodAnalysisSelectionMessage(
- this, model.getDataPointFromRow(row), row);
- message.setTag(Mediator.getParentDialogName(this));
- Mediator.getInstance().getPeriodAnalysisSelectionNotifier()
- .notifyListeners(message);
- }
- }
- }
-
- // Create a period analysis refinement listener which adds refinement
- // results to a collection that is checked to ensure that the user does not
- // select them (or the originating data row) again.
- private Listener createRefinementListener() {
- return new Listener() {
- @Override
- public void update(PeriodAnalysisRefinementMessage info) {
- if (!Mediator.isMsgForDialog(Mediator.getParentDialog(PeriodAnalysisTopHitsTablePane.this), info))
- return;
- resultantDataPoints.addAll(info.getNewTopHits());
- }
-
- @Override
- public boolean canBeRemoved() {
- return true;
- }
- };
- }
-
- /**
- * @see org.aavso.tools.vstar.ui.dialog.period.PeriodAnalysisDataTablePane#startup()
- */
- @Override
- public void startup() {
- super.startup();
-
- periodAnalysisRefinementListener = createRefinementListener();
- Mediator.getInstance().getPeriodAnalysisRefinementNotifier()
- .addListener(periodAnalysisRefinementListener);
- }
-
- /**
- * @see org.aavso.tools.vstar.ui.dialog.period.PeriodAnalysisDataTablePane#cleanup()
- */
- @Override
- public void cleanup() {
- super.cleanup();
-
- Mediator.getInstance().getPeriodAnalysisRefinementNotifier()
- .removeListenerIfWilling(periodAnalysisRefinementListener);
- }
+ private Set refinedDataPoints;
+ private Set resultantDataPoints;
+
+ private JButton refineButton;
+
+ private Listener periodAnalysisRefinementListener;
+
+ /**
+ * Constructor.
+ *
+ * @param topHitsModel The top hits data model.
+ * @param fullDataModel The full data data model.
+ * @param algorithm The period analysis algorithm.
+ */
+ public PeriodAnalysisTopHitsTablePane(PeriodAnalysisDataTableModel topHitsModel,
+ PeriodAnalysisDataTableModel fullDataModel, IPeriodAnalysisAlgorithm algorithm) {
+ super(topHitsModel, algorithm);
+
+ refinedDataPoints = new TreeSet(PeriodAnalysisDataPointComparator.instance);
+
+ resultantDataPoints = new TreeSet(PeriodAnalysisDataPointComparator.instance);
+ }
+
+ protected JPanel createButtonPanel() {
+ JPanel buttonPane = super.createButtonPanel();
+
+ String refineName = algorithm.getRefineByFrequencyName();
+ if (refineName != null) {
+ refineButton = new JButton(refineName);
+ refineButton.setEnabled(false);
+ refineButton.addActionListener(createRefineButtonHandler());
+ buttonPane.add(refineButton, BorderLayout.LINE_START);
+ }
+
+ return buttonPane;
+ }
+
+ // Refine button listener.
+ private ActionListener createRefineButtonHandler() {
+ final JPanel parent = this;
+ return new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ // Collect frequencies to be used in refinement, ensuring that
+ // we don't try to use a frequency that has already been used
+ // for refinement. We also do not want to use the result of a
+ // previous refinement.
+ List freqs = new ArrayList();
+ int[] selectedTableRowIndices = table.getSelectedRows();
+ List inputDataPoints = new ArrayList();
+
+ for (int row : selectedTableRowIndices) {
+ int modelRow = table.convertRowIndexToModel(row);
+ PeriodAnalysisDataPoint dataPoint = model.getDataPointFromRow(modelRow);
+
+ if (!refinedDataPoints.contains(dataPoint)) {
+ inputDataPoints.add(dataPoint);
+ freqs.add(dataPoint.getFrequency());
+ } else {
+ String msg = String.format(
+ "Top Hit with frequency %s" + " and power %s" + " has previously been used.",
+ NumericPrecisionPrefs.formatOther(dataPoint.getFrequency()),
+ NumericPrecisionPrefs.formatOther(dataPoint.getPower()));
+ MessageBox.showErrorDialog(parent, algorithm.getRefineByFrequencyName(), msg);
+ freqs.clear();
+ break;
+ }
+
+ if (resultantDataPoints.contains(dataPoint)) {
+ String msg = String.format(
+ "Top Hit with frequency %s" + " and power %s"
+ + " was generated by %s so cannot be used.",
+ dataPoint.getFrequency(), dataPoint.getPower(), algorithm.getRefineByFrequencyName());
+ MessageBox.showErrorDialog(parent, algorithm.getRefineByFrequencyName(), msg);
+ freqs.clear();
+ break;
+ }
+ }
+
+ if (!freqs.isEmpty()) {
+ try {
+ RefinementParameterDialog dialog = new RefinementParameterDialog(parent, freqs, 6);
+ if (!dialog.isCancelled()) {
+ List variablePeriods = dialog.getVariablePeriods();
+ List lockedPeriods = dialog.getLockedPeriods();
+
+ // Perform a refinement operation and get the new
+ // top-hits resulting from the refinement.
+ List newTopHits = algorithm.refineByFrequency(freqs,
+ variablePeriods, lockedPeriods);
+ // Mark input frequencies as refined so we don't
+ // try to refine them again.
+ refinedDataPoints.addAll(inputDataPoints);
+
+ // Update the model and tell anyone else who might
+ // be interested.
+ Map> data = algorithm.getResultSeries();
+ Map> topHits = algorithm.getTopHits();
+
+ model.setData(topHits);
+
+ PeriodAnalysisRefinementMessage msg = new PeriodAnalysisRefinementMessage(this, data,
+ topHits, newTopHits);
+ msg.setTag(Mediator.getParentDialogName(PeriodAnalysisTopHitsTablePane.this));
+ Mediator.getInstance().getPeriodAnalysisRefinementNotifier().notifyListeners(msg);
+ }
+ } catch (AlgorithmError ex) {
+ MessageBox.showErrorDialog(parent, algorithm.getRefineByFrequencyName(),
+ ex.getLocalizedMessage());
+ } catch (InterruptedException ex) {
+ // Do nothing; just return.
+ }
+ }
+ }
+ };
+ }
+
+ /**
+ * Select the row in the table corresponding to the period analysis selection.
+ * We also enable the "refine" button.
+ */
+ protected Listener createPeriodAnalysisListener() {
+ final Component parent = this;
+
+ return new Listener