statusChangeListeners;
/**
*
@@ -106,8 +105,9 @@ public class SearchTask extends Accessible implements Runnable {
* @param curve the {@code ExperimentalData}
*/
public SearchTask(ExperimentalData curve) {
- current = new Calculation();
- current.setParent(this);
+ this.statusChangeListeners = new CopyOnWriteArrayList<>();
+ this.listeners = new CopyOnWriteArrayList<>();
+ current = new Calculation(this);
this.identifier = new Identifier();
this.curve = curve;
curve.setParent(this);
@@ -285,7 +285,7 @@ public void run() {
/* search cycle */
- /* sets an independent thread for manipulating the buffer */
+ /* sets an independent thread for manipulating the buffer */
List> bufferFutures = new ArrayList<>(bufferSize);
var singleThreadExecutor = Executors.newSingleThreadExecutor();
@@ -598,7 +598,7 @@ public void initNormalityTest() {
}
public void initCorrelationTest() {
- correlationTest = instantiate(CorrelationTest.class, CorrelationTest.getSelectedTestDescriptor());
+ correlationTest = CorrelationTest.init();
correlationTest.setParent(this);
}
@@ -617,6 +617,11 @@ public Calculation getCurrentCalculation() {
public List getStoredCalculations() {
return this.stored;
}
+
+ public void storeCalculation() {
+ var copy = new Calculation(current);
+ stored.add(copy);
+ }
public void switchTo(Calculation calc) {
current.setParent(null);
@@ -625,10 +630,20 @@ public void switchTo(Calculation calc) {
var e = new TaskRepositoryEvent(TaskRepositoryEvent.State.TASK_MODEL_SWITCH, this.getIdentifier());
fireRepositoryEvent(e);
}
+
+ /**
+ * Finds the best calculation by comparing those already stored by their
+ * model selection statistics.
+ * @return the calculation showing the optimal value of the model selection statistic.
+ */
+
+ public Calculation findBestCalculation() {
+ var c = stored.stream().reduce((c1, c2) -> c1.compareTo(c2) > 0 ? c2 : c1);
+ return c.isPresent() ? c.get() : null;
+ }
public void switchToBestModel() {
- var best = stored.stream().reduce((c1, c2) -> c1.compareTo(c2) > 0 ? c2 : c1);
- this.switchTo(best.get());
+ this.switchTo(findBestCalculation());
var e = new TaskRepositoryEvent(TaskRepositoryEvent.State.BEST_MODEL_SELECTED, this.getIdentifier());
fireRepositoryEvent(e);
}
@@ -640,4 +655,4 @@ private void fireRepositoryEvent(TaskRepositoryEvent e) {
}
}
-}
+}
\ No newline at end of file
diff --git a/src/main/java/pulse/tasks/TaskManager.java b/src/main/java/pulse/tasks/TaskManager.java
index 44fa9c2..03aa334 100644
--- a/src/main/java/pulse/tasks/TaskManager.java
+++ b/src/main/java/pulse/tasks/TaskManager.java
@@ -38,6 +38,7 @@
import pulse.tasks.listeners.TaskRepositoryListener;
import pulse.tasks.listeners.TaskSelectionEvent;
import pulse.tasks.listeners.TaskSelectionListener;
+import pulse.tasks.logs.Status;
import pulse.tasks.processing.Result;
import pulse.tasks.processing.ResultFormat;
import pulse.util.Group;
@@ -120,7 +121,7 @@ public static TaskManager getManagerInstance() {
* @param t a {@code SearchTask} that will be executed
*/
public void execute(SearchTask t) {
- t.checkProblems(true);
+ t.checkProblems(t.getCurrentCalculation().getStatus() != Status.DONE);
//try to start cmputation
// notify listeners computation is about to start
@@ -139,12 +140,10 @@ public void execute(SearchTask t) {
current.setResult(new Result(t, ResultFormat.getInstance()));
//notify listeners before the task is re-assigned
notifyListeners(e);
- current.setParent(null);
- t.getStoredCalculations().add(current.copy());
- current.setParent(t);
- } else {
- notifyListeners(e);
+ t.storeCalculation();
}
+ else
+ notifyListeners(e);
});
}
@@ -170,7 +169,6 @@ public void executeAll() {
var queue = tasks.stream().filter(t -> {
switch (t.getCurrentCalculation().getStatus()) {
- case DONE:
case IN_PROGRESS:
case EXECUTION_ERROR:
return false;
diff --git a/src/main/java/pulse/tasks/logs/Details.java b/src/main/java/pulse/tasks/logs/Details.java
index 1552909..9c8f46a 100644
--- a/src/main/java/pulse/tasks/logs/Details.java
+++ b/src/main/java/pulse/tasks/logs/Details.java
@@ -41,7 +41,22 @@ public enum Details {
SIGNIFICANT_CORRELATION_BETWEEN_PARAMETERS,
PARAMETER_VALUES_NOT_SENSIBLE,
MAX_ITERATIONS_REACHED,
- ABNORMAL_DISTRIBUTION_OF_RESIDUALS;
+ ABNORMAL_DISTRIBUTION_OF_RESIDUALS,
+
+ /**
+ * Indicates that the result table had not been updated, as the selected
+ * model produced results worse than expected by the model selection criterion.
+ */
+
+ CALCULATION_RESULTS_WORSE_THAN_PREVIOUSLY_OBTAINED,
+
+
+ /**
+ * Indicates that the result table had been updated, as the current
+ * model selection criterion showed better result than already present.
+ */
+
+ BETTER_CALCULATION_RESULTS_THAN_PREVIOUSLY_OBTAINED;
@Override
public String toString() {
diff --git a/src/main/java/pulse/tasks/processing/CorrelationBuffer.java b/src/main/java/pulse/tasks/processing/CorrelationBuffer.java
index 5464396..31d3ef1 100644
--- a/src/main/java/pulse/tasks/processing/CorrelationBuffer.java
+++ b/src/main/java/pulse/tasks/processing/CorrelationBuffer.java
@@ -22,6 +22,8 @@ public class CorrelationBuffer {
private static Set> excludePairList;
private static Set excludeSingleList;
+ private final static double DEFAULT_THRESHOLD = 1E-3;
+
static {
excludePairList = new HashSet<>();
excludeSingleList = new HashSet<>();
@@ -44,6 +46,27 @@ public void inflate(SearchTask t) {
public void clear() {
params.clear();
}
+
+ /**
+ * Truncates the buffer by excluding nearly-converged results.
+ */
+
+ private void truncate(double threshold) {
+ int i = 0;
+ int size = params.size();
+ final double thresholdSq = threshold*threshold;
+
+ for(i = 0; i < size - 1; i = i + 2) {
+
+ ParameterVector diff = new ParameterVector( params.get(i), params.get(i + 1).subtract(params.get(i) ));
+ if(diff.lengthSq()/params.get(i).lengthSq() < thresholdSq)
+ break;
+ }
+
+ for(int j = size - 1; j > i; j--)
+ params.remove(j);
+
+ }
public Map, Double> evaluate(CorrelationTest t) {
if (params.isEmpty()) {
@@ -54,6 +77,8 @@ public Map, Double> evaluate(CorrelationTe
return null;
}
+ truncate(DEFAULT_THRESHOLD);
+
var indices = params.get(0).getIndices();
var map = indices.stream()
.map(index -> new ImmutableDataEntry<>(index, params.stream().mapToDouble(v -> v.getParameterValue(index)).toArray()))
diff --git a/src/main/java/pulse/ui/components/PulseMainMenu.java b/src/main/java/pulse/ui/components/PulseMainMenu.java
index 992157d..74aeeab 100644
--- a/src/main/java/pulse/ui/components/PulseMainMenu.java
+++ b/src/main/java/pulse/ui/components/PulseMainMenu.java
@@ -51,6 +51,7 @@
import pulse.ui.frames.dialogs.ExportDialog;
import pulse.ui.frames.dialogs.FormattedInputDialog;
import pulse.ui.frames.dialogs.ResultChangeDialog;
+import pulse.util.Reflexive;
@SuppressWarnings("serial")
public class PulseMainMenu extends JMenuBar {
@@ -270,16 +271,30 @@ private JMenu initAnalysisSubmenu() {
JRadioButtonMenuItem corrItem = null;
+ var ct = CorrelationTest.init();
+
for (var corrName : allDescriptors(CorrelationTest.class)) {
corrItem = new JRadioButtonMenuItem(corrName);
corrItems.add(corrItem);
correlationsSubMenu.add(corrItem);
+
+ if(ct.getDescriptor().equalsIgnoreCase(corrName))
+ corrItem.setSelected(true);
+
corrItem.addItemListener(e -> {
if (((AbstractButton) e.getItem()).isSelected()) {
var text = ((AbstractButton) e.getItem()).getText();
- CorrelationTest.setSelectedTestDescriptor(text);
- getManagerInstance().getTaskList().stream().forEach(t -> t.initCorrelationTest());
+ var allTests = Reflexive.instancesOf(CorrelationTest.class);
+ var optionalTest = allTests.stream().filter(test ->
+ test.getDescriptor().equalsIgnoreCase(corrName)).findAny();
+
+ if(optionalTest.isPresent()) {
+ CorrelationTest.getTestDescriptor()
+ .setSelectedDescriptor(optionalTest.get().getClass().getSimpleName());
+ getManagerInstance().getTaskList().stream().forEach(t -> t.initCorrelationTest());
+ }
+
}
});
@@ -294,8 +309,6 @@ private JMenu initAnalysisSubmenu() {
correlationsSubMenu.add(thrItem);
thrItem.addActionListener(e -> thresholdDialog.setVisible(true));
- correlationsSubMenu.getItem(0).setSelected(true);
-
analysisSubMenu.add(correlationsSubMenu);
return analysisSubMenu;
}
diff --git a/src/main/java/pulse/ui/components/ResultTable.java b/src/main/java/pulse/ui/components/ResultTable.java
index b47a0ee..141d0f4 100644
--- a/src/main/java/pulse/ui/components/ResultTable.java
+++ b/src/main/java/pulse/ui/components/ResultTable.java
@@ -8,6 +8,7 @@
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Comparator;
+import java.util.Objects;
import javax.swing.JTable;
import javax.swing.RowSorter;
@@ -79,7 +80,9 @@ public ResultTable(ResultFormat fmt) {
switch (e.getState()) {
case TASK_FINISHED:
var r = t.getCurrentCalculation().getResult();
- invokeLater(() -> ((ResultTableModel) getModel()).addRow(r));
+ var resultTableModel = (ResultTableModel) getModel();
+ Objects.requireNonNull(r, "Task finished with a null result!");
+ invokeLater(() -> resultTableModel.addRow(r));
break;
case TASK_REMOVED:
case TASK_RESET:
@@ -134,7 +137,7 @@ public double[][][] data() {
for (var i = 0; i < data.length; i++) {
for (var j = 0; j < data[0][0].length; j++) {
- property = (NumericProperty) getValueAt(j, i) ;
+ property = (NumericProperty) getValueAt(j, i);
data[i][0][j] = ((Number) property.getValue()).doubleValue()
* property.getDimensionFactor().doubleValue() + property.getDimensionDelta().doubleValue();
data[i][1][j] = property.getError() == null ? 0
@@ -230,6 +233,7 @@ public void undo() {
var instance = TaskManager.getManagerInstance();
instance.getTaskList().stream().map(t -> t.getStoredCalculations()).flatMap(list -> list.stream())
+ .filter(Objects::nonNull)
.forEach(c -> dtm.addRow(c.getResult()));
}
diff --git a/src/main/java/pulse/ui/components/buttons/ExecutionButton.java b/src/main/java/pulse/ui/components/buttons/ExecutionButton.java
index 943ce3e..37b84d5 100644
--- a/src/main/java/pulse/ui/components/buttons/ExecutionButton.java
+++ b/src/main/java/pulse/ui/components/buttons/ExecutionButton.java
@@ -32,14 +32,14 @@ public ExecutionButton() {
this.addActionListener((ActionEvent e) -> {
/*
- * STOP PRESSED?
+ * STOP PRESSED?
*/
if (state == STOP) {
instance.cancelAllTasks();
return;
}
/*
- * EXECUTE PRESSED?
+ * EXECUTE PRESSED?
*/
if (instance.getTaskList().isEmpty()) {
showMessageDialog(getWindowAncestor((Component) e.getSource()),
diff --git a/src/main/java/pulse/ui/components/models/ResultTableModel.java b/src/main/java/pulse/ui/components/models/ResultTableModel.java
index 4bb5ef1..5910a00 100644
--- a/src/main/java/pulse/ui/components/models/ResultTableModel.java
+++ b/src/main/java/pulse/ui/components/models/ResultTableModel.java
@@ -1,23 +1,25 @@
package pulse.ui.components.models;
import static java.lang.Math.abs;
-import static java.util.stream.Collectors.toList;
import static pulse.tasks.processing.AbstractResult.filterProperties;
import java.util.ArrayList;
-import java.util.Comparator;
import java.util.List;
+import java.util.Objects;
import java.util.Optional;
import static javax.swing.SwingUtilities.invokeLater;
import javax.swing.table.DefaultTableModel;
import pulse.properties.NumericProperties;
-import pulse.properties.NumericProperty;
import static pulse.properties.NumericPropertyKeyword.IDENTIFIER;
import static pulse.properties.NumericPropertyKeyword.TEST_TEMPERATURE;
+import pulse.tasks.Calculation;
import pulse.tasks.Identifier;
+import pulse.tasks.SearchTask;
import pulse.tasks.listeners.ResultFormatEvent;
+import pulse.tasks.logs.Details;
+import pulse.tasks.logs.Status;
import pulse.tasks.processing.AbstractResult;
import pulse.tasks.processing.AverageResult;
import pulse.tasks.processing.Result;
@@ -78,9 +80,7 @@ public void changeFormat(ResultFormat fmt) {
results.clear();
this.setColumnIdentifiers(fmt.abbreviations().toArray());
- for (var r : oldResults) {
- addRow(r);
- }
+ oldResults.stream().filter(Objects::nonNull).forEach(r -> addRow(r));
} else {
this.setColumnIdentifiers(fmt.abbreviations().toArray());
@@ -93,21 +93,22 @@ public void changeFormat(ResultFormat fmt) {
}
/**
- * Transforms the result model by merging individual results which:
- * (a) correspond to test temperatures within a specified {@code temperatureDelta}
- * (b) form a single sequence of measurements
- * @param temperatureDelta the maximum difference between the test temperature of two results being merged
+ * Transforms the result model by merging individual results which: (a)
+ * correspond to test temperatures within a specified
+ * {@code temperatureDelta} (b) form a single sequence of measurements
+ *
+ * @param temperatureDelta the maximum difference between the test
+ * temperature of two results being merged
*/
-
public void merge(double temperatureDelta) {
List skipList = new ArrayList<>();
List avgResults = new ArrayList<>();
List sortedResults = new ArrayList<>(results);
-
+
/*sort results in the order of their ids
* This is essential for the algorithm below which assumes the results
* are listed in the order of ascending ids.
- */
+ */
sortedResults.sort((AbstractResult arg0, AbstractResult arg1) -> {
var id1 = arg0.getProperties().get(fmt.indexOf(IDENTIFIER));
var id2 = arg1.getProperties().get(fmt.indexOf(IDENTIFIER));
@@ -146,22 +147,23 @@ public void merge(double temperatureDelta) {
invokeLater(() -> {
setRowCount(0);
results.clear();
- avgResults.stream().forEach(r -> addRow(r));
+ avgResults.stream().filter(Objects::nonNull).forEach(r -> addRow(r));
});
}
-
+
/**
- * Takes a list of results, which should be mandatory sorted in the order of ascending id values,
- * and searches for those results that can be merged with {@code r}, satisfying these criteria:
- * (a) these results correspond to test temperatures within a specified {@code temperatureDelta}
- * (b) they form a single sequence of measurements
+ * Takes a list of results, which should be mandatory sorted in the order of
+ * ascending id values, and searches for those results that can be merged
+ * with {@code r}, satisfying these criteria: (a) these results correspond
+ * to test temperatures within a specified {@code temperatureDelta} (b) they
+ * form a single sequence of measurements
+ *
* @param listOfResults an orderer list of results, as explained above
- * @param r the result of interest
- * @param propertyInterval an interval for the temperature merging
- * @return a group of results
+ * @param r the result of interest
+ * @param propertyInterval an interval for the temperature merging
+ * @return a group of results
*/
-
public List group(List listOfResults, AbstractResult r, double propertyInterval) {
List selection = new ArrayList<>();
@@ -214,8 +216,52 @@ private List tooltips() {
}
public void addRow(AbstractResult result) {
- if (result == null) {
- return;
+ Objects.requireNonNull(result, "Entry added to the results table must not be null");
+
+ //result must have a valid ancestor!
+ var ancestor = Objects.requireNonNull(
+ result.specificAncestor(SearchTask.class),
+ "Result " + result.toString() + " does not belong a SearchTask!");
+
+ //the ancestor then has the SearchTask type
+ SearchTask parentTask = (SearchTask) ancestor;
+
+ //any old result asssociated withis this task
+ var oldResult = results.stream().filter(r
+ -> r.specificAncestor(
+ SearchTask.class) == parentTask).findAny();
+
+ //ignore average results
+ if (result instanceof Result && oldResult.isPresent()) {
+ AbstractResult oldResultExisting = oldResult.get();
+ Optional oldCalculation = parentTask.getStoredCalculations().stream()
+ .filter(c -> c.getResult().equals(oldResultExisting)).findAny();
+
+ //old calculation found
+ if (oldCalculation.isPresent()) {
+
+ //since the task has already been completed anyway
+ Status status = Status.DONE;
+
+ //better result than already present -- update table
+ if (parentTask.getCurrentCalculation().isBetterThan(oldCalculation.get())) {
+ remove(oldResultExisting);
+ status.setDetails(Details.BETTER_CALCULATION_RESULTS_THAN_PREVIOUSLY_OBTAINED);
+ parentTask.setStatus(status);
+ } else {
+ //do not remove result and do not add new result
+ status.setDetails(Details.CALCULATION_RESULTS_WORSE_THAN_PREVIOUSLY_OBTAINED);
+ parentTask.setStatus(status);
+ return;
+ }
+
+ } else {
+ //calculation has been purged -- delete previous result
+
+ remove(oldResultExisting);
+
+ }
+
}
var propertyList = filterProperties(result, fmt);
diff --git a/src/main/java/pulse/ui/components/models/StoredCalculationTableModel.java b/src/main/java/pulse/ui/components/models/StoredCalculationTableModel.java
index e82d734..83255e3 100644
--- a/src/main/java/pulse/ui/components/models/StoredCalculationTableModel.java
+++ b/src/main/java/pulse/ui/components/models/StoredCalculationTableModel.java
@@ -32,8 +32,10 @@ public void update(SearchTask t) {
var list = t.getStoredCalculations();
for (Calculation c : list) {
- var problem = c.getProblem();
- var baseline = c.getProblem().getBaseline();
+ //we assume all problem descriptions contain the word Problem after their titles
+ String problem = c.getProblem().toString().split("Problem")[0] + "