From 7b9081f4a35f5faeb62d13968536bc18dcbce787 Mon Sep 17 00:00:00 2001 From: kotik-coder Date: Wed, 15 Dec 2021 21:04:27 +0100 Subject: [PATCH] v1.93F Fixes Abstract data Made the fields protected for easier access by subclasses ExportManager Introduced a current working directory field, so that the file chooser keeps track of the browsing history. Exporter Fixed directory not updating after selection in the file chooser NetzschCSVReader / PulseCSVReader Added support to different locales (delimiter chars and decimal separator). ReaderManager Importing all files in a directory (e.g. with Linseis format) now invokes an Execution Service to take advantage of the concurrency. Problem It is now possible to select sample thickness as an optimisation variable. NumericPropertyFormatter Added missing condition to skip scientific formatting if html had been disabled. Normality tests Minor fixes to logic Status Prevent status from updating to QUEUED when task is in progress or has failed Launcher Removed pop-up exception windows completely, as this caused uncontrollable breeding of pop-up windows Chart Fixed value markers not taking into account the numerical conversion factor (ms -> s and vice versa). MouseOnMarkerListener Fixed wrong concurrent updates of both value markers when switching statuses. MainGraphFrame Prevented tasks from plotting while being in progress. Avoids ConcurrentModificationException. ExportDialog Changed from Save dialog to Open dialog to avoid ambiguity Other minors changes --- pom.xml | 2 +- src/main/java/pulse/AbstractData.java | 4 +- .../java/pulse/input/ExperimentalData.java | 4 +- .../java/pulse/io/export/ExportManager.java | 5 +- src/main/java/pulse/io/export/Exporter.java | 53 ++++++------ .../java/pulse/io/readers/AbstractReader.java | 1 + .../pulse/io/readers/NetzschCSVReader.java | 86 +++++++++++++------ .../io/readers/NetzschPulseCSVReader.java | 27 +++--- .../java/pulse/io/readers/ReaderManager.java | 33 ++++++- .../pulse/problem/statements/Problem.java | 11 +++ .../properties/NumericPropertyFormatter.java | 4 +- .../properties/NumericPropertyKeyword.java | 11 +-- .../pulse/search/direction/ActiveFlags.java | 4 +- .../statistics/AndersonDarlingTest.java | 7 +- .../java/pulse/search/statistics/KSTest.java | 7 +- .../search/statistics/NormalityTest.java | 23 ++--- src/main/java/pulse/tasks/Calculation.java | 3 +- src/main/java/pulse/ui/Launcher.java | 12 +-- src/main/java/pulse/ui/components/Chart.java | 12 +-- .../listeners/MouseOnMarkerListener.java | 32 ++++--- .../java/pulse/ui/frames/MainGraphFrame.java | 4 +- .../pulse/ui/frames/SearchOptionsFrame.java | 2 +- .../pulse/ui/frames/dialogs/ExportDialog.java | 17 ++-- src/main/resources/NumericProperty.xml | 20 ++--- src/main/resources/Version.txt | 2 +- 25 files changed, 223 insertions(+), 163 deletions(-) diff --git a/pom.xml b/pom.xml index a71c065..88351e3 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ kotik-coder PULsE - 1.93 + 1.93F PULsE Processing Unit for Laser flash Experiments diff --git a/src/main/java/pulse/AbstractData.java b/src/main/java/pulse/AbstractData.java index f1f5ab8..2f58d47 100644 --- a/src/main/java/pulse/AbstractData.java +++ b/src/main/java/pulse/AbstractData.java @@ -32,8 +32,8 @@ public abstract class AbstractData extends PropertyHolder { private int count; - private List time; - private List signal; + protected List time; + protected List signal; private String name; diff --git a/src/main/java/pulse/input/ExperimentalData.java b/src/main/java/pulse/input/ExperimentalData.java index 3b2e5bd..bbbb2e6 100644 --- a/src/main/java/pulse/input/ExperimentalData.java +++ b/src/main/java/pulse/input/ExperimentalData.java @@ -419,7 +419,7 @@ private void doSetRange() { } /** - * Retrieves the + * Retrieves the time limit. * * @see pulse.problem.schemes.DifferenceScheme * @return a double, equal to the last element of the {@code time List}. @@ -427,6 +427,6 @@ private void doSetRange() { @Override public double timeLimit() { return timeAt(indexRange.getUpperBound()); - } + } } diff --git a/src/main/java/pulse/io/export/ExportManager.java b/src/main/java/pulse/io/export/ExportManager.java index ec8c81a..2064c50 100644 --- a/src/main/java/pulse/io/export/ExportManager.java +++ b/src/main/java/pulse/io/export/ExportManager.java @@ -21,6 +21,9 @@ * */ public class ExportManager { + + //current working dir + private static File cwd = null; private ExportManager() { // intentionally blank @@ -85,7 +88,7 @@ public static Exporter findExporter(Class target) public static void askToExport(T target, JFrame parentWindow, String fileTypeLabel) { var exporter = findExporter(target); if (exporter != null) { - exporter.askToExport(target, parentWindow, fileTypeLabel); + cwd = exporter.askToExport(target, parentWindow, fileTypeLabel, cwd); } else { throw new IllegalArgumentException("No exporter for " + target.getClass().getSimpleName()); } diff --git a/src/main/java/pulse/io/export/Exporter.java b/src/main/java/pulse/io/export/Exporter.java index 71cb1ba..d2d35c0 100644 --- a/src/main/java/pulse/io/export/Exporter.java +++ b/src/main/java/pulse/io/export/Exporter.java @@ -89,12 +89,11 @@ public default void export(T target, File directory, Extension extension) { * @param target the exported target * @param parentWindow the parent frame. * @param fileTypeLabel the label describing the specific type of files that - * will be saved. + * @param directory the default directory of the file will be saved. + * @return the directory where files were exported */ - public default void askToExport(T target, JFrame parentWindow, String fileTypeLabel) { - var fileChooser = new JFileChooser(); - var workingDirectory = new File(System.getProperty("user.home")); - fileChooser.setCurrentDirectory(workingDirectory); + public default File askToExport(T target, JFrame parentWindow, String fileTypeLabel, File directory) { + var fileChooser = new JFileChooser(directory); fileChooser.setMultiSelectionEnabled(true); FileNameExtensionFilter choosable = null; @@ -113,29 +112,35 @@ public default void askToExport(T target, JFrame parentWindow, String fileTypeLa var file = fileChooser.getSelectedFile(); var path = file.getPath(); - if (!(fileChooser.getFileFilter() instanceof FileNameExtensionFilter)) { - return; - } + directory = file.isDirectory() ? file : file.getParentFile(); - var currentFilter = (FileNameExtensionFilter) fileChooser.getFileFilter(); - var ext = currentFilter.getExtensions()[0]; + if ((fileChooser.getFileFilter() instanceof FileNameExtensionFilter)) { + + var currentFilter = (FileNameExtensionFilter) fileChooser.getFileFilter(); + var ext = currentFilter.getExtensions()[0]; + + if (!path.contains(".")) { + file = new File(path + "." + ext); + } else { + file = new File(path.substring(0, path.indexOf(".") + 1) + ext); + } + + try { + var fos = new FileOutputStream(file); + printToStream(target, fos, Extension.valueOf(ext.toUpperCase())); + fos.close(); + } catch (IOException e) { + System.err.println("An exception has been encountered while writing the contents of " + + target.getClass().getSimpleName() + " to " + file); + e.printStackTrace(); + } - if (!path.contains(".")) { - file = new File(path + "." + ext); - } - else - file = new File(path.substring(0, path.indexOf(".") + 1) + ext); - - try { - var fos = new FileOutputStream(file); - printToStream(target, fos, Extension.valueOf(ext.toUpperCase())); - fos.close(); - } catch (IOException e) { - System.err.println("An exception has been encountered while writing the contents of " - + target.getClass().getSimpleName() + " to " + file); - e.printStackTrace(); } + } + + return directory; + } /** diff --git a/src/main/java/pulse/io/readers/AbstractReader.java b/src/main/java/pulse/io/readers/AbstractReader.java index a557fcb..d98036e 100644 --- a/src/main/java/pulse/io/readers/AbstractReader.java +++ b/src/main/java/pulse/io/readers/AbstractReader.java @@ -13,6 +13,7 @@ * lists, arrays and containers may (and usually will) change as a result of * using the reader. *

+ * @param */ public interface AbstractReader extends AbstractHandler { diff --git a/src/main/java/pulse/io/readers/NetzschCSVReader.java b/src/main/java/pulse/io/readers/NetzschCSVReader.java index d3320e3..a50d4e7 100644 --- a/src/main/java/pulse/io/readers/NetzschCSVReader.java +++ b/src/main/java/pulse/io/readers/NetzschCSVReader.java @@ -7,10 +7,16 @@ import java.io.File; import java.io.FileReader; import java.io.IOException; +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.text.ParseException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Locale; import java.util.Objects; +import java.util.logging.Level; +import java.util.logging.Logger; import pulse.AbstractData; import pulse.input.ExperimentalData; @@ -44,10 +50,16 @@ public class NetzschCSVReader implements CurveReader { /** * Note comma is included as a delimiter character here. */ - public final static String delims = "[#();,/°Cx%^]+"; - + private final static String ENGLISH_DELIMS = "[#(),/°Cx%^]+"; + private final static String GERMAN_DELIMS = "[#();/°Cx%^]+"; + + private static String delims = ENGLISH_DELIMS; + + //default number format (British format) + private static Locale locale = Locale.ENGLISH; + private NetzschCSVReader() { - // intentionally blank + //intentionally blank } /** @@ -87,19 +99,25 @@ public List read(File file) throws IOException { Objects.requireNonNull(file, Messages.getString("DATReader.1")); ExperimentalData curve = new ExperimentalData(); + + //gets the number format for this locale try (BufferedReader reader = new BufferedReader(new FileReader(file))) { int shotId = determineShotID(reader, file); + + var format = DecimalFormat.getInstance(locale); + format.setGroupingUsed(false); var tempTokens = findLineByLabel(reader, THICKNESS, delims).split(delims); - final double thickness = Double.parseDouble(tempTokens[tempTokens.length - 1]) * TO_METRES; + + final double thickness = format.parse(tempTokens[tempTokens.length - 1]).doubleValue() * TO_METRES; tempTokens = findLineByLabel(reader, DIAMETER, delims).split(delims); - final double diameter = Double.parseDouble(tempTokens[tempTokens.length - 1]) * TO_METRES; + final double diameter = format.parse(tempTokens[tempTokens.length - 1]).doubleValue() * TO_METRES; tempTokens = findLineByLabel(reader, SAMPLE_TEMPERATURE, delims).split(delims); - final double sampleTemperature = Double.parseDouble(tempTokens[tempTokens.length - 1]) + TO_KELVIN; + final double sampleTemperature = format.parse(tempTokens[tempTokens.length - 1]).doubleValue() + TO_KELVIN; /* * Finds the detector keyword. @@ -122,32 +140,52 @@ public List read(File file) throws IOException { curve.setMetadata(met); curve.setRange(new Range(curve.getTimeSequence())); + return new ArrayList<>(Arrays.asList(curve)); + } catch (ParseException ex) { + Logger.getLogger(NetzschCSVReader.class.getName()).log(Level.SEVERE, null, ex); } - return new ArrayList<>(Arrays.asList(curve)); + return null; } - protected static void populate(AbstractData data, BufferedReader reader) throws IOException { + protected static void populate(AbstractData data, BufferedReader reader) throws IOException, ParseException { double time; double power; String[] tokens; + var format = DecimalFormat.getInstance(locale); + format.setGroupingUsed(false); for (String line = reader.readLine(); line != null && !line.trim().isEmpty(); line = reader.readLine()) { tokens = line.split(delims); - time = Double.parseDouble(tokens[0]) * NetzschCSVReader.TO_SECONDS; - power = Double.parseDouble(tokens[1]); + time = format.parse(tokens[0]).doubleValue() * NetzschCSVReader.TO_SECONDS; + power = format.parse(tokens[1]).doubleValue(); data.addPoint(time, power); } } protected static int determineShotID(BufferedReader reader, File file) throws IOException { - String[] shotID = reader.readLine().split(delims); + String shotIDLine = reader.readLine(); + String[] shotID = shotIDLine.split(delims); int shotId = -1; + + if(shotID.length < 3) { + + if(locale == Locale.ENGLISH) { + delims = GERMAN_DELIMS; + locale = Locale.GERMAN; + } + else { + delims = ENGLISH_DELIMS; + locale = Locale.ENGLISH; + } + + shotID = shotIDLine.split(delims); + } //check if first entry makes sense if (!shotID[shotID.length - 2].equalsIgnoreCase(SHOT_DATA)) { @@ -160,19 +198,7 @@ protected static int determineShotID(BufferedReader reader, File file) throws IO return shotId; } - - /* - private double parseDoubleWithComma(String s) { - var format = NumberFormat.getInstance(Locale.GERMANY); - try { - return format.parse(s).doubleValue(); - } catch (ParseException e) { - System.out.println("Couldn't parse double from: " + s); - e.printStackTrace(); - } - return Double.NaN; - } - */ + protected static String findLineByLabel(BufferedReader reader, String label, String delims) throws IOException { String line = ""; @@ -195,7 +221,6 @@ protected static String findLineByLabel(BufferedReader reader, String label, Str return line; } - /** * As this class uses the singleton pattern, only one instance is created * using an empty no-argument constructor. @@ -205,5 +230,14 @@ protected static String findLineByLabel(BufferedReader reader, String label, Str public static CurveReader getInstance() { return instance; } - + + /** + * Get the standard delimiter chars. + * @return delims + */ + + public static String getDelims() { + return delims; + } + } diff --git a/src/main/java/pulse/io/readers/NetzschPulseCSVReader.java b/src/main/java/pulse/io/readers/NetzschPulseCSVReader.java index 2b67fe4..4ba3303 100644 --- a/src/main/java/pulse/io/readers/NetzschPulseCSVReader.java +++ b/src/main/java/pulse/io/readers/NetzschPulseCSVReader.java @@ -4,7 +4,10 @@ import java.io.File; import java.io.FileReader; import java.io.IOException; +import java.text.ParseException; import java.util.Objects; +import java.util.logging.Level; +import java.util.logging.Logger; import pulse.problem.laser.NumericPulseData; import pulse.ui.Messages; @@ -37,10 +40,12 @@ public String getSupportedExtension() { /** * This performs a basic check, finding the shot ID, which is then passed to - * a new {@code NumericPulseData} object. The latter is populated using the - * time-power sequence stored in this file. If the {@value PULSE} keyword is + * a new {@code NumericPulseData} object.The latter is populated using the + * time-power sequence stored in this file.If the {@value PULSE} keyword is * not found, the method will display an error. * + * @param file + * @throws java.io.IOException * @see pulse.io.readers.NetzschCSVReader.read() * @return a new {@code NumericPulseData} object encapsulating the contents * of {@code file} @@ -49,14 +54,14 @@ public String getSupportedExtension() { public NumericPulseData read(File file) throws IOException { Objects.requireNonNull(file, Messages.getString("DATReader.1")); - NumericPulseData data; + NumericPulseData data = null; try (BufferedReader reader = new BufferedReader(new FileReader(file))) { int shotId = NetzschCSVReader.determineShotID(reader, file); data = new NumericPulseData(shotId); - var pulseLabel = NetzschCSVReader.findLineByLabel(reader, PULSE, NetzschCSVReader.delims); + var pulseLabel = NetzschCSVReader.findLineByLabel(reader, PULSE, NetzschCSVReader.getDelims()); if (pulseLabel == null) { System.err.println("Skipping " + file.getName()); @@ -66,24 +71,14 @@ public NumericPulseData read(File file) throws IOException { reader.readLine(); NetzschCSVReader.populate(data, reader); + } catch (ParseException ex) { + Logger.getLogger(NetzschPulseCSVReader.class.getName()).log(Level.SEVERE, null, ex); } return data; } - /* - private double parseDoubleWithComma(String s) { - var format = NumberFormat.getInstance(Locale.GERMANY); - try { - return format.parse(s).doubleValue(); - } catch (ParseException e) { - System.out.println("Couldn't parse double from: " + s); - e.printStackTrace(); - } - return Double.NaN; - } - */ /** * As this class uses the singleton pattern, only one instance is created * using an empty no-argument constructor. diff --git a/src/main/java/pulse/io/readers/ReaderManager.java b/src/main/java/pulse/io/readers/ReaderManager.java index 83c72ee..97bc4a2 100644 --- a/src/main/java/pulse/io/readers/ReaderManager.java +++ b/src/main/java/pulse/io/readers/ReaderManager.java @@ -8,6 +8,12 @@ import java.util.Objects; import java.util.Scanner; import java.util.Set; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.logging.Level; +import java.util.logging.Logger; import java.util.stream.Collectors; import org.apache.commons.io.FileUtils; @@ -207,13 +213,32 @@ public static Set readDirectory(List> readers, File dir throw new IllegalArgumentException("Not a directory: " + directory); } - var list = new HashSet(); - + var es = Executors.newSingleThreadExecutor(); + + var callableList = new ArrayList>(); + for (File f : directory.listFiles()) { - list.add(read(readers, f)); + Callable callable = () -> read(readers, f); + callableList.add(callable); + } + + Set result = new HashSet<>(); + + try { + List> futures = es.invokeAll(callableList); + + for(Future f : futures) + result.add(f.get()); + + } catch (InterruptedException ex) { + Logger.getLogger(ReaderManager.class.getName()).log(Level.SEVERE, + "Reading interrupted when loading files from " + directory.toString(), ex); + } catch (ExecutionException ex) { + Logger.getLogger(ReaderManager.class.getName()).log(Level.SEVERE, + "Error executing read operation using concurrency", ex); } - return list; + return result; } /** diff --git a/src/main/java/pulse/problem/statements/Problem.java b/src/main/java/pulse/problem/statements/Problem.java index acde58f..4b47ebd 100644 --- a/src/main/java/pulse/problem/statements/Problem.java +++ b/src/main/java/pulse/problem/statements/Problem.java @@ -16,6 +16,7 @@ import pulse.math.Segment; import pulse.math.transforms.InvLenSqTransform; import pulse.math.transforms.StandardTransformations; +import pulse.math.transforms.StickTransform; import pulse.problem.laser.DiscretePulse; import pulse.problem.schemes.DifferenceScheme; import pulse.problem.schemes.Grid; @@ -228,6 +229,13 @@ public void optimisationVector(ParameterVector output, List flags) { var key = output.getIndex(i); switch (key) { + case THICKNESS: + final double l = (double) properties.getSampleThickness().getValue(); + var bounds = Segment.boundsFrom(THICKNESS); + output.setParameterBounds(i, bounds); + output.setTransform(i, new StickTransform(bounds)); + output.set(i, l); + break; case DIFFUSIVITY: final double a = (double) properties.getDiffusivity().getValue(); output.setTransform(i, new InvLenSqTransform(properties)); @@ -284,6 +292,9 @@ public void assign(ParameterVector params) throws SolverException { var key = params.getIndex(i); switch (key) { + case THICKNESS: + properties.setSampleThickness(derive(THICKNESS, params.inverseTransform(i) )); + break; case DIFFUSIVITY: properties.setDiffusivity(derive(DIFFUSIVITY, params.inverseTransform(i))); break; diff --git a/src/main/java/pulse/properties/NumericPropertyFormatter.java b/src/main/java/pulse/properties/NumericPropertyFormatter.java index 1f98dc4..f3c83cf 100644 --- a/src/main/java/pulse/properties/NumericPropertyFormatter.java +++ b/src/main/java/pulse/properties/NumericPropertyFormatter.java @@ -74,7 +74,9 @@ public NumberFormat numberFormat(NumericProperty p) { : (double) value; double absAdjustedValue = Math.abs(adjustedValue); - if ((absAdjustedValue > UPPER_LIMIT) || (absAdjustedValue < LOWER_LIMIT && absAdjustedValue > ZERO)) { + if (addHtmlTags && + ( (absAdjustedValue > UPPER_LIMIT) + || (absAdjustedValue < LOWER_LIMIT && absAdjustedValue > ZERO)) ) { //format with scientific notations f = new ScientificFormat(p.getDimensionFactor(), p.getDimensionDelta()); } else { diff --git a/src/main/java/pulse/properties/NumericPropertyKeyword.java b/src/main/java/pulse/properties/NumericPropertyKeyword.java index 3a45116..65cdf46 100644 --- a/src/main/java/pulse/properties/NumericPropertyKeyword.java +++ b/src/main/java/pulse/properties/NumericPropertyKeyword.java @@ -210,23 +210,20 @@ public enum NumericPropertyKeyword { */ OPTICAL_THICKNESS, /** - * Time shift (pulse sync) + * Time shift (pulse sync). */ TIME_SHIFT, /** - * Statistical significance. + * Statistical significance for calculating the critical value. */ SIGNIFICANCE, - /** - * Statistical probability. - */ - PROBABILITY, + /** * Optimiser statistic (usually, RSS). */ OPTIMISER_STATISTIC, /** - * Model selection criterion (AIC, BIC, etc.) + * Model selection criterion (AIC, BIC, etc.). */ MODEL_CRITERION, /** diff --git a/src/main/java/pulse/search/direction/ActiveFlags.java b/src/main/java/pulse/search/direction/ActiveFlags.java index 5edc827..e13af6e 100644 --- a/src/main/java/pulse/search/direction/ActiveFlags.java +++ b/src/main/java/pulse/search/direction/ActiveFlags.java @@ -74,8 +74,8 @@ public static List activeParameters(SearchTask t) { //problem dependent var allActiveParams = selectActiveAndListed(flags, c.getProblem()); //problem independent (lower/upper bound) - var listed = selectActiveAndListed(flags, t.getExperimentalCurve() ); - allActiveParams.addAll( selectActiveAndListed(flags, t.getExperimentalCurve() ) ); + var listed = selectActiveAndListed(flags, t.getExperimentalCurve().getRange() ); + allActiveParams.addAll(listed); return allActiveParams; } diff --git a/src/main/java/pulse/search/statistics/AndersonDarlingTest.java b/src/main/java/pulse/search/statistics/AndersonDarlingTest.java index 60b970a..99ac004 100644 --- a/src/main/java/pulse/search/statistics/AndersonDarlingTest.java +++ b/src/main/java/pulse/search/statistics/AndersonDarlingTest.java @@ -1,7 +1,6 @@ package pulse.search.statistics; import static pulse.properties.NumericProperties.derive; -import static pulse.properties.NumericPropertyKeyword.PROBABILITY; import static pulse.properties.NumericPropertyKeyword.TEST_STATISTIC; import org.apache.commons.math3.stat.descriptive.moment.StandardDeviation; @@ -32,9 +31,9 @@ public boolean test(SearchTask task) { var testResult = GofStat.andersonDarling(residuals, nd); this.setStatistic(derive(TEST_STATISTIC, testResult[0])); - setProbability(derive(PROBABILITY, testResult[1])); - - return significanceTest(); + + //compare the p-value and the significance + return testResult[1] > significance; } @Override diff --git a/src/main/java/pulse/search/statistics/KSTest.java b/src/main/java/pulse/search/statistics/KSTest.java index d350d7e..ceb4ceb 100644 --- a/src/main/java/pulse/search/statistics/KSTest.java +++ b/src/main/java/pulse/search/statistics/KSTest.java @@ -1,7 +1,6 @@ package pulse.search.statistics; import static pulse.properties.NumericProperties.derive; -import static pulse.properties.NumericPropertyKeyword.PROBABILITY; import static pulse.properties.NumericPropertyKeyword.TEST_STATISTIC; import org.apache.commons.math3.distribution.NormalDistribution; @@ -23,8 +22,10 @@ public class KSTest extends NormalityTest { @Override public boolean test(SearchTask task) { evaluate(task); - setProbability(derive(PROBABILITY, TestUtils.kolmogorovSmirnovTest(nd, residuals))); - return significanceTest(); + + this.setStatistic(derive(TEST_STATISTIC, + TestUtils.kolmogorovSmirnovStatistic(nd, residuals))); + return !TestUtils.kolmogorovSmirnovTest(nd, residuals, this.significance); } @Override diff --git a/src/main/java/pulse/search/statistics/NormalityTest.java b/src/main/java/pulse/search/statistics/NormalityTest.java index f7bb3ed..a8de54c 100644 --- a/src/main/java/pulse/search/statistics/NormalityTest.java +++ b/src/main/java/pulse/search/statistics/NormalityTest.java @@ -3,7 +3,6 @@ import static pulse.properties.NumericProperties.def; import static pulse.properties.NumericProperties.derive; import static pulse.properties.NumericProperty.requireType; -import static pulse.properties.NumericPropertyKeyword.PROBABILITY; import static pulse.properties.NumericPropertyKeyword.SIGNIFICANCE; import static pulse.properties.NumericPropertyKeyword.TEST_STATISTIC; @@ -17,28 +16,25 @@ * * For the test to pass, the model residuals need be distributed according to a * (0, σ) normal distribution, where σ is the variance of the model - * residuals. As this is the pre-requisite for optimizers based on the ordinary + * residuals. As this is the pre-requisite for optimisers based on the ordinary * least-square statistic, the normality test can also be used to estimate if a * fit 'failed' or 'succeeded' in describing the data. + * + * The test consists in testing the relation statistic < critValue, + * where the critical value is determined based on a given level of significance. * */ public abstract class NormalityTest extends ResidualStatistic { private double statistic; - private double probability; - private static double significance = (double) def(SIGNIFICANCE).getValue(); + protected static double significance = (double) def(SIGNIFICANCE).getValue(); private static String selectedTestDescriptor; protected NormalityTest() { - probability = (double) def(PROBABILITY).getValue(); statistic = (double) def(TEST_STATISTIC).getValue(); } - public boolean significanceTest() { - return probability > significance; - } - public static NumericProperty getStatisticalSignifiance() { return derive(SIGNIFICANCE, significance); } @@ -48,10 +44,6 @@ public static void setStatisticalSignificance(NumericProperty alpha) { NormalityTest.significance = (double) alpha.getValue(); } - public NumericProperty getProbability() { - return derive(PROBABILITY, probability); - } - public abstract boolean test(SearchTask task); @Override @@ -65,11 +57,6 @@ public void setStatistic(NumericProperty statistic) { this.statistic = (double) statistic.getValue(); } - public void setProbability(NumericProperty probability) { - requireType(probability, PROBABILITY); - this.probability = (double) probability.getValue(); - } - @Override public void set(NumericPropertyKeyword type, NumericProperty property) { if (type == TEST_STATISTIC) { diff --git a/src/main/java/pulse/tasks/Calculation.java b/src/main/java/pulse/tasks/Calculation.java index 797b9f8..60cdd4c 100644 --- a/src/main/java/pulse/tasks/Calculation.java +++ b/src/main/java/pulse/tasks/Calculation.java @@ -186,9 +186,10 @@ public boolean setStatus(Status status) { switch(this.status) { case DONE: + case IN_PROGRESS: + case FAILED: case EXECUTION_ERROR: case INCOMPLETE: - case IN_PROGRESS: //if the TaskManager attempts to run this calculation if(status == Status.QUEUED) return false; diff --git a/src/main/java/pulse/ui/Launcher.java b/src/main/java/pulse/ui/Launcher.java index 65e31e4..1ee2635 100644 --- a/src/main/java/pulse/ui/Launcher.java +++ b/src/main/java/pulse/ui/Launcher.java @@ -95,17 +95,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/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/frames/MainGraphFrame.java b/src/main/java/pulse/ui/frames/MainGraphFrame.java index 3d81254..fff33ec 100644 --- a/src/main/java/pulse/ui/frames/MainGraphFrame.java +++ b/src/main/java/pulse/ui/frames/MainGraphFrame.java @@ -8,6 +8,7 @@ import javax.swing.JInternalFrame; import pulse.tasks.TaskManager; +import pulse.tasks.logs.Status; import pulse.ui.components.Chart; import pulse.ui.components.panels.ChartToolbar; import pulse.ui.components.panels.OpacitySlider; @@ -44,7 +45,8 @@ private void initComponents() { public void plot() { var task = TaskManager.getManagerInstance().getSelectedTask(); - if (task != null) { + //do not plot tasks that are not finished + if (task != null && task.getCurrentCalculation().getStatus() != Status.IN_PROGRESS) { Executors.newSingleThreadExecutor().submit(() -> chart.plot(task, false)); } } diff --git a/src/main/java/pulse/ui/frames/SearchOptionsFrame.java b/src/main/java/pulse/ui/frames/SearchOptionsFrame.java index d8b0b6c..2d7edda 100644 --- a/src/main/java/pulse/ui/frames/SearchOptionsFrame.java +++ b/src/main/java/pulse/ui/frames/SearchOptionsFrame.java @@ -50,7 +50,7 @@ public class SearchOptionsFrame extends JInternalFrame { private final static Font FONT = new Font(getString("PropertyHolderTable.FontName"), ITALIC, 16); private final static List pathSolvers = instancesOf(PathOptimiser.class); - private final NumericPropertyKeyword[] mandatorySelection = new NumericPropertyKeyword[]{DIFFUSIVITY, MAXTEMP}; + private final NumericPropertyKeyword[] mandatorySelection = new NumericPropertyKeyword[]{MAXTEMP}; /** * Create the frame. diff --git a/src/main/java/pulse/ui/frames/dialogs/ExportDialog.java b/src/main/java/pulse/ui/frames/dialogs/ExportDialog.java index f8a9300..ed0f7ee 100644 --- a/src/main/java/pulse/ui/frames/dialogs/ExportDialog.java +++ b/src/main/java/pulse/ui/frames/dialogs/ExportDialog.java @@ -14,6 +14,7 @@ import java.awt.Dimension; import java.io.File; +import java.nio.file.Paths; import java.util.HashMap; import java.util.Map; @@ -81,12 +82,12 @@ public ExportDialog() { } private File directoryQuery() { - var returnVal = fileChooser.showSaveDialog(this); + var returnVal = fileChooser.showOpenDialog(this); File f = null; if (returnVal == APPROVE_OPTION) { - f = fileChooser.getCurrentDirectory(); + dir = f = fileChooser.getSelectedFile(); } return f; @@ -171,8 +172,9 @@ private void initComponents() { fileChooser = new JFileChooser(); fileChooser.setMultiSelectionEnabled(false); fileChooser.setFileSelectionMode(DIRECTORIES_ONLY); - // Checkboxex - dir = fileChooser.getCurrentDirectory(); + + //get cwd + dir = new File("").getAbsoluteFile(); var directoryField = new JTextField(dir.getPath() + separator + projectName + separator); directoryField.setEditable(false); @@ -247,11 +249,8 @@ public void removeUpdate(DocumentEvent e) { var browseBtn = new JButton("Browse..."); - browseBtn.addActionListener(e -> { - if (directoryQuery() != null) { - directoryField.setText(dir.getPath() + separator + projectName + separator); - } - }); + browseBtn.addActionListener(e -> directoryField.setText(directoryQuery() + .getPath() + separator + projectName + separator) ); var exportBtn = new JButton("Export"); diff --git a/src/main/resources/NumericProperty.xml b/src/main/resources/NumericProperty.xml index 89dca9d..9c78b74 100644 --- a/src/main/resources/NumericProperty.xml +++ b/src/main/resources/NumericProperty.xml @@ -1,5 +1,10 @@ + + - - - + - - + minimum="1.0E-6" value="0.001" primitive-type="double" discreet="true" + default-search-variable="false">