diff --git a/pom.xml b/pom.xml index e511886..9605d72 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ kotik-coder PULsE - 1.97 + 1.97b PULsE Processing Unit for Laser flash Experiments @@ -18,8 +18,7 @@ The Apache License, Version 2.0 http://www.apache.org/licenses/LICENSE-2.0.txt - - + org.jfree @@ -27,11 +26,11 @@ 1.5.0 - com.weblookandfeel - weblaf-ui - 1.2.13 + com.formdev + flatlaf + 3.0 - + org.apache.commons commons-math3 3.6.1 diff --git a/src/main/java/pulse/baseline/LinearBaseline.java b/src/main/java/pulse/baseline/LinearBaseline.java index d3d899e..c4afb45 100644 --- a/src/main/java/pulse/baseline/LinearBaseline.java +++ b/src/main/java/pulse/baseline/LinearBaseline.java @@ -41,7 +41,7 @@ public LinearBaseline(double intercept, double slope) { super(intercept, slope); } - public LinearBaseline(LinearBaseline baseline) { + public LinearBaseline(AdjustableBaseline baseline) { super( (double) baseline.getIntercept().getValue(), (double) baseline.getSlope().getValue() ); diff --git a/src/main/java/pulse/io/export/LogPaneExporter.java b/src/main/java/pulse/io/export/TextLogPaneExporter.java similarity index 67% rename from src/main/java/pulse/io/export/LogPaneExporter.java rename to src/main/java/pulse/io/export/TextLogPaneExporter.java index ade0e0f..e6ea39e 100644 --- a/src/main/java/pulse/io/export/LogPaneExporter.java +++ b/src/main/java/pulse/io/export/TextLogPaneExporter.java @@ -4,22 +4,23 @@ import java.io.FileOutputStream; import java.io.IOException; +import javax.swing.JEditorPane; import javax.swing.text.BadLocationException; import javax.swing.text.html.HTMLEditorKit; -import pulse.ui.components.LogPane; +import pulse.ui.components.TextLogPane; /** * Similar to a {@code LogExporter}, except that it works only on the contents * of a {@code LogPane} currently being displayed to the user. * */ -public class LogPaneExporter implements Exporter { +public class TextLogPaneExporter implements Exporter { - private static LogPaneExporter instance = new LogPaneExporter(); + private static TextLogPaneExporter instance = new TextLogPaneExporter(); - private LogPaneExporter() { + private TextLogPaneExporter() { // intentionally blank } @@ -29,10 +30,11 @@ private LogPaneExporter() { * argument is ignored. After exporting, the stream is explicitly closed. */ @Override - public void printToStream(LogPane pane, FileOutputStream fos, Extension extension) { - var kit = (HTMLEditorKit) pane.getEditorKit(); + public void printToStream(TextLogPane pane, FileOutputStream fos, Extension extension) { + var editorPane = (JEditorPane) pane.getGUIComponent(); + var kit = (HTMLEditorKit) editorPane.getEditorKit(); try { - kit.write(fos, pane.getDocument(), 0, pane.getDocument().getLength()); + kit.write(fos, editorPane.getDocument(), 0, editorPane.getDocument().getLength()); } catch (IOException | BadLocationException e) { System.err.println("Could not export the log pane!"); e.printStackTrace(); @@ -50,7 +52,7 @@ public void printToStream(LogPane pane, FileOutputStream fos, Extension extensio * * @return an instance of{@code LogPaneExporter}. */ - public static LogPaneExporter getInstance() { + public static TextLogPaneExporter getInstance() { return instance; } @@ -58,8 +60,8 @@ public static LogPaneExporter getInstance() { * @return {@code LogPane.class}. */ @Override - public Class target() { - return LogPane.class; + public Class target() { + return TextLogPane.class; } /** @@ -70,4 +72,4 @@ public Extension[] getSupportedExtensions() { return new Extension[]{HTML}; } -} +} \ No newline at end of file diff --git a/src/main/java/pulse/math/ParameterIdentifier.java b/src/main/java/pulse/math/ParameterIdentifier.java index b96847d..3fb7bc4 100644 --- a/src/main/java/pulse/math/ParameterIdentifier.java +++ b/src/main/java/pulse/math/ParameterIdentifier.java @@ -1,5 +1,6 @@ package pulse.math; +import java.util.Objects; import pulse.properties.NumericPropertyKeyword; public class ParameterIdentifier { @@ -15,6 +16,14 @@ public ParameterIdentifier(NumericPropertyKeyword keyword, int index) { public ParameterIdentifier(NumericPropertyKeyword keyword) { this(keyword, 0); } + + @Override + public int hashCode() { + int hash = 7; + hash = 29 * hash + Objects.hashCode(this.keyword); + hash = 29 * hash + this.index; + return hash; + } public ParameterIdentifier(int index) { this.index = index; @@ -30,24 +39,28 @@ public int getIndex() { @Override public boolean equals(Object id) { - if(!id.getClass().equals(ParameterIdentifier.class)) { + if(id.getClass() == null) { return false; } - var pid = (ParameterIdentifier) id; - - boolean result = true; + var classA = id.getClass(); + var classB = this.getClass(); - if(keyword != pid.keyword || index != pid.index) - result = false; - - return result; + if(classA != classB) { + return false; + } + var pid = (ParameterIdentifier) id; + return keyword == pid.keyword && Math.abs(index - pid.index) < 1; } @Override public String toString() { - return keyword + " # " + index; + StringBuilder sb = new StringBuilder("").append(keyword); + if(index > 0) { + sb.append(" # ").append(index); + } + return sb.toString(); } } \ No newline at end of file diff --git a/src/main/java/pulse/problem/laser/DiscretePulse.java b/src/main/java/pulse/problem/laser/DiscretePulse.java index e1a283f..8891324 100644 --- a/src/main/java/pulse/problem/laser/DiscretePulse.java +++ b/src/main/java/pulse/problem/laser/DiscretePulse.java @@ -7,7 +7,10 @@ import pulse.problem.schemes.Grid; import pulse.problem.statements.Problem; import pulse.problem.statements.Pulse; +import static pulse.properties.NumericProperties.derive; +import static pulse.properties.NumericPropertyKeyword.TAU_FACTOR; import pulse.tasks.SearchTask; +import pulse.util.PropertyHolderListener; /** * A {@code DiscretePulse} is an object that acts as a medium between the @@ -20,9 +23,10 @@ public class DiscretePulse { private final Grid grid; private final Pulse pulse; - + private final ExperimentalData data; + private double widthOnGrid; - private double timeConversionFactor; + private double characteristicTime; private double invTotalEnergy; //normalisation factor /** @@ -49,33 +53,28 @@ public class DiscretePulse { */ public DiscretePulse(Problem problem, Grid grid) { this.grid = grid; - timeConversionFactor = problem.getProperties().timeFactor(); + characteristicTime = problem.getProperties().characteristicTime(); this.pulse = problem.getPulse(); Object ancestor = Objects.requireNonNull(problem.specificAncestor(SearchTask.class), "Problem has not been assigned to a SearchTask"); - ExperimentalData data = - (ExperimentalData) ( ((SearchTask) ancestor).getInput() ); - init(data); - + data = (ExperimentalData) (((SearchTask) ancestor).getInput()); + init(); + + PropertyHolderListener phl = e -> { + characteristicTime = problem.getProperties().characteristicTime(); + widthOnGrid = 0; + init(); + }; + pulse.addListener(e -> { - timeConversionFactor = problem.getProperties().timeFactor(); - init(data); + widthOnGrid = 0; + init(); }); + problem.addListener(phl); - grid.addListener(e - -> init(data) - ); - - } - - private void init(ExperimentalData data) { - widthOnGrid = 0; - recalculate(); - pulse.getPulseShape().init(data, this); - invTotalEnergy = 1.0/totalEnergy(); } /** @@ -95,39 +94,77 @@ public double laserPowerAt(double time) { * * @see pulse.problem.schemes.Grid.gridTime(double,double) */ - public final void recalculate() { - final double nominalWidth = ((Number) pulse.getPulseWidth().getValue()).doubleValue(); - final double resolvedWidth = timeConversionFactor / getWidthToleranceFactor(); + public final void init() { + final double nominalWidth = ((Number) pulse.getPulseWidth().getValue()).doubleValue(); + final double resolvedWidth = resolvedPulseWidthSeconds(); final double EPS = 1E-10; - + + double oldValue = widthOnGrid; + this.widthOnGrid = pulseWidthGrid(); + /** * The pulse is too short, which makes calculations too expensive. Can * we replace it with a rectangular pulse shape instead? */ - - if (nominalWidth < resolvedWidth - EPS && widthOnGrid < EPS) { + if (nominalWidth < resolvedWidth - EPS && oldValue < EPS) { //change shape to rectangular var shape = new RectangularPulse(); - pulse.setPulseShape(shape); - //change pulse width - setDiscreteWidth(resolvedWidth); + pulse.setPulseShape(shape); shape.init(null, this); - //adjust the pulse object to update the visualised pulse - } else if(nominalWidth > resolvedWidth + EPS) { - setDiscreteWidth(nominalWidth); - } - - invTotalEnergy = 1.0/totalEnergy(); - + } else { + pulse.getPulseShape().init(data, this); + } + + invTotalEnergy = 1.0 / totalEnergy(); } - + /** - * Calculates the total pulse energy using a numerical integrator.The - * normalisation factor is then equal to the inverse total energy. - * @return the total pulse energy, assuming sample area fully covered by the beam + * Optimises the {@code Grid} parameters so that the timestep is + * sufficiently small to enable accurate pulse correction. + *

+ * This can change the {@code tauFactor} and {@code tau} variables in the + * {@code Grid} object if {@code discretePulseWidth/(M - 1) < grid.tau}, + * where M is the required number of pulse calculations. + *

