diff --git a/pom.xml b/pom.xml index a71c065..e27af65 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ kotik-coder PULsE - 1.93 + 1.94 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..bdbeabf 100644 --- a/src/main/java/pulse/input/ExperimentalData.java +++ b/src/main/java/pulse/input/ExperimentalData.java @@ -12,7 +12,6 @@ import java.util.ArrayList; import java.util.Comparator; import java.util.List; -import java.util.Optional; import java.util.stream.Collectors; import pulse.AbstractData; @@ -53,11 +52,13 @@ public class ExperimentalData extends AbstractData { * Scientific Instruments, 91(6), 064902. */ public final static int REDUCTION_FACTOR = 32; + + public final static int MAX_REDUCTION_FACTOR = 256; /** * A fail-safe factor. */ - public final static double FAIL_SAFE_FACTOR = 3.0; + public final static double FAIL_SAFE_FACTOR = 10.0; private static Comparator pointComparator = (p1, p2) -> valueOf(p1.getY()).compareTo(valueOf(p2.getY())); @@ -217,19 +218,28 @@ public Point2D maxAdjustedSignal() { * @see getHalfTime() */ public void calculateHalfTime() { - var degraded = runningAverage(REDUCTION_FACTOR); - var max = (max(degraded, pointComparator)); var baseline = new FlatBaseline(); baseline.fitTo(this); - - double halfMax = (max.getY() + baseline.valueAt(0)) / 2.0; - - int cutoffIndex = degraded.indexOf(max); + + int curRedFactor = REDUCTION_FACTOR/2; // reduced twofold since first operation + // in the while loop will increase it likewise + int cutoffIndex = 0; + List degraded = null; //running average + Point2D max = null; + + do { + curRedFactor *= 2; + degraded = runningAverage(curRedFactor); + max = (max(degraded, pointComparator)); + cutoffIndex = degraded.indexOf(max); + } while(cutoffIndex < 1 && curRedFactor < MAX_REDUCTION_FACTOR); + + double halfMax = (max.getY() + baseline.valueAt(0)) / 2.0; degraded = degraded.subList(0, cutoffIndex); - + int index = IndexRange.closestLeft(halfMax, degraded.stream().map(point -> point.getY()).collect(Collectors.toList())); - + if (index < 1) { System.err.println(Messages.getString("ExperimentalData.HalfRiseError")); halfTime = max(getTimeSequence()) / FAIL_SAFE_FACTOR; @@ -419,7 +429,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 +437,6 @@ private void doSetRange() { @Override public double timeLimit() { return timeAt(indexRange.getUpperBound()); - } + } } diff --git a/src/main/java/pulse/input/IndexRange.java b/src/main/java/pulse/input/IndexRange.java index 742d77e..bdf4288 100644 --- a/src/main/java/pulse/input/IndexRange.java +++ b/src/main/java/pulse/input/IndexRange.java @@ -186,24 +186,31 @@ public static int closestRight(double of, List in) { } private static int closest(double of, List in, boolean reverseOrder) { - int sizeMinusOne = Math.max( in.size() - 1, 0); //has to be non-negative - - if (of > in.get(sizeMinusOne)) { - return sizeMinusOne; - } + int sizeMinusOne = in.size() - 1; //has to be non-negative + + int result = 0; + + if (sizeMinusOne < 1) { + result = 0; + } else if (of > in.get(sizeMinusOne)) { + result = sizeMinusOne; + } else { + + int start = reverseOrder ? sizeMinusOne - 1 : 0; + int increment = reverseOrder ? -1 : 1; - int start = reverseOrder ? sizeMinusOne - 1 : 0; - int increment = reverseOrder ? -1 : 1; + for (int i = start; reverseOrder ? (i > -1) : (i < sizeMinusOne); i += increment) { - for (int i = start; reverseOrder ? (i > -1) : (i < sizeMinusOne); i += increment) { + if (between(of, in.get(i), in.get(i + 1))) { + result = i; + break; + } - if (between(of, in.get(i), in.get(i + 1))) { - return i; } } - return 0; + return result; } diff --git a/src/main/java/pulse/input/InterpolationDataset.java b/src/main/java/pulse/input/InterpolationDataset.java index 63c9827..3db3182 100644 --- a/src/main/java/pulse/input/InterpolationDataset.java +++ b/src/main/java/pulse/input/InterpolationDataset.java @@ -52,7 +52,6 @@ public InterpolationDataset() { */ public double interpolateAt(double key) { return interpolation.value(key); - } /** diff --git a/src/main/java/pulse/input/Metadata.java b/src/main/java/pulse/input/Metadata.java index 89e8030..7b650e8 100644 --- a/src/main/java/pulse/input/Metadata.java +++ b/src/main/java/pulse/input/Metadata.java @@ -22,6 +22,7 @@ import pulse.problem.laser.RectangularPulse; import pulse.properties.NumericProperty; import pulse.properties.NumericPropertyKeyword; +import static pulse.properties.NumericPropertyKeyword.FOV_OUTER; import pulse.properties.Property; import pulse.properties.SampleName; import pulse.tasks.Identifier; @@ -199,6 +200,7 @@ public Set listedKeywords() { set.add(LASER_ENERGY); set.add(DETECTOR_GAIN); set.add(DETECTOR_IRIS); + set.add(FOV_OUTER); return set; } 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..dae82be 100644 --- a/src/main/java/pulse/io/readers/NetzschCSVReader.java +++ b/src/main/java/pulse/io/readers/NetzschCSVReader.java @@ -7,10 +7,15 @@ import java.io.File; import java.io.FileReader; import java.io.IOException; +import java.text.DecimalFormat; +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; @@ -39,15 +44,22 @@ public class NetzschCSVReader implements CurveReader { private final static String SHOT_DATA = "Shot_data"; private final static String DETECTOR = "DETECTOR"; private final static String THICKNESS = "Thickness_RT"; + private final static String DETECTOR_SPOT_SIZE = "Spotsize"; private final static String DIAMETER = "Diameter"; /** * 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,32 @@ 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 spot = findLineByLabel(reader, DETECTOR_SPOT_SIZE, THICKNESS, delims); + double spotSize = 0; + if(spot != null) { + var spotTokens = spot.split(delims); + spotSize = format.parse(spotTokens[spotTokens.length - 1]).doubleValue() * TO_METRES; + } + 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. @@ -117,37 +142,57 @@ public List read(File file) throws IOException { var met = new Metadata(derive(TEST_TEMPERATURE, sampleTemperature), shotId); met.set(NumericPropertyKeyword.THICKNESS, derive(NumericPropertyKeyword.THICKNESS, thickness)); met.set(NumericPropertyKeyword.DIAMETER, derive(NumericPropertyKeyword.DIAMETER, diameter)); - met.set(NumericPropertyKeyword.FOV_OUTER, derive(NumericPropertyKeyword.FOV_OUTER, 0.85 * diameter)); + met.set(NumericPropertyKeyword.FOV_OUTER, derive(NumericPropertyKeyword.FOV_OUTER, spotSize != 0 ? spotSize : 0.85 * diameter)); met.set(NumericPropertyKeyword.SPOT_DIAMETER, derive(NumericPropertyKeyword.SPOT_DIAMETER, 0.94 * diameter)); 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)) { @@ -161,23 +206,17 @@ protected static int determineShotID(BufferedReader reader, File file) throws IO } - /* - 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 { + return findLineByLabel(reader, label, "!!!", delims); + } + + protected static String findLineByLabel(BufferedReader reader, String label, String stopLabel, String delims) throws IOException { String line = ""; String[] tokens; + reader.mark(1000); + //find keyword outer: for (line = reader.readLine(); line != null; line = reader.readLine()) { @@ -185,9 +224,17 @@ protected static String findLineByLabel(BufferedReader reader, String label, Str tokens = line.split(delims); for (String token : tokens) { + if (token.equalsIgnoreCase(label)) { break outer; } + + if(token.equalsIgnoreCase(stopLabel)) { + line = null; + reader.reset(); + break outer; + } + } } @@ -195,7 +242,7 @@ 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 +252,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/math/ParameterVector.java b/src/main/java/pulse/math/ParameterVector.java index ca0cb49..295f5a3 100644 --- a/src/main/java/pulse/math/ParameterVector.java +++ b/src/main/java/pulse/math/ParameterVector.java @@ -236,9 +236,8 @@ public List findMalformedElements() { var list = new ArrayList(); for (int i = 0; i < dimension(); i++) { - var property = def(getIndex(i)); - boolean sensible = NumericProperties.isValueSensible(property, get(i)); - if (!sensible) { + var property = NumericProperties.derive(getIndex(i), inverseTransform(i)); + if (!property.validate()) { list.add(property); } } diff --git a/src/main/java/pulse/problem/laser/DiscretePulse.java b/src/main/java/pulse/problem/laser/DiscretePulse.java index dd397e2..d29c073 100644 --- a/src/main/java/pulse/problem/laser/DiscretePulse.java +++ b/src/main/java/pulse/problem/laser/DiscretePulse.java @@ -1,5 +1,7 @@ package pulse.problem.laser; +import java.util.Objects; +import pulse.input.ExperimentalData; import pulse.problem.schemes.Grid; import pulse.problem.statements.Problem; import pulse.problem.statements.Pulse; @@ -37,7 +39,11 @@ public DiscretePulse(Problem problem, Grid grid) { recalculate(); - var data = ((SearchTask) problem.specificAncestor(SearchTask.class)).getExperimentalCurve(); + Object ancestor = + Objects.requireNonNull( problem.specificAncestor(SearchTask.class), + "Problem has not been assigned to a SearchTask"); + + ExperimentalData data = ((SearchTask)ancestor).getExperimentalCurve(); pulse.getPulseShape().init(data, this); pulse.addListener(e -> { @@ -45,6 +51,7 @@ public DiscretePulse(Problem problem, Grid grid) { recalculate(); pulse.getPulseShape().init(data, this); }); + } /** diff --git a/src/main/java/pulse/problem/schemes/ADIScheme.java b/src/main/java/pulse/problem/schemes/ADIScheme.java index bad5501..2ff641f 100644 --- a/src/main/java/pulse/problem/schemes/ADIScheme.java +++ b/src/main/java/pulse/problem/schemes/ADIScheme.java @@ -19,7 +19,7 @@ public abstract class ADIScheme extends DifferenceScheme { * time factor. */ public ADIScheme() { - this(derive(GRID_DENSITY, 30), derive(TAU_FACTOR, 1.0)); + this(derive(GRID_DENSITY, 30), derive(TAU_FACTOR, 0.5)); } /** diff --git a/src/main/java/pulse/problem/schemes/CoupledImplicitScheme.java b/src/main/java/pulse/problem/schemes/CoupledImplicitScheme.java index 6981853..848482b 100644 --- a/src/main/java/pulse/problem/schemes/CoupledImplicitScheme.java +++ b/src/main/java/pulse/problem/schemes/CoupledImplicitScheme.java @@ -4,16 +4,14 @@ import static pulse.properties.NumericProperties.derive; import static pulse.properties.NumericPropertyKeyword.NONLINEAR_PRECISION; -import java.util.List; import java.util.Set; import pulse.problem.schemes.rte.RTECalculationStatus; +import pulse.problem.schemes.solvers.SolverException; import pulse.problem.statements.ParticipatingMedium; import pulse.problem.statements.Problem; import pulse.properties.NumericProperty; import pulse.properties.NumericPropertyKeyword; -import static pulse.properties.NumericPropertyKeyword.BASELINE_INTERCEPT; -import pulse.properties.Property; public abstract class CoupledImplicitScheme extends ImplicitScheme implements FixedPointIterations { @@ -37,17 +35,20 @@ public CoupledImplicitScheme(NumericProperty N, NumericProperty timeFactor, Nume } @Override - public void timeStep(final int m) { + public void timeStep(final int m) throws SolverException { pls = pulse(m); doIterations(getCurrentSolution(), nonlinearPrecision, m); } @Override - public void iteration(final int m) { + public void iteration(final int m) throws SolverException { super.timeStep(m); } - public void finaliseIteration(double[] V) { + @Override + public void finaliseIteration(double[] V) throws SolverException { + FixedPointIterations.super.finaliseIteration(V); + var rte = coupling.getRadiativeTransferEquation(); setCalculationStatus(coupling.getRadiativeTransferEquation().compute(V)); } @@ -55,13 +56,13 @@ public RadiativeTransferCoupling getCoupling() { return coupling; } - public void setCoupling(RadiativeTransferCoupling coupling) { + public final void setCoupling(RadiativeTransferCoupling coupling) { this.coupling = coupling; this.coupling.setParent(this); } @Override - public void finaliseStep() { + public void finaliseStep() throws SolverException { super.finaliseStep(); coupling.getRadiativeTransferEquation().getFluxes().store(); } @@ -104,8 +105,10 @@ public RTECalculationStatus getCalculationStatus() { return calculationStatus; } - public void setCalculationStatus(RTECalculationStatus calculationStatus) { + public void setCalculationStatus(RTECalculationStatus calculationStatus) throws SolverException { this.calculationStatus = calculationStatus; + if(calculationStatus != RTECalculationStatus.NORMAL) + throw new SolverException(calculationStatus.toString()); } public double getCurrentPulseValue() { diff --git a/src/main/java/pulse/problem/schemes/DifferenceScheme.java b/src/main/java/pulse/problem/schemes/DifferenceScheme.java index 309e19b..09ea1cf 100644 --- a/src/main/java/pulse/problem/schemes/DifferenceScheme.java +++ b/src/main/java/pulse/problem/schemes/DifferenceScheme.java @@ -5,16 +5,14 @@ import static pulse.properties.NumericProperty.requireType; import static pulse.properties.NumericPropertyKeyword.TIME_LIMIT; -import java.util.ArrayList; -import java.util.List; import java.util.Set; import pulse.problem.laser.DiscretePulse; +import pulse.problem.schemes.solvers.SolverException; import pulse.problem.statements.Problem; import pulse.properties.NumericProperty; import pulse.properties.NumericPropertyKeyword; -import static pulse.properties.NumericPropertyKeyword.NONLINEAR_PRECISION; -import pulse.properties.Property; +import static pulse.properties.NumericPropertyKeyword.NUMPOINTS; import pulse.util.PropertyHolder; import pulse.util.Reflexive; @@ -108,23 +106,25 @@ public void copyFrom(DifferenceScheme df) { * @see pulse.problem.schemes.Grid.adjustTo() */ protected void prepare(Problem problem) { - discretePulse = problem.discretePulseOn(grid); + if(discretePulse == null) + discretePulse = problem.discretePulseOn(grid); + else + discretePulse.recalculate(); + grid.adjustTo(discretePulse); var hc = problem.getHeatingCurve(); hc.clear(); } - public void runTimeSequence(Problem problem) { + public void runTimeSequence(Problem problem) throws SolverException { runTimeSequence(problem, 0, timeLimit); var curve = problem.getHeatingCurve(); final double maxTemp = (double) problem.getProperties().getMaximumTemperature().getValue(); curve.scale(maxTemp / curve.apparentMaximum()); } - public void runTimeSequence(Problem problem, final double offset, final double endTime) { - final var grid = getGrid(); - + public void runTimeSequence(Problem problem, final double offset, final double endTime) throws SolverException { var curve = problem.getHeatingCurve(); int adjustedNumPoints = (int) curve.getNumPoints().getValue(); @@ -133,7 +133,7 @@ public void runTimeSequence(Problem problem, final double offset, final double e final double timeSegment = (endTime - startTime - offset) / problem.getProperties().timeFactor(); final double tau = grid.getTimeStep(); - for (double dt = 0, factor = 1.0; dt < tau; adjustedNumPoints *= factor) { + for (double dt = 0, factor; dt < tau; adjustedNumPoints *= factor) { dt = timeSegment / (adjustedNumPoints - 1); factor = dt / tau; timeInterval = (int) factor; @@ -160,10 +160,19 @@ public void runTimeSequence(Problem problem, final double offset, final double e curve.addPoint(nextTime, signal()); } + + /** + * If the total number of points added by the procedure + * is actually less than the pre-set number of points -- change that number + */ + + if(curve.actualNumPoints() < (int)curve.getNumPoints().getValue()) { + curve.setNumPoints(derive(NUMPOINTS, curve.actualNumPoints())); + } } - private void timeSegment(final int m1, final int m2) { + private void timeSegment(final int m1, final int m2) throws SolverException { for (int m = m1; m < m2 && normalOperation(); m++) { timeStep(m); finaliseStep(); @@ -176,9 +185,9 @@ public double pulse(final int m) { public abstract double signal(); - public abstract void timeStep(final int m); + public abstract void timeStep(final int m) throws SolverException; - public abstract void finaliseStep(); + public abstract void finaliseStep() throws SolverException; public boolean normalOperation() { return true; @@ -205,7 +214,7 @@ public String toString() { * @return the discrete pulse * @see pulse.problem.statements.Pulse */ - public DiscretePulse getDiscretePulse() { + public final DiscretePulse getDiscretePulse() { return discretePulse; } @@ -215,7 +224,7 @@ public DiscretePulse getDiscretePulse() { * * @return the grid */ - public Grid getGrid() { + public final Grid getGrid() { return grid; } @@ -224,7 +233,7 @@ public Grid getGrid() { * * @param grid the grid */ - public void setGrid(Grid grid) { + public final void setGrid(Grid grid) { this.grid = grid; this.grid.setParent(this); } @@ -236,7 +245,7 @@ public void setGrid(Grid grid) { * * @return the time interval */ - public int getTimeInterval() { + public final int getTimeInterval() { return timeInterval; } @@ -245,7 +254,7 @@ public int getTimeInterval() { * * @param timeInterval a positive integer. */ - public void setTimeInterval(int timeInterval) { + public final void setTimeInterval(int timeInterval) { this.timeInterval = timeInterval; } @@ -255,7 +264,7 @@ public void setTimeInterval(int timeInterval) { * need to be displayed. */ @Override - public boolean areDetailsHidden() { + public final boolean areDetailsHidden() { return hideDetailedAdjustment; } @@ -265,7 +274,7 @@ public boolean areDetailsHidden() { * * @param b a boolean. */ - public static void setDetailsHidden(boolean b) { + public final static void setDetailsHidden(boolean b) { hideDetailedAdjustment = b; } @@ -277,7 +286,7 @@ public static void setDetailsHidden(boolean b) { * @return the {@code NumericProperty} with the type {@code TIME_LIMIT} * @see pulse.properties.NumericPropertyKeyword */ - public NumericProperty getTimeLimit() { + public final NumericProperty getTimeLimit() { return derive(TIME_LIMIT, timeLimit); } @@ -290,7 +299,7 @@ public NumericProperty getTimeLimit() { * {@code TIME_LIMIT} * @see pulse.properties.NumericPropertyKeyword */ - public void setTimeLimit(NumericProperty timeLimit) { + public final void setTimeLimit(NumericProperty timeLimit) { requireType(timeLimit, TIME_LIMIT); this.timeLimit = (double) timeLimit.getValue(); firePropertyChanged(this, timeLimit); diff --git a/src/main/java/pulse/problem/schemes/FixedPointIterations.java b/src/main/java/pulse/problem/schemes/FixedPointIterations.java index 221fe50..f2c3a1a 100644 --- a/src/main/java/pulse/problem/schemes/FixedPointIterations.java +++ b/src/main/java/pulse/problem/schemes/FixedPointIterations.java @@ -1,6 +1,8 @@ package pulse.problem.schemes; import static java.lang.Math.abs; +import java.util.Arrays; +import pulse.problem.schemes.solvers.SolverException; /** * @see Wiki @@ -10,18 +12,19 @@ public interface FixedPointIterations { /** - * Performs iterations until the convergence criterion is satisfied. The - * latter consists in having a difference two consequent iterations of V - * less than the specified error. At the end of each iteration, calls - * {@code finaliseIteration()}. + * Performs iterations until the convergence criterion is satisfied.The + latter consists in having a difference two consequent iterations of V + less than the specified error. At the end of each iteration, calls + {@code finaliseIteration()}. * * @param V the calculation array * @param error used in the convergence criterion * @param m time step + * @throws pulse.problem.schemes.solvers.SolverException if the calculation failed * @see finaliseIteration() * @see iteration() */ - public default void doIterations(double[] V, final double error, final int m) { + public default void doIterations(double[] V, final double error, final int m) throws SolverException { final int N = V.length - 1; @@ -39,16 +42,21 @@ public default void doIterations(double[] V, final double error, final int m) { * Performs an iteration at time {@code m} * * @param m time step + * @throws pulse.problem.schemes.solvers.SolverException if the calculation failed */ - public void iteration(final int m); + public void iteration(final int m) throws SolverException; /** - * Finalises the current iteration. By default, does nothing. + * Finalises the current iteration.By default, does nothing. * * @param V the current iteration + * @throws pulse.problem.schemes.solvers.SolverException if the calculation failed */ - public default void finaliseIteration(double[] V) { - // do nothing + public default void finaliseIteration(double[] V) throws SolverException { + final double threshold = 1E6; + double sum = Arrays.stream(V).sum(); + if( sum > threshold || !Double.isFinite(sum) ) + throw new SolverException("Invalid solution values in V array"); } } diff --git a/src/main/java/pulse/problem/schemes/ImplicitScheme.java b/src/main/java/pulse/problem/schemes/ImplicitScheme.java index f7b73bb..ae80435 100644 --- a/src/main/java/pulse/problem/schemes/ImplicitScheme.java +++ b/src/main/java/pulse/problem/schemes/ImplicitScheme.java @@ -1,5 +1,6 @@ package pulse.problem.schemes; +import pulse.problem.schemes.solvers.SolverException; import static pulse.properties.NumericProperties.derive; import static pulse.properties.NumericPropertyKeyword.GRID_DENSITY; import static pulse.properties.NumericPropertyKeyword.TAU_FACTOR; @@ -65,8 +66,17 @@ protected void prepare(Problem problem) { tridiagonal = new TridiagonalMatrixAlgorithm(getGrid()); } + /** + * Calculates the solution at the boundaries using the boundary conditions + * specific to the problem statement and runs the tridiagonal matrix algorithm + * to evaluate solution at the intermediate grid points. + * @param m the time step + * @throws SolverException if the calculation failed + * @see leftBoundary(), evalRightBoundary(), pulse.problem.schemes.TridiagonalMatrixAlgorithm.sweep() + */ + @Override - public void timeStep(final int m) { + public void timeStep(final int m) throws SolverException { leftBoundary(m); final var V = getCurrentSolution(); final int N = V.length - 1; diff --git a/src/main/java/pulse/problem/schemes/OneDimensionalScheme.java b/src/main/java/pulse/problem/schemes/OneDimensionalScheme.java index d899204..54b3395 100644 --- a/src/main/java/pulse/problem/schemes/OneDimensionalScheme.java +++ b/src/main/java/pulse/problem/schemes/OneDimensionalScheme.java @@ -1,5 +1,6 @@ package pulse.problem.schemes; +import pulse.problem.schemes.solvers.SolverException; import pulse.problem.statements.Problem; import pulse.properties.NumericProperty; @@ -29,8 +30,14 @@ public double signal() { return V[V.length - 1]; } + /** + * Overwrites previously calculated temperature values with the calculations + * made at the current time step + * @throws SolverException if the calculation failed + */ + @Override - public void finaliseStep() { + public void finaliseStep() throws SolverException { System.arraycopy(V, 0, U, 0, V.length); } diff --git a/src/main/java/pulse/problem/schemes/RadiativeTransferCoupling.java b/src/main/java/pulse/problem/schemes/RadiativeTransferCoupling.java index e3e6a8a..73801b7 100644 --- a/src/main/java/pulse/problem/schemes/RadiativeTransferCoupling.java +++ b/src/main/java/pulse/problem/schemes/RadiativeTransferCoupling.java @@ -1,7 +1,5 @@ package pulse.problem.schemes; -import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import pulse.problem.schemes.rte.RadiativeTransferSolver; @@ -38,7 +36,7 @@ public void init(ParticipatingMedium problem, Grid grid) { newRTE(problem, grid); rte.init(problem, grid); }); - + } else { rte.init(problem, grid); } diff --git a/src/main/java/pulse/problem/schemes/rte/Fluxes.java b/src/main/java/pulse/problem/schemes/rte/Fluxes.java index 0dff546..7c3bc78 100644 --- a/src/main/java/pulse/problem/schemes/rte/Fluxes.java +++ b/src/main/java/pulse/problem/schemes/rte/Fluxes.java @@ -1,5 +1,8 @@ package pulse.problem.schemes.rte; +import java.util.Arrays; +import static pulse.problem.schemes.rte.RTECalculationStatus.INVALID_FLUXES; +import static pulse.problem.schemes.rte.RTECalculationStatus.NORMAL; import pulse.properties.NumericProperty; public abstract class Fluxes implements DerivativeCalculator { @@ -20,6 +23,17 @@ public Fluxes(NumericProperty gridDensity, NumericProperty opticalThickness) { public void store() { System.arraycopy(fluxes, 0, storedFluxes, 0, N + 1); // store previous results } + + /** + * Checks whether all stored values are finite. This is equivalent to summing + * all elements and checking whether the sum if finite. + * @return {@code true} if the elements are finite. + */ + + public RTECalculationStatus checkArrays() { + double sum = Arrays.stream(fluxes).sum() + Arrays.stream(storedFluxes).sum(); + return Double.isFinite(sum) ? NORMAL : INVALID_FLUXES; + } /** * Retrieves the currently calculated flux at the {@code i} grid point @@ -63,13 +77,17 @@ public double getOpticalThickness() { return opticalThickness; } - public void setDensity(NumericProperty gridDensity) { + public final void setDensity(NumericProperty gridDensity) { this.N = (int) gridDensity.getValue(); + init(); + } + + public void init() { fluxes = new double[N + 1]; storedFluxes = new double[N + 1]; } - public void setOpticalThickness(NumericProperty opticalThickness) { + public final void setOpticalThickness(NumericProperty opticalThickness) { this.opticalThickness = (double) opticalThickness.getValue(); } diff --git a/src/main/java/pulse/problem/schemes/rte/FluxesAndExplicitDerivatives.java b/src/main/java/pulse/problem/schemes/rte/FluxesAndExplicitDerivatives.java index 0d16403..2b4ac0f 100644 --- a/src/main/java/pulse/problem/schemes/rte/FluxesAndExplicitDerivatives.java +++ b/src/main/java/pulse/problem/schemes/rte/FluxesAndExplicitDerivatives.java @@ -1,5 +1,8 @@ package pulse.problem.schemes.rte; +import java.util.Arrays; +import static pulse.problem.schemes.rte.RTECalculationStatus.INVALID_FLUXES; +import static pulse.problem.schemes.rte.RTECalculationStatus.NORMAL; import pulse.properties.NumericProperty; public class FluxesAndExplicitDerivatives extends Fluxes { @@ -10,10 +13,10 @@ public class FluxesAndExplicitDerivatives extends Fluxes { public FluxesAndExplicitDerivatives(NumericProperty gridDensity, NumericProperty opticalThickness) { super(gridDensity, opticalThickness); } - + @Override - public void setDensity(NumericProperty gridDensity) { - super.setDensity(gridDensity); + public void init() { + super.init(); fd = new double[getDensity() + 1]; fdStored = new double[getDensity() + 1]; } diff --git a/src/main/java/pulse/problem/schemes/rte/RTECalculationStatus.java b/src/main/java/pulse/problem/schemes/rte/RTECalculationStatus.java index 06d7b40..f579004 100644 --- a/src/main/java/pulse/problem/schemes/rte/RTECalculationStatus.java +++ b/src/main/java/pulse/problem/schemes/rte/RTECalculationStatus.java @@ -21,5 +21,12 @@ public enum RTECalculationStatus { /** * The grid density required to reach the error threshold was too large. */ - GRID_TOO_LARGE; + GRID_TOO_LARGE, + + /** + * The radiative fluxes contain illegal values. + */ + + INVALID_FLUXES; + } diff --git a/src/main/java/pulse/problem/schemes/rte/RadiativeTransferSolver.java b/src/main/java/pulse/problem/schemes/rte/RadiativeTransferSolver.java index 98dd903..bb23d09 100644 --- a/src/main/java/pulse/problem/schemes/rte/RadiativeTransferSolver.java +++ b/src/main/java/pulse/problem/schemes/rte/RadiativeTransferSolver.java @@ -55,6 +55,7 @@ public RadiativeTransferSolver() { public void init(ParticipatingMedium p, Grid grid) { if (fluxes != null) { fluxes.setDensity(grid.getGridDensity()); + fluxes.init(); var properties = (ThermoOpticalProperties) p.getProperties(); fluxes.setOpticalThickness(properties.getOpticalThickness()); } diff --git a/src/main/java/pulse/problem/schemes/rte/dom/DiscreteOrdinatesMethod.java b/src/main/java/pulse/problem/schemes/rte/dom/DiscreteOrdinatesMethod.java index 92031b5..5ad9d0d 100644 --- a/src/main/java/pulse/problem/schemes/rte/dom/DiscreteOrdinatesMethod.java +++ b/src/main/java/pulse/problem/schemes/rte/dom/DiscreteOrdinatesMethod.java @@ -77,7 +77,7 @@ public RTECalculationStatus compute(double[] tempArray) { if (status == RTECalculationStatus.NORMAL) { fluxesAndDerivatives(tempArray.length); } - + fireStatusUpdate(status); return status; } @@ -90,9 +90,11 @@ private void fluxesAndDerivatives(final int nExclusive) { var fluxes = (FluxesAndExplicitDerivatives) getFluxes(); for (int i = 0; i < nExclusive; i++) { - fluxes.setFlux(i, DOUBLE_PI * discrete.firstMoment(interpolation[0], i)); + double flux = DOUBLE_PI * discrete.firstMoment(interpolation[0], i); + fluxes.setFlux(i, flux); fluxes.setFluxDerivative(i, -DOUBLE_PI * discrete.firstMoment(interpolation[1], i)); } + } @Override diff --git a/src/main/java/pulse/problem/schemes/rte/dom/DiscreteQuantities.java b/src/main/java/pulse/problem/schemes/rte/dom/DiscreteQuantities.java index bdcf5d3..548e488 100644 --- a/src/main/java/pulse/problem/schemes/rte/dom/DiscreteQuantities.java +++ b/src/main/java/pulse/problem/schemes/rte/dom/DiscreteQuantities.java @@ -5,7 +5,7 @@ * This includes the various intensity and flux arrays used internally by the * integrators. */ -class DiscreteQuantities { +public class DiscreteQuantities { private double[][] I; private double[][] Ik; diff --git a/src/main/java/pulse/problem/schemes/rte/dom/IterativeSolver.java b/src/main/java/pulse/problem/schemes/rte/dom/IterativeSolver.java index 66cf0f0..6bfb45c 100644 --- a/src/main/java/pulse/problem/schemes/rte/dom/IterativeSolver.java +++ b/src/main/java/pulse/problem/schemes/rte/dom/IterativeSolver.java @@ -6,15 +6,11 @@ import static pulse.properties.NumericPropertyKeyword.DOM_ITERATION_ERROR; import static pulse.properties.NumericPropertyKeyword.RTE_MAX_ITERATIONS; -import java.util.ArrayList; -import java.util.List; import java.util.Set; import pulse.problem.schemes.rte.RTECalculationStatus; import pulse.properties.NumericProperty; import pulse.properties.NumericPropertyKeyword; -import static pulse.properties.NumericPropertyKeyword.NONLINEAR_PRECISION; -import pulse.properties.Property; import pulse.util.PropertyHolder; import pulse.util.Reflexive; diff --git a/src/main/java/pulse/problem/schemes/rte/dom/PhaseFunction.java b/src/main/java/pulse/problem/schemes/rte/dom/PhaseFunction.java index 6ed02e4..0892aab 100644 --- a/src/main/java/pulse/problem/schemes/rte/dom/PhaseFunction.java +++ b/src/main/java/pulse/problem/schemes/rte/dom/PhaseFunction.java @@ -6,7 +6,7 @@ public abstract class PhaseFunction implements Reflexive { - private Discretisation intensities; + private final Discretisation intensities; private double anisotropy; private double halfAlbedo; diff --git a/src/main/java/pulse/problem/schemes/solvers/ADILinearisedSolver.java b/src/main/java/pulse/problem/schemes/solvers/ADILinearisedSolver.java index e24120f..7ce79f9 100644 --- a/src/main/java/pulse/problem/schemes/solvers/ADILinearisedSolver.java +++ b/src/main/java/pulse/problem/schemes/solvers/ADILinearisedSolver.java @@ -162,7 +162,7 @@ private void initConst() { } @Override - public void solve(ClassicalProblem2D problem) { + public void solve(ClassicalProblem2D problem) throws SolverException { prepare(problem); runTimeSequence(problem); } diff --git a/src/main/java/pulse/problem/schemes/solvers/ExplicitCoupledSolver.java b/src/main/java/pulse/problem/schemes/solvers/ExplicitCoupledSolver.java index b41cb03..40927d7 100644 --- a/src/main/java/pulse/problem/schemes/solvers/ExplicitCoupledSolver.java +++ b/src/main/java/pulse/problem/schemes/solvers/ExplicitCoupledSolver.java @@ -47,7 +47,7 @@ public ExplicitCoupledSolver(NumericProperty N, NumericProperty timeFactor) { status = RTECalculationStatus.NORMAL; } - private void prepare(ParticipatingMedium problem) { + private void prepare(ParticipatingMedium problem) throws SolverException { super.prepare(problem); var grid = getGrid(); @@ -55,7 +55,8 @@ private void prepare(ParticipatingMedium problem) { coupling.init(problem, grid); rte = coupling.getRadiativeTransferEquation(); fluxes = coupling.getRadiativeTransferEquation().getFluxes(); - + setCalculationStatus(fluxes.checkArrays()); + N = (int) grid.getGridDensity().getValue(); hx = grid.getXStep(); @@ -74,13 +75,9 @@ private void prepare(ParticipatingMedium problem) { @Override public void solve(ParticipatingMedium problem) throws SolverException { - this.prepare(problem); - status = coupling.getRadiativeTransferEquation().compute(getPreviousSolution()); + this.prepare(problem); + setCalculationStatus(coupling.getRadiativeTransferEquation().compute(getPreviousSolution())); runTimeSequence(problem); - - if (status != RTECalculationStatus.NORMAL) { - throw new SolverException(status.toString()); - } } @Override @@ -89,7 +86,7 @@ public boolean normalOperation() { } @Override - public void timeStep(int m) { + public void timeStep(int m) throws SolverException { pls = pulse(m); doIterations(getCurrentSolution(), nonlinearPrecision, m); } @@ -111,8 +108,9 @@ public void iteration(final int m) { } @Override - public void finaliseIteration(double[] V) { - status = rte.compute(V); + public void finaliseIteration(double[] V) throws SolverException { + FixedPointIterations.super.finaliseIteration(V); + setCalculationStatus(rte.compute(V)); } @Override @@ -121,7 +119,7 @@ public double phi(final int i) { } @Override - public void finaliseStep() { + public void finaliseStep() throws SolverException { super.finaliseStep(); coupling.getRadiativeTransferEquation().getFluxes().store(); } @@ -130,7 +128,7 @@ public RadiativeTransferCoupling getCoupling() { return coupling; } - public void setCoupling(RadiativeTransferCoupling coupling) { + public final void setCoupling(RadiativeTransferCoupling coupling) { this.coupling = coupling; this.coupling.setParent(this); } @@ -155,5 +153,11 @@ public DifferenceScheme copy() { public Class domain() { return ParticipatingMedium.class; } + + public void setCalculationStatus(RTECalculationStatus calculationStatus) throws SolverException { + this.status = calculationStatus; + if(status != RTECalculationStatus.NORMAL) + throw new SolverException(status.toString()); + } } diff --git a/src/main/java/pulse/problem/schemes/solvers/ExplicitLinearisedSolver.java b/src/main/java/pulse/problem/schemes/solvers/ExplicitLinearisedSolver.java index 3e0fc4c..5d3a6f9 100644 --- a/src/main/java/pulse/problem/schemes/solvers/ExplicitLinearisedSolver.java +++ b/src/main/java/pulse/problem/schemes/solvers/ExplicitLinearisedSolver.java @@ -49,6 +49,7 @@ public class ExplicitLinearisedSolver extends ExplicitScheme implements Solver { @@ -44,14 +45,16 @@ private void prepare(PenetrationProblem problem) { final double Bi1H = (double) problem.getProperties().getHeatLoss().getValue() * grid.getXStep(); final double hx = grid.getXStep(); + + absorption = problem.getAbsorptionModel(); + HH = hx * hx; _2Bi1HTAU = 2.0 * Bi1H * tau; b11 = 1.0 / (1.0 + 2.0 * tau / HH * (1 + Bi1H)); - absorption = problem.getAbsorptionModel(); final double EPS = 1E-7; - rearAbsorption = tau * absorption.absorption(LASER, (N - EPS) * hx); - frontAbsorption = tau * absorption.absorption(LASER, 0.0); + rearAbsorption = tau * absorption.absorption(LASER, (N - EPS) * hx); + frontAbsorption = tau * absorption.absorption(LASER, 0.0) + 2.0*tau/hx; var tridiagonal = new TridiagonalMatrixAlgorithm(grid) { @@ -61,7 +64,7 @@ public double phi(final int i) { } }; - + // coefficients for difference equation tridiagonal.setCoefA(1. / HH); tridiagonal.setCoefB(1. / tau + 2. / HH); @@ -73,13 +76,13 @@ public double phi(final int i) { } @Override - public void solve(PenetrationProblem problem) { + public void solve(PenetrationProblem problem) throws SolverException { prepare(problem); runTimeSequence(problem); } @Override - public void timeStep(final int m) { + public void timeStep(final int m) throws SolverException { pls = pulse(m); super.timeStep(m); } diff --git a/src/main/java/pulse/problem/schemes/solvers/MixedCoupledSolver.java b/src/main/java/pulse/problem/schemes/solvers/MixedCoupledSolver.java index 2fcaea6..463ee02 100644 --- a/src/main/java/pulse/problem/schemes/solvers/MixedCoupledSolver.java +++ b/src/main/java/pulse/problem/schemes/solvers/MixedCoupledSolver.java @@ -7,27 +7,21 @@ import static pulse.properties.NumericPropertyKeyword.SCHEME_WEIGHT; import static pulse.properties.NumericPropertyKeyword.TAU_FACTOR; -import java.util.List; import java.util.Set; import pulse.problem.schemes.CoupledImplicitScheme; import pulse.problem.schemes.DifferenceScheme; import pulse.problem.schemes.TridiagonalMatrixAlgorithm; -import pulse.problem.schemes.rte.Fluxes; -import pulse.problem.schemes.rte.RTECalculationStatus; import pulse.problem.schemes.rte.RadiativeTransferSolver; import pulse.problem.statements.ParticipatingMedium; import pulse.problem.statements.model.ThermoOpticalProperties; import pulse.properties.NumericProperty; import pulse.properties.NumericPropertyKeyword; -import static pulse.properties.NumericPropertyKeyword.NONLINEAR_PRECISION; -import pulse.properties.Property; import pulse.ui.Messages; public class MixedCoupledSolver extends CoupledImplicitScheme implements Solver { private RadiativeTransferSolver rte; - private Fluxes fluxes; private int N; private double hx; @@ -64,7 +58,7 @@ public MixedCoupledSolver(NumericProperty N, NumericProperty timeFactor, Numeric sigma = (double) def(SCHEME_WEIGHT).getValue(); } - private void prepare(ParticipatingMedium problem) { + private void prepare(ParticipatingMedium problem) throws SolverException { super.prepare(problem); var grid = getGrid(); @@ -73,31 +67,30 @@ private void prepare(ParticipatingMedium problem) { coupling.init(problem, grid); rte = coupling.getRadiativeTransferEquation(); - var U = getPreviousSolution(); - N = (int) grid.getGridDensity().getValue(); hx = grid.getXStep(); tau = grid.getTimeStep(); Bi1 = (double) problem.getProperties().getHeatLoss().getValue(); - - fluxes = rte.getFluxes(); - + var tridiagonal = new TridiagonalMatrixAlgorithm(grid) { @Override public double phi(int i) { + var fluxes = rte.getFluxes(); return A * fluxes.meanFluxDerivative(i) + B * (fluxes.meanFluxDerivative(i - 1) + fluxes.meanFluxDerivative(i + 1)); } @Override public double beta(final double f, final double phi, final int i) { + var U = getPreviousSolution(); return super.beta(f + ONE_MINUS_SIGMA * (U[i] - 2.0 * U[i - 1] + U[i - 2]) / HX2, TAU0_NP * phi, i); } - + @Override public void evaluateBeta(final double[] U) { + var fluxes = rte.getFluxes(); final double phiSecond = A * fluxes.meanFluxDerivative(1) + B * (fluxes.meanFluxDerivativeFront() + fluxes.meanFluxDerivative(2)); setBeta(2, beta(U[1] / tau, phiSecond, 2)); @@ -150,15 +143,7 @@ private void initConst(ParticipatingMedium problem) { public void solve(ParticipatingMedium problem) throws SolverException { this.prepare(problem); initConst(problem); - - setCalculationStatus(rte.compute(getPreviousSolution())); this.runTimeSequence(problem); - - var status = getCalculationStatus(); - if (status != RTECalculationStatus.NORMAL) { - throw new SolverException(status.toString()); - } - } @Override @@ -171,6 +156,7 @@ public double pulse(final int m) { @Override public double firstBeta(final int m) { + var fluxes = rte.getFluxes(); var U = getPreviousSolution(); final double phi = TAU0_NP * fluxes.fluxDerivativeFront(); return (_2TAUHX @@ -180,6 +166,7 @@ public double firstBeta(final int m) { @Override public double evalRightBoundary(final int m, final double alphaN, final double betaN) { + var fluxes = rte.getFluxes(); final double phi = TAU0_NP * fluxes.fluxDerivativeRear(); final var U = getPreviousSolution(); return (sigma * betaN + HX2_2TAU * U[N] + 0.5 * HX2 * phi @@ -229,4 +216,4 @@ public DifferenceScheme copy() { return new MixedCoupledSolver(grid.getGridDensity(), grid.getTimeFactor(), getTimeLimit()); } -} +} \ No newline at end of file diff --git a/src/main/java/pulse/problem/schemes/solvers/MixedLinearisedSolver.java b/src/main/java/pulse/problem/schemes/solvers/MixedLinearisedSolver.java index 2816684..979d50f 100644 --- a/src/main/java/pulse/problem/schemes/solvers/MixedLinearisedSolver.java +++ b/src/main/java/pulse/problem/schemes/solvers/MixedLinearisedSolver.java @@ -139,7 +139,7 @@ public double firstBeta(final int m) { } @Override - public void solve(ClassicalProblem problem) { + public void solve(ClassicalProblem problem) throws SolverException { this.prepare(problem); runTimeSequence(problem); } diff --git a/src/main/java/pulse/problem/statements/ClassicalProblem.java b/src/main/java/pulse/problem/statements/ClassicalProblem.java index 381e33a..72796cc 100644 --- a/src/main/java/pulse/problem/statements/ClassicalProblem.java +++ b/src/main/java/pulse/problem/statements/ClassicalProblem.java @@ -1,8 +1,21 @@ package pulse.problem.statements; +import java.util.List; +import java.util.Set; +import pulse.math.ParameterVector; +import pulse.math.Segment; +import pulse.math.transforms.StickTransform; import pulse.problem.schemes.DifferenceScheme; import pulse.problem.schemes.solvers.ImplicitLinearisedSolver; +import pulse.problem.schemes.solvers.SolverException; import pulse.problem.statements.model.ThermalProperties; +import pulse.properties.Flag; +import static pulse.properties.NumericProperties.def; +import static pulse.properties.NumericProperties.derive; +import pulse.properties.NumericProperty; +import static pulse.properties.NumericProperty.requireType; +import pulse.properties.NumericPropertyKeyword; +import static pulse.properties.NumericPropertyKeyword.SOURCE_GEOMETRIC_FACTOR; import pulse.ui.Messages; /** @@ -12,16 +25,27 @@ */ public class ClassicalProblem extends Problem { + private double bias; + public ClassicalProblem() { super(); + bias = (double) def(SOURCE_GEOMETRIC_FACTOR).getValue(); setPulse(new Pulse()); } public ClassicalProblem(Problem p) { super(p); + bias = (double) def(SOURCE_GEOMETRIC_FACTOR).getValue(); setPulse(new Pulse(p.getPulse())); } + @Override + public Set listedKeywords() { + var set = super.listedKeywords(); + set.add(SOURCE_GEOMETRIC_FACTOR); + return set; + } + @Override public Class defaultScheme() { return ImplicitLinearisedSolver.class; @@ -52,4 +76,58 @@ public Problem copy() { return new ClassicalProblem(this); } + public NumericProperty getGeometricFactor() { + return derive(SOURCE_GEOMETRIC_FACTOR, bias); + } + + public void setGeometricFactor(NumericProperty bias) { + requireType(bias, SOURCE_GEOMETRIC_FACTOR); + this.bias = (double) bias.getValue(); + firePropertyChanged(this, bias); + } + + @Override + public void set(NumericPropertyKeyword type, NumericProperty value) { + super.set(type, value); + if (type == SOURCE_GEOMETRIC_FACTOR) { + setGeometricFactor(value); + } + } + + @Override + public void optimisationVector(ParameterVector output, List flags) { + + super.optimisationVector(output, flags); + + for (int i = 0, size = output.dimension(); i < size; i++) { + + var key = output.getIndex(i); + + if (key == SOURCE_GEOMETRIC_FACTOR) { + var bounds = Segment.boundsFrom(SOURCE_GEOMETRIC_FACTOR); + output.setParameterBounds(i, bounds); + output.setTransform(i, new StickTransform(bounds)); + output.set(i, bias); + } + + } + + } + + @Override + public void assign(ParameterVector params) throws SolverException { + super.assign(params); + for (int i = 0, size = params.dimension(); i < size; i++) { + + double value = params.get(i); + var key = params.getIndex(i); + + if (key == SOURCE_GEOMETRIC_FACTOR) { + setGeometricFactor(derive(SOURCE_GEOMETRIC_FACTOR, value)); + } + + } + + } + } diff --git a/src/main/java/pulse/problem/statements/DiathermicMedium.java b/src/main/java/pulse/problem/statements/DiathermicMedium.java index 2082d03..b2aca28 100644 --- a/src/main/java/pulse/problem/statements/DiathermicMedium.java +++ b/src/main/java/pulse/problem/statements/DiathermicMedium.java @@ -34,11 +34,9 @@ */ public class DiathermicMedium extends ClassicalProblem { - private final static int DEFAULT_CURVE_POINTS = 300; public DiathermicMedium() { super(); - getHeatingCurve().setNumPoints(derive(NUMPOINTS, DEFAULT_CURVE_POINTS)); } public DiathermicMedium(Problem p) { @@ -95,7 +93,7 @@ public void assign(ParameterVector params) throws SolverException { break; case HEAT_LOSS: if (properties.areThermalPropertiesLoaded()) { - properties.emissivity(); + properties.calculateEmissivity(); final double emissivity = (double) properties.getEmissivity().getValue(); properties .setDiathermicCoefficient(derive(DIATHERMIC_COEFFICIENT, emissivity / (2.0 - emissivity))); diff --git a/src/main/java/pulse/problem/statements/NonlinearProblem.java b/src/main/java/pulse/problem/statements/NonlinearProblem.java index 3bfc634..5097ab3 100644 --- a/src/main/java/pulse/problem/statements/NonlinearProblem.java +++ b/src/main/java/pulse/problem/statements/NonlinearProblem.java @@ -1,26 +1,26 @@ package pulse.problem.statements; +import java.util.List; import static pulse.properties.NumericProperties.derive; import static pulse.properties.NumericPropertyKeyword.CONDUCTIVITY; import static pulse.properties.NumericPropertyKeyword.DENSITY; -import static pulse.properties.NumericPropertyKeyword.HEAT_LOSS; import static pulse.properties.NumericPropertyKeyword.SPECIFIC_HEAT; import static pulse.properties.NumericPropertyKeyword.SPOT_DIAMETER; import static pulse.properties.NumericPropertyKeyword.TEST_TEMPERATURE; -import java.util.List; import java.util.Set; import pulse.input.ExperimentalData; import pulse.math.ParameterVector; import pulse.math.Segment; -import pulse.math.transforms.StandardTransformations; +import pulse.math.transforms.StickTransform; import pulse.problem.schemes.DifferenceScheme; import pulse.problem.schemes.ImplicitScheme; import pulse.problem.schemes.solvers.SolverException; import pulse.properties.Flag; import pulse.properties.NumericProperty; import pulse.properties.NumericPropertyKeyword; +import static pulse.properties.NumericPropertyKeyword.LASER_ENERGY; import pulse.ui.Messages; public class NonlinearProblem extends ClassicalProblem { @@ -66,54 +66,54 @@ public NumericProperty getThermalConductivity() { return derive(CONDUCTIVITY, getProperties().thermalConductivity()); } + /** + * + * Does the same as super-class method plus updates the laser energy, if needed. + * @param params + * @throws pulse.problem.schemes.solvers.SolverException + * @see pulse.problem.statements.Problem.getPulse() + * + */ + @Override - public void optimisationVector(ParameterVector output, List flags) { - super.optimisationVector(output, flags); - int size = output.dimension(); - var properties = getProperties(); - - for (int i = 0; i < size; i++) { - - var key = output.getIndex(i); + public void assign(ParameterVector params) throws SolverException { + super.assign(params); + getProperties().calculateEmissivity(); - if (key == HEAT_LOSS) { + for (int i = 0, size = params.dimension(); i < size; i++) { - var bounds = new Segment(0.0, properties.maxBiot()); - final double Bi1 = (double) properties.getHeatLoss().getValue(); - output.setTransform(i, StandardTransformations.ABS); - output.set(i, Bi1); - output.setParameterBounds(i, bounds); + double value = params.inverseTransform(i); + NumericPropertyKeyword key = params.getIndex(i); + if (key == LASER_ENERGY) { + this.getPulse().setLaserEnergy(derive(key, value)); } } - } - + /** - * Assigns parameter values of this {@code Problem} using the optimisation - * vector {@code params}. Only those parameters will be updated, the types - * of which are listed as indices in the {@code params} vector. - * - * @param params the optimisation vector, containing a similar set of - * parameters to this {@code Problem} - * @throws SolverException - * @see listedTypes() + * + * Does the same as super-class method plus extracts the laser energy and stores it in the {@code output}, if needed. + * @param output + * @param flags + * @see pulse.problem.statements.Problem.getPulse() + * */ + @Override - public void assign(ParameterVector params) throws SolverException { - super.assign(params); - var p = getProperties(); - - for (int i = 0, size = params.dimension(); i < size; i++) { - - var key = params.getIndex(i); - - if (key == HEAT_LOSS) { + public void optimisationVector(ParameterVector output, List flags) { + super.optimisationVector(output, flags); + + for (int i = 0, size = output.dimension(); i < size; i++) { - p.setHeatLoss(derive(HEAT_LOSS, params.inverseTransform(i))); - p.emissivity(); + var key = output.getIndex(i); + if(key == LASER_ENERGY) { + var bounds = Segment.boundsFrom(LASER_ENERGY); + output.setParameterBounds(i, bounds); + output.setTransform(i, new StickTransform(bounds)); + output.set(i, (double) getPulse().getLaserEnergy().getValue()); } } diff --git a/src/main/java/pulse/problem/statements/ParticipatingMedium.java b/src/main/java/pulse/problem/statements/ParticipatingMedium.java index c61083f..5674dcc 100644 --- a/src/main/java/pulse/problem/statements/ParticipatingMedium.java +++ b/src/main/java/pulse/problem/statements/ParticipatingMedium.java @@ -1,14 +1,11 @@ package pulse.problem.statements; -import static pulse.math.transforms.StandardTransformations.LOG; import static pulse.properties.NumericProperties.derive; -import static pulse.properties.NumericPropertyKeyword.NUMPOINTS; import java.util.List; import pulse.math.ParameterVector; import pulse.math.Segment; -import pulse.math.transforms.AtanhTransform; import pulse.math.transforms.StickTransform; import pulse.math.transforms.Transformable; import pulse.problem.schemes.DifferenceScheme; @@ -17,15 +14,14 @@ import pulse.problem.statements.model.ThermalProperties; import pulse.problem.statements.model.ThermoOpticalProperties; import pulse.properties.Flag; +import static pulse.properties.NumericPropertyKeyword.OPTICAL_THICKNESS; +import static pulse.properties.NumericPropertyKeyword.PLANCK_NUMBER; import pulse.ui.Messages; public class ParticipatingMedium extends NonlinearProblem { - private final static int DEFAULT_CURVE_POINTS = 300; - public ParticipatingMedium() { super(); - getHeatingCurve().setNumPoints(derive(NUMPOINTS, DEFAULT_CURVE_POINTS)); setComplexity(ProblemComplexity.HIGH); } @@ -44,8 +40,8 @@ public void optimisationVector(ParameterVector output, List flags) { super.optimisationVector(output, flags); var properties = (ThermoOpticalProperties) getProperties(); - Segment bounds; - double value = 0; + Segment bounds = null; + double value; Transformable transform; for (int i = 0, size = output.dimension(); i < size; i++) { @@ -54,30 +50,28 @@ public void optimisationVector(ParameterVector output, List flags) { switch (key) { case PLANCK_NUMBER: - bounds = new Segment(1E-5, properties.maxNp()); + final double lowerBound = Segment.boundsFrom(PLANCK_NUMBER).getMinimum(); + bounds = new Segment(lowerBound, properties.maxNp()); value = (double) properties.getPlanckNumber().getValue(); - transform = new AtanhTransform(bounds); break; case OPTICAL_THICKNESS: value = (double) properties.getOpticalThickness().getValue(); - bounds = new Segment(1E-8, 1E5); - transform = LOG; - break; + bounds = Segment.boundsFrom(OPTICAL_THICKNESS); + break; case SCATTERING_ALBEDO: value = (double) properties.getScatteringAlbedo().getValue(); bounds = new Segment(0.0, 1.0); - transform = new StickTransform(bounds); break; case SCATTERING_ANISOTROPY: value = (double) properties.getScatteringAnisostropy().getValue(); bounds = new Segment(-1.0, 1.0); - transform = new StickTransform(bounds); break; default: continue; } + transform = new StickTransform(bounds); output.setTransform(i, transform); output.set(i, value); output.setParameterBounds(i, bounds); @@ -89,8 +83,9 @@ public void optimisationVector(ParameterVector output, List flags) { @Override public void assign(ParameterVector params) throws SolverException { super.assign(params); + var properties = (ThermoOpticalProperties) getProperties(); - + for (int i = 0, size = params.dimension(); i < size; i++) { var type = params.getIndex(i); @@ -103,17 +98,13 @@ public void assign(ParameterVector params) throws SolverException { case OPTICAL_THICKNESS: properties.set(type, derive(type, params.inverseTransform(i))); break; - case HEAT_LOSS: - case DIFFUSIVITY: - properties.emissivity(); - break; default: break; } } - + } @Override diff --git a/src/main/java/pulse/problem/statements/PenetrationProblem.java b/src/main/java/pulse/problem/statements/PenetrationProblem.java index bca2c67..46a2b84 100644 --- a/src/main/java/pulse/problem/statements/PenetrationProblem.java +++ b/src/main/java/pulse/problem/statements/PenetrationProblem.java @@ -1,14 +1,8 @@ package pulse.problem.statements; -import static pulse.math.transforms.StandardTransformations.LOG; -import static pulse.properties.NumericProperties.derive; -import static pulse.properties.NumericPropertyKeyword.NUMPOINTS; - import java.util.List; import pulse.math.ParameterVector; -import pulse.math.Segment; -import static pulse.math.transforms.StandardTransformations.ABS; import pulse.problem.schemes.DifferenceScheme; import pulse.problem.schemes.solvers.ImplicitTranslucentSolver; import pulse.problem.schemes.solvers.SolverException; @@ -21,8 +15,6 @@ public class PenetrationProblem extends ClassicalProblem { - private final static int DEFAULT_CURVE_POINTS = 300; - private InstanceDescriptor instanceDescriptor = new InstanceDescriptor( "Absorption Model Selector", AbsorptionModel.class); @@ -31,7 +23,6 @@ public class PenetrationProblem extends ClassicalProblem { public PenetrationProblem() { super(); - getHeatingCurve().setNumPoints(derive(NUMPOINTS, DEFAULT_CURVE_POINTS)); instanceDescriptor.setSelectedDescriptor(BeerLambertAbsorption.class.getSimpleName()); instanceDescriptor.addListener(() -> initAbsorption()); absorption.setParent(this); @@ -72,56 +63,13 @@ public InstanceDescriptor getAbsorptionSelector() { @Override public void optimisationVector(ParameterVector output, List flags) { super.optimisationVector(output, flags); - - for (int i = 0, size = output.dimension(); i < size; i++) { - var key = output.getIndex(i); - double value = 0; - - switch (key) { - case LASER_ABSORPTIVITY: - value = (double) (absorption.getLaserAbsorptivity()).getValue(); - break; - case THERMAL_ABSORPTIVITY: - value = (double) (absorption.getThermalAbsorptivity()).getValue(); - break; - case COMBINED_ABSORPTIVITY: - value = (double) (absorption.getCombinedAbsorptivity()).getValue(); - break; - default: - continue; - } - - //do this for the listed key values - output.setTransform(i, ABS); - output.set(i, value); - output.setParameterBounds(i, new Segment(1E-2, 1000.0)); - - } - + absorption.optimisationVector(output, flags); } @Override public void assign(ParameterVector params) throws SolverException { super.assign(params); - - double value; - - for (int i = 0, size = params.dimension(); i < size; i++) { - var key = params.getIndex(i); - - switch (key) { - case LASER_ABSORPTIVITY: - case THERMAL_ABSORPTIVITY: - case COMBINED_ABSORPTIVITY: - value = params.inverseTransform(i); - break; - default: - continue; - } - - absorption.set(key, derive(key, value)); - - } + absorption.assign(params); } @Override @@ -139,4 +87,4 @@ public Problem copy() { return new PenetrationProblem(this); } -} +} \ No newline at end of file diff --git a/src/main/java/pulse/problem/statements/Problem.java b/src/main/java/pulse/problem/statements/Problem.java index acde58f..d0e8adc 100644 --- a/src/main/java/pulse/problem/statements/Problem.java +++ b/src/main/java/pulse/problem/statements/Problem.java @@ -16,6 +16,8 @@ import pulse.math.Segment; import pulse.math.transforms.InvLenSqTransform; import pulse.math.transforms.StandardTransformations; +import static pulse.math.transforms.StandardTransformations.ABS; +import pulse.math.transforms.StickTransform; import pulse.problem.laser.DiscretePulse; import pulse.problem.schemes.DifferenceScheme; import pulse.problem.schemes.Grid; @@ -23,7 +25,6 @@ import pulse.problem.schemes.solvers.SolverException; import pulse.problem.statements.model.ThermalProperties; import pulse.properties.Flag; -import static pulse.properties.NumericProperties.def; import pulse.properties.NumericProperty; import pulse.properties.NumericPropertyKeyword; import static pulse.properties.NumericPropertyKeyword.DIFFUSIVITY; @@ -155,7 +156,7 @@ public HeatingCurve getHeatingCurve() { return curve; } - public Pulse getPulse() { + public final Pulse getPulse() { return pulse; } @@ -165,7 +166,7 @@ public Pulse getPulse() { * * @param pulse a {@code Pulse} object */ - public void setPulse(Pulse pulse) { + public final void setPulse(Pulse pulse) { this.pulse = pulse; pulse.setParent(this); } @@ -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)); @@ -236,11 +244,13 @@ public void optimisationVector(ParameterVector output, List flags) { break; case MAXTEMP: final double signalHeight = (double) properties.getMaximumTemperature().getValue(); - output.set(i, signalHeight); + output.setTransform(i, ABS); output.setParameterBounds(i, new Segment(0.5 * signalHeight, 1.5 * signalHeight)); + output.set(i, signalHeight); break; case HEAT_LOSS: final double Bi = (double) properties.getHeatLoss().getValue(); + output.setParameterBounds(i, Segment.boundsFrom(HEAT_LOSS)); setHeatLossParameter(output, i, Bi); break; case TIME_SHIFT: @@ -249,22 +259,14 @@ public void optimisationVector(ParameterVector output, List flags) { output.setParameterBounds(i, new Segment(-magnitude, magnitude)); break; default: - continue; } } } - //TODO remove atanh transform and replace with abs protected void setHeatLossParameter(ParameterVector output, int i, double Bi) { - if (output.getTransform(i) == null) { - final double min = (double) def(HEAT_LOSS).getMinimum(); - final double max = (double) def(HEAT_LOSS).getMaximum(); - var bounds = new Segment(min, properties.areThermalPropertiesLoaded() ? properties.maxBiot() : max); - output.setTransform(i, StandardTransformations.ABS); - output.setParameterBounds(i, bounds); - } + output.setTransform(i, StandardTransformations.ABS); output.set(i, Bi); } @@ -278,12 +280,26 @@ protected void setHeatLossParameter(ParameterVector output, int i, double Bi) { @Override public void assign(ParameterVector params) throws SolverException { baseline.assign(params); + + List malformedList = params.findMalformedElements(); + + if(!malformedList.isEmpty()) { + StringBuilder sb = new StringBuilder("Cannot assign values: "); + malformedList.forEach(p -> + sb.append(String.format("%n %-25s", p.toString())) + ); + throw new SolverException(sb.toString()); + } + for (int i = 0, size = params.dimension(); i < size; i++) { double value = params.get(i); 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; @@ -297,7 +313,6 @@ public void assign(ParameterVector params) throws SolverException { curve.set(TIME_SHIFT, derive(TIME_SHIFT, value)); break; default: - continue; } } @@ -379,11 +394,11 @@ public String toString() { return this.getClass().getSimpleName(); } - public ProblemComplexity getComplexity() { + public final ProblemComplexity getComplexity() { return complexity; } - public void setComplexity(ProblemComplexity complexity) { + public final void setComplexity(ProblemComplexity complexity) { this.complexity = complexity; } diff --git a/src/main/java/pulse/problem/statements/model/AbsorptionModel.java b/src/main/java/pulse/problem/statements/model/AbsorptionModel.java index 4a27a0f..434db3c 100644 --- a/src/main/java/pulse/problem/statements/model/AbsorptionModel.java +++ b/src/main/java/pulse/problem/statements/model/AbsorptionModel.java @@ -10,22 +10,25 @@ import java.util.List; import java.util.Map; import java.util.Set; +import pulse.math.ParameterVector; +import pulse.math.Segment; +import static pulse.math.transforms.StandardTransformations.ABS; +import pulse.math.transforms.Transformable; +import pulse.problem.schemes.solvers.SolverException; +import pulse.properties.Flag; +import static pulse.properties.NumericProperties.derive; import pulse.properties.NumericProperty; import pulse.properties.NumericPropertyKeyword; import static pulse.properties.NumericPropertyKeyword.COMBINED_ABSORPTIVITY; -import static pulse.properties.NumericPropertyKeyword.DIFFUSIVITY; -import static pulse.properties.NumericPropertyKeyword.HEAT_LOSS; -import static pulse.properties.NumericPropertyKeyword.MAXTEMP; -import static pulse.properties.NumericPropertyKeyword.THICKNESS; -import pulse.properties.Property; import pulse.util.PropertyHolder; import pulse.util.Reflexive; +import pulse.search.Optimisable; -public abstract class AbsorptionModel extends PropertyHolder implements Reflexive { +public abstract class AbsorptionModel extends PropertyHolder implements Reflexive, Optimisable { private Map absorptionMap; - + protected AbsorptionModel() { setPrefix("Absorption model"); absorptionMap = new HashMap<>(); @@ -100,5 +103,58 @@ public Set listedKeywords() { set.add(COMBINED_ABSORPTIVITY); return set; } + + @Override + public void optimisationVector(ParameterVector output, List flags) { + for (int i = 0, size = output.dimension(); i < size; i++) { + var key = output.getIndex(i); + double value = 0; + + Transformable transform = ABS; + output.setParameterBounds(i, new Segment(1E-2, 1000.0)); + + switch (key) { + case LASER_ABSORPTIVITY: + value = (double) (getLaserAbsorptivity()).getValue(); + break; + case THERMAL_ABSORPTIVITY: + value = (double) (getThermalAbsorptivity()).getValue(); + break; + case COMBINED_ABSORPTIVITY: + value = (double) (getCombinedAbsorptivity()).getValue(); + break; + default: + continue; + } + + //do this for the listed key values + output.setTransform(i, transform); + output.set(i, value); + + } + + } + + @Override + public void assign(ParameterVector params) throws SolverException { + double value; + + for (int i = 0, size = params.dimension(); i < size; i++) { + var key = params.getIndex(i); + + switch (key) { + case LASER_ABSORPTIVITY: + case THERMAL_ABSORPTIVITY: + case COMBINED_ABSORPTIVITY: + value = params.inverseTransform(i); + break; + default: + continue; + } + + set(key, derive(key, value)); + + } + } } diff --git a/src/main/java/pulse/problem/statements/model/BeerLambertAbsorption.java b/src/main/java/pulse/problem/statements/model/BeerLambertAbsorption.java index c771987..3171335 100644 --- a/src/main/java/pulse/problem/statements/model/BeerLambertAbsorption.java +++ b/src/main/java/pulse/problem/statements/model/BeerLambertAbsorption.java @@ -2,6 +2,10 @@ public class BeerLambertAbsorption extends AbsorptionModel { + public BeerLambertAbsorption() { + super(); + } + @Override public double absorption(SpectralRange range, double y) { double a = (double) (this.getAbsorptivity(range).getValue()); diff --git a/src/main/java/pulse/problem/statements/model/Insulator.java b/src/main/java/pulse/problem/statements/model/Insulator.java index b4c37b9..0cbba0c 100644 --- a/src/main/java/pulse/problem/statements/model/Insulator.java +++ b/src/main/java/pulse/problem/statements/model/Insulator.java @@ -4,12 +4,10 @@ import static pulse.properties.NumericProperties.derive; import static pulse.properties.NumericPropertyKeyword.REFLECTANCE; -import java.util.List; import java.util.Set; import pulse.properties.NumericProperty; import pulse.properties.NumericPropertyKeyword; -import pulse.properties.Property; public class Insulator extends AbsorptionModel { diff --git a/src/main/java/pulse/problem/statements/model/ThermalProperties.java b/src/main/java/pulse/problem/statements/model/ThermalProperties.java index 8cce847..1039f25 100644 --- a/src/main/java/pulse/problem/statements/model/ThermalProperties.java +++ b/src/main/java/pulse/problem/statements/model/ThermalProperties.java @@ -1,6 +1,7 @@ package pulse.problem.statements.model; import static java.lang.Math.PI; +import java.util.List; import static pulse.input.InterpolationDataset.getDataset; import static pulse.input.InterpolationDataset.StandartType.HEAT_CAPACITY; import static pulse.properties.NumericProperties.def; @@ -9,10 +10,13 @@ import static pulse.properties.NumericPropertyKeyword.*; import java.util.Set; +import java.util.stream.Collectors; import pulse.input.ExperimentalData; import pulse.input.InterpolationDataset; import pulse.input.InterpolationDataset.StandartType; +import pulse.math.Segment; +import pulse.math.transforms.StickTransform; import pulse.problem.statements.Pulse2D; import pulse.properties.NumericProperty; import pulse.properties.NumericPropertyKeyword; @@ -67,14 +71,20 @@ public ThermalProperties(ThermalProperties p) { fill(); } + public List findMalformedProperties() { + var list = this.numericData().stream() + .filter(np -> !np.validate()).collect(Collectors.toList()); + return list; + } + private void fill() { var rhoCurve = getDataset(StandartType.DENSITY); var cpCurve = getDataset(StandartType.HEAT_CAPACITY); if (rhoCurve != null) { - rhoCurve.interpolateAt(T); + rho = rhoCurve.interpolateAt(T); } if (cpCurve != null) { - cpCurve.interpolateAt(T); + cP = cpCurve.interpolateAt(T); } } @@ -270,15 +280,14 @@ public NumericProperty getThermalConductivity() { return derive(CONDUCTIVITY, thermalConductivity()); } - public void emissivity() { - setEmissivity(derive(EMISSIVITY, Bi * thermalConductivity() / (4. * Math.pow(T, 3) * l * STEFAN_BOTLZMAN))); - } - - public double maxBiot() { - double lambda = thermalConductivity(); - return 4.0 * STEFAN_BOTLZMAN * Math.pow(T, 3) * l / lambda; + public void calculateEmissivity() { + double newEmissivity = Bi * thermalConductivity() / (4. * Math.pow(T, 3) * l * STEFAN_BOTLZMAN); + var transform = new StickTransform(new Segment(0.01, 1.0)); + setEmissivity(derive(EMISSIVITY, + transform.transform(newEmissivity)) + ); } - + public double biot() { double lambda = thermalConductivity(); return 4.0 * emissivity * STEFAN_BOTLZMAN * Math.pow(T, 3) * l / lambda; @@ -329,7 +338,6 @@ public NumericProperty getEmissivity() { public void setEmissivity(NumericProperty e) { requireType(e, EMISSIVITY); this.emissivity = (double) e.getValue(); - setHeatLoss(derive(HEAT_LOSS, biot())); } @Override @@ -339,7 +347,12 @@ public String getDescriptor() { @Override public String toString() { - return "Show Details..."; + StringBuilder sb = new StringBuilder(getDescriptor()); + sb.append(":"); + sb.append(String.format("%n %-25s", this.getDiffusivity())); + sb.append(String.format("%n %-25s", this.getMaximumTemperature())); + sb.append(String.format("%n %-25s", this.getHeatLoss())); + return sb.toString(); } } diff --git a/src/main/java/pulse/problem/statements/model/ThermoOpticalProperties.java b/src/main/java/pulse/problem/statements/model/ThermoOpticalProperties.java index f6304e3..feafb90 100644 --- a/src/main/java/pulse/problem/statements/model/ThermoOpticalProperties.java +++ b/src/main/java/pulse/problem/statements/model/ThermoOpticalProperties.java @@ -9,19 +9,11 @@ import static pulse.properties.NumericPropertyKeyword.SCATTERING_ALBEDO; import static pulse.properties.NumericPropertyKeyword.SCATTERING_ANISOTROPY; -import java.util.List; import java.util.Set; import pulse.input.ExperimentalData; import pulse.properties.NumericProperty; import pulse.properties.NumericPropertyKeyword; -import static pulse.properties.NumericPropertyKeyword.DENSITY; -import static pulse.properties.NumericPropertyKeyword.DIFFUSIVITY; -import static pulse.properties.NumericPropertyKeyword.HEAT_LOSS; -import static pulse.properties.NumericPropertyKeyword.MAXTEMP; -import static pulse.properties.NumericPropertyKeyword.SPECIFIC_HEAT; -import static pulse.properties.NumericPropertyKeyword.THICKNESS; -import pulse.properties.Property; public class ThermoOpticalProperties extends ThermalProperties { @@ -155,5 +147,17 @@ public void useTheoreticalEstimates(ExperimentalData c) { public String getDescriptor() { return "Thermo-Physical & Optical Properties"; } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(super.toString()); + sb.append(String.format("%n %-25s", this.getOpticalThickness())); + sb.append(String.format("%n %-25s", this.getPlanckNumber())); + sb.append(String.format("%n %-25s", this.getScatteringAlbedo())); + sb.append(String.format("%n %-25s", this.getScatteringAnisostropy())); + sb.append(String.format("%n %-25s", this.getSpecificHeat())); + sb.append(String.format("%n %-25s", this.getDensity())); + return sb.toString(); + } } diff --git a/src/main/java/pulse/properties/NumericProperties.java b/src/main/java/pulse/properties/NumericProperties.java index cdda622..cd5279e 100644 --- a/src/main/java/pulse/properties/NumericProperties.java +++ b/src/main/java/pulse/properties/NumericProperties.java @@ -42,15 +42,16 @@ public static boolean isValueSensible(NumericProperty property, Number val) { } double v = val.doubleValue(); - final double EPS = 1E-12; - - if (v > property.getMaximum().doubleValue() + EPS) { - return false; + boolean ok = true; + + if( !Double.isFinite(v) + || v > property.getMaximum().doubleValue() + EPS + || v < property.getMinimum().doubleValue() - EPS) { + ok = false; } - return v >= property.getMinimum().doubleValue() - EPS; - + return ok; } public static String printRangeAndNumber(NumericProperty p, Number value) { @@ -102,7 +103,9 @@ public static int compare(NumericProperty a, NumericProperty b) { * Searches for the default {@code NumericProperty} corresponding to * {@code keyword} in the list of pre-defined properties loaded from the * respective {@code .xml} file, and if found creates a new - * {@NumericProperty} which will replicate all field of the latter, but will + * { + * + * @NumericProperty} which will replicate all field of the latter, but will * set its value to {@code value}. * * @param keyword one of the constant {@code NumericPropertyKeyword}s 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..c9e63cb 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, /** @@ -349,7 +346,14 @@ public enum NumericPropertyKeyword { * Levenberg-Marquardt damping ratio. A zero value presents pure Levenberg * damping. A value of 1 gives pure Marquardt damping. */ - DAMPING_RATIO; + DAMPING_RATIO, + + /** + * Determines how much weight is attributed to the front-face light source + * compared to rear face. Can be a number between zero and unity. + */ + + SOURCE_GEOMETRIC_FACTOR; public static Optional findAny(String key) { return Arrays.asList(values()).stream().filter(keys -> keys.toString().equalsIgnoreCase(key)).findAny(); 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/direction/CompositePathOptimiser.java b/src/main/java/pulse/search/direction/CompositePathOptimiser.java index 99a243f..9b4c0bf 100644 --- a/src/main/java/pulse/search/direction/CompositePathOptimiser.java +++ b/src/main/java/pulse/search/direction/CompositePathOptimiser.java @@ -1,5 +1,6 @@ package pulse.search.direction; +import java.util.Arrays; import static pulse.properties.NumericProperties.compare; import java.util.List; @@ -70,6 +71,10 @@ public boolean iteration(SearchTask task) throws SolverException { // new set of parameters determined through search var candidateParams = parameters.sum(dir.multiply(step)); + if( Arrays.stream( candidateParams.getData() ).anyMatch(el -> !Double.isFinite(el) ) ) { + throw new SolverException("Illegal candidate parameters: not finite! " + p.getIteration()); + } + task.assign(new ParameterVector(parameters, candidateParams)); // assign new parameters double newCost = task.solveProblemAndCalculateCost(); // calculate the sum of squared residuals diff --git a/src/main/java/pulse/search/direction/LMOptimiser.java b/src/main/java/pulse/search/direction/LMOptimiser.java index 72c8b83..80bd5e5 100644 --- a/src/main/java/pulse/search/direction/LMOptimiser.java +++ b/src/main/java/pulse/search/direction/LMOptimiser.java @@ -1,5 +1,6 @@ package pulse.search.direction; +import java.util.Arrays; import static pulse.math.linear.SquareMatrix.asSquareMatrix; import static pulse.properties.NumericProperties.compare; import static pulse.properties.NumericProperties.def; @@ -76,8 +77,14 @@ public boolean iteration(SearchTask task) throws SolverException { var lmDirection = getSolver().direction(p); var candidate = parameters.sum(lmDirection); + + if( Arrays.stream( candidate.getData() ).anyMatch(el -> !Double.isFinite(el) ) ) { + throw new SolverException("Illegal candidate parameters: not finite! " + p.getIteration()); + } + task.assign(new ParameterVector( parameters, candidate)); // assign new parameters + double newCost = task.solveProblemAndCalculateCost(); // calculate the sum of squared residuals /* @@ -123,7 +130,11 @@ public void prepare(SearchTask task) throws SolverException { // the Jacobian is then used to calculate the 'gradient' Vector g1 = halfGradient(p); // g1 p.setGradient(g1); - + + if(Arrays.stream(g1.getData()).anyMatch(v -> !Double.isFinite(v))) { + throw new SolverException("Could not calculate objective function gradient"); + } + // the Hessian is then regularised by adding labmda*I var hessian = p.getNonregularisedHessian(); var damping = (levenbergDamping(hessian).multiply(dampingRatio) @@ -158,7 +169,7 @@ public void prepare(SearchTask task) throws SolverException { public RectangularMatrix jacobian(SearchTask task) throws SolverException { var residualCalculator = task.getCurrentCalculation().getOptimiserStatistic(); - + var p = ((LMPath) task.getIterativeState()); final var params = p.getParameters(); @@ -192,7 +203,7 @@ public RectangularMatrix jacobian(SearchTask task) throws SolverException { } } - + // revert to original params task.assign(params); diff --git a/src/main/java/pulse/search/direction/PathOptimiser.java b/src/main/java/pulse/search/direction/PathOptimiser.java index d033dc8..b11adc0 100644 --- a/src/main/java/pulse/search/direction/PathOptimiser.java +++ b/src/main/java/pulse/search/direction/PathOptimiser.java @@ -208,11 +208,11 @@ public static void setInstance(PathOptimiser selectedPathOptimiser) { selectedPathOptimiser.setParent(TaskManager.getManagerInstance()); } - protected DirectionSolver getSolver() { + protected final DirectionSolver getSolver() { return solver; } - protected void setSolver(DirectionSolver solver) { + protected final void setSolver(DirectionSolver solver) { this.solver = solver; } 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/CorrelationTest.java b/src/main/java/pulse/search/statistics/CorrelationTest.java index f724a75..67191bc 100644 --- a/src/main/java/pulse/search/statistics/CorrelationTest.java +++ b/src/main/java/pulse/search/statistics/CorrelationTest.java @@ -7,18 +7,34 @@ import pulse.properties.NumericProperty; import pulse.properties.NumericPropertyKeyword; +import pulse.util.InstanceDescriptor; import pulse.util.PropertyHolder; import pulse.util.Reflexive; public abstract class CorrelationTest extends PropertyHolder implements Reflexive { private static double threshold = (double) def(CORRELATION_THRESHOLD).getValue(); - private static String selectedTestDescriptor; + private static InstanceDescriptor instanceDescriptor + = new InstanceDescriptor( + "Correlation Test Selector", CorrelationTest.class); + + static { + instanceDescriptor.setSelectedDescriptor(PearsonCorrelation.class.getSimpleName()); + } + public CorrelationTest() { //intentionally blank } + public static CorrelationTest init() { + return instanceDescriptor.newInstance(CorrelationTest.class); + } + + public final static InstanceDescriptor getTestDescriptor() { + return instanceDescriptor; + } + public abstract double evaluate(double[] x, double[] y); public boolean compareToThreshold(double value) { @@ -41,12 +57,4 @@ public void set(NumericPropertyKeyword type, NumericProperty property) { } } - public static String getSelectedTestDescriptor() { - return selectedTestDescriptor; - } - - public static void setSelectedTestDescriptor(String selectedTestDescriptor) { - CorrelationTest.selectedTestDescriptor = selectedTestDescriptor; - } - } diff --git a/src/main/java/pulse/search/statistics/FTest.java b/src/main/java/pulse/search/statistics/FTest.java new file mode 100644 index 0000000..672bdc1 --- /dev/null +++ b/src/main/java/pulse/search/statistics/FTest.java @@ -0,0 +1,149 @@ +/* + * Copyright 2021 Artem Lunev . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package pulse.search.statistics; + +import org.apache.commons.math3.distribution.FDistribution; +import pulse.tasks.Calculation; + +/** + * A static class for testing two calculations based on the Fischer test (F-Test) + * implemented in Apache Commons Math. + * @author Artem Lunev + */ +public class FTest { + + /** + * False-rejection probability for the F-test, equal to {@value FALSE_REJECTION_PROBABILITY} + */ + + public final static double FALSE_REJECTION_PROBABILITY = 0.05; + + private FTest() { + //intentionall blank + } + + /** + * Tests two models to see which one is better according to the F-test + * @param a a calculation + * @param b another calculation + * @return {@code null} if the result is inconclusive, otherwise the + * best of two calculations. + * @see FTest.evaluate() + */ + + public static Calculation test(Calculation a, Calculation b) { + + double[] data = evaluate(a, b); + + Calculation best = null; + + if(data != null) { + + //Under the null hypothesis the general model does not provide + //a significantly better fit than the nested model + + Calculation nested = findNested(a, b); + + //if the F-statistic is greater than the F-critical, reject the null hypothesis. + + if(nested == a) + best = data[0] > data[1] ? b : a; + else + best = data[0] > data[1] ? a : b; + + } + + return best; + + } + + /** + * Evaluates the F-statistic for two calculations. + * @param a a calculation + * @param b another calculation + * @return {@code null} if the test is inconclusive, i.e., if models are not + * nested, or if the model selection criteria are based on a statistic different + * from least-squares, or if the calculations refer to different data ranges. + * Otherwise returns an double array, consisting of two elements {@code [fStatistic, fCritical] } + */ + + public static double[] evaluate(Calculation a, Calculation b) { + + Calculation nested = findNested(a, b); + + double[] result = null; + + //if one of the models is nested into the other + if(nested != null) { + Calculation general = nested == a ? b : a; + + ResidualStatistic nestedResiduals = nested.getModelSelectionCriterion().getOptimiserStatistic(); + ResidualStatistic generalResiduals = general.getModelSelectionCriterion().getOptimiserStatistic(); + + final int nNested = nestedResiduals.getResiduals().size(); //sample size + final int nGeneral = generalResiduals.getResiduals().size(); //sample size + + //if both models use a sum-of-square statistic for the model selection criteria + //and if both calculations refer to the same calculation range + if(nestedResiduals.getClass() == generalResiduals.getClass() + && nestedResiduals.getClass() == SumOfSquares.class + && nNested == nGeneral) { + + double rssNested = ( (Number) ((SumOfSquares)nestedResiduals).getStatistic().getValue() ).doubleValue(); + double rssGeneral = ( (Number) ((SumOfSquares)generalResiduals).getStatistic().getValue() ).doubleValue(); + + int kGeneral = general.getModelSelectionCriterion().getNumVariables(); + int kNested = nested.getModelSelectionCriterion().getNumVariables(); + + double fStatistic = (rssNested - rssGeneral) + /(kGeneral - kNested) + /(rssGeneral/(nGeneral - kGeneral)); + + var fDistribution = new FDistribution(kGeneral - kNested, nGeneral - kGeneral); + + double fCritical = fDistribution.inverseCumulativeProbability(1.0 - FALSE_REJECTION_PROBABILITY); + + result = new double[]{fStatistic, fCritical}; + + } + + } + + return result; + + } + + /** + * Tests two models to see which one is nested in the other. A model is + * considered nested if it refers to the same class of problems but has + * fewer parameters. + * @param a a calculation + * @param b another calculation + * @return {@code null} if the models refer to different problem classes. + * Otherwise returns the model that is nested in the second model. + */ + + public static Calculation findNested(Calculation a, Calculation b) { + if(a.getProblem().getClass() != b.getProblem().getClass()) + return null; + + int aParams = a.getModelSelectionCriterion().getNumVariables(); + int bParams = b.getModelSelectionCriterion().getNumVariables(); + + return aParams > bParams ? b : a; + } + +} \ No newline at end of file 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/ModelSelectionCriterion.java b/src/main/java/pulse/search/statistics/ModelSelectionCriterion.java index 63e7bbb..324acc5 100644 --- a/src/main/java/pulse/search/statistics/ModelSelectionCriterion.java +++ b/src/main/java/pulse/search/statistics/ModelSelectionCriterion.java @@ -28,7 +28,7 @@ public abstract class ModelSelectionCriterion extends Statistic { public ModelSelectionCriterion(OptimiserStatistic os) { super(); - setOptimiser(os); + setOptimiserStatistic(os); } public ModelSelectionCriterion(ModelSelectionCriterion another) { @@ -96,20 +96,15 @@ public double probability(List all) { return exp(-0.5 * di); } - @Override - public String getDescriptor() { - return "Akaike Information Criterion (AIC)"; - } - public int getNumVariables() { return kq; } - public OptimiserStatistic getOptimiser() { + public OptimiserStatistic getOptimiserStatistic() { return os; } - public void setOptimiser(OptimiserStatistic os) { + public void setOptimiserStatistic(OptimiserStatistic os) { this.os = os; } 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/search/statistics/Statistic.java b/src/main/java/pulse/search/statistics/Statistic.java index 6ac94f1..7a3c5e1 100644 --- a/src/main/java/pulse/search/statistics/Statistic.java +++ b/src/main/java/pulse/search/statistics/Statistic.java @@ -1,6 +1,5 @@ package pulse.search.statistics; -import pulse.properties.NumericProperty; import pulse.tasks.SearchTask; import pulse.util.PropertyHolder; import pulse.util.Reflexive; @@ -14,8 +13,4 @@ public abstract class Statistic extends PropertyHolder implements Reflexive { public abstract void evaluate(SearchTask t); - public abstract NumericProperty getStatistic(); - - public abstract void setStatistic(NumericProperty statistic); - } diff --git a/src/main/java/pulse/tasks/Calculation.java b/src/main/java/pulse/tasks/Calculation.java index 797b9f8..7943c11 100644 --- a/src/main/java/pulse/tasks/Calculation.java +++ b/src/main/java/pulse/tasks/Calculation.java @@ -19,7 +19,8 @@ import pulse.problem.statements.Problem; import pulse.properties.NumericProperty; import pulse.properties.NumericPropertyKeyword; -import pulse.search.statistics.AICStatistic; +import pulse.search.statistics.BICStatistic; +import pulse.search.statistics.FTest; import pulse.search.statistics.ModelSelectionCriterion; import pulse.search.statistics.OptimiserStatistic; import pulse.tasks.logs.Status; @@ -43,38 +44,32 @@ public class Calculation extends PropertyHolder implements Comparable instanceDescriptor = new InstanceDescriptor<>( "Model Selection Criterion", ModelSelectionCriterion.class); + //BIC as default static { - instanceDescriptor.setSelectedDescriptor(AICStatistic.class.getSimpleName()); + instanceDescriptor.setSelectedDescriptor(BICStatistic.class.getSimpleName()); } - public Calculation() { + public Calculation(SearchTask t) { status = INCOMPLETE; this.initOptimiser(); + setParent(t); instanceDescriptor.addListener(() -> initModelCriterion()); } - public Calculation(Problem problem, DifferenceScheme scheme, ModelSelectionCriterion rs) { - this(); - this.problem = problem; - this.scheme = scheme; - this.os = rs.getOptimiser(); - this.rs = rs; - problem.setParent(this); - scheme.setParent(this); - os.setParent(this); - rs.setParent(this); - } - - public Calculation copy() { - var status = this.status; - var nCalc = new Calculation(problem.copy(), scheme.copy(), rs.copy()); - var p = nCalc.getProblem(); - p.getProperties().setMaximumTemperature(problem.getProperties().getMaximumTemperature()); - nCalc.status = status; - if (this.getResult() != null) { - nCalc.setResult(new Result(this.getResult())); + /** + * Creates an orphan Calculation, retaining some properties of the argument + * + * @param c another calculation to be archived. + */ + public Calculation(Calculation c) { + this.problem = c.problem.copy(); + this.scheme = c.scheme.copy(); + this.rs = c.rs.copy(); + this.os = c.os.copy(); + this.status = c.status; + if (c.getResult() != null) { + this.result = new Result(c.getResult()); } - return nCalc; } public void clear() { @@ -136,11 +131,10 @@ private void addProblemListeners(Problem problem, ExperimentalData curve) { /** * Adopts the {@code scheme} by this {@code SearchTask} and updates the time - * limit of { - * - * @scheme} to match {@code ExperimentalData}. + * limit of {@code scheme} to match {@code ExperimentalData}. * * @param scheme the {@code DiffenceScheme}. + * @param curve */ public void setScheme(DifferenceScheme scheme, ExperimentalData curve) { this.scheme = scheme; @@ -165,6 +159,13 @@ public void setScheme(DifferenceScheme scheme, ExperimentalData curve) { */ @SuppressWarnings({"unchecked", "rawtypes"}) public void process() throws SolverException { + var list = problem.getProperties().findMalformedProperties(); + if(!list.isEmpty()) { + StringBuilder sb = new StringBuilder("Illegal values:"); + for(NumericProperty np : list) + sb.append(String.format("%n %-25s", np)); + throw new SolverException(sb.toString()); + } ((Solver) scheme).solve(problem); } @@ -174,30 +175,44 @@ public Status getStatus() { /** * Attempts to set the status of this calculation to {@code status}. + * * @param status a status - * @return {@code true} if this attempt is successful, including the case + * @return {@code true} if this attempt is successful, including the case * when the status being set is equal to the current status. {@code false} - * if the current status is one of the following: {@code DONE}, {@code EXECUTION_ERROR}, - * {@code INCOMPLETE}, {@code IN_PROGRES}, AND the {@code status} being set - * is {@code QUEUED}. + * if the current status is one of the following: {@code DONE}, + * {@code EXECUTION_ERROR}, {@code INCOMPLETE}, {@code IN_PROGRES}, AND the + * {@code status} being set is {@code QUEUED}. */ - public boolean setStatus(Status status) { - switch(this.status) { - case DONE: + boolean changeStatus = true; + + switch (this.status) { + case QUEUED: + case IN_PROGRESS: + switch (status) { + case QUEUED: + case READY: + case INCOMPLETE: + changeStatus = false; + break; + default: + } + break; + case FAILED: case EXECUTION_ERROR: case INCOMPLETE: - case IN_PROGRESS: - //if the TaskManager attempts to run this calculation - if(status == Status.QUEUED) - return false; + //if the TaskManager attempts to run this calculation + changeStatus = status != Status.QUEUED; + break; default: } + + if(changeStatus) + this.status = status; - this.status = status; - return true; - + return changeStatus; + } public NumericProperty weight(List all) { @@ -258,10 +273,44 @@ public void set(NumericPropertyKeyword type, NumericProperty property) { // intentionally left blank } + /** + * Checks if this {@code Calculation} is better than {@code a}. + * + * @param a another completed calculation + * @return {@code true} if another calculation hasn't been completed or if + * this calculation's statistic is lower than statistic of {@code a}. + */ + public boolean isBetterThan(Calculation a) { + boolean result = true; + + if (a.getStatus() == Status.DONE) { + result = compareTo(a) < 0; //compare statistic + + //do F-test + Calculation fBest = FTest.test(this, a); + //if the models are nested and calculations can be compared + if (fBest != null) { + //use the F-test result instead + result = fBest == this; + } + + } + + return result; + } + + /** + * Compares two calculations based on their model selection criteria. + * + * @param arg0 another calculation + * @return the result of comparing the model selection statistics of + * {@code this} and {@code arg0}. + */ @Override public int compareTo(Calculation arg0) { - var s1 = arg0.getModelSelectionCriterion().getStatistic(); - return getModelSelectionCriterion().getStatistic().compareTo(s1); + var sAnother = arg0.getModelSelectionCriterion().getStatistic(); + var sThis = getModelSelectionCriterion().getStatistic();; + return sThis.compareTo(sAnother); } @Override diff --git a/src/main/java/pulse/tasks/SearchTask.java b/src/main/java/pulse/tasks/SearchTask.java index 33dbfb5..717b7bc 100644 --- a/src/main/java/pulse/tasks/SearchTask.java +++ b/src/main/java/pulse/tasks/SearchTask.java @@ -51,6 +51,7 @@ import pulse.tasks.logs.CorrelationLogEntry; import pulse.tasks.logs.DataLogEntry; import pulse.tasks.logs.Details; +import static pulse.tasks.logs.Details.SOLVER_ERROR; import pulse.tasks.logs.Log; import pulse.tasks.logs.LogEntry; import pulse.tasks.logs.StateEntry; @@ -84,15 +85,14 @@ public class SearchTask extends Accessible implements Runnable { private CorrelationTest correlationTest; private NormalityTest normalityTest; - private Identifier identifier; - + private final Identifier identifier; /** * If {@code SearchTask} finishes, and its R2 value is * lower than this constant, the result will be considered * {@code AMBIGUOUS}. */ - private List listeners = new CopyOnWriteArrayList<>(); - private List statusChangeListeners = new CopyOnWriteArrayList<>(); + private List listeners; + private List statusChangeListeners; /** *

@@ -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 getStoredCalculations() { return this.stored; } + public void storeCalculation() { + var copy = new Calculation(current); + stored.add(copy); + } + public void switchTo(Calculation calc) { current.setParent(null); current = calc; @@ -626,9 +637,20 @@ public void switchTo(Calculation calc) { 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); } 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..acaf6df 100644 --- a/src/main/java/pulse/tasks/logs/Details.java +++ b/src/main/java/pulse/tasks/logs/Details.java @@ -41,7 +41,24 @@ 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, + + SOLVER_ERROR; @Override public String toString() { diff --git a/src/main/java/pulse/tasks/logs/StateEntry.java b/src/main/java/pulse/tasks/logs/StateEntry.java index f73282d..17333a6 100644 --- a/src/main/java/pulse/tasks/logs/StateEntry.java +++ b/src/main/java/pulse/tasks/logs/StateEntry.java @@ -41,6 +41,10 @@ public String toString() { if (status.getDetails() != NONE) { sb.append(" due to " + status.getDetails() + ""); } + if(status.getDetailedMessage().length() > 0) { + sb.append(" Details: "); + sb.append(status.getDetailedMessage()); + } sb.append(" at "); sb.append(getTime()); return sb.toString(); diff --git a/src/main/java/pulse/tasks/logs/Status.java b/src/main/java/pulse/tasks/logs/Status.java index 5520dbe..080f6ed 100644 --- a/src/main/java/pulse/tasks/logs/Status.java +++ b/src/main/java/pulse/tasks/logs/Status.java @@ -55,6 +55,7 @@ public enum Status { private final Color clr; private Details details = Details.NONE; + private String message = ""; Status(Color clr) { this.clr = clr; @@ -71,6 +72,14 @@ public Details getDetails() { public void setDetails(Details details) { this.details = details; } + + public String getDetailedMessage() { + return message; + } + + public void setDetailedMessage(String str) { + this.message = str; + } static String parse(String str) { var tokens = str.split("_"); 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/Launcher.java b/src/main/java/pulse/ui/Launcher.java index 65e31e4..6843a20 100644 --- a/src/main/java/pulse/ui/Launcher.java +++ b/src/main/java/pulse/ui/Launcher.java @@ -19,6 +19,9 @@ import com.alee.laf.WebLookAndFeel; import com.alee.skin.dark.WebDarkSkin; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; /** *

@@ -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 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] + ""; + //likewise -- for baselines containing Baseline + String baseline = c.getProblem().getBaseline().getSimpleName().split("Baseline")[0]; var optimiser = c.getOptimiserStatistic(); var criterion = c.getModelSelectionCriterion(); var parameters = c.getModelSelectionCriterion().getNumVariables(); 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/java/pulse/util/InstanceDescriptor.java b/src/main/java/pulse/util/InstanceDescriptor.java index 54ee4db..bead937 100644 --- a/src/main/java/pulse/util/InstanceDescriptor.java +++ b/src/main/java/pulse/util/InstanceDescriptor.java @@ -50,10 +50,13 @@ public Object getValue() { @Override public boolean attemptUpdate(Object object) { var string = object.toString(); - - if (selectedDescriptor.equals(string) || !allDescriptors.contains(string)) { + + if (selectedDescriptor.equals(string)) { return false; } + + if(!allDescriptors.contains(string)) + throw new IllegalArgumentException("Unknown descriptor: " + selectedDescriptor); this.selectedDescriptor = string; listeners.stream().forEach(l -> l.onDescriptorChanged()); diff --git a/src/main/java/pulse/util/UpwardsNavigable.java b/src/main/java/pulse/util/UpwardsNavigable.java index 7a3079c..70bc5e2 100644 --- a/src/main/java/pulse/util/UpwardsNavigable.java +++ b/src/main/java/pulse/util/UpwardsNavigable.java @@ -90,7 +90,7 @@ public UpwardsNavigable specificAncestor(Class aClas * @param parent the new parent that will adopt this * {@code UpwardsNavigable}. */ - public void setParent(UpwardsNavigable parent) { + public final void setParent(UpwardsNavigable parent) { this.parent = parent; } diff --git a/src/main/resources/NumericProperty.xml b/src/main/resources/NumericProperty.xml index 89dca9d..3e3180a 100644 --- a/src/main/resources/NumericProperty.xml +++ b/src/main/resources/NumericProperty.xml @@ -1,5 +1,10 @@ + + + maximum="10000" minimum="1" primitive-type="int" value="200"> - - - + - - + + + discreet="false" default-search-variable="true"> + + THICKNESS + + + minimum="1.0E-6" value="0.001" primitive-type="double" discreet="true" + default-search-variable="false"> + + DIFFUSIVITY + + dimensionfactor="1.0" keyword="LASER_ENERGY" maximum="32.0" + minimum="0.01" value="5.0" primitive-type="double" discreet="false" + default-search-variable="false"/>