diff --git a/pom.xml b/pom.xml
index a71c065..e27af65 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
@@ -106,8 +106,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); @@ -115,30 +116,32 @@ public SearchTask(ExperimentalData curve) { clear(); addListeners(); } - + /** - * Update the best state. The instance of this class stores two objects - * of the type IterativeState: the current state of the optimiser and - * the global best state. Calling this method will check if a new global - * best is found, and if so, this will store its parameters in the corresponding - * variable. This will then be used at the final stage of running the search task, - * comparing the converged result to the global best, and selecting whichever - * has the lowest cost. Such routine is required due to the possibility of - * some optimisers going uphill. + * Update the best state. The instance of this class stores two objects of + * the type IterativeState: the current state of the optimiser and the + * global best state. Calling this method will check if a new global best is + * found, and if so, this will store its parameters in the corresponding + * variable. This will then be used at the final stage of running the search + * task, comparing the converged result to the global best, and selecting + * whichever has the lowest cost. Such routine is required due to the + * possibility of some optimisers going uphill. */ - public void storeState() { - if(best == null || best.getCost() > path.getCost()) + if (best == null || best.getCost() > path.getCost()) { best = new IterativeState(path); + } } private void addListeners() { InterpolationDataset.addListener(e -> { - var p = current.getProblem().getProperties(); - if (p.areThermalPropertiesLoaded()) { - p.useTheoreticalEstimates(curve); + if (current.getProblem() != null) { + var p = current.getProblem().getProperties(); + if (p.areThermalPropertiesLoaded()) { + p.useTheoreticalEstimates(curve); + } } - }); + }); /** * Sets the difference scheme's time limit to the upper bound of the @@ -237,10 +240,7 @@ public void assign(ParameterVector searchParameters) { current.getProblem().assign(searchParameters); curve.getRange().assign(searchParameters); } catch (SolverException e) { - var status = FAILED; - status.setDetails(Details.PARAMETER_VALUES_NOT_SENSIBLE); - setStatus(status); - e.printStackTrace(); + notifyFailedStatus(e); } } @@ -292,8 +292,7 @@ public void run() { try { solveProblemAndCalculateCost(); } catch (SolverException e1) { - System.err.println("Failed on first calculation. Details:"); - e1.printStackTrace(); + notifyFailedStatus(e1); } final int maxIterations = (int) getInstance().getMaxIterations().getValue(); @@ -316,9 +315,7 @@ public void run() { finished = optimiser.iteration(this); } } catch (SolverException e) { - setStatus(FAILED); - System.err.println(this + " failed during execution. Details: "); - e.printStackTrace(); + notifyFailedStatus(e); break outer; } @@ -327,16 +324,16 @@ public void run() { fail.setDetails(MAX_ITERATIONS_REACHED); setStatus(fail); } - + //if global best is better than the converged value - if(best != null && best.getCost() < path.getCost()) { + if (best != null && best.getCost() < path.getCost()) { //assign the global best parameters assign(path.getParameters()); //and try to re-calculate try { solveProblemAndCalculateCost(); } catch (SolverException ex) { - Logger.getLogger(SearchTask.class.getName()).log(Level.SEVERE, null, ex); + notifyFailedStatus(ex); } } @@ -398,6 +395,13 @@ private void runChecks() { } } + + private void notifyFailedStatus(SolverException e1) { + var status = Status.FAILED; + status.setDetails(Details.SOLVER_ERROR); + status.setDetailedMessage(e1.getMessage()); + setStatus(status); + } public void addTaskListener(DataCollectionListener toAdd) { listeners.add(toAdd); @@ -441,41 +445,43 @@ public void setExperimentalCurve(ExperimentalData curve) { } } - + /** - * Will return {@code true} if status could be updated. + * Will return {@code true} if status could be updated. + * * @param status the status of the task - * @return {@code} true if status has been updated. {@code false} if - * the status was already set to {@code status} previously, or if it could - * not be updated at this time. + * @return {@code} true if status has been updated. {@code false} if the + * status was already set to {@code status} previously, or if it could not + * be updated at this time. * @see Calculation.setStatus() */ - public boolean setStatus(Status status) { Objects.requireNonNull(status); - + Status oldStatus = current.getStatus(); - boolean changed = current.setStatus(status) + boolean changed = current.setStatus(status) && (oldStatus != current.getStatus()); if (changed) { notifyStatusListeners(new StateEntry(this, status)); - } - + } + return changed; } /** *
- * Checks if this {@code SearchTask} is ready to be run. Performs basic - * check to see whether the user has uploaded all necessary data. If not, - * will create a status update with information about the missing data. + * Checks if this {@code SearchTask} is ready to be run.Performs basic check + * to see whether the user has uploaded all necessary data. If not, will + * create a status update with information about the missing data. *
* - * @return {@code READY} if the task is ready to be run, {@code DONE} if has - * already been done previously, {@code INCOMPLETE} if some problems exist. - * For the latter, additional details will be available using the - * {@code status.getDetails()} method. + * Status will be set to {@code READY} if the task is ready to be run, + * {@code DONE} if has already been done previously, {@code INCOMPLETE} if + * some problems exist. For the latter, additional details will be available + * using the {@code status.getDetails()} method. * + * + * @param updateStatus */ public void checkProblems(boolean updateStatus) { var status = current.getStatus(); @@ -598,7 +604,7 @@ public void initNormalityTest() { } public void initCorrelationTest() { - correlationTest = instantiate(CorrelationTest.class, CorrelationTest.getSelectedTestDescriptor()); + correlationTest = CorrelationTest.init(); correlationTest.setParent(this); } @@ -618,6 +624,11 @@ public List
@@ -35,6 +38,8 @@ public class Launcher {
private File errorLog;
private final static boolean DEBUG = false;
+ private static final File LOCK = new File("pulse.lock");
+
private Launcher() {
if (!DEBUG) {
arrangeErrorOutput();
@@ -47,28 +52,44 @@ private Launcher() {
*/
public static void main(String[] args) {
new Launcher();
- splashScreen();
+
+ if (!LOCK.exists()) {
- WebLookAndFeel.install(WebDarkSkin.class);
- try {
- UIManager.setLookAndFeel(new WebLookAndFeel());
- } catch (Exception ex) {
- System.err.println("Failed to initialize LaF");
- }
-
- var newVersion = Version.getCurrentVersion().checkNewVersion();
+ try {
+ LOCK.createNewFile();
+ } catch (IOException ex) {
+ Logger.getLogger(Launcher.class.getName()).log(Level.SEVERE, "Unable to create lock file", ex);
+ }
+
+ LOCK.deleteOnExit();
- /* Create and display the form */
- invokeLater(() -> {
- getInstance().setLocationRelativeTo(null);
- getInstance().setVisible(true);
+ splashScreen();
- if (newVersion != null) {
- JOptionPane.showMessageDialog(null, "A new version of this software is available: "
- + newVersion.toString() + "
Please visit the PULsE website for more details.");
+ WebLookAndFeel.install(WebDarkSkin.class);
+ try {
+ UIManager.setLookAndFeel(new WebLookAndFeel());
+ } catch (Exception ex) {
+ System.err.println("Failed to initialize LaF");
}
- });
+ var newVersion = Version.getCurrentVersion().checkNewVersion();
+
+ /* Create and display the form */
+ invokeLater(() -> {
+ getInstance().setLocationRelativeTo(null);
+ getInstance().setVisible(true);
+
+ if (newVersion != null) {
+ JOptionPane.showMessageDialog(null, "A new version of this software is available: "
+ + newVersion.toString() + "
Please visit the PULsE website for more details.");
+ }
+
+ });
+
+ } else {
+ System.out.println("An instance of PULsE is already running!");
+ }
+
}
private static void splashScreen() {
@@ -95,17 +116,7 @@ private void arrangeErrorOutput() {
try {
var dir = new File(decodedPath).getParent();
errorLog = new File(dir + File.separator + "ErrorLog_" + now() + ".log");
- setErr(new PrintStream(errorLog) {
-
- @Override
- public void println(String str) {
- super.println(str);
- JOptionPane.showMessageDialog(null, "An exception has occurred. "
- + "Please check the stored log!", "Exception", JOptionPane.ERROR_MESSAGE);
- }
-
- }
- );
+ setErr(new PrintStream(errorLog));
} catch (FileNotFoundException e) {
System.err.println("Unable to set up error stream");
e.printStackTrace();
diff --git a/src/main/java/pulse/ui/components/Chart.java b/src/main/java/pulse/ui/components/Chart.java
index a485806..2158758 100644
--- a/src/main/java/pulse/ui/components/Chart.java
+++ b/src/main/java/pulse/ui/components/Chart.java
@@ -94,7 +94,7 @@ public void mouseDragged(MouseEvent e) {
//process dragged events
Range range = instance.getSelectedTask()
.getExperimentalCurve().getRange();
- double value = xCoord(e);
+ double value = xCoord(e) / factor; //convert to seconds back from ms -- if needed
if (lowerMarker.getState() != MovableValueMarker.State.IDLE) {
if (range.boundLimits(false).contains(value)) {
@@ -124,8 +124,8 @@ public void mouseDragged(MouseEvent e) {
if (instance.getSelectedTask() == eventTask) {
//update marker values
var segment = eventTask.getExperimentalCurve().getRange().getSegment();
- lowerMarker.setValue(segment.getMinimum());
- upperMarker.setValue(segment.getMaximum());
+ lowerMarker.setValue(segment.getMinimum() * factor); //convert to ms -- if needed
+ upperMarker.setValue(segment.getMaximum() * factor); //convert to ms -- if needed
}
});
} //tasks that have been finihed
@@ -241,11 +241,11 @@ public void plot(SearchTask task, boolean extendedCurve) {
lowerMarker = new MovableValueMarker(segment.getMinimum() * factor);
upperMarker = new MovableValueMarker(segment.getMaximum() * factor);
- final double margin = segment.getMaximum() / 20.0;
+ final double margin = (lowerMarker.getValue() + upperMarker.getValue())/20.0;
//add listener to handle range adjustment
- var lowerMarkerListener = new MouseOnMarkerListener(this, lowerMarker, margin);
- var upperMarkerListener = new MouseOnMarkerListener(this, upperMarker, margin);
+ var lowerMarkerListener = new MouseOnMarkerListener(this, lowerMarker, upperMarker, margin);
+ var upperMarkerListener = new MouseOnMarkerListener(this, upperMarker, upperMarker, margin);
chartPanel.addChartMouseListener(lowerMarkerListener);
chartPanel.addChartMouseListener(upperMarkerListener);
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/listeners/MouseOnMarkerListener.java b/src/main/java/pulse/ui/components/listeners/MouseOnMarkerListener.java
index 4a4d1ef..834dc1c 100644
--- a/src/main/java/pulse/ui/components/listeners/MouseOnMarkerListener.java
+++ b/src/main/java/pulse/ui/components/listeners/MouseOnMarkerListener.java
@@ -27,16 +27,19 @@
*/
public class MouseOnMarkerListener implements ChartMouseListener {
- private final MovableValueMarker marker;
+ private final MovableValueMarker lower;
+ private final MovableValueMarker upper;
+
private final Chart chart;
private final double margin;
private final static Cursor CROSSHAIR = new Cursor(Cursor.CROSSHAIR_CURSOR);
private final static Cursor RESIZE = new Cursor(Cursor.E_RESIZE_CURSOR);
- public MouseOnMarkerListener(Chart chart, MovableValueMarker marker, double margin) {
+ public MouseOnMarkerListener(Chart chart, MovableValueMarker lower, MovableValueMarker upper, double margin) {
this.chart = chart;
- this.marker = marker;
+ this.lower = lower;
+ this.upper = upper;
this.margin = margin;
}
@@ -48,20 +51,29 @@ public void chartMouseClicked(ChartMouseEvent arg0) {
@Override
public void chartMouseMoved(ChartMouseEvent arg0) {
double xCoord = chart.xCoord(arg0.getTrigger());
- highlightMarker(xCoord, marker);
+ highlightMarker(xCoord);
}
- private void highlightMarker(double xCoord, MovableValueMarker marker) {
+ private void highlightMarker(double xCoord) {
- if (xCoord > (marker.getValue() - margin)
- & xCoord < (marker.getValue() + margin)) {
+ if (xCoord > (lower.getValue() - margin)
+ & xCoord < (lower.getValue() + margin)) {
- marker.setState(MovableValueMarker.State.SELECTED);
+ lower.setState(MovableValueMarker.State.SELECTED);
chart.getChartPanel().setCursor(RESIZE);
- } else {
+ }
+ else if (xCoord > (upper.getValue() - margin)
+ & xCoord < (upper.getValue() + margin)) {
+
+ upper.setState(MovableValueMarker.State.SELECTED);
+ chart.getChartPanel().setCursor(RESIZE);
+
+ }
+ else {
- marker.setState(MovableValueMarker.State.IDLE);
+ lower.setState(MovableValueMarker.State.IDLE);
+ upper.setState(MovableValueMarker.State.IDLE);
chart.getChartPanel().setCursor(CROSSHAIR);
}
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