+ * + * @see PulseTemporalShape.getRequiredDiscretisation() */ + public double pulseWidthGrid() { + //minimum number of points for pulse calculation + int reqPoints = pulse.getPulseShape().getRequiredDiscretisation(); + //physical pulse width in time units + double experimentalWidth = (double) pulse.getPulseWidth().getValue(); + + //minimum resolved pulse width in time units for that specific problem + double resolvedWidth = resolvedPulseWidthSeconds(); + + double pWidth = Math.max(experimentalWidth, resolvedWidth); + + final double EPS = 1E-10; + double newTau = pWidth / characteristicTime / reqPoints; + + double result = 0; + + if (newTau < grid.getTimeStep() - EPS) { + double newTauFactor = (double) grid.getTimeFactor().getValue() / 2.0; + grid.setTimeFactor(derive(TAU_FACTOR, newTauFactor)); + result = pulseWidthGrid(); + } else { + result = grid.gridTime(pWidth, characteristicTime); + } + + return result; + } + + /** + * Calculates the total pulse energy using a numerical integrator.The + * normalisation factor is then equal to the inverse total energy. + * + * @return the total pulse energy, assuming sample area fully covered by the + * beam + */ public final double totalEnergy() { var pulseShape = pulse.getPulseShape(); @@ -144,27 +181,22 @@ public double integrand(double... vars) { } /** - * Gets the discrete dimensionless pulse width, which is a multiplier of the current - * grid timestep. The pulse width is converted to the dimensionless pulse width by - * dividing the real value by l2/a. + * Gets the discrete dimensionless pulse width, which is a multiplier of the + * current grid timestep. The pulse width is converted to the dimensionless + * pulse width by dividing the real value by l2/a. * * @return the dimensionless pulse width mapped to the grid. */ public double getDiscreteWidth() { return widthOnGrid; } - - private void setDiscreteWidth(double width) { - widthOnGrid = grid.gridTime(width, timeConversionFactor); - grid.adjustTimeStep(this); - } /** * Gets the physical {@code Pulse} * * @return the {@code Pulse} object */ - public Pulse getPulse() { + public Pulse getPhysicalPulse() { return pulse; } @@ -176,36 +208,38 @@ public Pulse getPulse() { public Grid getGrid() { return grid; } - + /** - * Gets the dimensional factor required to convert real time variable into - * a dimensional variable, defined in the {@code Problem} class + * Gets the dimensional factor required to convert real time variable into a + * dimensional variable, defined in the {@code Problem} class + * * @return the conversion factor */ - - public double getConversionFactor() { - return timeConversionFactor; + public double getCharacteristicTime() { + return characteristicTime; } - + /** - * Gets the minimal resolved pulse width defined by the {@code WIDTH_TOLERANCE_FACTOR} - * and the characteristic time given by the {@code getConversionFactor}. - * @return + * Gets the minimal resolved pulse width defined by the + * {@code WIDTH_TOLERANCE_FACTOR} and the characteristic time given by the + * {@code getConversionFactor}. + * + * @return */ - - public double resolvedPulseWidth() { - return timeConversionFactor / getWidthToleranceFactor(); + public double resolvedPulseWidthSeconds() { + return characteristicTime / getWidthToleranceFactor(); } - - /** - * Assuming a characteristic time is divided by the return value of this method - * and is set to the minimal resolved pulse width, shows how small a pulse width - * can be to enable finite pulse correction. - * @return the smallest fraction of a characteristic time resolved as a finite pulse. + + /** + * Assuming a characteristic time is divided by the return value of this + * method and is set to the minimal resolved pulse width, shows how small a + * pulse width can be to enable finite pulse correction. + * + * @return the smallest fraction of a characteristic time resolved as a + * finite pulse. */ - public int getWidthToleranceFactor() { return WIDTH_TOLERANCE_FACTOR; } -} \ No newline at end of file +} diff --git a/src/main/java/pulse/problem/laser/DiscretePulse2D.java b/src/main/java/pulse/problem/laser/DiscretePulse2D.java index ab9fd0e..02a60f0 100644 --- a/src/main/java/pulse/problem/laser/DiscretePulse2D.java +++ b/src/main/java/pulse/problem/laser/DiscretePulse2D.java @@ -26,7 +26,7 @@ public class DiscretePulse2D extends DiscretePulse { * This had to be decreased for the 2d pulses. */ - private final static int WIDTH_TOLERANCE_FACTOR = 200; + private final static int WIDTH_TOLERANCE_FACTOR = 1000; /** * The constructor for {@code DiscretePulse2D}. @@ -90,7 +90,7 @@ private void calcPulseSpot(ExtendedThermalProperties properties) { * @see pulse.problem.schemes.Grid2D.gridRadialDistance(double,double) */ public final void evalPulseSpot() { - var pulse = (Pulse2D) getPulse(); + var pulse = (Pulse2D) getPhysicalPulse(); var grid2d = (Grid2D) getGrid(); final double spotRadius = (double) pulse.getSpotDiameter().getValue() / 2.0; discretePulseSpot = grid2d.gridRadialDistance(spotRadius, sampleRadius); diff --git a/src/main/java/pulse/problem/laser/NumericPulse.java b/src/main/java/pulse/problem/laser/NumericPulse.java index e834f02..9dbaadb 100644 --- a/src/main/java/pulse/problem/laser/NumericPulse.java +++ b/src/main/java/pulse/problem/laser/NumericPulse.java @@ -64,7 +64,7 @@ public void init(ExperimentalData data, DiscretePulse pulse) { setPulseWidthOf(problem); //convert to dimensionless time and interpolate - double timeFactor = problem.getProperties().timeFactor(); + double timeFactor = problem.getProperties().characteristicTime(); doInterpolation(timeFactor); } diff --git a/src/main/java/pulse/problem/laser/RectangularPulse.java b/src/main/java/pulse/problem/laser/RectangularPulse.java index 2ba6b9d..583a92e 100644 --- a/src/main/java/pulse/problem/laser/RectangularPulse.java +++ b/src/main/java/pulse/problem/laser/RectangularPulse.java @@ -14,7 +14,7 @@ */ public class RectangularPulse extends PulseTemporalShape { - private final static int MIN_POINTS = 4; + private final static int MIN_POINTS = 2; /** * @param time the time measured from the start of the laser pulse. @@ -41,4 +41,4 @@ public int getRequiredDiscretisation() { return MIN_POINTS; } -} +} \ No newline at end of file diff --git a/src/main/java/pulse/problem/laser/TrapezoidalPulse.java b/src/main/java/pulse/problem/laser/TrapezoidalPulse.java index c305b81..a061a87 100644 --- a/src/main/java/pulse/problem/laser/TrapezoidalPulse.java +++ b/src/main/java/pulse/problem/laser/TrapezoidalPulse.java @@ -22,7 +22,7 @@ public class TrapezoidalPulse extends PulseTemporalShape { private double fall; private double h; - private final static int MIN_POINTS = 6; + private final static int MIN_POINTS = 8; /** * Constructs a trapezoidal pulse using a default segmentation principle. diff --git a/src/main/java/pulse/problem/schemes/DifferenceScheme.java b/src/main/java/pulse/problem/schemes/DifferenceScheme.java index 59c9220..8ad8dfd 100644 --- a/src/main/java/pulse/problem/schemes/DifferenceScheme.java +++ b/src/main/java/pulse/problem/schemes/DifferenceScheme.java @@ -1,5 +1,6 @@ package pulse.problem.schemes; +import java.util.Objects; import static pulse.properties.NumericProperties.def; import static pulse.properties.NumericProperties.derive; import static pulse.properties.NumericProperty.requireType; @@ -92,7 +93,7 @@ protected void prepare(Problem problem) throws SolverException { if (discretePulse == null) { discretePulse = problem.discretePulseOn(grid); } - discretePulse.recalculate(); + discretePulse.init(); clearArrays(); } @@ -114,13 +115,13 @@ public void runTimeSequence(Problem problem, final double offset, final double e int numPoints = (int) curve.getNumPoints().getValue(); final double startTime = (double) curve.getTimeShift().getValue(); - final double timeSegment = (endTime - startTime - offset) / problem.getProperties().timeFactor(); + final double timeSegment = (endTime - startTime - offset) / problem.getProperties().characteristicTime(); double tau = grid.getTimeStep(); final double dt = timeSegment / (numPoints - 1); timeInterval = Math.max( (int) (dt / tau), 1); - double wFactor = timeInterval * tau * problem.getProperties().timeFactor(); + double wFactor = timeInterval * tau * problem.getProperties().characteristicTime(); // First point (index = 0) is always (0.0, 0.0) curve.addPoint(0.0, 0.0); diff --git a/src/main/java/pulse/problem/schemes/Grid.java b/src/main/java/pulse/problem/schemes/Grid.java index f68467b..2d7128d 100644 --- a/src/main/java/pulse/problem/schemes/Grid.java +++ b/src/main/java/pulse/problem/schemes/Grid.java @@ -11,6 +11,7 @@ import java.util.Set; import pulse.problem.laser.DiscretePulse; +import pulse.problem.statements.Pulse; import pulse.properties.NumericProperty; import pulse.properties.NumericPropertyKeyword; import pulse.util.PropertyHolder; @@ -63,37 +64,6 @@ public Grid copy() { return new Grid(getGridDensity(), getTimeFactor()); } - /** - * Optimises the {@code Grid} parameters so that the timestep is - * sufficiently small to enable accurate pulse correction. - *

- * This can change the {@code tauFactor} and {@code tau} variables in the - * {@code Grid} object if {@code discretePulseWidth/(M - 1) < grid.tau}, - * where M is the required number of pulse calculations. - *

- * - * @param pulse the discrete pulse representation - * @see PulseTemporalShape.getRequiredDiscretisation() - */ - public final void adjustTimeStep(DiscretePulse pulse) { - double timeFactor = pulse.getConversionFactor(); - - final int reqPoints = pulse.getPulse().getPulseShape().getRequiredDiscretisation(); - - double pNominalWidth = (double) pulse.getPulse().getPulseWidth().getValue(); - double pResolvedWidth = pulse.resolvedPulseWidth(); - double pWidth = pNominalWidth < pResolvedWidth ? pResolvedWidth : pNominalWidth; - - double newTau = pWidth / timeFactor / (reqPoints > 1 ? reqPoints - 1 : 1); - double newTauFactor = newTau / (hx * hx); - - final double EPS = 1E-10; - if (newTauFactor < tauFactor - EPS) { - setTimeFactor(derive(TAU_FACTOR, newTauFactor)); - } - - } - /** * The listed properties include {@code GRID_DENSITY} and * {@code TAU_FACTOR}. @@ -223,7 +193,7 @@ public void setTimeFactor(NumericProperty timeFactor) { * @return a double representing the time on the finite grid */ public final double gridTime(double time, double dimensionFactor) { - return rint((time / dimensionFactor) / tau) * tau; + return ( (int) (time / dimensionFactor / tau) ) * tau; } /** diff --git a/src/main/java/pulse/problem/schemes/Grid2D.java b/src/main/java/pulse/problem/schemes/Grid2D.java index 7c34840..af2284b 100644 --- a/src/main/java/pulse/problem/schemes/Grid2D.java +++ b/src/main/java/pulse/problem/schemes/Grid2D.java @@ -46,7 +46,7 @@ public Grid2D copy() { @Override public void setTimeFactor(NumericProperty timeFactor) { super.setTimeFactor(timeFactor); - setTimeStep((double) timeFactor.getValue() * (pow(getXStep(), 2) + pow(hy, 2))); + setTimeStep((double) timeFactor.getValue() * (pow(getXStep(), 2) + pow(hy, 2)) ); } /** @@ -68,7 +68,6 @@ public void adjustStepSize(DiscretePulse pulse) { adjustStepSize(pulse); } - adjustTimeStep(pulse); } @Override diff --git a/src/main/java/pulse/problem/statements/AdiabaticSolution.java b/src/main/java/pulse/problem/statements/AdiabaticSolution.java index a335fac..5fbb5a8 100644 --- a/src/main/java/pulse/problem/statements/AdiabaticSolution.java +++ b/src/main/java/pulse/problem/statements/AdiabaticSolution.java @@ -75,7 +75,7 @@ public static HeatingCurve classicSolution(Problem p, double timeLimit, int prec private final static double solutionAt(ThermalProperties p, double time, int precision) { final double EPS = 1E-8; - final double Fo = time / p.timeFactor(); + final double Fo = time / p.characteristicTime(); if (time < EPS) { return 0; diff --git a/src/main/java/pulse/problem/statements/ClassicalProblem2D.java b/src/main/java/pulse/problem/statements/ClassicalProblem2D.java index 74b74e0..9960a8b 100644 --- a/src/main/java/pulse/problem/statements/ClassicalProblem2D.java +++ b/src/main/java/pulse/problem/statements/ClassicalProblem2D.java @@ -28,7 +28,7 @@ * pulse-to-diameter ratio. * */ -public class ClassicalProblem2D extends Problem { +public class ClassicalProblem2D extends ClassicalProblem { public ClassicalProblem2D() { super(); @@ -82,12 +82,14 @@ public void optimisationVector(ParameterVector output) { switch (key) { case FOV_OUTER: value = (double) properties.getFOVOuter().getValue(); + transform = new StickTransform(bounds); break; case FOV_INNER: value = (double) properties.getFOVInner().getValue(); break; case SPOT_DIAMETER: value = (double) ((Pulse2D) getPulse()).getSpotDiameter().getValue(); + transform = new StickTransform(bounds); break; case HEAT_LOSS_SIDE: value = (double) properties.getSideLosses().getValue(); diff --git a/src/main/java/pulse/problem/statements/Problem.java b/src/main/java/pulse/problem/statements/Problem.java index f228515..a9752e2 100644 --- a/src/main/java/pulse/problem/statements/Problem.java +++ b/src/main/java/pulse/problem/statements/Problem.java @@ -6,6 +6,7 @@ import static pulse.properties.NumericPropertyKeyword.TIME_SHIFT; import java.util.List; +import java.util.Objects; import java.util.Set; import java.util.concurrent.Executors; import java.util.stream.Collectors; @@ -245,7 +246,7 @@ public void optimisationVector(ParameterVector output) { p.setTransform(new StickTransform(bounds)); break; case TIME_SHIFT: - double magnitude = 0.25 * properties.timeFactor(); + double magnitude = 0.25 * properties.characteristicTime(); bounds = new Segment(-magnitude, magnitude); value = (double) curve.getTimeShift().getValue(); break; @@ -446,4 +447,4 @@ public final void setProperties(ThermalProperties properties) { public abstract boolean isReady(); -} +} \ No newline at end of file diff --git a/src/main/java/pulse/problem/statements/Pulse.java b/src/main/java/pulse/problem/statements/Pulse.java index 1b0fbf3..eac31f9 100644 --- a/src/main/java/pulse/problem/statements/Pulse.java +++ b/src/main/java/pulse/problem/statements/Pulse.java @@ -7,7 +7,6 @@ import static pulse.properties.NumericPropertyKeyword.PULSE_WIDTH; import java.util.List; -import java.util.Objects; import java.util.Set; import pulse.input.ExperimentalData; @@ -217,7 +216,8 @@ public PulseTemporalShape getPulseShape() { public void setPulseShape(PulseTemporalShape pulseShape) { this.pulseShape = pulseShape; - pulseShape.setParent(this); + pulseShape.setParent(this); + } } diff --git a/src/main/java/pulse/problem/statements/model/ThermalProperties.java b/src/main/java/pulse/problem/statements/model/ThermalProperties.java index 153faa1..295f6b8 100644 --- a/src/main/java/pulse/problem/statements/model/ThermalProperties.java +++ b/src/main/java/pulse/problem/statements/model/ThermalProperties.java @@ -325,7 +325,7 @@ public double maxRadiationBiot() { * * @return the time factor */ - public double timeFactor() { + public double characteristicTime() { return l * l / a; } diff --git a/src/main/java/pulse/properties/NumericProperties.java b/src/main/java/pulse/properties/NumericProperties.java index cd5279e..738885a 100644 --- a/src/main/java/pulse/properties/NumericProperties.java +++ b/src/main/java/pulse/properties/NumericProperties.java @@ -1,11 +1,8 @@ package pulse.properties; -import java.text.DecimalFormat; -import java.text.NumberFormat; import java.util.List; import pulse.io.export.XMLConverter; -import pulse.ui.Messages; /** * Default operations with NumericProperties @@ -59,9 +56,9 @@ public static String printRangeAndNumber(NumericProperty p, Number value) { msg.append("Acceptable region for "); msg.append("parameter : "); msg.append(p.getValue().getClass().getSimpleName()); - msg.append(" [ " + p.getMinimum()); - msg.append(" : " + p.getMaximum() + " ]. "); - msg.append("Value received: " + value); + msg.append(" [ ").append(p.getMinimum()); + msg.append(" : ").append(p.getMaximum()).append(" ]. "); + msg.append("Value received: ").append(value); return msg.toString(); } diff --git a/src/main/java/pulse/properties/NumericPropertyKeyword.java b/src/main/java/pulse/properties/NumericPropertyKeyword.java index a657171..36b1666 100644 --- a/src/main/java/pulse/properties/NumericPropertyKeyword.java +++ b/src/main/java/pulse/properties/NumericPropertyKeyword.java @@ -390,7 +390,13 @@ public enum NumericPropertyKeyword { * Heat loss for the gas in the 2T-model. */ - HEAT_LOSS_GAS; + HEAT_LOSS_GAS, + + /** + * Value of objective function. + */ + + OBJECTIVE_FUNCTION; 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/GeneralTask.java b/src/main/java/pulse/search/GeneralTask.java index 44c64ac..3bd42d0 100644 --- a/src/main/java/pulse/search/GeneralTask.java +++ b/src/main/java/pulse/search/GeneralTask.java @@ -64,6 +64,7 @@ public GeneralTask() { @Override public void run() { setDefaultOptimiser(); + best = null; setIterativeState( optimiser.initState(this) ); var errorTolerance = (double) optimiser.getErrorTolerance().getValue(); diff --git a/src/main/java/pulse/search/direction/ComplexPath.java b/src/main/java/pulse/search/direction/ComplexPath.java index e6fc8a3..bfe1ef8 100644 --- a/src/main/java/pulse/search/direction/ComplexPath.java +++ b/src/main/java/pulse/search/direction/ComplexPath.java @@ -4,7 +4,6 @@ import pulse.math.linear.SquareMatrix; import pulse.search.GeneralTask; -import pulse.tasks.SearchTask; /** *

diff --git a/src/main/java/pulse/search/direction/CompositePathOptimiser.java b/src/main/java/pulse/search/direction/CompositePathOptimiser.java index 7976646..48766c5 100644 --- a/src/main/java/pulse/search/direction/CompositePathOptimiser.java +++ b/src/main/java/pulse/search/direction/CompositePathOptimiser.java @@ -61,6 +61,7 @@ public boolean iteration(GeneralTask task) throws SolverException { } else { double initialCost = task.getResponse().objectiveFunction(task); + p.setCost(initialCost); var parameters = task.searchVector(); p.setParameters(parameters); // store current parameters @@ -94,6 +95,7 @@ public boolean iteration(GeneralTask task) throws SolverException { task.storeState(); p.resetFailedAttempts(); this.prepare(task); // update gradients, Hessians, etc. -> for the next step, [k + 1] + p.setCost(newCost); p.incrementStep(); // increment the counter of successful steps } @@ -142,4 +144,4 @@ public GradientGuidedPath initState(GeneralTask t) { return new ComplexPath(t); } -} +} \ No newline at end of file diff --git a/src/main/java/pulse/search/direction/IterativeState.java b/src/main/java/pulse/search/direction/IterativeState.java index 05fbaca..9045d46 100644 --- a/src/main/java/pulse/search/direction/IterativeState.java +++ b/src/main/java/pulse/search/direction/IterativeState.java @@ -41,6 +41,7 @@ public void setCost(double cost) { public void reset() { iteration = 0; + setCost(Double.POSITIVE_INFINITY); } public NumericProperty getIteration() { diff --git a/src/main/java/pulse/search/direction/LMOptimiser.java b/src/main/java/pulse/search/direction/LMOptimiser.java index 4c3247a..86ee9e4 100644 --- a/src/main/java/pulse/search/direction/LMOptimiser.java +++ b/src/main/java/pulse/search/direction/LMOptimiser.java @@ -69,6 +69,7 @@ public boolean iteration(GeneralTask task) throws SolverException { } else { double initialCost = task.objectiveFunction(); + p.setCost(initialCost); var parameters = task.searchVector(); p.setParameters(parameters); // store current parameters @@ -88,7 +89,7 @@ public boolean iteration(GeneralTask task) throws SolverException { parameters, candidate)); // assign new parameters double newCost = task.objectiveFunction(); // calculate the sum of squared residuals - + /* * Delayed gratification */ @@ -103,6 +104,7 @@ public boolean iteration(GeneralTask task) throws SolverException { p.resetFailedAttempts(); p.setLambda(p.getLambda() / 3.0); p.setComputeJacobian(false); + p.setCost(newCost); p.incrementStep(); // increment the counter of successful steps } diff --git a/src/main/java/pulse/search/direction/pso/ParticleSwarmOptimiser.java b/src/main/java/pulse/search/direction/pso/ParticleSwarmOptimiser.java index 6f39f8b..323dfab 100644 --- a/src/main/java/pulse/search/direction/pso/ParticleSwarmOptimiser.java +++ b/src/main/java/pulse/search/direction/pso/ParticleSwarmOptimiser.java @@ -46,7 +46,8 @@ public boolean iteration(GeneralTask task) throws SolverException { swarmState.incrementStep(); task.assign(swarmState.getBestSoFar().getPosition()); - task.objectiveFunction(); + double cost = task.objectiveFunction(); + swarmState.setCost(cost); return true; } diff --git a/src/main/java/pulse/search/statistics/AICStatistic.java b/src/main/java/pulse/search/statistics/AICStatistic.java index 529b2e6..3279461 100644 --- a/src/main/java/pulse/search/statistics/AICStatistic.java +++ b/src/main/java/pulse/search/statistics/AICStatistic.java @@ -11,7 +11,7 @@ public AICStatistic(OptimiserStatistic os) { super(os); } - public AICStatistic(AICStatistic another) { + public AICStatistic(ModelSelectionCriterion another) { super(another); } diff --git a/src/main/java/pulse/search/statistics/BICStatistic.java b/src/main/java/pulse/search/statistics/BICStatistic.java index 2c6edfe..72409f0 100644 --- a/src/main/java/pulse/search/statistics/BICStatistic.java +++ b/src/main/java/pulse/search/statistics/BICStatistic.java @@ -11,7 +11,7 @@ */ public class BICStatistic extends ModelSelectionCriterion { - public BICStatistic(BICStatistic another) { + public BICStatistic(ModelSelectionCriterion another) { super(another); } diff --git a/src/main/java/pulse/search/statistics/FTest.java b/src/main/java/pulse/search/statistics/FTest.java index 9a39bc4..4723f5f 100644 --- a/src/main/java/pulse/search/statistics/FTest.java +++ b/src/main/java/pulse/search/statistics/FTest.java @@ -4,130 +4,137 @@ import pulse.tasks.Calculation; /** - * A static class for testing two calculations based on the Fischer test (F-Test) - * implemented in Apache Commons Math. + * A static class for testing two calculations based on the Fischer test + * (F-Test) implemented in Apache Commons Math. */ public class FTest { - + /** - * False-rejection probability for the F-test, equal to {@value FALSE_REJECTION_PROBABILITY} + * 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. + * @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) { - + + 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) + if (nested == a) { best = data[0] > data[1] ? b : a; - else + } else { best = data[0] > data[1] ? a : b; - + } + } - + return best; - + } - + /** - * Evaluates the F-statistic for two calculations. + * 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] } + * @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) { + if (nested != null) { Calculation general = nested == a ? b : a; - + ResidualStatistic nestedResiduals = nested.getModelSelectionCriterion().getOptimiserStatistic(); - ResidualStatistic generalResiduals = general.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 + 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}; - + + 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(); + + if (kGeneral > kNested && nGeneral > kGeneral) { + + 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 + * 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; + var classA = a.getProblem().getClass(); + var classB = b.getProblem().getClass(); + if (!classA.isAssignableFrom(classB) && !classB.isAssignableFrom(classA)) { + 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/ModelSelectionCriterion.java b/src/main/java/pulse/search/statistics/ModelSelectionCriterion.java index fbba1cc..6951845 100644 --- a/src/main/java/pulse/search/statistics/ModelSelectionCriterion.java +++ b/src/main/java/pulse/search/statistics/ModelSelectionCriterion.java @@ -37,6 +37,35 @@ public ModelSelectionCriterion(ModelSelectionCriterion another) { this.criterion = another.criterion; } + @Override + public int hashCode() { + int hash = 7; + hash = 43 * hash + this.kq; + hash = 43 * hash + (int) (Double.doubleToLongBits(this.criterion) ^ (Double.doubleToLongBits(this.criterion) >>> 32)); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final ModelSelectionCriterion other = (ModelSelectionCriterion) obj; + if (this.kq != other.kq) { + return false; + } + if (Double.doubleToLongBits(this.criterion) != Double.doubleToLongBits(other.criterion)) { + return false; + } + return true; + } + @Override public void evaluate(GeneralTask t) { kq = t.searchVector().dimension(); //number of parameters diff --git a/src/main/java/pulse/search/statistics/ResidualStatistic.java b/src/main/java/pulse/search/statistics/ResidualStatistic.java index a00ad58..5b9d3e5 100644 --- a/src/main/java/pulse/search/statistics/ResidualStatistic.java +++ b/src/main/java/pulse/search/statistics/ResidualStatistic.java @@ -6,6 +6,7 @@ import static pulse.properties.NumericPropertyKeyword.OPTIMISER_STATISTIC; import java.util.List; +import java.util.Objects; import pulse.DiscreteInput; import pulse.Response; import pulse.input.IndexRange; @@ -31,6 +32,39 @@ public abstract class ResidualStatistic extends Statistic { private List rx; private List ry; + @Override + public int hashCode() { + int hash = 5; + hash = 53 * hash + (int) (Double.doubleToLongBits(this.statistic) ^ (Double.doubleToLongBits(this.statistic) >>> 32)); + hash = 53 * hash + Objects.hashCode(this.rx); + hash = 53 * hash + Objects.hashCode(this.ry); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final ResidualStatistic other = (ResidualStatistic) obj; + if (Double.doubleToLongBits(this.statistic) != Double.doubleToLongBits(other.statistic)) { + return false; + } + if (!Objects.equals(this.rx, other.rx)) { + return false; + } + if (!Objects.equals(this.ry, other.ry)) { + return false; + } + return true; + } + public ResidualStatistic() { super(); ry = new ArrayList<>(); @@ -40,8 +74,8 @@ public ResidualStatistic() { public ResidualStatistic(ResidualStatistic another) { this.statistic = another.statistic; - ry = new ArrayList<>(); - rx = new ArrayList<>(); + ry = new ArrayList<>(another.rx); + rx = new ArrayList<>(another.ry); } /** @@ -144,4 +178,4 @@ public void set(NumericPropertyKeyword type, NumericProperty property) { } } -} +} \ No newline at end of file diff --git a/src/main/java/pulse/tasks/Calculation.java b/src/main/java/pulse/tasks/Calculation.java index 4755a1d..2e38a79 100644 --- a/src/main/java/pulse/tasks/Calculation.java +++ b/src/main/java/pulse/tasks/Calculation.java @@ -9,6 +9,7 @@ import static pulse.util.Reflexive.instantiate; import java.util.List; +import java.util.Objects; import java.util.stream.Collectors; import pulse.Response; @@ -27,12 +28,14 @@ import pulse.search.statistics.FTest; import pulse.search.statistics.ModelSelectionCriterion; import pulse.search.statistics.OptimiserStatistic; +import pulse.search.statistics.Statistic; import pulse.tasks.logs.Status; import pulse.tasks.processing.Result; import pulse.ui.components.PropertyHolderTable; import pulse.util.InstanceDescriptor; import pulse.util.PropertyEvent; import pulse.util.PropertyHolder; +import pulse.util.UpwardsNavigable; public class Calculation extends PropertyHolder implements Comparable, Response { @@ -57,7 +60,7 @@ public Calculation(SearchTask t) { status = INCOMPLETE; this.initOptimiser(); setParent(t); - instanceDescriptor.addListener(() -> initModelCriterion()); + instanceDescriptor.addListener(() -> initModelCriterion(rs)); } /** @@ -66,22 +69,31 @@ public Calculation(SearchTask t) { * @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; + problem = c.problem.copy(); + scheme = c.scheme.copy(); + rs = c.rs.copy(); + os = c.os.copy(); + status = c.status; if (c.getResult() != null) { - this.result = new Result(c.getResult()); + result = new Result(c.getResult()); } + instanceDescriptor.addListener(() -> initModelCriterion(rs)); } - + + public void conformTo(UpwardsNavigable owner) { + problem.setParent(owner); + scheme.setParent(owner); + rs.setParent(owner); + os.setParent(owner); + result.setParent(owner); + } + public void clear() { this.status = INCOMPLETE; this.problem = null; this.scheme = null; } - + /** *

* After setting and adopting the {@code problem} by this @@ -248,7 +260,7 @@ public ModelSelectionCriterion getModelSelectionCriterion() { public void setOptimiserStatistic(OptimiserStatistic os) { this.os = os; os.setParent(this); - initModelCriterion(); + initModelCriterion(os); } @Override @@ -263,11 +275,11 @@ public Problem getProblem() { public void initOptimiser() { this.setOptimiserStatistic( instantiate(OptimiserStatistic.class, OptimiserStatistic.getSelectedOptimiserDescriptor())); - this.initModelCriterion(); + initModelCriterion(os); } - public void initModelCriterion() { - setModelSelectionCriterion(instanceDescriptor.newInstance(ModelSelectionCriterion.class, os)); + protected void initModelCriterion(Statistic res) { + setModelSelectionCriterion(instanceDescriptor.newInstance(ModelSelectionCriterion.class, res)); } public DifferenceScheme getScheme() { @@ -319,27 +331,6 @@ public int compareTo(Calculation arg0) { return sThis.compareTo(sAnother); } - @Override - public boolean equals(Object o) { - if (o == this) { - return true; - } - - if (o == null) { - return false; - } - - if (!(o instanceof Calculation)) { - return false; - } - - var c = (Calculation) o; - - return (os.getStatistic().equals(c.getOptimiserStatistic().getStatistic()) - && rs.getStatistic().equals(c.getModelSelectionCriterion().getStatistic())); - - } - public static InstanceDescriptor getModelSelectionDescriptor() { return instanceDescriptor; } @@ -384,4 +375,37 @@ public double objectiveFunction(GeneralTask task) throws SolverException { return (double) os.getStatistic().getValue(); } + @Override + public int hashCode() { + int hash = 7; + hash = 79 * hash + Objects.hashCode(this.problem); + hash = 79 * hash + Objects.hashCode(this.scheme); + hash = 79 * hash + Objects.hashCode(this.result); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final Calculation other = (Calculation) obj; + if (!Objects.equals(this.problem, other.problem)) { + return false; + } + if (!Objects.equals(this.scheme, other.scheme)) { + return false; + } + if (!Objects.equals(this.result, other.result)) { + return false; + } + return true; + } + } \ No newline at end of file diff --git a/src/main/java/pulse/tasks/SearchTask.java b/src/main/java/pulse/tasks/SearchTask.java index 733a939..91633c8 100644 --- a/src/main/java/pulse/tasks/SearchTask.java +++ b/src/main/java/pulse/tasks/SearchTask.java @@ -280,8 +280,7 @@ public void run() { } current.getProblem().parameterListChanged(); // get updated list of parameters - setDefaultOptimiser(); - + super.run(); } @@ -333,8 +332,12 @@ public void storeCalculation() { } public void switchTo(Calculation calc) { - current = calc; + current.setParent(null); + current.conformTo(null); + current = new Calculation(calc); current.setParent(this); + calc.conformTo(calc); + current.setStatus(Status.READY); var e = new TaskRepositoryEvent(TaskRepositoryEvent.State.TASK_MODEL_SWITCH, this.getIdentifier()); fireRepositoryEvent(e); } diff --git a/src/main/java/pulse/tasks/logs/AbstractLogger.java b/src/main/java/pulse/tasks/logs/AbstractLogger.java new file mode 100644 index 0000000..f128837 --- /dev/null +++ b/src/main/java/pulse/tasks/logs/AbstractLogger.java @@ -0,0 +1,82 @@ +package pulse.tasks.logs; + +import java.util.concurrent.ExecutorService; +import static java.util.concurrent.Executors.newSingleThreadExecutor; +import javax.swing.JComponent; +import pulse.tasks.TaskManager; +import static pulse.tasks.logs.Status.DONE; +import pulse.util.Descriptive; + +public abstract class AbstractLogger implements Descriptive { + + private final ExecutorService updateExecutor; + + public AbstractLogger() { + updateExecutor = newSingleThreadExecutor(); + } + + public synchronized void update() { + var task = TaskManager.getManagerInstance().getSelectedTask(); + + if (task == null) { + return; + } + + var log = task.getLog(); + + if (log.isStarted()) { + post(log.lastEntry()); + } + + } + + public ExecutorService getUpdateExecutor() { + return updateExecutor; + } + + public synchronized void callUpdate() { + updateExecutor.submit(() -> update()); + } + + public void postAll() { + clear(); + + var task = TaskManager.getManagerInstance().getSelectedTask(); + + if (task != null) { + + var log = task.getLog(); + + if (log.isStarted()) { + + log.getLogEntries().stream().forEach(entry -> post(entry)); + + if (task.getStatus() == DONE) { + printTimeTaken(log); + } + + } + + } + + } + + @Override + public String describe() { + var task = TaskManager.getManagerInstance().getSelectedTask(); + return "Log" + (task == null ? "" : "_" + task.getIdentifier().getValue()); + } + + public abstract JComponent getGUIComponent(); + + public abstract void printTimeTaken(Log log); + + public abstract void post(LogEntry logEntry); + + public abstract void post(String text); + + public abstract void clear(); + + public abstract boolean isEmpty(); + +} diff --git a/src/main/java/pulse/tasks/logs/DataLogEntry.java b/src/main/java/pulse/tasks/logs/DataLogEntry.java index 2fad022..94b8774 100644 --- a/src/main/java/pulse/tasks/logs/DataLogEntry.java +++ b/src/main/java/pulse/tasks/logs/DataLogEntry.java @@ -5,6 +5,8 @@ import pulse.math.Parameter; import pulse.math.ParameterIdentifier; import pulse.properties.NumericProperties; +import static pulse.properties.NumericProperties.def; +import static pulse.properties.NumericPropertyKeyword.OBJECTIVE_FUNCTION; import pulse.tasks.SearchTask; import pulse.tasks.TaskManager; @@ -54,10 +56,19 @@ public DataLogEntry(SearchTask task) { private void fill() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { var task = TaskManager.getManagerInstance().getTask(getIdentifier()); entry = task.searchVector().getParameters(); + //iteration var pval = task.getIterativeState().getIteration(); var pid = new Parameter(new ParameterIdentifier(pval.getType())); - pid.setValue( (int) pval.getValue() ); + pid.setValue((int) pval.getValue()); + //cost + var costId = new Parameter(new ParameterIdentifier(OBJECTIVE_FUNCTION)); + var costval = task.getIterativeState().getCost(); + // entry.add(0, pid); + if (NumericProperties.isValueSensible(def(OBJECTIVE_FUNCTION), costval)) { + costId.setValue(costval); + entry.add(costId); + } } public List getData() { @@ -90,15 +101,15 @@ public String toString() { var def = NumericProperties.def(p.getIdentifier().getKeyword()); boolean b = def.getValue() instanceof Integer; Number val; - if(b) { + if (b) { val = (int) Math.rint(p.getApparentValue()); - } else{ + } else { val = p.getApparentValue(); } def.setValue(val); sb.append(def.getAbbreviation(false)); int index = p.getIdentifier().getIndex(); - if(index > 0) { + if (index > 0) { sb.append(" - ").append(index); } sb.append("<"); @@ -116,4 +127,4 @@ public String toString() { } -} +} \ No newline at end of file diff --git a/src/main/java/pulse/tasks/logs/Log.java b/src/main/java/pulse/tasks/logs/Log.java index 5b51953..21a32df 100644 --- a/src/main/java/pulse/tasks/logs/Log.java +++ b/src/main/java/pulse/tasks/logs/Log.java @@ -1,6 +1,8 @@ package pulse.tasks.logs; import java.time.LocalTime; +import static java.time.temporal.ChronoUnit.MILLIS; +import static java.time.temporal.ChronoUnit.SECONDS; import java.util.List; import java.util.Objects; import java.util.concurrent.CopyOnWriteArrayList; @@ -24,7 +26,7 @@ public class Log extends Group { private LocalTime end; private final Identifier id; private final List listeners; - private static boolean verbose = false; + private static boolean graphical = true; /** * Creates a {@code Log} for this {@code task} that will automatically store @@ -48,7 +50,7 @@ public Log(SearchTask task) { /** * Do these actions each time data has been collected for this task. */ - if (task.getStatus() != Status.INCOMPLETE && verbose) { + if (task.getStatus() != Status.INCOMPLETE) { logEntries.add(le); notifyListeners(le); } @@ -105,7 +107,7 @@ public final Identifier getIdentifier() { * @return {@code true} if the start time is not {@code null} */ public boolean isStarted() { - return start != null; + return logEntries.size() > 0; } /** @@ -173,18 +175,29 @@ public LogEntry lastEntry() { * * @return {@code true} if the verbose flag is on */ - public static boolean isVerbose() { - return verbose; + public static boolean isGraphicalLog() { + return graphical; } /** * Sets the verbose flag to {@code verbose} * * @param verbose the new value of the flag - * @see isVerbose() + * @see #isGraphicalLog() */ - public static void setVerbose(boolean verbose) { - Log.verbose = verbose; + public static void setGraphicalLog(boolean verbose) { + Log.graphical = verbose; + } + + /** + * Time taken where the first array element contains seconds [0] and the second contains milliseconds [1]. + * @return an array of long values that sum um to the time taken to process a task + */ + + public long[] timeTaken() { + var seconds = SECONDS.between(getStart(), getEnd()); + var ms = MILLIS.between(getStart(), getEnd()) - 1000L * seconds; + return new long[] {seconds, ms}; } -} +} \ No newline at end of file diff --git a/src/main/java/pulse/tasks/logs/LogEntry.java b/src/main/java/pulse/tasks/logs/LogEntry.java index 7841a91..f311fd1 100644 --- a/src/main/java/pulse/tasks/logs/LogEntry.java +++ b/src/main/java/pulse/tasks/logs/LogEntry.java @@ -3,6 +3,7 @@ import java.time.LocalDateTime; import java.time.LocalTime; import java.util.Objects; +import pulse.Response; import pulse.tasks.Identifier; import pulse.tasks.SearchTask; @@ -19,9 +20,10 @@ */ public class LogEntry { - private Identifier identifier; - private LocalTime time; - + private final Identifier identifier; + private final LocalTime time; + private final LogEntry previous; + /** *

* Creates a {@code LogEntry} from this {@code SearchTask}. The data of the @@ -34,6 +36,17 @@ public LogEntry(SearchTask t) { Objects.requireNonNull(t, Messages.getString("LogEntry.NullTaskError")); time = LocalDateTime.now().toLocalTime(); identifier = t.getIdentifier(); + var list = t.getLog().getLogEntries(); + if(list != null && !list.isEmpty()) { + previous = list.get(list.size() - 1); + } + else { + previous = null; + } + } + + public LogEntry getPreviousEntry() { + return previous; } public Identifier getIdentifier() { @@ -44,4 +57,4 @@ public LocalTime getTime() { return time; } -} +} \ No newline at end of file diff --git a/src/main/java/pulse/tasks/processing/Buffer.java b/src/main/java/pulse/tasks/processing/Buffer.java index e3a1684..90ef5bd 100644 --- a/src/main/java/pulse/tasks/processing/Buffer.java +++ b/src/main/java/pulse/tasks/processing/Buffer.java @@ -8,10 +8,14 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.stream.Stream; +import org.apache.commons.math3.stat.regression.SimpleRegression; +import pulse.math.ParameterIdentifier; import pulse.math.ParameterVector; import pulse.properties.NumericProperty; import pulse.properties.NumericPropertyKeyword; +import static pulse.properties.NumericPropertyKeyword.OBJECTIVE_FUNCTION; import pulse.properties.Property; import pulse.search.GeneralTask; import pulse.util.PropertyHolder; @@ -48,7 +52,7 @@ public ParameterVector[] getData() { /* * Re-inits the storage. */ - public void init() { + public final void init() { this.data = new ParameterVector[size]; statistic = new double[size]; } @@ -105,11 +109,17 @@ public double average(NumericPropertyKeyword index) { double av = 0; - for (ParameterVector v : data) { - av += v.getParameterValue(index, 0); + if (index == OBJECTIVE_FUNCTION) { + av = Arrays.stream(statistic).average().getAsDouble(); + } else { + + for (ParameterVector v : data) { + av += v.getParameterValue(index, 0); + } + av /= data.length; } - return av / data.length; + return av; } diff --git a/src/main/java/pulse/ui/ColorGenerator.java b/src/main/java/pulse/ui/ColorGenerator.java new file mode 100644 index 0000000..2e2388d --- /dev/null +++ b/src/main/java/pulse/ui/ColorGenerator.java @@ -0,0 +1,45 @@ +package pulse.ui; + +import java.awt.Color; +import static java.awt.Color.BLUE; +import static java.awt.Color.GREEN; +import static java.awt.Color.RED; +import java.util.ArrayList; +import java.util.Collections; + +public class ColorGenerator { + + private Color a, b, c; + + public ColorGenerator() { + a = RED; + b = GREEN; + c = BLUE; + } + + public Color[] random(int number) { + var list = new ArrayList(); + for(int i = 0; i < number; i++) { + list.add(sample(i/(double)(number - 1))); + } + //Collections.shuffle(list); + return list.toArray(new Color[list.size()]); + } + + public Color sample(double seed) { + return seed < 0.5 ? + mix(a, b, (float) (seed*2)) + : mix(b, c,(float)((seed-0.5)*2)); + } + + private static Color mix(Color a, Color b, float ratio) { + float[] aRgb = a.getRGBComponents(null); + float[] bRgb = b.getRGBComponents(null); + float[] cRgb = new float[3]; + for(int i = 0; i < cRgb.length; i++) { + cRgb[i] = aRgb[i] * (1.0f - ratio) + bRgb[i] * ratio; + } + return new Color(cRgb[0], cRgb[1], cRgb[2]); + } + +} \ No newline at end of file diff --git a/src/main/java/pulse/ui/Launcher.java b/src/main/java/pulse/ui/Launcher.java index de97832..ee238ac 100644 --- a/src/main/java/pulse/ui/Launcher.java +++ b/src/main/java/pulse/ui/Launcher.java @@ -17,8 +17,7 @@ import javax.swing.JOptionPane; import javax.swing.UIManager; -import com.alee.laf.WebLookAndFeel; -import com.alee.skin.dark.WebDarkSkin; +import com.formdev.flatlaf.FlatDarkLaf; import java.io.IOException; import java.util.logging.Level; import java.util.logging.Logger; @@ -65,9 +64,10 @@ public static void main(String[] args) { splashScreen(); - WebLookAndFeel.install(WebDarkSkin.class); + //WebLookAndFeel.install(WebDarkSkin.class); + FlatDarkLaf.setup(); try { - UIManager.setLookAndFeel(new WebLookAndFeel()); + UIManager.setLookAndFeel(new FlatDarkLaf()); } catch (Exception ex) { System.err.println("Failed to initialize LaF"); } diff --git a/src/main/java/pulse/ui/components/AuxPlotter.java b/src/main/java/pulse/ui/components/AuxPlotter.java index 8150c4c..602a3d6 100644 --- a/src/main/java/pulse/ui/components/AuxPlotter.java +++ b/src/main/java/pulse/ui/components/AuxPlotter.java @@ -1,58 +1,85 @@ package pulse.ui.components; +import java.awt.Color; import java.awt.Font; +import javax.swing.JLabel; import javax.swing.UIManager; +import org.jfree.chart.ChartFactory; import org.jfree.chart.ChartPanel; import org.jfree.chart.JFreeChart; +import org.jfree.chart.plot.CombinedDomainXYPlot; +import static org.jfree.chart.plot.PlotOrientation.VERTICAL; import org.jfree.chart.plot.XYPlot; public abstract class AuxPlotter { - + private ChartPanel chartPanel; private JFreeChart chart; private XYPlot plot; - + + public AuxPlotter() { + //empty + } + public AuxPlotter(String xLabel, String yLabel) { - createChart(xLabel, yLabel); - chart.setBackgroundPaint(UIManager.getColor("Panel.background")); + setChart( ChartFactory.createScatterPlot("", xLabel, yLabel, null, VERTICAL, true, true, false) ); + + setPlot( chart.getXYPlot() ); + chart.removeLegend(); - plot = chart.getXYPlot(); setFonts(); - - chart.removeLegend(); - chartPanel = new ChartPanel(chart); } - - public void setFonts() { - var fontLabel = new Font("Arial", Font.PLAIN, 20); - var fontTicks = new Font("Arial", Font.PLAIN, 16); - var plot = getPlot(); - plot.getDomainAxis().setLabelFont(fontLabel); - plot.getDomainAxis().setTickLabelFont(fontTicks); - plot.getRangeAxis().setLabelFont(fontLabel); - plot.getRangeAxis().setTickLabelFont(fontTicks); + + public final void setFonts() { + var jlabel = new JLabel(); + var label = jlabel.getFont().deriveFont(20f); + var ticks = jlabel.getFont().deriveFont(16f); + chart.getTitle().setFont(jlabel.getFont().deriveFont(20f)); + + if (plot instanceof CombinedDomainXYPlot) { + var combinedPlot = (CombinedDomainXYPlot) plot; + combinedPlot.getSubplots().stream().forEach(sp -> setFontsForPlot((XYPlot)sp, label, ticks)); + } else { + setFontsForPlot(plot, label, ticks); + } + } - - public abstract void createChart(String xLabel, String yLabel); - + + private void setFontsForPlot(XYPlot p, Font label, Font ticks) { + var foreColor = UIManager.getColor("Label.foreground"); + var domainAxis = p.getDomainAxis(); + Chart.setAxisFontColor(domainAxis, foreColor); + var rangeAxis = p.getRangeAxis(); + Chart.setAxisFontColor(rangeAxis, foreColor); + } + public abstract void plot(T t); - - public ChartPanel getChartPanel() { + + public final ChartPanel getChartPanel() { return chartPanel; } - - public JFreeChart getChart() { + + public final JFreeChart getChart() { return chart; } - - public XYPlot getPlot() { + + public final XYPlot getPlot() { return plot; } - - public void setChart(JFreeChart chart) { + + public final void setPlot(XYPlot plot) { + this.plot = plot; + plot.setBackgroundPaint(chart.getBackgroundPaint()); + } + + public final void setChart(JFreeChart chart) { this.chart = chart; + var color = UIManager.getLookAndFeelDefaults().getColor("TextPane.background"); + chart.setBackgroundPaint(color); + chartPanel = new ChartPanel(chart); + this.plot = chart.getXYPlot(); } - -} + +} \ No newline at end of file diff --git a/src/main/java/pulse/ui/components/CalculationTable.java b/src/main/java/pulse/ui/components/CalculationTable.java index 95c4ab1..4ebf483 100644 --- a/src/main/java/pulse/ui/components/CalculationTable.java +++ b/src/main/java/pulse/ui/components/CalculationTable.java @@ -8,6 +8,7 @@ import java.util.concurrent.Executors; import javax.swing.JTable; +import javax.swing.ListSelectionModel; import javax.swing.SwingUtilities; import javax.swing.event.ListSelectionEvent; import javax.swing.table.TableCellRenderer; @@ -50,10 +51,7 @@ public CalculationTable() { var instance = TaskManager.getManagerInstance(); instance.addTaskRepositoryListener(e -> { - if (e.getState() == TaskRepositoryEvent.State.TASK_MODEL_SWITCH) { - var t = instance.getTask(e.getId()); - identifySelection(t); - } else if (e.getState() == TaskRepositoryEvent.State.TASK_CRITERION_SWITCH) { + if (e.getState() == TaskRepositoryEvent.State.TASK_CRITERION_SWITCH) { update(TaskManager.getManagerInstance().getSelectedTask()); } @@ -84,7 +82,9 @@ public void initListeners() { lsm.addListSelectionListener((ListSelectionEvent e) -> { var task = TaskManager.getManagerInstance().getSelectedTask(); var id = convertRowIndexToModel(this.getSelectedRow()); - if (!lsm.getValueIsAdjusting() && id > -1 && id < task.getStoredCalculations().size()) { + if (!lsm.getValueIsAdjusting() + && id > -1 + && id < task.getStoredCalculations().size()) { plotExecutor.submit(() -> { task.switchTo(task.getStoredCalculations().get(id)); diff --git a/src/main/java/pulse/ui/components/Chart.java b/src/main/java/pulse/ui/components/Chart.java index 68dda2d..7fe372e 100644 --- a/src/main/java/pulse/ui/components/Chart.java +++ b/src/main/java/pulse/ui/components/Chart.java @@ -26,6 +26,7 @@ import org.jfree.chart.ChartPanel; import org.jfree.chart.JFreeChart; import org.jfree.chart.annotations.XYTitleAnnotation; +import org.jfree.chart.axis.Axis; import org.jfree.chart.block.BlockBorder; import org.jfree.chart.plot.XYPlot; import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer; @@ -81,7 +82,7 @@ public Chart() { final TaskManager instance = TaskManager.getManagerInstance(); chart.removeLegend(); - chart.setBackgroundPaint(UIManager.getColor("Panel.background")); + chart.setBackgroundPaint(UIManager.getColor("TextPane.background")); chartPanel = new ChartPanel(chart) { @Override @@ -146,16 +147,18 @@ public double xCoord(MouseEvent e) { } private void setFonts() { - var fontLabel = new Font("Arial", Font.PLAIN, 20); - var fontTicks = new Font("Arial", Font.PLAIN, 14); - plot.getDomainAxis().setLabelFont(fontLabel); - plot.getDomainAxis().setTickLabelFont(fontTicks); - plot.getRangeAxis().setLabelFont(fontLabel); - plot.getRangeAxis().setTickLabelFont(fontTicks); + var foreColor = UIManager.getColor("Label.foreground"); + setAxisFontColor(plot.getDomainAxis(), foreColor); + setAxisFontColor(plot.getRangeAxis(), foreColor); + } + + public static void setAxisFontColor(Axis axis, Color color) { + axis.setLabelPaint(color); + axis.setTickLabelPaint(color); } private void setBackgroundAndGrid() { - // plot.setBackgroundPaint(UIManager.getColor("Panel.background")); + plot.setBackgroundPaint(UIManager.getColor("TextPane.background")); plot.setRangeGridlinesVisible(true); plot.setRangeGridlinePaint(GRAY); diff --git a/src/main/java/pulse/ui/components/GraphicalLogPane.java b/src/main/java/pulse/ui/components/GraphicalLogPane.java new file mode 100644 index 0000000..82f8424 --- /dev/null +++ b/src/main/java/pulse/ui/components/GraphicalLogPane.java @@ -0,0 +1,98 @@ +package pulse.ui.components; + +import javax.swing.JComponent; +import javax.swing.JScrollPane; +import javax.swing.ScrollPaneConstants; +import static pulse.properties.NumericPropertyKeyword.ITERATION; +import pulse.tasks.TaskManager; +import pulse.tasks.listeners.TaskRepositoryEvent; +import pulse.tasks.logs.AbstractLogger; +import pulse.tasks.logs.DataLogEntry; +import pulse.tasks.logs.Log; +import pulse.tasks.logs.LogEntry; +import static pulse.tasks.logs.Status.DONE; + +@SuppressWarnings("serial") +public class GraphicalLogPane extends AbstractLogger { + + private final LogChart chart; + private final JScrollPane pane; + + public GraphicalLogPane() { + pane = new JScrollPane(); + pane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + chart = new LogChart(); + pane.setViewportView(chart.getChartPanel()); + TaskManager.getManagerInstance().addTaskRepositoryListener( e -> { + if(e.getState() == TaskRepositoryEvent.State.TASK_SUBMITTED) { + chart.clear(); + } + }); + } + + @Override + public JComponent getGUIComponent() { + return pane; + } + + @Override + public void printTimeTaken(Log log) { + long[] time = log.timeTaken(); + StringBuilder sb = new StringBuilder("Finished in "); + sb.append(time[0]).append(" s ").append(time[1]).append(" ms."); + } + + @Override + public void post(LogEntry logEntry) { + if(logEntry instanceof DataLogEntry) { + var dle = (DataLogEntry) logEntry; + double iteration = dle.getData().stream() + .filter(p -> p.getIdentifier().getKeyword() == ITERATION) + .findAny().get().getApparentValue(); + chart.changeAxis(true); + chart.plot((DataLogEntry)logEntry, iteration); + } + } + + @Override + public void postAll() { + clear(); + + var task = TaskManager.getManagerInstance().getSelectedTask(); + + if (task != null) { + + var log = task.getLog(); + + if (log.isStarted()) { + + chart.clear(); + chart.changeAxis(false); + chart.plot(log); + + if (task.getStatus() == DONE) { + printTimeTaken(log); + } + + } + + } + + } + + @Override + public void post(String text) { + //not supported + } + + @Override + public void clear() { + chart.clear(); + } + + @Override + public boolean isEmpty() { + return false; + } + +} \ No newline at end of file diff --git a/src/main/java/pulse/ui/components/LogChart.java b/src/main/java/pulse/ui/components/LogChart.java new file mode 100644 index 0000000..ba40ae4 --- /dev/null +++ b/src/main/java/pulse/ui/components/LogChart.java @@ -0,0 +1,220 @@ +package pulse.ui.components; + +import static java.util.Objects.requireNonNull; + +import java.awt.BasicStroke; +import java.awt.Color; +import static java.awt.Color.WHITE; +import static java.awt.Color.black; +import java.awt.Dimension; +import java.time.Duration; +import java.time.LocalTime; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import javax.swing.SwingUtilities; + +import org.jfree.chart.JFreeChart; +import org.jfree.chart.annotations.XYTitleAnnotation; +import org.jfree.chart.axis.NumberAxis; +import org.jfree.chart.axis.NumberTickUnit; +import org.jfree.chart.block.BlockBorder; +import org.jfree.chart.plot.CombinedDomainXYPlot; +import org.jfree.chart.plot.Plot; +import org.jfree.chart.plot.PlotOrientation; +import org.jfree.chart.plot.XYPlot; +import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer; +import org.jfree.chart.title.LegendTitle; +import org.jfree.chart.ui.RectangleAnchor; +import org.jfree.chart.ui.RectangleEdge; +import org.jfree.data.Range; +import org.jfree.data.xy.XYSeries; +import org.jfree.data.xy.XYSeriesCollection; +import pulse.Response; +import pulse.math.ParameterIdentifier; + +import static pulse.properties.NumericPropertyKeyword.ITERATION; +import pulse.tasks.SearchTask; +import pulse.tasks.TaskManager; +import pulse.tasks.logs.DataLogEntry; +import pulse.tasks.logs.Log; +import pulse.tasks.logs.StateEntry; +import pulse.tasks.logs.Status; +import pulse.tasks.processing.Buffer; +import pulse.ui.ColorGenerator; + +public class LogChart extends AuxPlotter { + + private final Map plots; + private Color[] colors; + private static final ColorGenerator cg = new ColorGenerator(); + public final static int HEIGHT_FACTOR = 75; + public final static int MARGIN = 10; + + public LogChart() { + var plot = new CombinedDomainXYPlot(new NumberAxis("Iteration")); + plot.setGap(10.0); + plot.setOrientation(PlotOrientation.VERTICAL); + var chart = new JFreeChart("", JFreeChart.DEFAULT_TITLE_FONT, plot, true); + setChart(chart); + plots = new HashMap<>(); + getChart().removeLegend(); + } + + public final void clear() { + var p = (CombinedDomainXYPlot) getPlot(); + if (p != null) { + if (p.getDomainAxis() != null) { + p.getDomainAxis().setAutoRange(true); + } + plots.values().stream().forEach(pp -> p.remove(pp)); + } + plots.clear(); + colors = new Color[0]; + } + + private void setLegendTitle(Plot plot) { + var lt = new LegendTitle(plot); + lt.setBackgroundPaint(new Color(200, 200, 255, 100)); + lt.setFrame(new BlockBorder(black)); + lt.setPosition(RectangleEdge.RIGHT); + var ta = new XYTitleAnnotation(0.0, 0.8, lt, RectangleAnchor.LEFT); + ta.setMaxWidth(0.58); + ((XYPlot) plot).addAnnotation(ta); + } + + public final void add(ParameterIdentifier key, int no) { + var plot = new XYPlot(); + var axis = new NumberAxis(); + axis.setAutoRangeIncludesZero(false); + plot.setRangeAxis(axis); + + plot.setBackgroundPaint(getChart().getBackgroundPaint()); + + plots.put(key, plot); + ((CombinedDomainXYPlot) getPlot()).add(plot); + + int height = HEIGHT_FACTOR * plots.size(); + int width = getChartPanel().getParent().getWidth() - MARGIN; + getChartPanel().setPreferredSize(new Dimension(width, height)); + getChartPanel().revalidate(); + + var dataset = new XYSeriesCollection(); + var series = new XYSeries(key.toString()); + + dataset.addSeries(series); + dataset.addSeries(new XYSeries("Running average")); + plot.setDataset(dataset); + setLegendTitle(plot); + + setRenderer(plot, colors[no]); + setFonts(); + } + + private void setRenderer(XYPlot plt, Color clr) { + var renderer = new XYLineAndShapeRenderer(true, false); + renderer.setSeriesPaint(0, clr); + renderer.setSeriesPaint(1, WHITE); + var dashed = new BasicStroke(1.0f, BasicStroke.CAP_BUTT, + BasicStroke.JOIN_MITER, 10.0f, new float[]{10.0f}, 0.0f); + renderer.setSeriesStroke(1, dashed); + renderer.setSeriesVisibleInLegend(1, Boolean.FALSE); + plt.setRenderer(renderer); + } + + public void changeAxis(boolean iterationMode) { + var domainAxis = (NumberAxis) getPlot().getDomainAxis(); + domainAxis.setLabel(iterationMode ? "Iteration" : "Time (ms)"); + domainAxis.setAutoRange(!iterationMode); + if (iterationMode) { + domainAxis.setTickUnit(new NumberTickUnit(1)); + } else { + domainAxis.setAutoTickUnitSelection(true); + } + } + + @Override + public void plot(Log l) { + requireNonNull(l); + + List startTimes = l.getLogEntries().stream() + .filter(le -> le instanceof DataLogEntry && le.getPreviousEntry() instanceof StateEntry) + .map(entry -> entry.getTime()).collect(Collectors.toList()); + + if (!startTimes.isEmpty()) { + var recentStart = startTimes.get(startTimes.size() - 1); + l.getLogEntries().stream().filter(le -> le.getTime().isAfter(recentStart)) + .filter(e -> e instanceof DataLogEntry).forEach(dle + -> plot((DataLogEntry) dle, + Duration.between(recentStart, dle.getTime()).toMillis())); + } + + } + + private static void adjustRange(XYPlot pl, int iteration, int bufSize) { + int lower = (iteration / bufSize) * bufSize; + + var domainAxis = pl.getDomainAxis(); + var r = domainAxis.getRange(); + var newR = new Range(lower, lower + bufSize); + + if (!r.equals(newR) && iteration > lower) { + ((XYPlot) pl).getDomainAxis().setRange(lower, lower + bufSize); + } + } + + public final void plot(DataLogEntry dle, double iterationOrTime) { + requireNonNull(dle); + + var data = dle.getData(); + int size = data.size(); + + if (colors == null || colors.length < size) { + colors = cg.random(size - 1); + } + + SearchTask task = TaskManager.getManagerInstance().getTask(dle.getIdentifier()); + Buffer buf = task.getBuffer(); + final int bufSize = buf.getData().length; + + for (int i = 0, j = 0; i < size; i++) { + var p = data.get(i); + var np = p.getIdentifier(); + + if (np.getKeyword() == ITERATION) { + continue; + } + + double value = p.getApparentValue(); + + if (!plots.containsKey(np)) { + add(np, j++); + } + + Plot pl = plots.get(np); + + var dataset = (XYSeriesCollection) ((XYPlot) pl).getDataset(); + XYSeries series = (XYSeries) dataset.getSeries(0); + series.add(iterationOrTime, value); + + if (task.getStatus() == Status.IN_PROGRESS) { + + XYSeries runningAverage = dataset.getSeries(1); + if (iterationOrTime > buf.getData().length - 1) { + runningAverage.add(iterationOrTime, buf.average(np.getKeyword())); + } + + SwingUtilities.invokeLater(() -> adjustRange((XYPlot) pl, (int) iterationOrTime, bufSize)); + + } else { + var domainAxis = ((XYPlot) pl).getDomainAxis(); + domainAxis.setAutoRange(true); + } + + } + + } + +} diff --git a/src/main/java/pulse/ui/components/LogPane.java b/src/main/java/pulse/ui/components/LogPane.java deleted file mode 100644 index 5af4215..0000000 --- a/src/main/java/pulse/ui/components/LogPane.java +++ /dev/null @@ -1,136 +0,0 @@ -package pulse.ui.components; - -import static java.lang.System.err; -import static java.time.temporal.ChronoUnit.MILLIS; -import static java.time.temporal.ChronoUnit.SECONDS; -import static java.util.concurrent.Executors.newSingleThreadExecutor; -import static javax.swing.text.DefaultCaret.ALWAYS_UPDATE; -import static pulse.tasks.logs.Status.DONE; -import static pulse.ui.Messages.getString; - -import java.io.IOException; -import java.util.concurrent.ExecutorService; - -import javax.swing.JEditorPane; -import javax.swing.text.BadLocationException; -import javax.swing.text.DefaultCaret; -import javax.swing.text.html.HTMLDocument; -import javax.swing.text.html.HTMLEditorKit; - -import pulse.tasks.TaskManager; -import pulse.tasks.logs.Log; -import pulse.tasks.logs.LogEntry; -import pulse.util.Descriptive; - -@SuppressWarnings("serial") -public class LogPane extends JEditorPane implements Descriptive { - - private ExecutorService updateExecutor = newSingleThreadExecutor(); - - public LogPane() { - super(); - setContentType("text/html"); - setEditable(false); - var c = (DefaultCaret) getCaret(); - c.setUpdatePolicy(ALWAYS_UPDATE); - } - - private void post(LogEntry logEntry) { - post(logEntry.toString()); - } - - /* - private void postError(String text) { - var sb = new StringBuilder(); - sb.append(getString("DataLogEntry.FontTagError")); - sb.append(text); - sb.append(getString("DataLogEntry.FontTagClose")); - post(sb.toString()); - }*/ - private void post(String text) { - - final var doc = (HTMLDocument) getDocument(); - final var kit = (HTMLEditorKit) this.getEditorKit(); - try { - kit.insertHTML(doc, doc.getLength(), text, 0, 0, null); - } catch (BadLocationException e) { - err.println(getString("LogPane.InsertError")); //$NON-NLS-1$ - e.printStackTrace(); - } catch (IOException e) { - err.println(getString("LogPane.PrintError")); //$NON-NLS-1$ - e.printStackTrace(); - } - - } - - public void printTimeTaken(Log log) { - var seconds = SECONDS.between(log.getStart(), log.getEnd()); - var ms = MILLIS.between(log.getStart(), log.getEnd()) - 1000L * seconds; - var sb = new StringBuilder(); - sb.append(getString("LogPane.TimeTaken")); //$NON-NLS-1$ - sb.append(seconds + getString("LogPane.Seconds")); //$NON-NLS-1$ - sb.append(ms + getString("LogPane.Milliseconds")); //$NON-NLS-1$ - post(sb.toString()); - } - - public synchronized void callUpdate() { - updateExecutor.submit(() -> update()); - } - - public void printAll() { - clear(); - - var task = TaskManager.getManagerInstance().getSelectedTask(); - - if (task != null) { - - var log = task.getLog(); - - if (log.isStarted()) { - - log.getLogEntries().stream().forEach(entry -> post(entry)); - - if (task.getStatus() == DONE) { - printTimeTaken(log); - } - - } - - } - - } - - private synchronized void update() { - var task = TaskManager.getManagerInstance().getSelectedTask(); - - if (task == null) { - return; - } - - var log = task.getLog(); - - if (!log.isStarted()) { - return; - } - - post(log.lastEntry()); - } - - public void clear() { - try { - getDocument().remove(0, getDocument().getLength()); - } catch (BadLocationException e) { - e.printStackTrace(); - } - } - - public ExecutorService getUpdateExecutor() { - return updateExecutor; - } - - @Override - public String describe() { - return "Log_" + TaskManager.getManagerInstance().getSelectedTask().getIdentifier().getValue(); - } - -} diff --git a/src/main/java/pulse/ui/components/PulseChart.java b/src/main/java/pulse/ui/components/PulseChart.java index bb33646..5b4151a 100644 --- a/src/main/java/pulse/ui/components/PulseChart.java +++ b/src/main/java/pulse/ui/components/PulseChart.java @@ -4,13 +4,11 @@ import static java.awt.Color.black; import static java.awt.Font.PLAIN; import static java.util.Objects.requireNonNull; -import static org.jfree.chart.plot.PlotOrientation.VERTICAL; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Font; -import org.jfree.chart.ChartFactory; import org.jfree.chart.annotations.XYTitleAnnotation; import org.jfree.chart.block.BlockBorder; import org.jfree.chart.renderer.xy.XYDifferenceRenderer; @@ -41,16 +39,11 @@ private void setRenderer() { getPlot().setRenderer(rendererPulse); } - @Override - public void createChart(String xLabel, String yLabel) { - setChart(ChartFactory.createScatterPlot("", xLabel, yLabel, null, VERTICAL, true, true, false)); - } - private void setLegendTitle() { var plot = getPlot(); var lt = new LegendTitle(plot); lt.setItemFont(new Font("Dialog", PLAIN, 16)); - //lt.setBackgroundPaint(new Color(200, 200, 255, 100)); + lt.setBackgroundPaint(new Color(200, 200, 255, 100)); lt.setFrame(new BlockBorder(black)); lt.setPosition(RectangleEdge.RIGHT); var ta = new XYTitleAnnotation(0.5, 0.2, lt, RectangleAnchor.CENTER); @@ -68,8 +61,8 @@ public void plot(Calculation c) { var pulseDataset = new XYSeriesCollection(); - pulseDataset.addSeries(series(problem.getPulse(), c.getScheme().getGrid().getTimeStep(), - problem.getProperties().timeFactor(), startTime)); + pulseDataset.addSeries(series(problem.getPulse(), c.getScheme().getGrid().getTimeStep()/20.0, + problem.getProperties().characteristicTime(), startTime)); getPlot().setDataset(0, pulseDataset); } @@ -81,8 +74,8 @@ private static XYSeries series(Pulse pulse, double dx, double timeFactor, double double timeLimit = pulseShape.getPulseWidth(); double x = startTime/timeFactor; - series.add(TO_MILLIS * (startTime - dx * timeFactor / 10.), 0.0); - series.add(TO_MILLIS * (startTime + timeFactor*(timeLimit + dx / 10.)), 0.0); + series.add(TO_MILLIS * (startTime - dx * timeFactor / 100.), 0.0); + series.add(TO_MILLIS * (startTime + timeFactor*(timeLimit + dx / 100.)), 0.0); for (int i = 0, numPoints = (int) (timeLimit/dx); i < numPoints; i++) { series.add(x * timeFactor * TO_MILLIS, pulseShape.evaluateAt(x - startTime/timeFactor)); diff --git a/src/main/java/pulse/ui/components/ResidualsChart.java b/src/main/java/pulse/ui/components/ResidualsChart.java index 7a78ee2..7543707 100644 --- a/src/main/java/pulse/ui/components/ResidualsChart.java +++ b/src/main/java/pulse/ui/components/ResidualsChart.java @@ -1,9 +1,8 @@ package pulse.ui.components; import static java.util.Objects.requireNonNull; -import static org.jfree.chart.plot.PlotOrientation.VERTICAL; - import org.jfree.chart.ChartFactory; +import static org.jfree.chart.plot.PlotOrientation.VERTICAL; import org.jfree.data.statistics.HistogramDataset; import org.jfree.data.statistics.HistogramType; @@ -14,15 +13,13 @@ public class ResidualsChart extends AuxPlotter { private int binCount; public ResidualsChart(String xLabel, String yLabel) { - super(xLabel, yLabel); - binCount = 32; - } - - @Override - public void createChart(String xLabel, String yLabel) { setChart(ChartFactory.createHistogram("", xLabel, yLabel, null, VERTICAL, true, true, false)); + setPlot(getChart().getXYPlot()); + getChart().removeLegend(); + setFonts(); + binCount = 32; } - + @Override public void plot(ResidualStatistic stat) { requireNonNull(stat); diff --git a/src/main/java/pulse/ui/components/TaskBox.java b/src/main/java/pulse/ui/components/TaskBox.java index 9be85ed..13b8445 100644 --- a/src/main/java/pulse/ui/components/TaskBox.java +++ b/src/main/java/pulse/ui/components/TaskBox.java @@ -1,6 +1,5 @@ package pulse.ui.components; -import static java.awt.Color.WHITE; import static java.awt.event.ItemEvent.SELECTED; import static pulse.ui.Messages.getString; @@ -46,11 +45,10 @@ public TaskBox() { }); } - public void init() { + public final void init() { setMaximumSize(new Dimension(32767, 24)); setMinimumSize(new Dimension(250, 20)); setToolTipText(getString("TaskBox.DefaultText")); //$NON-NLS-1$ - setBackground(WHITE); } } diff --git a/src/main/java/pulse/ui/components/TextLogPane.java b/src/main/java/pulse/ui/components/TextLogPane.java new file mode 100644 index 0000000..f1b7dfc --- /dev/null +++ b/src/main/java/pulse/ui/components/TextLogPane.java @@ -0,0 +1,85 @@ +package pulse.ui.components; + +import pulse.tasks.logs.AbstractLogger; +import static java.lang.System.err; +import static javax.swing.text.DefaultCaret.ALWAYS_UPDATE; +import static pulse.ui.Messages.getString; + +import java.io.IOException; +import javax.swing.JComponent; + +import javax.swing.JEditorPane; +import javax.swing.JScrollPane; +import javax.swing.text.BadLocationException; +import javax.swing.text.DefaultCaret; +import javax.swing.text.html.HTMLDocument; +import javax.swing.text.html.HTMLEditorKit; + +import pulse.tasks.logs.Log; +import pulse.tasks.logs.LogEntry; + +@SuppressWarnings("serial") +public class TextLogPane extends AbstractLogger { + + private final JEditorPane editor; + private final JScrollPane pane; + + public TextLogPane() { + editor = new JEditorPane(); + editor.setContentType("text/html"); + editor.setEditable(false); + ( (DefaultCaret) editor.getCaret() ).setUpdatePolicy(ALWAYS_UPDATE); + pane = new JScrollPane(); + pane.setViewportView(editor); + } + + @Override + public void post(LogEntry logEntry) { + post(logEntry.toString()); + } + + @Override + public void post(String text) { + + final var doc = (HTMLDocument) editor.getDocument(); + final var kit = (HTMLEditorKit) editor.getEditorKit(); + try { + kit.insertHTML(doc, doc.getLength(), text, 0, 0, null); + } catch (BadLocationException e) { + err.println(getString("LogPane.InsertError")); //$NON-NLS-1$ + } catch (IOException e) { + err.println(getString("LogPane.PrintError")); //$NON-NLS-1$ + } + + } + + + public void printTimeTaken(Log log) { + var time = log.timeTaken(); + var sb = new StringBuilder(); + sb.append(getString("LogPane.TimeTaken")); //$NON-NLS-1$ + sb.append(time[0]).append(getString("LogPane.Seconds")); //$NON-NLS-1$ + sb.append(time[1]).append(getString("LogPane.Milliseconds")); //$NON-NLS-1$ + post(sb.toString()); + } + + @Override + public void clear() { + try { + editor.getDocument().remove(0, editor.getDocument().getLength()); + } catch (BadLocationException e) { + e.printStackTrace(); + } + } + + @Override + public JComponent getGUIComponent() { + return pane; + } + + @Override + public boolean isEmpty() { + return editor.getDocument().getLength() < 1; + } + +} \ No newline at end of file diff --git a/src/main/java/pulse/ui/components/buttons/LoaderButton.java b/src/main/java/pulse/ui/components/buttons/LoaderButton.java index 46c9b43..cad1066 100644 --- a/src/main/java/pulse/ui/components/buttons/LoaderButton.java +++ b/src/main/java/pulse/ui/components/buttons/LoaderButton.java @@ -19,7 +19,6 @@ import java.io.File; import java.io.IOException; -import javax.swing.BorderFactory; import javax.swing.JButton; import javax.swing.JFileChooser; import javax.swing.UIManager; @@ -27,7 +26,6 @@ import org.apache.commons.math3.exception.OutOfRangeException; import pulse.input.InterpolationDataset; -import pulse.ui.Messages; import pulse.util.ImageUtils; @SuppressWarnings("serial") @@ -36,8 +34,8 @@ public class LoaderButton extends JButton { private InterpolationDataset.StandartType dataType; private static File dir; - private final static Color NOT_HIGHLIGHTED = UIManager.getColor("Button.background"); - private final static Color HIGHLIGHTED = ImageUtils.blend(NOT_HIGHLIGHTED, Color.red, 0.75f); + private final static Color NOT_HIGHLIGHTED = UIManager.getColor("Button.background"); + private final static Color HIGHLIGHTED = ImageUtils.blend(NOT_HIGHLIGHTED, Color.red, 0.35f); public LoaderButton() { super(); @@ -49,7 +47,7 @@ public LoaderButton(String str) { init(); } - public void init() { + public final void init() { InterpolationDataset.addListener(e -> { if (dataType == e) { @@ -86,18 +84,17 @@ public void init() { showMessageDialog(getWindowAncestor((Component) arg0.getSource()), getString("LoaderButton.ReadError"), //$NON-NLS-1$ getString("LoaderButton.IOError"), //$NON-NLS-1$ ERROR_MESSAGE); - } - catch(OutOfRangeException ofre) { + } catch (OutOfRangeException ofre) { getDefaultToolkit().beep(); StringBuilder sb = new StringBuilder(getString("TextWrap.0")); - sb.append(getString("LoaderButton.OFRErrorDescriptor") ); + sb.append(getString("LoaderButton.OFRErrorDescriptor")); sb.append(ofre.getMessage()); sb.append(getString("LoaderButton.OFRErrorDescriptor2")); sb.append(getString("TextWrap.1")); - showMessageDialog(getWindowAncestor((Component) arg0.getSource()), - sb.toString(), + showMessageDialog(getWindowAncestor((Component) arg0.getSource()), + sb.toString(), getString("LoaderButton.OFRError"), //$NON-NLS-1$ - ERROR_MESSAGE); + ERROR_MESSAGE); } var size = getDataset(dataType).getData().size(); var label = ""; @@ -113,8 +110,10 @@ public void init() { default: throw new IllegalStateException("Unknown data type: " + dataType); } + StringBuilder sb = new StringBuilder(""); + sb.append(label).append(" data loaded! A total of ").append(size).append(" data points loaded."); showMessageDialog(getWindowAncestor((Component) arg0.getSource()), - "" + label + " data loaded! A total of " + size + " data points loaded.", + sb.toString(), "Data loaded", INFORMATION_MESSAGE); }); } @@ -124,11 +123,11 @@ public void setDataType(InterpolationDataset.StandartType dataType) { } public void highlight(boolean highlighted) { - setBorder(highlighted ? BorderFactory.createLineBorder(HIGHLIGHTED) : null); + setBackground(highlighted ? HIGHLIGHTED : NOT_HIGHLIGHTED); } public void highlightIfNeeded() { highlight(getDataset(dataType) == null); } -} +} \ No newline at end of file diff --git a/src/main/java/pulse/ui/components/controllers/AccessibleTableRenderer.java b/src/main/java/pulse/ui/components/controllers/AccessibleTableRenderer.java index b297d1a..04727c0 100644 --- a/src/main/java/pulse/ui/components/controllers/AccessibleTableRenderer.java +++ b/src/main/java/pulse/ui/components/controllers/AccessibleTableRenderer.java @@ -1,6 +1,5 @@ package pulse.ui.components.controllers; -import static java.awt.Color.RED; import java.awt.Component; import java.awt.Font; @@ -8,7 +7,6 @@ import javax.swing.JButton; import javax.swing.JLabel; import javax.swing.JTable; -import javax.swing.UIManager; import pulse.properties.Flag; import pulse.properties.NumericProperty; diff --git a/src/main/java/pulse/ui/components/controllers/InstanceCellEditor.java b/src/main/java/pulse/ui/components/controllers/InstanceCellEditor.java index 526823f..e9611d7 100644 --- a/src/main/java/pulse/ui/components/controllers/InstanceCellEditor.java +++ b/src/main/java/pulse/ui/components/controllers/InstanceCellEditor.java @@ -1,13 +1,11 @@ package pulse.ui.components.controllers; -import com.alee.utils.swing.PopupMenuAdapter; import java.awt.Component; import java.awt.event.ItemEvent; import javax.swing.DefaultCellEditor; import javax.swing.JComboBox; import javax.swing.JTable; -import javax.swing.event.PopupMenuEvent; import pulse.util.InstanceDescriptor; @@ -39,17 +37,7 @@ public Component getTableCellEditorComponent(JTable table, Object value, boolean } } }); - - combobox.addPopupMenuListener(new PopupMenuAdapter() { - - @Override - public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { - fireEditingCanceled(); - } - - } - ); - + return combobox; } diff --git a/src/main/java/pulse/ui/components/controllers/NumericPropertyRenderer.java b/src/main/java/pulse/ui/components/controllers/NumericPropertyRenderer.java index e655f9b..5f05afc 100644 --- a/src/main/java/pulse/ui/components/controllers/NumericPropertyRenderer.java +++ b/src/main/java/pulse/ui/components/controllers/NumericPropertyRenderer.java @@ -7,10 +7,8 @@ import javax.swing.JTable; import javax.swing.UIManager; import javax.swing.table.DefaultTableCellRenderer; -import pulse.math.Segment; import pulse.properties.NumericProperty; -import pulse.properties.Property; import pulse.properties.NumericPropertyFormatter; @SuppressWarnings("serial") @@ -29,25 +27,22 @@ public Component getTableCellRendererComponent(JTable table, Object value, boole if (value instanceof NumericProperty) { var jtf = initTextField((NumericProperty) value, table.isRowSelected(row)); - if (table.getEditorComponent() != null) { result = jtf; } else { - result = new JLabel(jtf.getText(), JLabel.RIGHT); - jtf = null; + result = (JLabel) super.getTableCellRendererComponent(table, + jtf.getText(), isSelected, hasFocus, row, column); + ((JLabel) result).setHorizontalAlignment(RIGHT); } - } else { var superRenderer = (JLabel) super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); superRenderer.setHorizontalAlignment(JLabel.LEFT); - superRenderer.setBackground( - isSelected - ? UIManager.getColor("JFormattedTextField.selectionBackground") - : UIManager.getColor("JFormattedTextField.background")); result = superRenderer; } + + result.setForeground(UIManager.getColor("List.foreground")); return result; } @@ -59,4 +54,4 @@ private static JFormattedTextField initTextField(NumericProperty np, boolean row return jtf; } -} \ No newline at end of file +} diff --git a/src/main/java/pulse/ui/components/controllers/ProblemCellRenderer.java b/src/main/java/pulse/ui/components/controllers/ProblemCellRenderer.java index 7c36077..4827624 100644 --- a/src/main/java/pulse/ui/components/controllers/ProblemCellRenderer.java +++ b/src/main/java/pulse/ui/components/controllers/ProblemCellRenderer.java @@ -4,20 +4,20 @@ import javax.swing.ImageIcon; import javax.swing.JTree; -import javax.swing.UIManager; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeCellRenderer; -import com.alee.managers.icon.LazyIcon; +//import com.alee.managers.icon.LazyIcon; import pulse.problem.statements.Problem; import pulse.util.ImageUtils; +import static pulse.util.ImageUtils.loadIcon; @SuppressWarnings("serial") public class ProblemCellRenderer extends DefaultTreeCellRenderer { - private static ImageIcon defaultIcon = (ImageIcon) ((LazyIcon) UIManager.getIcon("Tree.leafIcon")).getIcon(); - + private static ImageIcon defaultIcon = loadIcon("leaf.png", 16); + public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) { diff --git a/src/main/java/pulse/ui/components/controllers/SearchListRenderer.java b/src/main/java/pulse/ui/components/controllers/SearchListRenderer.java index e74951a..cfc884c 100644 --- a/src/main/java/pulse/ui/components/controllers/SearchListRenderer.java +++ b/src/main/java/pulse/ui/components/controllers/SearchListRenderer.java @@ -2,7 +2,6 @@ import static javax.swing.BorderFactory.createEmptyBorder; -import java.awt.Color; import java.awt.Component; import javax.swing.DefaultListCellRenderer; @@ -18,8 +17,7 @@ public Component getListCellRendererComponent(JList list, Object value, int i var renderer = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); ((JComponent) renderer).setBorder(createEmptyBorder(10, 10, 10, 10)); - renderer.setForeground(isSelected ? Color.DARK_GRAY : Color.white); - + return renderer; } diff --git a/src/main/java/pulse/ui/components/controllers/TaskTableRenderer.java b/src/main/java/pulse/ui/components/controllers/TaskTableRenderer.java index 85d3ba6..33ccdcb 100644 --- a/src/main/java/pulse/ui/components/controllers/TaskTableRenderer.java +++ b/src/main/java/pulse/ui/components/controllers/TaskTableRenderer.java @@ -3,16 +3,13 @@ import static java.awt.Font.BOLD; import java.awt.Component; -import java.awt.Font; import javax.swing.JLabel; import javax.swing.JTable; import pulse.properties.NumericProperty; -import pulse.properties.Property; import pulse.tasks.Identifier; import pulse.tasks.logs.Status; -import pulse.util.PropertyHolder; @SuppressWarnings("serial") public class TaskTableRenderer extends NumericPropertyRenderer { diff --git a/src/main/java/pulse/ui/components/listeners/LogExportListener.java b/src/main/java/pulse/ui/components/listeners/LogExportListener.java deleted file mode 100644 index 99a32ae..0000000 --- a/src/main/java/pulse/ui/components/listeners/LogExportListener.java +++ /dev/null @@ -1,7 +0,0 @@ -package pulse.ui.components.listeners; - -public interface LogExportListener { - - public void onLogExportRequest(); - -} diff --git a/src/main/java/pulse/ui/components/listeners/LogListener.java b/src/main/java/pulse/ui/components/listeners/LogListener.java new file mode 100644 index 0000000..5a07f5d --- /dev/null +++ b/src/main/java/pulse/ui/components/listeners/LogListener.java @@ -0,0 +1,8 @@ +package pulse.ui.components.listeners; + +public interface LogListener { + + public void onLogExportRequest(); + public void onLogModeChanged(boolean graphical); + +} \ No newline at end of file diff --git a/src/main/java/pulse/ui/components/panels/ChartToolbar.java b/src/main/java/pulse/ui/components/panels/ChartToolbar.java index b4d5e54..dca6e62 100644 --- a/src/main/java/pulse/ui/components/panels/ChartToolbar.java +++ b/src/main/java/pulse/ui/components/panels/ChartToolbar.java @@ -38,7 +38,7 @@ public final class ChartToolbar extends JToolBar { private final static int ICON_SIZE = 16; - private List listeners; + private final List listeners; private RangeTextFields rtf; diff --git a/src/main/java/pulse/ui/components/panels/DoubleTablePanel.java b/src/main/java/pulse/ui/components/panels/DoubleTablePanel.java index 11a5c19..ebb6bf6 100644 --- a/src/main/java/pulse/ui/components/panels/DoubleTablePanel.java +++ b/src/main/java/pulse/ui/components/panels/DoubleTablePanel.java @@ -1,18 +1,3 @@ -/* - * Copyright 2021 kotik. - * - * 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.ui.components.panels; import java.awt.GridBagConstraints; @@ -87,7 +72,6 @@ public void initComponents(JTable leftTable, String titleLeft, JTable rightTable var borderLeft = createTitledBorder(titleLeft); leftScroller.setBorder(borderLeft); - borderLeft.setTitleColor(java.awt.Color.WHITE); leftTable.setRowHeight(80); @@ -102,7 +86,6 @@ public void initComponents(JTable leftTable, String titleLeft, JTable rightTable var borderRight = createTitledBorder(titleRight); rightScroller.setBorder(borderRight); - borderRight.setTitleColor(java.awt.Color.WHITE); rightTable.setRowHeight(80); rightScroller.setViewportView(rightTable); diff --git a/src/main/java/pulse/ui/components/panels/LogToolbar.java b/src/main/java/pulse/ui/components/panels/LogToolbar.java index a9fd6a1..5db207b 100644 --- a/src/main/java/pulse/ui/components/panels/LogToolbar.java +++ b/src/main/java/pulse/ui/components/panels/LogToolbar.java @@ -1,7 +1,5 @@ package pulse.ui.components.panels; -import static pulse.tasks.logs.Log.isVerbose; -import static pulse.tasks.logs.Log.setVerbose; import static pulse.ui.Messages.getString; import static pulse.util.ImageUtils.loadIcon; @@ -14,13 +12,15 @@ import javax.swing.JCheckBox; import javax.swing.JToolBar; -import pulse.ui.components.listeners.LogExportListener; +import static pulse.tasks.logs.Log.setGraphicalLog; +import static pulse.tasks.logs.Log.isGraphicalLog; +import pulse.ui.components.listeners.LogListener; @SuppressWarnings("serial") public class LogToolbar extends JToolBar { private final static int ICON_SIZE = 16; - private List listeners; + private List listeners; public LogToolbar() { super(); @@ -35,24 +35,25 @@ public void initComponents() { var saveLogBtn = new JButton(loadIcon("save.png", ICON_SIZE, Color.white)); saveLogBtn.setToolTipText("Save"); - var verboseCheckBox = new JCheckBox(getString("LogToolBar.Verbose")); //$NON-NLS-1$ - verboseCheckBox.setSelected(isVerbose()); - verboseCheckBox.setHorizontalAlignment(CENTER); + var logmodeCheckbox = new JCheckBox(getString("LogToolBar.Verbose")); //$NON-NLS-1$ + logmodeCheckbox.setSelected(isGraphicalLog()); + logmodeCheckbox.setHorizontalAlignment(CENTER); - verboseCheckBox.addActionListener(event -> setVerbose(verboseCheckBox.isSelected())); + logmodeCheckbox.addActionListener(event -> { + boolean selected = logmodeCheckbox.isSelected(); + setGraphicalLog(selected); + listeners.stream().forEach(l -> l.onLogModeChanged(selected)); + }); - saveLogBtn.addActionListener(e -> notifyLog()); + saveLogBtn.addActionListener(e -> listeners.stream().forEach(l -> l.onLogExportRequest())); add(saveLogBtn); - add(verboseCheckBox); - } - public void notifyLog() { - listeners.stream().forEach(l -> l.onLogExportRequest()); + add(logmodeCheckbox); } - public void addLogExportListener(LogExportListener l) { + public void addLogListener(LogListener l) { listeners.add(l); } -} +} \ No newline at end of file diff --git a/src/main/java/pulse/ui/components/panels/ModelToolbar.java b/src/main/java/pulse/ui/components/panels/ModelToolbar.java index 7c0b550..770734e 100644 --- a/src/main/java/pulse/ui/components/panels/ModelToolbar.java +++ b/src/main/java/pulse/ui/components/panels/ModelToolbar.java @@ -18,7 +18,7 @@ @SuppressWarnings("serial") public class ModelToolbar extends JToolBar { - private final static int ICON_SIZE = 20; + private final static int ICON_SIZE = 16; public ModelToolbar() { super(); diff --git a/src/main/java/pulse/ui/components/panels/ProblemToolbar.java b/src/main/java/pulse/ui/components/panels/ProblemToolbar.java index 92e42ca..e06abd0 100644 --- a/src/main/java/pulse/ui/components/panels/ProblemToolbar.java +++ b/src/main/java/pulse/ui/components/panels/ProblemToolbar.java @@ -13,7 +13,6 @@ import java.awt.Component; import java.awt.GridLayout; import java.awt.event.ActionEvent; -import java.util.concurrent.Executors; import javax.swing.JButton; import javax.swing.JToolBar; diff --git a/src/main/java/pulse/ui/frames/LogFrame.java b/src/main/java/pulse/ui/frames/LogFrame.java index 6410fb7..7eeae66 100644 --- a/src/main/java/pulse/ui/frames/LogFrame.java +++ b/src/main/java/pulse/ui/frames/LogFrame.java @@ -6,7 +6,6 @@ import static java.awt.GridBagConstraints.WEST; import static java.lang.System.err; import static java.util.concurrent.TimeUnit.MILLISECONDS; -import static javax.swing.SwingUtilities.getWindowAncestor; import static pulse.io.export.ExportManager.askToExport; import static pulse.tasks.listeners.TaskRepositoryEvent.State.TASK_ADDED; import static pulse.ui.Messages.getString; @@ -14,22 +13,26 @@ import java.awt.BorderLayout; import java.awt.GridBagConstraints; -import javax.swing.JFrame; import javax.swing.JInternalFrame; -import javax.swing.JScrollPane; +import javax.swing.SwingUtilities; import pulse.tasks.TaskManager; import pulse.tasks.listeners.LogEntryListener; import pulse.tasks.logs.Log; import pulse.tasks.logs.LogEntry; -import pulse.ui.components.LogPane; +import pulse.tasks.logs.AbstractLogger; +import pulse.ui.components.GraphicalLogPane; +import pulse.ui.components.TextLogPane; +import pulse.ui.components.listeners.LogListener; import pulse.ui.components.panels.LogToolbar; import pulse.ui.components.panels.SystemPanel; @SuppressWarnings("serial") public class LogFrame extends JInternalFrame { - private LogPane logTextPane; + private AbstractLogger logger; + private final static AbstractLogger graphical = new GraphicalLogPane(); + private final static AbstractLogger text = new TextLogPane(); public LogFrame() { super("Log", true, false, true, true); @@ -39,12 +42,10 @@ public LogFrame() { } private void initComponents() { - logTextPane = new LogPane(); - var logScroller = new JScrollPane(); - logScroller.setViewportView(logTextPane); + logger = Log.isGraphicalLog() ? graphical : text; getContentPane().setLayout(new BorderLayout()); - getContentPane().add(logScroller, CENTER); + getContentPane().add(logger.getGUIComponent(), CENTER); var gridBagConstraints = new GridBagConstraints(); gridBagConstraints.anchor = WEST; @@ -53,19 +54,31 @@ private void initComponents() { getContentPane().add(new SystemPanel(), PAGE_END); var logToolbar = new LogToolbar(); - logToolbar.addLogExportListener(() -> { - if (logTextPane.getDocument().getLength() > 0) { - askToExport(logTextPane, (JFrame) getWindowAncestor(this), - getString("LogToolBar.FileFormatDescriptor")); + + var lel = new LogListener() { + @Override + public void onLogExportRequest() { + if (logger == text) { + askToExport(logger, null, getString("LogToolBar.FileFormatDescriptor")); + } else { + System.out.println("To export the log entries, please switch to text mode first!"); + } } - }); + + @Override + public void onLogModeChanged(boolean graphical) { + SwingUtilities.invokeLater(() -> setGraphicalLogger(graphical)); + } + }; + + logToolbar.addLogListener(lel); getContentPane().add(logToolbar, NORTH); } private void scheduleLogEvents() { var instance = TaskManager.getManagerInstance(); - instance.addSelectionListener(e -> logTextPane.printAll()); + instance.addSelectionListener(e -> logger.postAll()); instance.addTaskRepositoryListener(event -> { if (event.getState() != TASK_ADDED) { @@ -81,13 +94,12 @@ public void onLogFinished(Log log) { if (instance.getSelectedTask() == task) { try { - logTextPane.getUpdateExecutor().awaitTermination(10, MILLISECONDS); + logger.getUpdateExecutor().awaitTermination(10, MILLISECONDS); } catch (InterruptedException e) { err.println("Log not finished in time"); - e.printStackTrace(); } - logTextPane.printTimeTaken(log); + logger.printTimeTaken(log); } } @@ -95,7 +107,7 @@ public void onLogFinished(Log log) { @Override public void onNewEntry(LogEntry e) { if (instance.getSelectedTask() == task) { - logTextPane.callUpdate(); + logger.callUpdate(); } } @@ -105,8 +117,20 @@ public void onNewEntry(LogEntry e) { }); } - public LogPane getLogTextPane() { - return logTextPane; + public AbstractLogger getLogger() { + return logger; + } + + private void setGraphicalLogger(boolean graphicalLog) { + var old = logger; + logger = graphicalLog ? graphical : text; + + if (old != logger) { + getContentPane().remove(old.getGUIComponent()); + getContentPane().add(logger.getGUIComponent(), BorderLayout.CENTER); + logger.postAll(); + } + } -} +} \ No newline at end of file diff --git a/src/main/java/pulse/ui/frames/PreviewFrame.java b/src/main/java/pulse/ui/frames/PreviewFrame.java index 76a249d..508d5a7 100644 --- a/src/main/java/pulse/ui/frames/PreviewFrame.java +++ b/src/main/java/pulse/ui/frames/PreviewFrame.java @@ -15,6 +15,7 @@ import java.awt.BasicStroke; import java.awt.BorderLayout; import java.awt.Color; +import static java.awt.Color.WHITE; import java.awt.GridLayout; import java.awt.Rectangle; import java.util.ArrayList; @@ -46,6 +47,7 @@ import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer; import pulse.tasks.processing.ResultFormat; +import pulse.ui.components.Chart; @SuppressWarnings("serial") public class PreviewFrame extends JInternalFrame { @@ -93,7 +95,6 @@ private void init() { toolbar.add(selectX); selectXBox = new JComboBox<>(); - selectXBox.setFont(selectXBox.getFont().deriveFont(11)); toolbar.add(selectXBox); toolbar.add(new JSeparator()); @@ -102,7 +103,6 @@ private void init() { toolbar.add(selectY); selectYBox = new JComboBox<>(); - selectYBox.setFont(selectYBox.getFont().deriveFont(11)); toolbar.add(selectYBox); var drawSmoothBtn = new JToggleButton(); @@ -229,6 +229,9 @@ private static ChartPanel createEmptyPanel() { //plot.setRangeGridlinesVisible(false); //plot.setDomainGridlinesVisible(false); + var fore = UIManager.getColor("Label.foreground"); + plot.setDomainGridlinePaint(fore); + plot.getRenderer(1).setSeriesPaint(1, SMOOTH_COLOR); plot.getRenderer(0).setSeriesPaint(0, RESULT_COLOR); plot.getRenderer(0).setSeriesShape(0, @@ -244,6 +247,9 @@ private static ChartPanel createEmptyPanel() { cp.setMinimumDrawHeight(10); chart.setBackgroundPaint(UIManager.getColor("Panel.background")); + plot.setBackgroundPaint(chart.getBackgroundPaint()); + Chart.setAxisFontColor(plot.getDomainAxis(), fore); + Chart.setAxisFontColor(plot.getRangeAxis(), fore); return cp; } diff --git a/src/main/java/pulse/ui/frames/SearchOptionsFrame.java b/src/main/java/pulse/ui/frames/SearchOptionsFrame.java index 567c449..f638f4d 100644 --- a/src/main/java/pulse/ui/frames/SearchOptionsFrame.java +++ b/src/main/java/pulse/ui/frames/SearchOptionsFrame.java @@ -48,7 +48,6 @@ public class SearchOptionsFrame extends JInternalFrame { private final JTable rightTable; private final PathSolversList pathList; - 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[]{MAXTEMP}; @@ -196,7 +195,6 @@ public PathOptimiser getElementAt(int index) { } }); - setFont(FONT); setSelectionMode(SINGLE_SELECTION); setCellRenderer(new SearchListRenderer()); diff --git a/src/main/java/pulse/ui/frames/TaskControlFrame.java b/src/main/java/pulse/ui/frames/TaskControlFrame.java index f04f050..f56d6ed 100644 --- a/src/main/java/pulse/ui/frames/TaskControlFrame.java +++ b/src/main/java/pulse/ui/frames/TaskControlFrame.java @@ -55,6 +55,8 @@ public class TaskControlFrame extends JFrame { private InternalGraphFrame pulseFrame; private PulseMainMenu mainMenu; + + private final static int ICON_SIZE = 16; public static TaskControlFrame getInstance() { return instance; @@ -157,13 +159,13 @@ public void onRemoveRequest() { @Override public void onClearRequest() { - logFrame.getLogTextPane().clear(); + logFrame.getLogger().clear(); resultsFrame.getResultTable().clear(); } @Override public void onResetRequest() { - logFrame.getLogTextPane().clear(); + logFrame.getLogger().clear(); resultsFrame.getResultTable().removeAll(); } @@ -188,26 +190,26 @@ private void initComponents() { setJMenuBar(mainMenu); logFrame = new LogFrame(); - logFrame.setFrameIcon(loadIcon("log.png", 20, Color.white)); + logFrame.setFrameIcon(loadIcon("log.png", ICON_SIZE, Color.white)); resultsFrame = new ResultFrame(); - resultsFrame.setFrameIcon(loadIcon("result.png", 20, Color.white)); + resultsFrame.setFrameIcon(loadIcon("result.png", ICON_SIZE, Color.white)); previewFrame = new PreviewFrame(); - previewFrame.setFrameIcon(loadIcon("preview.png", 20, Color.white)); + previewFrame.setFrameIcon(loadIcon("preview.png", ICON_SIZE, Color.white)); taskManagerFrame = new TaskManagerFrame(); - taskManagerFrame.setFrameIcon(loadIcon("task_manager.png", 20, Color.white)); + taskManagerFrame.setFrameIcon(loadIcon("task_manager.png", ICON_SIZE, Color.white)); graphFrame = MainGraphFrame.getInstance(); - graphFrame.setFrameIcon(loadIcon("curves.png", 20, Color.white)); + graphFrame.setFrameIcon(loadIcon("curves.png", ICON_SIZE, Color.white)); problemStatementFrame = new ProblemStatementFrame(); - problemStatementFrame.setFrameIcon(loadIcon("heat_problem.png", 20, Color.white)); + problemStatementFrame.setFrameIcon(loadIcon("heat_problem.png", ICON_SIZE, Color.white)); modelFrame = new ModelSelectionFrame(); - modelFrame.setFrameIcon(loadIcon("stored.png", 20, Color.white)); + modelFrame.setFrameIcon(loadIcon("stored.png", ICON_SIZE, Color.white)); searchOptionsFrame = new SearchOptionsFrame(); - searchOptionsFrame.setFrameIcon(loadIcon("optimiser.png", 20, Color.white)); + searchOptionsFrame.setFrameIcon(loadIcon("optimiser.png", ICON_SIZE, Color.white)); pulseFrame = new InternalGraphFrame("Pulse Shape", new PulseChart("Time (ms)", "Laser Power (a. u.)")); - pulseFrame.setFrameIcon(loadIcon("pulse.png", 20, Color.white)); + pulseFrame.setFrameIcon(loadIcon("pulse.png", ICON_SIZE, Color.white)); pulseFrame.setVisible(false); /* diff --git a/src/main/resources/NumericProperty.xml b/src/main/resources/NumericProperty.xml index edf471e..06c47f7 100644 --- a/src/main/resources/NumericProperty.xml +++ b/src/main/resources/NumericProperty.xml @@ -1,5 +1,10 @@ + + + minimum="1.0E-4" value="0.01" primitive-type="double" discreet="true" default-search-variable="false">
Time taken: LogToolBar.FileFormatDescriptor=HTML Log Files LogToolBar.SaveButton=Save Log -LogToolBar.Verbose=Verbose log +LogToolBar.Verbose=Graphical NumberEditor.EditText=Edit NumberEditor.IllegalTableEntry=Illegal table entry NumberEditor.InvalidText=Invalid Text Entered