From ac550160d44799c095d436ec166749d170cee14f Mon Sep 17 00:00:00 2001 From: kotik-coder Date: Mon, 6 Jun 2022 20:26:00 +0300 Subject: [PATCH 1/3] Major Changes and Fixes #LinearAnisotropicPF - Changed reference from ParticipatingMedium to ThermoOpticalProperties - function(...) : replaced calculation with cosineTheta(i,k) call #CurveEventType - Added CALCULATION_FINISHED event type #DataEvent - Replaced reference to ExperimentalData with a more general reference to AbstractData #NetzschPulseCSVReader - Added locale support #SampleName - A default sample name of null is used, in order to avoid having too many 'Nameless' calculations #TridiagonalMatrixAlgorithm - Instead of holding a reference to the Grid object, the fields tau, N and h are now added - As the instance of this class is created just before the calculation starts, it does not need to reference Grid #ResultTableModel - Better handling of average results vs individual results - Average result now ignored when checking for previously calculated results of a task. This was done because an average result is not assigned to a task, therefore, it returns null when searching for a SearchTask ancestor. This led to a NullPointerException previously, but now has been fixed. #ProblemToolbar - Replaced unnecessary SingleThreadExecturo with a direct call to the plot (...) method #DataLoader - Added missing .incrementProgress() call when loading pulse data #Problem - Removed unnecessary check c.isIncomplete() for ExperimentalData when applying baseline- - Changed availableSolutions() to accomodate for possible multiple application of a single solver - Improved confusing assignments in the optimisationVector(...) method - Fixed error: assign(...) -> changed .get(...) to inverseTransform(...) - Consequently, made sure that this latter value is referenced by the setter methods - Removed shortName(...) method - Removed conditional statement prior to applying baseline in setBaseline(....) #Discretisation - Switched to ThermoOpticalProperties reference #HeatingCurve - Added lastCalculation(...) array to store last successful calculation - Added copyToLastCalculation() - apply(Baseline) now only works for a positive size of the time sequence #ADILinearisedSolver - Added an overriden clearArrays() method for array init purposes - Removed array initialisation from prepare(...) - prepare() now publicly overrides superclass method #ImplicitScheme - Removed unnecessary final int m argument from several methods #MixedLinearisedSolver - Changed scheme to accomodate the additional zeta parameter for rear-side parasitic heating - Added an overriding pulse(...) method - Changed firstBeta(...) #ImplicitLinearisedSolver - Removed unnecessary call to super.pulse(...) #General changes: - Added zeta to 1D Classical, Diathermic and ParticipatingMedium problems. Zeta serves to simulate a possible rear-surface heat source, either due to beam deflection or circumferential conduction #DiathermicMedium - Bounds in optimisationVector(...) for the DIATHERMIC_COEFFICIENT have been changed from arbitrary valueto those initially set in the xml document - Removed constraint on the diathermic coefficient for curves having pre-loaded property tables #DiscreteOrdinatesMethod - Replaced references to Problem with ThermoOpticalProperties where possible #RadiativeTransferCoupling - Added a safety check to init(...) to avoid potential handling of unsupported problems - Changed signature of newRTE method #ExplicitCoupledSolver - Added an overriding timeStep(...) method, which redirects to explicitSolution(...) - Changed finaliseStep() overriding method. If the fluxes are auto-updated, computes the solution to the RTE. After that, stores the fluxes. - Added autoUpdatesFluxes capability and the zeta factor - Removed unnecessary RTE calculation before main sequence started - Removed pls accessor, which is defined in the superclass. Instead, replaced with a getCurrentPulse() method #Chart - Removed unnecessary call in the conditional statement of a for loop to .isIncomplete() #GradientGuidedOptimisation - Replaced the body of the catch block in configure(...) by a call to notifyFailedStatus() in the SearchTask #ThermoOpticalProperties - Made this class implement the Optimisable interface and moved all property assignments from ParticipatingMedium to this class #Pulse - Fixed incorrect range updating after pulse changes when the lower range would be shifted to a lower value automatically although a higher value was set #Metadata - Added call to setPulseWidth after pulse data loaded #OneDimensionalScheme - Moved array initialisation to clearArrays() removing them from prepare() #LoaderButton - Added OutOfRangeException checks if thermal property table does not cover a wide enough temperature range #SearchTask - Removed unnecessary catch clause in assign - Removed wrong logic in relation to maxIterations assignment and the inner iteration cycle - Removed inefficient checks for IN_PROGRESS calculations within the inner cycle - Added distinction between AWAITING_TERMINATION and TERMINATED statuses #DifferenceScheme - Added pls field as a generic place holder for the current pulse value - Added call to clearArrays() from inside the prepare(...) method - Removed maxTemp/apparentMaximum scaling to enable Cp calculation in future - Removed adjustment cycle from runTimeSequence. Prevented number of points from being adjusted - Added missing prepareStep() in the timeSegment() method - Added the clearArrays() method - Somewhat more logical structure of the runTimeSequence method #ResultTable - Added a more informative content to the describe() method so that it actually describes the sample #MixedCoupledSolver - Replaced private prepare() with overriden public prepare() - Added rear-surface heating to BCs - removed unnecessary arguments from evalRightBoundary #AbsorptionModel - Error fixed: Remvoed premature setParameterBounds() call, which overrides all bounds in the vector #BlockMatrixAlgorithm - Fixed problem with grid density not being assigned #RectangularPulse - Added a required number of points threshold of 4 #ImplicitTranslucentSolver - Removed superfluous fields - Replaced pls with reference to getCurrentPulse() - Removed unnecessary timeStep() overriding - Removed redundant parameter arguments #ResultTableExporter - Improved output format of the CSV summary tables, adding version, date, and column identifiers - Fixed an error with number of columns changing - Changed the plus-minus delimiter to ; #ExponentiallyModifiedGaussian #TrapezoidalPulse - Added required number of points (10) - Remove redundant init() override #DiscretePulse2D - A new private init(...) method has been introduced. - A new evalPulseSpot() method is introduced, which caclculates a non-zero pulse spot in multiples of the grid radial step and automatically adjusts the grid to allow a large enough step size - A listener has now been added to the properties of the problem under study, which triggers recalculation of the radial spot size #DiscretePulse - Added WIDTH_TOLERANCE_FACTOR, which determines the minimal allowed pulse width for pulse corrections - Added listener to Grid changes, requesting to re-init the DiscretePulse - Added the init() method - Pulse normalisation (area calculation) now done from within DiscretePulse - recalculate(...) now checks whether the nominal width is feasible. Otherwise adjusts the pulse shape and width to prevent calculations from slowing down dramatically - Upon setting the discreteWidth, the grid is re-adjusted to accomodate these changes #CoupledImplicitScheme - Separated nonlinear BC treatment from the main logic #ImplicitNonlinearSolver #implicitCoupledSolver #ExplicitCoupledSolver - Replaced private prepare(...) method with a public overriden prepare(...) with a Problem argument #ClassicalProblem2D - Fixed optimisationVector #TaskManager - Added support for awaiting termination / terminated statuses - generateTask() now triggers DATA_LOADED events after the experimental profiles have been loaded and before they are transferred to tasks - describe() became slightly more informative #ParticipatingMedium - Bulk of optimisationVector() and assign() moved to ThermoOpticalProperties #ImplicitDiathermicSolver - Fixed possible error in BC equations - Added the rear-surface heat source - private prepare() to public prepare() #InstanceCellEditor - Added check for NullPointerException in case when a change is not possible (e.g. setting a NumericPulse when no such data is available) #NetzschCSVReader - Better handling of locale-specific delimiters - Added guessLocaleAndFormat() method - Delimiters can now be changed multiple times during reading the same document #AbstractData - Removed redundant isIncomplete() method - Added isFull() helper method (note the usage is different from isIncomplete) #NumericPulse - Added required number of calculation points, equal to 20. - init(...) has been simplified by splitting it up in smaller chunks of code - pulse width is now unambigously defined via the doInterpolation(...) method where it is set to the last element of the scaled time sequence (dimensionless) - As a consequence, adjustedPulseWidth field is no longer needed and has been removed #NumericPulseData - Removed redundant scale() #CompositePathOptimiser - Removed redundant checks for malformed candidate parameters #Grid2D - adjustTo() -> adjustStepSize() - replaced for(...) loop with a recursive call to adjustStepSize - after step size changed, calls adjustTimeStep #Grid - adjustTo() -> adjustTimeStep() - Exploits required number of points, which differs depending on pulse shape. Checks if the nominal pulse width is greater than the resolved width. Adjusts time factor #Status - Added AWAITING_TERMINATION and TERMINATED #Added classes: - CornetteSchanksPF represents a new type of a phase function selectable for PaticipatingMedium calculations - ImplicitCoupledSolverNL, ExplicitCoupledSolverNL and MixedCoupledSolverNL, which extends over MixedCoupledSolver and adds functionality specifically to deal with the nonlinear BC terms #Classes removed: - LayeredGrid2D - Partition - CoreShellProblem (no longer considered necessary after introducing zeta) - ADILayeredSolver Minor changes --- pom.xml | 2 +- src/main/java/pulse/AbstractData.java | 28 ++- src/main/java/pulse/HeatingCurve.java | 56 +++--- src/main/java/pulse/HeatingCurveListener.java | 1 + .../java/pulse/input/ExperimentalData.java | 25 +-- src/main/java/pulse/input/Metadata.java | 16 +- .../pulse/input/listeners/CurveEventType.java | 9 +- .../java/pulse/input/listeners/DataEvent.java | 8 +- .../pulse/input/listeners/DataEventType.java | 8 +- .../pulse/io/export/ResultTableExporter.java | 44 +++-- .../pulse/io/readers/NetzschCSVReader.java | 98 +++++++---- .../io/readers/NetzschPulseCSVReader.java | 5 +- .../java/pulse/io/readers/ReaderManager.java | 4 +- .../pulse/math/FixedIntervalIntegrator.java | 10 +- src/main/java/pulse/math/ParameterVector.java | 1 - .../math/transforms/InvLenSqTransform.java | 4 +- .../pulse/math/transforms/StickTransform.java | 1 + .../pulse/problem/laser/DiscretePulse.java | 137 ++++++++++++--- .../pulse/problem/laser/DiscretePulse2D.java | 36 ++-- .../laser/ExponentiallyModifiedGaussian.java | 29 +--- .../pulse/problem/laser/NumericPulse.java | 81 ++++----- .../pulse/problem/laser/NumericPulseData.java | 21 +-- .../problem/laser/PulseTemporalShape.java | 50 +----- .../pulse/problem/laser/RectangularPulse.java | 8 + .../pulse/problem/laser/TrapezoidalPulse.java | 26 ++- .../java/pulse/problem/schemes/ADIScheme.java | 11 ++ .../problem/schemes/BlockMatrixAlgorithm.java | 21 ++- .../schemes/CoupledImplicitScheme.java | 95 ++++------ .../problem/schemes/DifferenceScheme.java | 151 ++++++++-------- .../problem/schemes/DistributedDetection.java | 2 +- src/main/java/pulse/problem/schemes/Grid.java | 76 +++++--- .../java/pulse/problem/schemes/Grid2D.java | 37 ++-- .../pulse/problem/schemes/ImplicitScheme.java | 16 +- .../pulse/problem/schemes/LayeredGrid2D.java | 85 --------- .../problem/schemes/OneDimensionalScheme.java | 7 +- .../java/pulse/problem/schemes/Partition.java | 66 ------- .../schemes/RadiativeTransferCoupling.java | 12 +- .../schemes/TridiagonalMatrixAlgorithm.java | 43 +++-- .../schemes/rte/BlackbodySpectrum.java | 42 +++-- .../schemes/rte/RadiativeTransferSolver.java | 14 +- .../schemes/rte/dom/CornetteSchanksPF.java | 42 +++++ .../rte/dom/DiscreteOrdinatesMethod.java | 39 +++-- .../schemes/rte/dom/DiscreteQuantities.java | 26 +-- .../schemes/rte/dom/Discretisation.java | 14 +- .../schemes/rte/dom/ExplicitRungeKutta.java | 2 +- .../schemes/rte/dom/HenyeyGreensteinPF.java | 13 +- .../schemes/rte/dom/LinearAnisotropicPF.java | 12 +- .../schemes/rte/dom/ODEIntegrator.java | 25 +-- .../problem/schemes/rte/dom/OrdinateSet.java | 4 +- .../schemes/rte/dom/PhaseFunction.java | 20 ++- .../NonscatteringAnalyticalDerivatives.java | 5 +- .../exact/NonscatteringRadiativeTransfer.java | 10 +- .../schemes/solvers/ADILayeredSolver.java | 89 ---------- .../schemes/solvers/ADILinearisedSolver.java | 33 ++-- .../solvers/ExplicitCoupledSolver.java | 92 +++++----- .../solvers/ExplicitCoupledSolverNL.java | 95 ++++++++++ .../solvers/ExplicitLinearisedSolver.java | 8 +- .../solvers/ExplicitNonlinearSolver.java | 11 +- .../solvers/ExplicitTranslucentSolver.java | 17 +- .../solvers/ImplicitCoupledSolver.java | 30 ++-- .../solvers/ImplicitCoupledSolverNL.java | 94 ++++++++++ .../solvers/ImplicitDiathermicSolver.java | 29 ++-- .../solvers/ImplicitLinearisedSolver.java | 19 +- .../solvers/ImplicitNonlinearSolver.java | 20 +-- .../solvers/ImplicitTranslucentSolver.java | 49 ++---- .../schemes/solvers/MixedCoupledSolver.java | 42 ++--- .../schemes/solvers/MixedCoupledSolverNL.java | 92 ++++++++++ .../solvers/MixedLinearisedSolver.java | 30 ++-- .../statements/ClassicalProblem2D.java | 26 +-- .../problem/statements/CoreShellProblem.java | 164 ------------------ .../problem/statements/DiathermicMedium.java | 13 +- .../problem/statements/NonlinearProblem.java | 2 + .../statements/ParticipatingMedium.java | 80 ++------- .../statements/PenetrationProblem.java | 13 +- .../pulse/problem/statements/Problem.java | 89 +++++----- .../java/pulse/problem/statements/Pulse.java | 38 ++-- .../statements/model/AbsorptionModel.java | 2 +- .../model/ExtendedThermalProperties.java | 4 - .../statements/model/ThermalProperties.java | 6 +- .../model/ThermoOpticalProperties.java | 82 ++++++++- .../java/pulse/properties/SampleName.java | 2 +- .../pulse/search/direction/ComplexPath.java | 6 +- .../direction/CompositePathOptimiser.java | 10 +- .../search/direction/GradientGuidedPath.java | 11 +- .../pulse/search/direction/PathOptimiser.java | 3 - .../pulse/search/linear/LinearOptimiser.java | 11 +- .../pulse/search/linear/WolfeOptimiser.java | 45 ++--- .../search/statistics/CorrelationTest.java | 4 +- src/main/java/pulse/tasks/Calculation.java | 12 +- src/main/java/pulse/tasks/SearchTask.java | 66 +++---- src/main/java/pulse/tasks/TaskManager.java | 74 ++++---- src/main/java/pulse/tasks/logs/Status.java | 9 +- src/main/java/pulse/ui/Launcher.java | 6 +- .../pulse/ui/components/CalculationTable.java | 2 + src/main/java/pulse/ui/components/Chart.java | 4 +- .../java/pulse/ui/components/DataLoader.java | 8 +- .../java/pulse/ui/components/ResultTable.java | 2 +- .../ui/components/buttons/LoaderButton.java | 15 +- .../controllers/InstanceCellEditor.java | 9 +- .../components/models/ResultTableModel.java | 81 +++++---- .../ui/components/panels/ProblemToolbar.java | 6 +- src/main/java/pulse/util/PropertyHolder.java | 1 + src/main/resources/NumericProperty.xml | 8 +- src/main/resources/Version.txt | 2 +- src/main/resources/messages.properties | 9 +- src/test/java/test/NonscatteringSetup.java | 3 +- 106 files changed, 1689 insertions(+), 1587 deletions(-) delete mode 100644 src/main/java/pulse/problem/schemes/LayeredGrid2D.java delete mode 100644 src/main/java/pulse/problem/schemes/Partition.java create mode 100644 src/main/java/pulse/problem/schemes/rte/dom/CornetteSchanksPF.java delete mode 100644 src/main/java/pulse/problem/schemes/solvers/ADILayeredSolver.java create mode 100644 src/main/java/pulse/problem/schemes/solvers/ExplicitCoupledSolverNL.java create mode 100644 src/main/java/pulse/problem/schemes/solvers/ImplicitCoupledSolverNL.java create mode 100644 src/main/java/pulse/problem/schemes/solvers/MixedCoupledSolverNL.java delete mode 100644 src/main/java/pulse/problem/statements/CoreShellProblem.java diff --git a/pom.xml b/pom.xml index e27af65..3dc51b9 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ kotik-coder PULsE - 1.94 + 1.94F PULsE Processing Unit for Laser flash Experiments diff --git a/src/main/java/pulse/AbstractData.java b/src/main/java/pulse/AbstractData.java index 2f58d47..1fd6264 100644 --- a/src/main/java/pulse/AbstractData.java +++ b/src/main/java/pulse/AbstractData.java @@ -8,13 +8,11 @@ import static pulse.properties.NumericPropertyKeyword.NUMPOINTS; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.Set; import pulse.properties.NumericProperty; import pulse.properties.NumericPropertyKeyword; -import pulse.properties.Property; import pulse.util.PropertyHolder; /** @@ -105,7 +103,7 @@ public void clear() { * @return a {@code NumericProperty} derived from * {@code NumericPropertyKeyword.NUMPOINTS} with the value of {@code count} */ - public NumericProperty getNumPoints() { + public final NumericProperty getNumPoints() { return derive(NUMPOINTS, count); } @@ -117,7 +115,7 @@ public NumericProperty getNumPoints() { * * @param c */ - public void setNumPoints(NumericProperty c) { + public final void setNumPoints(NumericProperty c) { requireType(c, NUMPOINTS); this.count = (int) c.getValue(); firePropertyChanged(this, c); @@ -168,7 +166,7 @@ public void addPoint(double time, double sgn) { this.signal.add(sgn); } - protected void incrementCount() { + protected final void incrementCount() { count++; } @@ -179,7 +177,7 @@ protected void incrementCount() { * @param index the index * @param t the new time value at this index */ - public void setTimeAt(int index, double t) { + public final void setTimeAt(int index, double t) { time.set(index, t); } @@ -190,7 +188,7 @@ public void setTimeAt(int index, double t) { * @param index the index * @param t the new signal value at this index */ - public void setSignalAt(int index, double t) { + public final void setSignalAt(int index, double t) { signal.set(index, t); } @@ -200,20 +198,10 @@ public void setSignalAt(int index, double t) { * @return the maximum signal value * @see java.util.Collections.max */ - public double apparentMaximum() { + public final double apparentMaximum() { return max(signal); } - /** - * Checks if the time list is incomplete. - * - * @return {@code false} if the list with time values has less elements than - * initially declared, {@code true} otherwise. - */ - public boolean isIncomplete() { - return time.size() < count; - } - @Override public String toString() { return name != null ? name : getClass().getSimpleName() + " (" + getNumPoints() + ")"; @@ -270,6 +258,10 @@ public void remove(int i) { public boolean ignoreSiblings() { return true; } + + public boolean isFull() { + return actualNumPoints() >= count; + } public List getTimeSequence() { return time; diff --git a/src/main/java/pulse/HeatingCurve.java b/src/main/java/pulse/HeatingCurve.java index 8111212..8fb0f55 100644 --- a/src/main/java/pulse/HeatingCurve.java +++ b/src/main/java/pulse/HeatingCurve.java @@ -22,7 +22,6 @@ import pulse.input.listeners.CurveEvent; import pulse.properties.NumericProperty; import pulse.properties.NumericPropertyKeyword; -import pulse.properties.Property; /** * The {@code HeatingCurve} represents a time-temperature profile (a @@ -42,10 +41,12 @@ */ public class HeatingCurve extends AbstractData { - private List adjustedSignal; + private final List adjustedSignal; + private List lastCalculation; private double startTime; - private List listeners = new ArrayList(); + private final List listeners + = new ArrayList<>(); private UnivariateInterpolator splineInterpolator; private UnivariateFunction splineInterpolation; @@ -62,7 +63,7 @@ protected HeatingCurve(List time, List signal, final double star */ public HeatingCurve() { super(); - adjustedSignal = new ArrayList((int) this.getNumPoints().getValue()); + adjustedSignal = new ArrayList<>((int) this.getNumPoints().getValue()); splineInterpolator = new SplineInterpolator(); } @@ -102,10 +103,16 @@ public HeatingCurve(NumericProperty count) { splineInterpolator = new SplineInterpolator(); } + //TODO + public void copyToLastCalculation() { + lastCalculation = new ArrayList<>(0); + lastCalculation = new ArrayList<>(adjustedSignal); + } + @Override public void clear() { super.clear(); - this.adjustedSignal.clear(); + adjustedSignal.clear(); } /** @@ -128,6 +135,7 @@ public double timeAt(int index) { * @return a double, representing the baseline-corrected temperature at * {@code index} */ + @Override public double signalAt(int index) { return adjustedSignal.get(index); } @@ -154,7 +162,6 @@ public double signalAt(int index) { * @see pulse.input.listeners.CurveEvent */ public void scale(double scale) { - var signal = getSignalData(); final int count = this.actualNumPoints(); for (int i = 0; i < count; i++) { signal.set(i, signal.get(i) * scale); @@ -166,9 +173,8 @@ public void scale(double scale) { private void refreshInterpolation() { /* - * Prepare extended time array + * Prepare extended time array */ - var time = this.getTimeSequence(); var timeExtended = new double[time.size() + 1]; for (int i = 1; i < timeExtended.length; i++) { @@ -188,11 +194,12 @@ private void refreshInterpolation() { } final double alpha = -1.0; - adjustedSignalExtended[0] = alpha * adjustedSignalExtended[2] - (1.0 - alpha) * adjustedSignalExtended[1]; // extrapolate + adjustedSignalExtended[0] = alpha * adjustedSignalExtended[2] + - (1.0 - alpha) * adjustedSignalExtended[1]; // extrapolate // linearly /* - * Submit to spline interpolation + * Submit to spline interpolation */ splineInterpolation = splineInterpolator.interpolate(timeExtended, adjustedSignalExtended); } @@ -220,19 +227,24 @@ public double maxAdjustedSignal() { * heating curve. */ public void apply(Baseline baseline) { - var time = this.getTimeSequence(); - var signal = this.getSignalData(); adjustedSignal.clear(); - for (int i = 0, size = time.size(); i < size; i++) { - adjustedSignal.add(signal.get(i) + baseline.valueAt(timeAt(i))); - } + int size = time.size(); + + if (size > 0) { + + for (int i = 0; i < size; i++) { + adjustedSignal.add(signal.get(i) + baseline.valueAt(timeAt(i))); + } + + if (time.get(0) > -startTime) { + time.add(0, -startTime); + adjustedSignal.add(0, baseline.valueAt(-startTime)); + } + + refreshInterpolation(); - if (time.get(0) > -startTime) { - time.add(0, -startTime); - adjustedSignal.add(0, baseline.valueAt(-startTime)); } - refreshInterpolation(); } /** @@ -251,6 +263,7 @@ public void apply(Baseline baseline) { * * @param data the experimental data, with a time range broader than the * time range of this {@code HeatingCurve}. + * @param baseline * @return a new {@code HeatingCurve}, extended to match the time limits of * {@code data} */ @@ -266,10 +279,9 @@ public final HeatingCurve extendedTo(ExperimentalData data, Baseline baseline) { var baselineTime = data.getTimeSequence().stream().filter(t -> t < 0).collect(toList()); var baselineSignal = baselineTime.stream().map(bTime -> baseline.valueAt(bTime)).collect(toList()); - var time = this.getTimeSequence(); - baselineTime.addAll(time); - baselineSignal.addAll(adjustedSignal); + this.copyToLastCalculation(); + baselineSignal.addAll(lastCalculation); return new HeatingCurve(baselineTime, baselineSignal, startTime, getName()); } diff --git a/src/main/java/pulse/HeatingCurveListener.java b/src/main/java/pulse/HeatingCurveListener.java index dd2219f..7dd972c 100644 --- a/src/main/java/pulse/HeatingCurveListener.java +++ b/src/main/java/pulse/HeatingCurveListener.java @@ -10,6 +10,7 @@ public interface HeatingCurveListener { /** * Signals that a {@code CurveEvent} has occurred. + * @param event */ public void onCurveEvent(CurveEvent event); diff --git a/src/main/java/pulse/input/ExperimentalData.java b/src/main/java/pulse/input/ExperimentalData.java index bdbeabf..15776db 100644 --- a/src/main/java/pulse/input/ExperimentalData.java +++ b/src/main/java/pulse/input/ExperimentalData.java @@ -19,7 +19,6 @@ import pulse.input.listeners.DataEvent; import pulse.input.listeners.DataEventType; import pulse.input.listeners.DataListener; -import pulse.properties.NumericProperty; import pulse.ui.Messages; import pulse.util.PropertyHolderListener; @@ -79,15 +78,15 @@ public ExperimentalData() { } - public void addDataListener(DataListener listener) { + public final void addDataListener(DataListener listener) { dataListeners.add(listener); } - public void clearDataListener() { + public final void clearDataListener() { dataListeners.clear(); } - public void fireDataChanged(DataEvent dataEvent) { + public final void fireDataChanged(DataEvent dataEvent) { dataListeners.stream().forEach(l -> l.onDataChanged(dataEvent)); } @@ -98,7 +97,7 @@ public void fireDataChanged(DataEvent dataEvent) { * @see pulse.input.Range.reset() * @see pulse.input.IndexRange.reset() */ - public void resetRanges() { + public final void resetRanges() { indexRange.reset(getTimeSequence()); range.reset(indexRange, getTimeSequence()); } @@ -335,19 +334,6 @@ private void doSetMetadata() { range.updateMinimum(metadata.numericProperty(PULSE_WIDTH)); } - metadata.addListener(event -> { - - if (event.getProperty() instanceof NumericProperty) { - var p = (NumericProperty) event.getProperty(); - - if (p.getType() == PULSE_WIDTH) { - range.updateMinimum(metadata.numericProperty(PULSE_WIDTH)); - } - - } - - }); - } /** @@ -413,7 +399,6 @@ public void setRange(Range range) { } private void doSetRange() { - var time = getTimeSequence(); indexRange.set(time, range); addHierarchyListener(l -> { @@ -439,4 +424,4 @@ public double timeLimit() { return timeAt(indexRange.getUpperBound()); } -} +} \ No newline at end of file diff --git a/src/main/java/pulse/input/Metadata.java b/src/main/java/pulse/input/Metadata.java index 7b650e8..cd9369c 100644 --- a/src/main/java/pulse/input/Metadata.java +++ b/src/main/java/pulse/input/Metadata.java @@ -1,7 +1,6 @@ package pulse.input; import static java.lang.System.lineSeparator; -import static pulse.properties.NumericProperties.def; import static pulse.properties.NumericPropertyKeyword.DETECTOR_GAIN; import static pulse.properties.NumericPropertyKeyword.DETECTOR_IRIS; import static pulse.properties.NumericPropertyKeyword.DIAMETER; @@ -20,6 +19,7 @@ import pulse.problem.laser.NumericPulseData; import pulse.problem.laser.PulseTemporalShape; import pulse.problem.laser.RectangularPulse; +import static pulse.properties.NumericProperties.derive; import pulse.properties.NumericProperty; import pulse.properties.NumericPropertyKeyword; import static pulse.properties.NumericPropertyKeyword.FOV_OUTER; @@ -46,8 +46,8 @@ public class Metadata extends PropertyHolder implements Reflexive { private SampleName sampleName; private int externalID; - private InstanceDescriptor pulseDescriptor = new InstanceDescriptor( - "Pulse Shape Selector", PulseTemporalShape.class); + private InstanceDescriptor pulseDescriptor + = new InstanceDescriptor<>("Pulse Shape Selector", PulseTemporalShape.class); private NumericPulseData pulseData; @@ -64,7 +64,7 @@ public Metadata(NumericProperty temperature, int externalId) { sampleName = new SampleName(); setExternalID(externalId); pulseDescriptor.setSelectedDescriptor(RectangularPulse.class.getSimpleName()); - data = new TreeSet(); + data = new TreeSet<>(); set(TEST_TEMPERATURE, temperature); } @@ -115,15 +115,17 @@ public void setSampleName(SampleName sampleName) { this.sampleName = sampleName; } - public void setPulseData(NumericPulseData pulseData) { + public final void setPulseData(NumericPulseData pulseData) { this.pulseData = pulseData; + this.set(PULSE_WIDTH, derive(PULSE_WIDTH, pulseData.pulseWidth()) ); } /** * If a Numerical Pulse has been loaded (for example, when importing from * Proteus), this will return an object describing this data. + * @return */ - public NumericPulseData getPulseData() { + public final NumericPulseData getPulseData() { return pulseData; } @@ -270,4 +272,4 @@ public boolean equals(Object o) { } -} +} \ No newline at end of file diff --git a/src/main/java/pulse/input/listeners/CurveEventType.java b/src/main/java/pulse/input/listeners/CurveEventType.java index 65b364e..3dcb5d0 100644 --- a/src/main/java/pulse/input/listeners/CurveEventType.java +++ b/src/main/java/pulse/input/listeners/CurveEventType.java @@ -18,6 +18,13 @@ public enum CurveEventType { * shifting it relative to the experimental data points) or by the search * procedure. */ - TIME_ORIGIN_CHANGED; + TIME_ORIGIN_CHANGED, + + /** + * A calculation associated with this curve has finished and + * the required arrays have been filled. + */ + + CALCULATION_FINISHED; } diff --git a/src/main/java/pulse/input/listeners/DataEvent.java b/src/main/java/pulse/input/listeners/DataEvent.java index f2b8ca4..d42c1a2 100644 --- a/src/main/java/pulse/input/listeners/DataEvent.java +++ b/src/main/java/pulse/input/listeners/DataEvent.java @@ -1,6 +1,6 @@ package pulse.input.listeners; -import pulse.input.ExperimentalData; +import pulse.AbstractData; /** * A {@code DataEvent} is used to track changes happening with a @@ -10,7 +10,7 @@ public class DataEvent { private DataEventType type; - private ExperimentalData data; + private AbstractData data; /** * Constructs a {@code DataEvent} object, combining the {@code type} and @@ -19,7 +19,7 @@ public class DataEvent { * @param type the type of this event * @param data the source of the event */ - public DataEvent(DataEventType type, ExperimentalData data) { + public DataEvent(DataEventType type, AbstractData data) { this.type = type; this.data = data; } @@ -39,7 +39,7 @@ public DataEventType getType() { * * @return the associated data */ - public ExperimentalData getData() { + public AbstractData getData() { return data; } diff --git a/src/main/java/pulse/input/listeners/DataEventType.java b/src/main/java/pulse/input/listeners/DataEventType.java index 0423e37..da2160c 100644 --- a/src/main/java/pulse/input/listeners/DataEventType.java +++ b/src/main/java/pulse/input/listeners/DataEventType.java @@ -14,6 +14,12 @@ public enum DataEventType { * @see pulse.input.ExperimentalData.truncate() */ - RANGE_CHANGED + RANGE_CHANGED, + + /** + * All data points loaded and are ready for processing. + */ + + DATA_LOADED; } diff --git a/src/main/java/pulse/io/export/ResultTableExporter.java b/src/main/java/pulse/io/export/ResultTableExporter.java index f61631e..ea6bd54 100644 --- a/src/main/java/pulse/io/export/ResultTableExporter.java +++ b/src/main/java/pulse/io/export/ResultTableExporter.java @@ -2,13 +2,16 @@ import java.io.FileOutputStream; import java.io.PrintStream; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import java.util.List; import pulse.properties.NumericProperty; -import pulse.properties.NumericPropertyKeyword; +import pulse.tasks.TaskManager; import pulse.tasks.processing.AbstractResult; import pulse.tasks.processing.AverageResult; import pulse.ui.Messages; +import pulse.ui.Version; import pulse.ui.components.ResultTable; import pulse.ui.components.models.ResultTableModel; @@ -61,26 +64,33 @@ public void printToStream(ResultTable table, FileOutputStream fos, Extension ext } private void printHeaderCSV(ResultTable table, PrintStream stream) { - NumericPropertyKeyword p = null; + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + stream.println("Summary report on " + + LocalDateTime.now().format(formatter)); + stream.println("PULsE Version: " + Version.getCurrentVersion().toString()); + stream.println("Sample: " + TaskManager.getManagerInstance().getSampleName()); + stream.println("Ouput format sequence below: "); + + var fmt = ((ResultTableModel) table.getModel()).getFormat(); + for (int col = 0; col < table.getColumnCount(); col++) { - p = ((ResultTableModel) table.getModel()).getFormat().fromAbbreviation(table.getColumnName(col)); - stream.printf("%20s ", p); + var colName = fmt.fromAbbreviation(table.getColumnName(col)); + stream.println("Col. no.: " + col + " - " + colName); } - stream.println(""); + + stream.println("Note: average results are formatted as ; in the list below."); + stream.println(); } private void printIndividualCSV(NumericProperty p, PrintStream stream) { - if(p.getError() == null || p.getError().doubleValue() < 1E-20 ) { - if(p.getValue() instanceof Double) - stream.printf("%12.5e", p.valueInCurrentUnits()); - else - stream.printf("%12d", p.valueInCurrentUnits().intValue()); - } - else { - if(p.getValue() instanceof Double) - stream.printf("%12.5e +/- %12.5e", p.valueInCurrentUnits(), p.errorInCurrentUnits()); - else - stream.printf("%12d +/- %12d", p.valueInCurrentUnits().intValue(), p.errorInCurrentUnits().intValue()); + String fmt = p.getValue() instanceof Double ? "%-2.5e" : "%-6d"; + String s1 = String.format(fmt, p.getValue()).trim(); + String s2 = ""; + if (p.getError() != null) { + s2 = String.format(fmt, p.getError()).trim(); + stream.print(s1 + " ; " + s2 + " "); + } else { + stream.print(s1 + " "); } } @@ -104,8 +114,6 @@ private void printCSV(ResultTable table, FileOutputStream fos) { stream.print(Messages.getString("ResultTable.SeparatorCSV")); stream.println(""); - printHeaderCSV(table, stream); - results.stream().filter(r -> r instanceof AverageResult) .forEach(ar -> ((AverageResult) ar).getIndividualResults().stream().forEach(ir -> { var props = AbstractResult.filterProperties(ir); diff --git a/src/main/java/pulse/io/readers/NetzschCSVReader.java b/src/main/java/pulse/io/readers/NetzschCSVReader.java index dae82be..4445add 100644 --- a/src/main/java/pulse/io/readers/NetzschCSVReader.java +++ b/src/main/java/pulse/io/readers/NetzschCSVReader.java @@ -8,6 +8,7 @@ import java.io.FileReader; import java.io.IOException; import java.text.DecimalFormat; +import java.text.NumberFormat; import java.text.ParseException; import java.util.ArrayList; import java.util.Arrays; @@ -50,16 +51,22 @@ public class NetzschCSVReader implements CurveReader { /** * Note comma is included as a delimiter character here. */ - private final static String ENGLISH_DELIMS = "[#(),/°Cx%^]+"; + private final static String ENGLISH_DELIMS = "[#(),;/°Cx%^]+"; private final static String GERMAN_DELIMS = "[#();/°Cx%^]+"; - private static String delims = ENGLISH_DELIMS; - + private static String delims; //default number format (British format) - private static Locale locale = Locale.ENGLISH; + private static Locale locale; + + private static NumberFormat format; private NetzschCSVReader() { - //intentionally blank + //do nothing + } + + protected void setDefaultLocale() { + delims = ENGLISH_DELIMS; + locale = Locale.ENGLISH; } /** @@ -97,39 +104,39 @@ public String getSupportedExtension() { @Override public List read(File file) throws IOException { Objects.requireNonNull(file, Messages.getString("DATReader.1")); - ExperimentalData curve = new ExperimentalData(); + setDefaultLocale(); //always start with a default locale + //gets the number format for this locale try (BufferedReader reader = new BufferedReader(new FileReader(file))) { int shotId = determineShotID(reader, file); - var format = DecimalFormat.getInstance(locale); - format.setGroupingUsed(false); - - var spot = findLineByLabel(reader, DETECTOR_SPOT_SIZE, THICKNESS, delims); + String spot = findLineByLabel(reader, DETECTOR_SPOT_SIZE, THICKNESS, false); + double spotSize = 0; if(spot != null) { var spotTokens = spot.split(delims); spotSize = format.parse(spotTokens[spotTokens.length - 1]).doubleValue() * TO_METRES; } - var tempTokens = findLineByLabel(reader, THICKNESS, delims).split(delims); + String tempLine = findLineByLabel(reader, THICKNESS, false); + var tempTokens = tempLine.split(delims); final double thickness = format.parse(tempTokens[tempTokens.length - 1]).doubleValue() * TO_METRES; - tempTokens = findLineByLabel(reader, DIAMETER, delims).split(delims); + tempTokens = findLineByLabel(reader, DIAMETER, false).split(delims); final double diameter = format.parse(tempTokens[tempTokens.length - 1]).doubleValue() * TO_METRES; - tempTokens = findLineByLabel(reader, SAMPLE_TEMPERATURE, delims).split(delims); + tempTokens = findLineByLabel(reader, SAMPLE_TEMPERATURE, false).split(delims); final double sampleTemperature = format.parse(tempTokens[tempTokens.length - 1]).doubleValue() + TO_KELVIN; /* * Finds the detector keyword. */ - var detectorLabel = findLineByLabel(reader, DETECTOR, delims); + var detectorLabel = findLineByLabel(reader, DETECTOR, true); if (detectorLabel == null) { System.err.println("Skipping " + file.getName()); @@ -156,17 +163,41 @@ public List read(File file) throws IOException { return null; } + + /** + * Note: the {@code line} must contain a decimal-separated number. + * @param line a line containing number with a decimal separator + */ + + private static void guessLocaleAndFormat(String line) { + + if(line.contains(".")) { + delims = ENGLISH_DELIMS; + locale = Locale.ENGLISH; + } + + else { + delims = GERMAN_DELIMS; + locale = Locale.GERMAN; + } + + format = DecimalFormat.getInstance(locale); + format.setGroupingUsed(false); + } protected static void populate(AbstractData data, BufferedReader reader) throws IOException, ParseException { double time; double power; String[] tokens; - var format = DecimalFormat.getInstance(locale); - format.setGroupingUsed(false); for (String line = reader.readLine(); line != null && !line.trim().isEmpty(); line = reader.readLine()) { tokens = line.split(delims); - + + if(tokens.length < 2) { + guessLocaleAndFormat(line); + tokens = line.split(delims); + } + time = format.parse(tokens[0]).doubleValue() * NetzschCSVReader.TO_SECONDS; power = format.parse(tokens[1]).doubleValue(); data.addPoint(time, power); @@ -178,39 +209,25 @@ protected static int determineShotID(BufferedReader reader, File file) throws IO String shotIDLine = reader.readLine(); String[] shotID = shotIDLine.split(delims); - int shotId = -1; + int id; - if(shotID.length < 3) { - - if(locale == Locale.ENGLISH) { - delims = GERMAN_DELIMS; - locale = Locale.GERMAN; - } - else { - delims = ENGLISH_DELIMS; - locale = Locale.ENGLISH; - } - - shotID = shotIDLine.split(delims); - } - //check if first entry makes sense if (!shotID[shotID.length - 2].equalsIgnoreCase(SHOT_DATA)) { throw new IllegalArgumentException(file.getName() + " is not a recognised Netzch CSV file. First entry is: " + shotID[shotID.length - 2]); } else { - shotId = Integer.parseInt(shotID[shotID.length - 1]); + id = Integer.parseInt(shotID[shotID.length - 1]); } - return shotId; + return id; } - protected static String findLineByLabel(BufferedReader reader, String label, String delims) throws IOException { - return findLineByLabel(reader, label, "!!!", delims); + protected static String findLineByLabel(BufferedReader reader, String label, boolean ignoreLocale) throws IOException { + return findLineByLabel(reader, label, "!!!", ignoreLocale); } - protected static String findLineByLabel(BufferedReader reader, String label, String stopLabel, String delims) throws IOException { + protected static String findLineByLabel(BufferedReader reader, String label, String stopLabel, boolean ignoreLocale) throws IOException { String line = ""; String[] tokens; @@ -221,6 +238,11 @@ protected static String findLineByLabel(BufferedReader reader, String label, Str outer: for (line = reader.readLine(); line != null; line = reader.readLine()) { + if(line.isBlank()) + continue; + + if(!ignoreLocale) + guessLocaleAndFormat(line); tokens = line.split(delims); for (String token : tokens) { @@ -262,4 +284,4 @@ public static String getDelims() { return delims; } -} +} \ No newline at end of file diff --git a/src/main/java/pulse/io/readers/NetzschPulseCSVReader.java b/src/main/java/pulse/io/readers/NetzschPulseCSVReader.java index 4ba3303..7d759e6 100644 --- a/src/main/java/pulse/io/readers/NetzschPulseCSVReader.java +++ b/src/main/java/pulse/io/readers/NetzschPulseCSVReader.java @@ -55,13 +55,16 @@ public NumericPulseData read(File file) throws IOException { Objects.requireNonNull(file, Messages.getString("DATReader.1")); NumericPulseData data = null; + + ( (NetzschCSVReader) NetzschCSVReader.getInstance() ) + .setDefaultLocale(); //always start with a default locale try (BufferedReader reader = new BufferedReader(new FileReader(file))) { int shotId = NetzschCSVReader.determineShotID(reader, file); data = new NumericPulseData(shotId); - var pulseLabel = NetzschCSVReader.findLineByLabel(reader, PULSE, NetzschCSVReader.getDelims()); + var pulseLabel = NetzschCSVReader.findLineByLabel(reader, PULSE, false); if (pulseLabel == null) { System.err.println("Skipping " + file.getName()); diff --git a/src/main/java/pulse/io/readers/ReaderManager.java b/src/main/java/pulse/io/readers/ReaderManager.java index 97bc4a2..0f8201b 100644 --- a/src/main/java/pulse/io/readers/ReaderManager.java +++ b/src/main/java/pulse/io/readers/ReaderManager.java @@ -165,12 +165,12 @@ public static List datasetReaders() { /** * Attempts to find a {@code DatasetReader} for processing {@code file}. * + * @param + * @param readers * @param file the target file supposedly containing data for an * {@code InterpolationDataset}. * @return an {@code InterpolationDataset} extracted from { * @file} using the first available {@code DatasetReader} from the list - * @throws IOException if the reader has been found, but an error occurred - * when reading the file * @throws IllegalArgumentException if the file has an unsupported extension */ public static T read(List> readers, File file) { diff --git a/src/main/java/pulse/math/FixedIntervalIntegrator.java b/src/main/java/pulse/math/FixedIntervalIntegrator.java index 8a85798..ac77ccb 100644 --- a/src/main/java/pulse/math/FixedIntervalIntegrator.java +++ b/src/main/java/pulse/math/FixedIntervalIntegrator.java @@ -5,14 +5,10 @@ import static pulse.properties.NumericProperty.requireType; import static pulse.properties.NumericPropertyKeyword.INTEGRATION_SEGMENTS; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; import java.util.Set; import pulse.properties.NumericProperty; import pulse.properties.NumericPropertyKeyword; -import pulse.properties.Property; /** * A fixed-interval integrator implements a numerical scheme in which the domain @@ -64,7 +60,7 @@ public NumericProperty getIntegrationSegments() { * @param integrationSegments a property of the {@code INTEGRATION_SEGMENTS} * type */ - public void setIntegrationSegments(NumericProperty integrationSegments) { + public final void setIntegrationSegments(NumericProperty integrationSegments) { requireType(integrationSegments, INTEGRATION_SEGMENTS); this.integrationSegments = (int) integrationSegments.getValue(); } @@ -76,7 +72,7 @@ public void setIntegrationSegments(NumericProperty integrationSegments) { * @param bounds the integration bounds */ @Override - public void setBounds(Segment bounds) { + public final void setBounds(Segment bounds) { super.setBounds(bounds); } @@ -104,7 +100,7 @@ public Set listedKeywords() { * * @return the integration step size. */ - public double stepSize() { + public final double stepSize() { return getBounds().length() / (double) this.integrationSegments; } diff --git a/src/main/java/pulse/math/ParameterVector.java b/src/main/java/pulse/math/ParameterVector.java index 295f5a3..4b85221 100644 --- a/src/main/java/pulse/math/ParameterVector.java +++ b/src/main/java/pulse/math/ParameterVector.java @@ -7,7 +7,6 @@ import pulse.math.linear.Vector; import pulse.math.transforms.Transformable; import pulse.properties.NumericProperties; -import static pulse.properties.NumericProperties.def; import pulse.properties.NumericProperty; import pulse.properties.NumericPropertyKeyword; diff --git a/src/main/java/pulse/math/transforms/InvLenSqTransform.java b/src/main/java/pulse/math/transforms/InvLenSqTransform.java index 2a84913..8b51287 100644 --- a/src/main/java/pulse/math/transforms/InvLenSqTransform.java +++ b/src/main/java/pulse/math/transforms/InvLenSqTransform.java @@ -16,12 +16,12 @@ public InvLenSqTransform(ThermalProperties tp) { @Override public double transform(double value) { - return value / (l * l); + return Math.abs(value) / (l * l); } @Override public double inverse(double t) { - return t * (l * l); + return Math.abs(t) * (l * l); } } diff --git a/src/main/java/pulse/math/transforms/StickTransform.java b/src/main/java/pulse/math/transforms/StickTransform.java index d68c935..00239cb 100644 --- a/src/main/java/pulse/math/transforms/StickTransform.java +++ b/src/main/java/pulse/math/transforms/StickTransform.java @@ -40,6 +40,7 @@ public StickTransform(Segment bounds) { } /** + * @param a * @see pulse.math.MathUtils.atanh() * @see pulse.math.Segment.getBounds() */ diff --git a/src/main/java/pulse/problem/laser/DiscretePulse.java b/src/main/java/pulse/problem/laser/DiscretePulse.java index d29c073..8160344 100644 --- a/src/main/java/pulse/problem/laser/DiscretePulse.java +++ b/src/main/java/pulse/problem/laser/DiscretePulse.java @@ -2,6 +2,8 @@ import java.util.Objects; import pulse.input.ExperimentalData; +import pulse.math.MidpointIntegrator; +import pulse.math.Segment; import pulse.problem.schemes.Grid; import pulse.problem.statements.Problem; import pulse.problem.statements.Pulse; @@ -16,10 +18,23 @@ */ public class DiscretePulse { - private Grid grid; - private Pulse pulse; - private double discretePulseWidth; - private double timeFactor; + private final Grid grid; + private final Pulse pulse; + + private double widthOnGrid; + private double timeConversionFactor; + private double invTotalEnergy; //normalisation factor + + /** + * This number shows how small the actual pulse may be compared to the + * half-time. If the pulse is shorter than + * tc/{@value WIDTH_TOLERANCE_FACTOR}, it will be replaced + * by a rectangular pulse with the width equal to + * tc/{@value WIDTH_TOLERANCE_FACTOR}. Here + * tc + * is the time factor defined in the {@code Problem} class. + */ + public final static int WIDTH_TOLERANCE_FACTOR = 1000; /** * This creates a one-dimensional discrete pulse on a {@code grid}. @@ -34,25 +49,33 @@ public class DiscretePulse { */ public DiscretePulse(Problem problem, Grid grid) { this.grid = grid; - timeFactor = problem.getProperties().timeFactor(); + timeConversionFactor = problem.getProperties().timeFactor(); this.pulse = problem.getPulse(); - recalculate(); + Object ancestor + = Objects.requireNonNull(problem.specificAncestor(SearchTask.class), + "Problem has not been assigned to a SearchTask"); - Object ancestor = - Objects.requireNonNull( problem.specificAncestor(SearchTask.class), - "Problem has not been assigned to a SearchTask"); + ExperimentalData data = ((SearchTask) ancestor).getExperimentalCurve(); + init(data); - ExperimentalData data = ((SearchTask)ancestor).getExperimentalCurve(); - - pulse.getPulseShape().init(data, this); pulse.addListener(e -> { - timeFactor = problem.getProperties().timeFactor(); - recalculate(); - pulse.getPulseShape().init(data, this); + timeConversionFactor = problem.getProperties().timeFactor(); + init(data); }); + + grid.addListener(e + -> init(data) + ); } + + private void init(ExperimentalData data) { + widthOnGrid = 0; + recalculate(); + pulse.getPulseShape().init(data, this); + normalise(); + } /** * Uses the {@code PulseTemporalShape} of the {@code Pulse} object to @@ -62,7 +85,7 @@ public DiscretePulse(Problem problem, Grid grid) { * @return the laser power at the specified moment of {@code time} */ public double laserPowerAt(double time) { - return pulse.getPulseShape().evaluateAt(time); + return invTotalEnergy * pulse.getPulseShape().evaluateAt(time); } /** @@ -71,18 +94,66 @@ public double laserPowerAt(double time) { * * @see pulse.problem.schemes.Grid.gridTime(double,double) */ - public void recalculate() { - final double width = ((Number) pulse.getPulseWidth().getValue()).doubleValue(); - discretePulseWidth = Math.max(grid.gridTime(width, timeFactor), grid.getTimeStep()); + public final void recalculate() { + final double nominalWidth = ((Number) pulse.getPulseWidth().getValue()).doubleValue(); + final double resolvedWidth = timeConversionFactor / WIDTH_TOLERANCE_FACTOR; + + final double EPS = 1E-10; + + /** + * 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) { + //change shape to rectangular + var shape = new RectangularPulse(); + pulse.setPulseShape(shape); + //change pulse width + setDiscreteWidth(resolvedWidth); + shape.init(null, this); + } else if(nominalWidth > resolvedWidth + EPS) { + setDiscreteWidth(nominalWidth); + } + + } + + /** + * Calculates the total pulse energy using a numerical integrator. The + * normalisation factor is then equal to the inverse total energy. + */ + + public final void normalise() { + invTotalEnergy = 1.0; + var pulseShape = pulse.getPulseShape(); + + var integrator = new MidpointIntegrator(new Segment(0, widthOnGrid)) { + + @Override + public double integrand(double... vars) { + return pulseShape.evaluateAt(vars[0]); + } + + }; + + invTotalEnergy = 1.0 / integrator.integrate(); + } /** - * Gets the discrete pulse width defined by {@code DiscretePulse}. + * 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 a double, representing the discrete pulse width. + * @return the dimensionless pulse width mapped to the grid. */ public double getDiscreteWidth() { - return discretePulseWidth; + return widthOnGrid; + } + + private void setDiscreteWidth(double width) { + widthOnGrid = grid.gridTime(width, timeConversionFactor); + grid.adjustTimeStep(this); } /** @@ -102,5 +173,25 @@ 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 + * @return the conversion factor + */ + + public double getConversionFactor() { + return timeConversionFactor; + } + + /** + * 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 / 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 96ad965..a34102f 100644 --- a/src/main/java/pulse/problem/laser/DiscretePulse2D.java +++ b/src/main/java/pulse/problem/laser/DiscretePulse2D.java @@ -19,7 +19,7 @@ public class DiscretePulse2D extends DiscretePulse { private double discretePulseSpot; - private double coordFactor; + private double radialFactor; /** * The constructor for {@code DiscretePulse2D}. @@ -35,12 +35,11 @@ public class DiscretePulse2D extends DiscretePulse { public DiscretePulse2D(ClassicalProblem2D problem, Grid2D grid) { super(problem, grid); var properties = (ExtendedThermalProperties) problem.getProperties(); - coordFactor = (double) properties.getSampleDiameter().getValue() / 2.0; - var pulse = (Pulse2D) problem.getPulse(); - discretePulseSpot = grid.gridRadialDistance((double) pulse.getSpotDiameter().getValue() / 2.0, coordFactor); - + init(properties); + + properties.addListener(e -> init(properties) ); } - + /** * This calculates the dimensionless, discretised pulse function at a * dimensionless radial coordinate {@code coord}. @@ -59,22 +58,31 @@ public DiscretePulse2D(ClassicalProblem2D problem, Grid2D grid) { public double evaluateAt(double time, double radialCoord) { return laserPowerAt(time) * (0.5 + 0.5 * signum(discretePulseSpot - radialCoord)); } + + private void init(ExtendedThermalProperties properties) { + radialFactor = (double) properties.getSampleDiameter().getValue() / 2.0; + evalPulseSpot(); + } /** - * Calls the superclass method, then calculates the - * {@code discretePulseSpot} using the {@code gridRadialDistance} method. + * Calculates the {@code discretePulseSpot} using the {@code gridRadialDistance} method. * * @see pulse.problem.schemes.Grid2D.gridRadialDistance(double,double) */ - @Override - public void recalculate() { - super.recalculate(); - final double radius = (double) ((Pulse2D) getPulse()).getSpotDiameter().getValue() / 2.0; - discretePulseSpot = ((Grid2D) getGrid()).gridRadialDistance(radius, coordFactor); + public final void evalPulseSpot() { + var pulse = (Pulse2D) getPulse(); + var grid2d = (Grid2D) getGrid(); + final double radius = (double) pulse.getSpotDiameter().getValue() / 2.0; + discretePulseSpot = grid2d.gridRadialDistance(radius, radialFactor); + grid2d.adjustStepSize(this); } - public double getDiscretePulseSpot() { + public final double getDiscretePulseSpot() { return discretePulseSpot; } + + public final double getRadialConversionFactor() { + return radialFactor; + } } diff --git a/src/main/java/pulse/problem/laser/ExponentiallyModifiedGaussian.java b/src/main/java/pulse/problem/laser/ExponentiallyModifiedGaussian.java index 21c3974..379ab97 100644 --- a/src/main/java/pulse/problem/laser/ExponentiallyModifiedGaussian.java +++ b/src/main/java/pulse/problem/laser/ExponentiallyModifiedGaussian.java @@ -10,14 +10,10 @@ import static pulse.properties.NumericPropertyKeyword.SKEW_MU; import static pulse.properties.NumericPropertyKeyword.SKEW_SIGMA; -import java.util.List; import java.util.Set; -import pulse.input.ExperimentalData; import pulse.properties.NumericProperty; import pulse.properties.NumericPropertyKeyword; -import static pulse.properties.NumericPropertyKeyword.INTEGRATION_SEGMENTS; -import pulse.properties.Property; /** * Represents the exponentially modified Gaussian function, which is given by @@ -31,7 +27,8 @@ public class ExponentiallyModifiedGaussian extends PulseTemporalShape { private double mu; private double sigma; private double lambda; - private double norm; + + private final static int MIN_POINTS = 10; /** * Creates an exponentially modified Gaussian with the default parameter @@ -41,7 +38,6 @@ public ExponentiallyModifiedGaussian() { mu = (double) def(SKEW_MU).getValue(); lambda = (double) def(SKEW_LAMBDA).getValue(); sigma = (double) def(SKEW_SIGMA).getValue(); - norm = 1.0; } public ExponentiallyModifiedGaussian(ExponentiallyModifiedGaussian another) { @@ -49,18 +45,6 @@ public ExponentiallyModifiedGaussian(ExponentiallyModifiedGaussian another) { this.mu = another.mu; this.sigma = another.sigma; this.lambda = another.lambda; - this.norm = another.norm; - } - - /** - * This calls the superclass {@code init method} and sets the normalisation - * factor to 1/∫Φ(Fo)dFo. - */ - @Override - public void init(ExperimentalData data, DiscretePulse pulse) { - super.init(data, pulse); - norm = 1.0 / area(); // calculates the area. The normalisation factor is then set to the inverse of - // the area. } /** @@ -77,7 +61,7 @@ public double evaluateAt(double time) { final double lambdaHalf = 0.5 * lambda; final double sigmaSq = sigma * sigma; - return norm * lambdaHalf * exp(lambdaHalf * (2.0 * mu + lambda * sigmaSq - 2.0 * reducedTime)) + return lambdaHalf * exp(lambdaHalf * (2.0 * mu + lambda * sigmaSq - 2.0 * reducedTime)) * erfc((mu + lambda * sigmaSq - reducedTime) / (sqrt(2) * sigma)); } @@ -170,4 +154,9 @@ public PulseTemporalShape copy() { return new ExponentiallyModifiedGaussian(this); } -} + @Override + public int getRequiredDiscretisation() { + return MIN_POINTS; + } + +} \ No newline at end of file diff --git a/src/main/java/pulse/problem/laser/NumericPulse.java b/src/main/java/pulse/problem/laser/NumericPulse.java index 60989bf..265ac2a 100644 --- a/src/main/java/pulse/problem/laser/NumericPulse.java +++ b/src/main/java/pulse/problem/laser/NumericPulse.java @@ -25,7 +25,8 @@ public class NumericPulse extends PulseTemporalShape { private NumericPulseData pulseData; private UnivariateFunction interpolation; - private double adjustedPulseWidth; + + private final static int MIN_POINTS = 20; public NumericPulse() { //intentionally blank @@ -47,59 +48,47 @@ public NumericPulse(NumericPulse pulse) { * interpolates the input pulse using spline functions and normalises the * output. * + * @param data * @see normalise() * */ @Override public void init(ExperimentalData data, DiscretePulse pulse) { - pulseData = data.getMetadata().getPulseData(); - - //subtracts a horizontal baseline from the pulse data - var baseline = new FlatBaseline(); - baseline.fitNegative(pulseData); - - for(int i = 0, size = pulseData.getTimeSequence().size(); i < size; i++) - pulseData.setSignalAt(i, - pulseData.signalAt(i) - baseline.valueAt(pulseData.timeAt(i))); + //generate baseline-subtracted numeric data from ExperimentalData + baselineSubtractedFrom(data); + //notify host pulse object of a new pulse width var problem = ((SearchTask) data.getParent()).getCurrentCalculation().getProblem(); - setPulseWidth(problem); + setPulseWidthOf(problem); + //convert to dimensionless time and interpolate double timeFactor = problem.getProperties().timeFactor(); - - super.init(data, pulse); - doInterpolation(timeFactor); - - normalise(problem); } - + /** - * Checks that the area of the pulse curve is unity (within a small error - * margin). If this is {@code false}, re-scales the numeric data using - * {@code 1/area} as the scaling factor. - * - * @param problem defines the {@code timeFactor} needed for re-building the - * interpolation - * @see pulse.problem.laser.NumericPulseData.scale() + * Copies the numeric pulse from metadata and subtracts a horizontal baseline + * from the data points assigned to {@code pulseData}. + * @param data the experimental data containing the metadata with numeric pulse data. */ - public void normalise(Problem problem) { - - final double EPS = 1E-2; - double timeFactor = problem.getProperties().timeFactor(); - - for (double area = area(); Math.abs(area - 1.0) > EPS; area = area()) { - pulseData.scale(1.0 / area); - doInterpolation(timeFactor); - } - + + private void baselineSubtractedFrom(ExperimentalData data) { + pulseData = new NumericPulseData(data.getMetadata().getPulseData()); + + //subtracts a horizontal baseline from the pulse data + var baseline = new FlatBaseline(); + baseline.fitNegative(pulseData); + + for(int i = 0, size = pulseData.getTimeSequence().size(); i < size; i++) + pulseData.setSignalAt(i, + pulseData.signalAt(i) - baseline.valueAt(pulseData.timeAt(i))); } - private void setPulseWidth(Problem problem) { - var timeSequence = pulseData.getTimeSequence(); - double pulseWidth = timeSequence.get(timeSequence.size() - 1); + private void setPulseWidthOf(Problem problem) { + var timeSequence = pulseData.getTimeSequence(); + double pulseWidth = timeSequence.get(timeSequence.size() - 1); - var pulseObject = problem.getPulse(); + var pulseObject = problem.getPulse(); pulseObject.setPulseWidth(derive(PULSE_WIDTH, pulseWidth)); } @@ -107,13 +96,13 @@ private void setPulseWidth(Problem problem) { private void doInterpolation(double timeFactor) { var interpolator = new AkimaSplineInterpolator(); - var timeList = pulseData.getTimeSequence().stream().mapToDouble(d -> d / timeFactor).toArray(); - adjustedPulseWidth = timeList[timeList.length - 1]; - var powerList = pulseData.getSignalData(); + var timeList = pulseData.getTimeSequence().stream().mapToDouble(d -> d / timeFactor).toArray(); + var powerList = pulseData.getSignalData(); + this.setPulseWidth(timeList[timeList.length - 1]); + interpolation = interpolator.interpolate(timeList, powerList.stream().mapToDouble(d -> d).toArray()); - } /** @@ -122,7 +111,7 @@ private void doInterpolation(double timeFactor) { */ @Override public double evaluateAt(double time) { - return time > adjustedPulseWidth ? 0.0 : interpolation.value(time); + return time > getPulseWidth() ? 0.0 : interpolation.value(time); } @Override @@ -144,10 +133,16 @@ public NumericPulseData getData() { public void setData(NumericPulseData pulseData) { this.pulseData = pulseData; + } public UnivariateFunction getInterpolation() { return interpolation; } + @Override + public int getRequiredDiscretisation() { + return MIN_POINTS; + } + } diff --git a/src/main/java/pulse/problem/laser/NumericPulseData.java b/src/main/java/pulse/problem/laser/NumericPulseData.java index 0a0493e..e23c023 100644 --- a/src/main/java/pulse/problem/laser/NumericPulseData.java +++ b/src/main/java/pulse/problem/laser/NumericPulseData.java @@ -10,7 +10,7 @@ */ public class NumericPulseData extends AbstractData { - private int externalID; + private final int externalID; /** * Stores {@code id} and calls super-constructor @@ -50,20 +50,9 @@ public void addPoint(double time, double power) { public int getExternalID() { return externalID; } - - /** - * Uniformly scales the values of the pulse power by {@code factor}. - * - * @param factor the scaling factor - */ - public void scale(double factor) { - - var power = this.getSignalData(); - - for (int i = 0, size = power.size(); i < size; i++) { - power.set(i, power.get(i) * factor); - } - + + public double pulseWidth() { + return super.timeLimit(); } -} +} \ No newline at end of file diff --git a/src/main/java/pulse/problem/laser/PulseTemporalShape.java b/src/main/java/pulse/problem/laser/PulseTemporalShape.java index 5701951..6c3814e 100644 --- a/src/main/java/pulse/problem/laser/PulseTemporalShape.java +++ b/src/main/java/pulse/problem/laser/PulseTemporalShape.java @@ -1,12 +1,7 @@ package pulse.problem.laser; -import static pulse.properties.NumericProperties.derive; -import static pulse.properties.NumericPropertyKeyword.INTEGRATION_SEGMENTS; import pulse.input.ExperimentalData; -import pulse.math.FixedIntervalIntegrator; -import pulse.math.MidpointIntegrator; -import pulse.math.Segment; import pulse.util.PropertyHolder; import pulse.util.Reflexive; @@ -21,49 +16,14 @@ public abstract class PulseTemporalShape extends PropertyHolder implements Refle private double width; - private final static int DEFAULT_POINTS = 256; - private FixedIntervalIntegrator integrator; - public PulseTemporalShape() { //intentionlly blank } public PulseTemporalShape(PulseTemporalShape another) { - this.integrator = another.integrator; - } - - /** - * Creates a new midpoint-integrator using the number of segments equal to - * {@value DEFAULT_POINTS}. The integrand function is specified by the - * {@code evaluateAt} method of this class. - * - * @see pulse.math.MidpointIntegrator - * @see evaluateAt() - */ - public void initAreaIntegrator() { - integrator = new MidpointIntegrator(new Segment(0.0, getPulseWidth()), - derive(INTEGRATION_SEGMENTS, DEFAULT_POINTS)) { - - @Override - public double integrand(double... vars) { - return evaluateAt(vars[0]); - } - - }; + this.width = another.width; } - - /** - * Uses numeric integration (midpoint rule) to calculate the area of the - * pulse shape corresponding to the selected parameters. The integration - * bounds are non-negative. - * - * @return the area - */ - public double area() { - integrator.setBounds(new Segment(0.0, getPulseWidth())); - return integrator.integrate(); - } - + /** * This evaluates the dimensionless, discretised pulse function on a * {@code grid} needed to evaluate the heat source in the difference scheme. @@ -78,11 +38,11 @@ public double area() { * Stores the pulse width from {@code pulse} and initialises area * integration. * + * @param data * @param pulse the discrete pulse containing the pulse width */ public void init(ExperimentalData data, DiscretePulse pulse) { width = pulse.getDiscreteWidth(); - this.initAreaIntegrator(); } public abstract PulseTemporalShape copy(); @@ -104,5 +64,7 @@ public double getPulseWidth() { public void setPulseWidth(double width) { this.width = width; } + + public abstract int getRequiredDiscretisation(); -} +} \ No newline at end of file diff --git a/src/main/java/pulse/problem/laser/RectangularPulse.java b/src/main/java/pulse/problem/laser/RectangularPulse.java index 39369a2..2ba6b9d 100644 --- a/src/main/java/pulse/problem/laser/RectangularPulse.java +++ b/src/main/java/pulse/problem/laser/RectangularPulse.java @@ -14,8 +14,11 @@ */ public class RectangularPulse extends PulseTemporalShape { + private final static int MIN_POINTS = 4; + /** * @param time the time measured from the start of the laser pulse. + * @return */ @Override public double evaluateAt(double time) { @@ -32,5 +35,10 @@ public void set(NumericPropertyKeyword type, NumericProperty property) { public PulseTemporalShape copy() { return new RectangularPulse(); } + + @Override + public int getRequiredDiscretisation() { + return MIN_POINTS; + } } diff --git a/src/main/java/pulse/problem/laser/TrapezoidalPulse.java b/src/main/java/pulse/problem/laser/TrapezoidalPulse.java index 82bb854..c305b81 100644 --- a/src/main/java/pulse/problem/laser/TrapezoidalPulse.java +++ b/src/main/java/pulse/problem/laser/TrapezoidalPulse.java @@ -6,14 +6,10 @@ import static pulse.properties.NumericPropertyKeyword.TRAPEZOIDAL_FALL_PERCENTAGE; import static pulse.properties.NumericPropertyKeyword.TRAPEZOIDAL_RISE_PERCENTAGE; -import java.util.List; import java.util.Set; -import pulse.input.ExperimentalData; import pulse.properties.NumericProperty; import pulse.properties.NumericPropertyKeyword; -import static pulse.properties.NumericPropertyKeyword.INTEGRATION_SEGMENTS; -import pulse.properties.Property; /** * A trapezoidal pulse shape, which combines a rise segment, a constant-power @@ -26,6 +22,8 @@ public class TrapezoidalPulse extends PulseTemporalShape { private double fall; private double h; + private final static int MIN_POINTS = 6; + /** * Constructs a trapezoidal pulse using a default segmentation principle. * The reader is referred to the {@code .xml} file containing the default @@ -36,7 +34,7 @@ public class TrapezoidalPulse extends PulseTemporalShape { public TrapezoidalPulse() { rise = (int) def(TRAPEZOIDAL_RISE_PERCENTAGE).getValue() / 100.0; fall = (int) def(TRAPEZOIDAL_FALL_PERCENTAGE).getValue() / 100.0; - h = height(); + h = 1.0; } public TrapezoidalPulse(TrapezoidalPulse another) { @@ -44,16 +42,7 @@ public TrapezoidalPulse(TrapezoidalPulse another) { this.fall = another.fall; this.h = another.h; } - - /** - * Calculates the height of the trapez after calling the super-class method. - */ - @Override - public void init(ExperimentalData data, DiscretePulse pulse) { - super.init(data, pulse); - h = height(); - } - + /** * Calculates the height of the trapezium which under current segmentation * will yield an area of unity. @@ -136,5 +125,10 @@ public void set(NumericPropertyKeyword type, NumericProperty property) { public PulseTemporalShape copy() { return new TrapezoidalPulse(this); } + + @Override + public int getRequiredDiscretisation() { + return MIN_POINTS; + } -} +} \ No newline at end of file diff --git a/src/main/java/pulse/problem/schemes/ADIScheme.java b/src/main/java/pulse/problem/schemes/ADIScheme.java index 2ff641f..db0c7e4 100644 --- a/src/main/java/pulse/problem/schemes/ADIScheme.java +++ b/src/main/java/pulse/problem/schemes/ADIScheme.java @@ -56,5 +56,16 @@ public ADIScheme(NumericProperty N, NumericProperty timeFactor, NumericProperty public String toString() { return getString("ADIScheme.4"); } + + /** + * Contains only an empty statement, as the pulse needs to be calculated not only + * for the time step {@code m} but also accounting for the radial coordinate + * @param m thte time step + */ + + @Override + public void prepareStep(int m) { + //do nothing + } } diff --git a/src/main/java/pulse/problem/schemes/BlockMatrixAlgorithm.java b/src/main/java/pulse/problem/schemes/BlockMatrixAlgorithm.java index 0c108ab..acaa5cd 100644 --- a/src/main/java/pulse/problem/schemes/BlockMatrixAlgorithm.java +++ b/src/main/java/pulse/problem/schemes/BlockMatrixAlgorithm.java @@ -11,15 +11,16 @@ */ public class BlockMatrixAlgorithm extends TridiagonalMatrixAlgorithm { - private double[] gamma; - private double[] p; - private double[] q; + private final double[] gamma; + private final double[] p; + private final double[] q; public BlockMatrixAlgorithm(Grid grid) { super(grid); - gamma = new double[getAlpha().length]; - p = new double[gamma.length - 1]; - q = new double[gamma.length - 1]; + final int N = this.getGridPoints(); + gamma = new double[N + 2]; + p = new double[N]; + q = new double[N]; } @Override @@ -33,9 +34,10 @@ public void sweep(double[] V) { @Override public void evaluateBeta(final double[] U) { super.evaluateBeta(U); - final int N = getGrid().getGridDensityValue(); var alpha = getAlpha(); var beta = getBeta(); + + final int N = getGridPoints(); p[N - 1] = beta[N]; q[N - 1] = alpha[N] + gamma[N]; @@ -49,8 +51,9 @@ public void evaluateBeta(final double[] U) { @Override public void evaluateBeta(final double[] U, final int start, final int endExclusive) { var alpha = getAlpha(); - var grid = getGrid(); - final double HX2_TAU = grid.getXStep() * grid.getXStep() / getGrid().getTimeStep(); + + final double h = this.getGridStep(); + final double HX2_TAU = h * h / this.getTimeStep(); final double a = getCoefA(); final double b = getCoefB(); diff --git a/src/main/java/pulse/problem/schemes/CoupledImplicitScheme.java b/src/main/java/pulse/problem/schemes/CoupledImplicitScheme.java index 848482b..879a51c 100644 --- a/src/main/java/pulse/problem/schemes/CoupledImplicitScheme.java +++ b/src/main/java/pulse/problem/schemes/CoupledImplicitScheme.java @@ -1,7 +1,5 @@ package pulse.problem.schemes; -import static pulse.properties.NumericProperties.def; -import static pulse.properties.NumericProperties.derive; import static pulse.properties.NumericPropertyKeyword.NONLINEAR_PRECISION; import java.util.Set; @@ -13,18 +11,15 @@ import pulse.properties.NumericProperty; import pulse.properties.NumericPropertyKeyword; -public abstract class CoupledImplicitScheme extends ImplicitScheme implements FixedPointIterations { +public abstract class CoupledImplicitScheme extends ImplicitScheme { private RadiativeTransferCoupling coupling; private RTECalculationStatus calculationStatus; - private double nonlinearPrecision; - - private double pls; + private boolean autoUpdateFluxes = true; //should be false for nonlinear solvers public CoupledImplicitScheme(NumericProperty N, NumericProperty timeFactor) { super(); setGrid(new Grid(N, timeFactor)); - nonlinearPrecision = (double) def(NONLINEAR_PRECISION).getValue(); setCoupling(new RadiativeTransferCoupling()); calculationStatus = RTECalculationStatus.NORMAL; } @@ -33,37 +28,14 @@ public CoupledImplicitScheme(NumericProperty N, NumericProperty timeFactor, Nume this(N, timeFactor); setTimeLimit(timeLimit); } - - @Override - public void timeStep(final int m) throws SolverException { - pls = pulse(m); - doIterations(getCurrentSolution(), nonlinearPrecision, m); - } - - @Override - public void iteration(final int m) throws SolverException { - super.timeStep(m); - } - - @Override - public void finaliseIteration(double[] V) throws SolverException { - FixedPointIterations.super.finaliseIteration(V); - var rte = coupling.getRadiativeTransferEquation(); - setCalculationStatus(coupling.getRadiativeTransferEquation().compute(V)); - } - - public RadiativeTransferCoupling getCoupling() { - return coupling; - } - - public final void setCoupling(RadiativeTransferCoupling coupling) { - this.coupling = coupling; - this.coupling.setParent(this); - } - + @Override public void finaliseStep() throws SolverException { super.finaliseStep(); + if(autoUpdateFluxes) { + var rte = this.getCoupling().getRadiativeTransferEquation(); + setCalculationStatus(rte.compute(getCurrentSolution())); + } coupling.getRadiativeTransferEquation().getFluxes().store(); } @@ -74,45 +46,42 @@ public Set listedKeywords() { return set; } - public NumericProperty getNonlinearPrecision() { - return derive(NONLINEAR_PRECISION, nonlinearPrecision); - } - - public void setNonlinearPrecision(NumericProperty nonlinearPrecision) { - this.nonlinearPrecision = (double) nonlinearPrecision.getValue(); - } - - @Override - public Class domain() { - return ParticipatingMedium.class; - } - @Override public boolean normalOperation() { return super.normalOperation() && (getCalculationStatus() == RTECalculationStatus.NORMAL); } - @Override - public void set(NumericPropertyKeyword type, NumericProperty property) { - if (type == NONLINEAR_PRECISION) { - setNonlinearPrecision(property); - } else { - super.set(type, property); - } - } - - public RTECalculationStatus getCalculationStatus() { + public final RTECalculationStatus getCalculationStatus() { return calculationStatus; } - public void setCalculationStatus(RTECalculationStatus calculationStatus) throws SolverException { + public final void setCalculationStatus(RTECalculationStatus calculationStatus) throws SolverException { this.calculationStatus = calculationStatus; - if(calculationStatus != RTECalculationStatus.NORMAL) + if (calculationStatus != RTECalculationStatus.NORMAL) { throw new SolverException(calculationStatus.toString()); + } + } + + public final RadiativeTransferCoupling getCoupling() { + return coupling; } - public double getCurrentPulseValue() { - return pls; + public final void setCoupling(RadiativeTransferCoupling coupling) { + this.coupling = coupling; + this.coupling.setParent(this); + } + + public final boolean isAutoUpdateFluxes() { + return this.autoUpdateFluxes; + } + + public final void setAutoUpdateFluxes(boolean auto) { + this.autoUpdateFluxes = auto; + } + + @Override + public Class[] domain() { + return new Class[]{ParticipatingMedium.class}; } -} +} \ No newline at end of file diff --git a/src/main/java/pulse/problem/schemes/DifferenceScheme.java b/src/main/java/pulse/problem/schemes/DifferenceScheme.java index 09ea1cf..64e33e1 100644 --- a/src/main/java/pulse/problem/schemes/DifferenceScheme.java +++ b/src/main/java/pulse/problem/schemes/DifferenceScheme.java @@ -12,7 +12,8 @@ import pulse.problem.statements.Problem; import pulse.properties.NumericProperty; import pulse.properties.NumericPropertyKeyword; -import static pulse.properties.NumericPropertyKeyword.NUMPOINTS; +import static pulse.properties.NumericPropertyKeyword.GRID_DENSITY; +import static pulse.properties.NumericPropertyKeyword.TAU_FACTOR; import pulse.util.PropertyHolder; import pulse.util.Reflexive; @@ -33,6 +34,7 @@ public abstract class DifferenceScheme extends PropertyHolder implements Reflexi private Grid grid; private double timeLimit; + private double pls; private int timeInterval; private static boolean hideDetailedAdjustment = true; @@ -61,23 +63,6 @@ public void initFrom(DifferenceScheme another) { this.timeInterval = another.timeInterval; } - /** - * Used to get a class of problems on which this difference scheme is - * applicable. - * - * @return a subclass of the {@code Problem} class which can be used as - * input for this difference scheme. - */ - public abstract Class domain(); - - /** - * Creates a {@code DifferenceScheme}, which is an exact copy of this - * object. - * - * @return an exact copy of this {@code DifferenceScheme}. - */ - public abstract DifferenceScheme copy(); - /** * Copies the {@code Grid} and {@code timeLimit} from {@code df}. * @@ -91,91 +76,88 @@ public void copyFrom(DifferenceScheme df) { /** *

- * Contains preparatory steps to ensure smooth running of the solver. This - * includes creating a {@code DiscretePulse} object and adjusting the grid - * of this scheme to match the {@code DiscretePulse} created for this - * {@code problem}. Finally, a heating curve is cleared from the previously - * calculated values. - *

+ * Contains preparatory steps to ensure smooth running of the solver.This + * includes creating a {@code DiscretePulse}object and adjusting the grid of + * this scheme to match the {@code DiscretePulse}created for this + * {@code problem} Finally, a heating curve is cleared from the previously + * calculated values.

*

* All subclasses of {@code DifferenceScheme} should override and explicitly * call this superclass method where appropriate. *

* * @param problem the heat problem to be solved + * @throws pulse.problem.schemes.solvers.SolverException * @see pulse.problem.schemes.Grid.adjustTo() */ - protected void prepare(Problem problem) { - if(discretePulse == null) + protected void prepare(Problem problem) throws SolverException { + if (discretePulse == null) { discretePulse = problem.discretePulseOn(grid); - else + } + else { discretePulse.recalculate(); + } - grid.adjustTo(discretePulse); - - var hc = problem.getHeatingCurve(); - hc.clear(); + clearArrays(); } public void runTimeSequence(Problem problem) throws SolverException { runTimeSequence(problem, 0, timeLimit); + } + + public void scaleSolution(Problem problem) { var curve = problem.getHeatingCurve(); final double maxTemp = (double) problem.getProperties().getMaximumTemperature().getValue(); - curve.scale(maxTemp / curve.apparentMaximum()); + //curve.scale(maxTemp / curve.apparentMaximum()); + curve.scale(maxTemp); } public void runTimeSequence(Problem problem, final double offset, final double endTime) throws SolverException { var curve = problem.getHeatingCurve(); + curve.clear(); - int adjustedNumPoints = (int) curve.getNumPoints().getValue(); + int numPoints = (int) curve.getNumPoints().getValue(); - final double startTime = (double) curve.getTimeShift().getValue(); + final double startTime = (double) curve.getTimeShift().getValue(); final double timeSegment = (endTime - startTime - offset) / problem.getProperties().timeFactor(); - final double tau = grid.getTimeStep(); - for (double dt = 0, factor; dt < tau; adjustedNumPoints *= factor) { - dt = timeSegment / (adjustedNumPoints - 1); - factor = dt / tau; - timeInterval = (int) factor; - } + double tau = grid.getTimeStep(); + final double dt = timeSegment / (numPoints - 1); + timeInterval = Math.max( (int) (dt / tau), 1); - final double wFactor = timeInterval * tau * problem.getProperties().timeFactor(); + double wFactor = timeInterval * tau * problem.getProperties().timeFactor(); // First point (index = 0) is always (0.0, 0.0) + curve.addPoint(0.0, 0.0); + + double nextTime; + int previous; /* - * The outer cycle iterates over the number of points of the HeatingCurve + * The outer cycle iterates over the number of points of the HeatingCurve */ - double nextTime = offset + wFactor; - curve.addPoint(0.0, 0.0); - - for (int w = 1; nextTime < 1.01 * endTime; nextTime = offset + (++w) * wFactor) { + for (previous = 1, nextTime = offset; nextTime < endTime || !curve.isFull(); + previous += timeInterval) { /* - * Two adjacent points of the heating curves are separated by timeInterval on - * the time grid. Thus, to calculate the next point on the heating curve, - * timeInterval/tau time steps have to be made first. + * Two adjacent points of the heating curves are separated by timeInterval on + * the time grid. Thus, to calculate the next point on the heating curve, + * timeInterval/tau time steps have to be made first. */ - timeSegment((w - 1) * timeInterval + 1, w * timeInterval + 1); + timeSegment(previous, previous + timeInterval); + nextTime += wFactor; curve.addPoint(nextTime, signal()); - - } - - /** - * If the total number of points added by the procedure - * is actually less than the pre-set number of points -- change that number - */ - - if(curve.actualNumPoints() < (int)curve.getNumPoints().getValue()) { - curve.setNumPoints(derive(NUMPOINTS, curve.actualNumPoints())); } + curve.copyToLastCalculation(); + scaleSolution(problem); } private void timeSegment(final int m1, final int m2) throws SolverException { for (int m = m1; m < m2 && normalOperation(); m++) { - timeStep(m); - finaliseStep(); + prepareStep(m); //prepare + timeStep(m); //calculate + finaliseStep(); //finalise } } @@ -183,11 +165,15 @@ public double pulse(final int m) { return getDiscretePulse().laserPowerAt((m - EPS) * getGrid().getTimeStep()); } - public abstract double signal(); - - public abstract void timeStep(final int m) throws SolverException; - - public abstract void finaliseStep() throws SolverException; + /** + * Do preparatory calculations that depend only on the time variable, e.g., + * calculate the pulse power. + * + * @param m the time step number + */ + public void prepareStep(int m) { + pls = pulse(m); + } public boolean normalOperation() { return true; @@ -290,6 +276,10 @@ public final NumericProperty getTimeLimit() { return derive(TIME_LIMIT, timeLimit); } + public double getCurrentPulseValue() { + return pls; + } + /** * Sets the time limit (in units defined by the corresponding * {@code NumericProperty}), which serves as the breakpoint for the @@ -311,5 +301,30 @@ public void set(NumericPropertyKeyword type, NumericProperty property) { setTimeLimit(property); } } + + public abstract double signal(); + + public abstract void clearArrays(); + + public abstract void timeStep(int m) throws SolverException; -} + public abstract void finaliseStep() throws SolverException; + + /** + * Retrieves all problem statements that can be solved with this + * implementation of the difference scheme. + * + * @return an array containing subclasses of the {@code Problem} class which + * can be used as input for this difference scheme. + */ + public abstract Class[] domain(); + + /** + * Creates a {@code DifferenceScheme}, which is an exact copy of this + * object. + * + * @return an exact copy of this {@code DifferenceScheme}. + */ + public abstract DifferenceScheme copy(); + +} \ No newline at end of file diff --git a/src/main/java/pulse/problem/schemes/DistributedDetection.java b/src/main/java/pulse/problem/schemes/DistributedDetection.java index 2ef708d..88831e4 100644 --- a/src/main/java/pulse/problem/schemes/DistributedDetection.java +++ b/src/main/java/pulse/problem/schemes/DistributedDetection.java @@ -35,4 +35,4 @@ public static double evaluateSignal(final AbsorptionModel absorption, final Grid return signal * 0.5 * hx; } -} +} \ No newline at end of file diff --git a/src/main/java/pulse/problem/schemes/Grid.java b/src/main/java/pulse/problem/schemes/Grid.java index d62e7dc..f68467b 100644 --- a/src/main/java/pulse/problem/schemes/Grid.java +++ b/src/main/java/pulse/problem/schemes/Grid.java @@ -3,21 +3,16 @@ import static java.lang.Math.pow; import static java.lang.Math.rint; import static java.lang.String.format; -import static pulse.properties.NumericProperties.def; import static pulse.properties.NumericProperties.derive; import static pulse.properties.NumericProperty.requireType; import static pulse.properties.NumericPropertyKeyword.GRID_DENSITY; import static pulse.properties.NumericPropertyKeyword.TAU_FACTOR; -import java.util.ArrayList; -import java.util.List; import java.util.Set; import pulse.problem.laser.DiscretePulse; import pulse.properties.NumericProperty; import pulse.properties.NumericPropertyKeyword; -import static pulse.properties.NumericPropertyKeyword.NONLINEAR_PRECISION; -import pulse.properties.Property; import pulse.util.PropertyHolder; /** @@ -48,8 +43,10 @@ public class Grid extends PropertyHolder { * @see pulse.properties.NumericPropertyKeyword */ public Grid(NumericProperty gridDensity, NumericProperty timeFactor) { - setGridDensity(gridDensity); - setTimeFactor(timeFactor); + this.N = (int) gridDensity.getValue(); + this.tauFactor = (double) timeFactor.getValue(); + hx = 1. / N; + setTimeStep(tauFactor * pow(hx, 2)); } protected Grid() { @@ -67,20 +64,34 @@ public Grid copy() { } /** - * Optimises the {@code Grid} parameters. + * 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 < grid.tau}. + * {@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 void adjustTo(DiscretePulse pulse) { - final double ADJUSTMENT_FACTOR = 0.75; - for (final double factor = 0.95; factor * tau > pulse.getDiscreteWidth(); pulse.recalculate()) { - tauFactor *= ADJUSTMENT_FACTOR; - tau = tauFactor * pow(hx, 2); + 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)); } + } /** @@ -115,7 +126,7 @@ public void set(NumericPropertyKeyword type, NumericProperty property) { * * @return a double, representing the {@code hx} value. */ - public double getXStep() { + public final double getXStep() { return hx; } @@ -124,7 +135,7 @@ public double getXStep() { * * @param hx a double, representing the new {@code hx} value. */ - public void setXStep(double hx) { + public final void setXStep(double hx) { this.hx = hx; } @@ -134,12 +145,13 @@ public void setXStep(double hx) { * * @return a double, representing the {@code tau} value. */ - public double getTimeStep() { + public final double getTimeStep() { return tau; } - protected void setTimeStep(double tau) { + protected final void setTimeStep(double tau) { this.tau = tau; + } /** @@ -150,7 +162,7 @@ protected void setTimeStep(double tau) { * @return a NumericProperty of the {@code TAU_FACTOR} type, representing * the {@code tauFactor} value. */ - public NumericProperty getTimeFactor() { + public final NumericProperty getTimeFactor() { return derive(TAU_FACTOR, tauFactor); } @@ -161,16 +173,17 @@ public NumericProperty getTimeFactor() { * @return a NumericProperty of the {@code GRID_DENSITY} type, representing * the {@code gridDensity} value. */ - public NumericProperty getGridDensity() { + public final NumericProperty getGridDensity() { return derive(GRID_DENSITY, N); } - protected int getGridDensityValue() { + protected final int getGridDensityValue() { return N; } protected void setGridDensityValue(int N) { this.N = N; + hx = 1. / N; } /** @@ -183,7 +196,8 @@ public void setGridDensity(NumericProperty gridDensity) { requireType(gridDensity, GRID_DENSITY); this.N = (int) gridDensity.getValue(); hx = 1. / N; - setTimeFactor(derive(TAU_FACTOR, 1.0)); + setTimeStep(tauFactor * pow(hx, 2)); + firePropertyChanged(this, gridDensity); } /** @@ -195,7 +209,8 @@ public void setGridDensity(NumericProperty gridDensity) { public void setTimeFactor(NumericProperty timeFactor) { requireType(timeFactor, TAU_FACTOR); this.tauFactor = (double) timeFactor.getValue(); - setTimeStep(tauFactor * pow(hx, 2)); + setTimeStep(tauFactor * pow(hx, 2)); + firePropertyChanged(this, timeFactor); } /** @@ -207,7 +222,7 @@ public void setTimeFactor(NumericProperty timeFactor) { * @param dimensionFactor a conversion factor with the dimension of time * @return a double representing the time on the finite grid */ - public double gridTime(double time, double dimensionFactor) { + public final double gridTime(double time, double dimensionFactor) { return rint((time / dimensionFactor) / tau) * tau; } @@ -220,16 +235,21 @@ public double gridTime(double time, double dimensionFactor) { * @param lengthFactor a conversion factor with the dimension of length * @return a double representing the axial distance on the finite grid */ - public double gridAxialDistance(double distance, double lengthFactor) { + public final double gridAxialDistance(double distance, double lengthFactor) { return rint((distance / lengthFactor) / hx) * hx; } @Override public String toString() { var sb = new StringBuilder(); - sb.append(""); - sb.append(getClass().getSimpleName() + ": hx=" + format("%3.2e", hx) + "; "); - sb.append("τ=" + format("%3.2e", tau) + "; "); + sb.append(""). + append(getClass().getSimpleName()) + .append(": hx=") + .append(format("%3.2e", hx)) + .append("; "). + append("τ=") + .append(format("%3.2e", tau)) + .append("; "); return sb.toString(); } diff --git a/src/main/java/pulse/problem/schemes/Grid2D.java b/src/main/java/pulse/problem/schemes/Grid2D.java index e70710a..7c34840 100644 --- a/src/main/java/pulse/problem/schemes/Grid2D.java +++ b/src/main/java/pulse/problem/schemes/Grid2D.java @@ -56,24 +56,25 @@ public void setTimeFactor(NumericProperty timeFactor) { * * @param pulse the discrete puls representation */ - @Override - public void adjustTo(DiscretePulse pulse) { - super.adjustTo(pulse); - if (pulse instanceof DiscretePulse2D) { - adjustTo((DiscretePulse2D) pulse); + + public void adjustStepSize(DiscretePulse pulse) { + var pulse2d = (DiscretePulse2D)pulse; + double pulseSpotSize = pulse2d.getDiscretePulseSpot(); + + if(hy > pulseSpotSize) { + final int INCREMENT = 5; + final int newN = getGridDensityValue() + INCREMENT; + setGridDensityValue(newN); + adjustStepSize(pulse); } + + adjustTimeStep(pulse); } - private void adjustTo(DiscretePulse2D pulse) { - final int GRID_DENSITY_INCREMENT = 5; - - for (final var factor = 1.05; factor * hy > pulse.getDiscretePulseSpot(); pulse.recalculate()) { - int N = getGridDensityValue(); - setGridDensityValue(N + GRID_DENSITY_INCREMENT); - hy = 1. / N; - setXStep(1. / N); - } - + @Override + protected void setGridDensityValue(int N) { + super.setGridDensityValue(N); + hy = 1. / N; } /** @@ -101,13 +102,11 @@ public double gridRadialDistance(double radial, double lengthFactor) { @Override public String toString() { - var sb = new StringBuilder(super.toString()); - sb.append("hy=" + format("%3.3f", hy)); - return sb.toString(); + return super.toString() + "hy=" + format("%3.3f", hy); } public double getYStep() { return hy; } -} +} \ No newline at end of file diff --git a/src/main/java/pulse/problem/schemes/ImplicitScheme.java b/src/main/java/pulse/problem/schemes/ImplicitScheme.java index ae80435..8d85284 100644 --- a/src/main/java/pulse/problem/schemes/ImplicitScheme.java +++ b/src/main/java/pulse/problem/schemes/ImplicitScheme.java @@ -61,7 +61,7 @@ public ImplicitScheme(NumericProperty N, NumericProperty timeFactor, NumericProp } @Override - protected void prepare(Problem problem) { + protected void prepare(Problem problem) throws SolverException { super.prepare(problem); tridiagonal = new TridiagonalMatrixAlgorithm(getGrid()); } @@ -77,21 +77,21 @@ protected void prepare(Problem problem) { @Override public void timeStep(final int m) throws SolverException { - leftBoundary(m); + leftBoundary(); final var V = getCurrentSolution(); final int N = V.length - 1; - setSolutionAt(N, evalRightBoundary(m, tridiagonal.getAlpha()[N], tridiagonal.getBeta()[N])); + setSolutionAt(N, evalRightBoundary(tridiagonal.getAlpha()[N], tridiagonal.getBeta()[N])); tridiagonal.sweep(V); } - public void leftBoundary(final int m) { - tridiagonal.setBeta(1, firstBeta(m)); + public void leftBoundary() { + tridiagonal.setBeta(1, firstBeta()); tridiagonal.evaluateBeta(getPreviousSolution()); } - public abstract double evalRightBoundary(final int m, final double alphaN, final double betaN); + public abstract double evalRightBoundary(final double alphaN, final double betaN); - public abstract double firstBeta(final int m); + public abstract double firstBeta(); /** * Prints out the description of this problem type. @@ -111,4 +111,4 @@ public void setTridiagonalMatrixAlgorithm(TridiagonalMatrixAlgorithm tridiagonal this.tridiagonal = tridiagonal; } -} +} \ No newline at end of file diff --git a/src/main/java/pulse/problem/schemes/LayeredGrid2D.java b/src/main/java/pulse/problem/schemes/LayeredGrid2D.java deleted file mode 100644 index 0cea4c9..0000000 --- a/src/main/java/pulse/problem/schemes/LayeredGrid2D.java +++ /dev/null @@ -1,85 +0,0 @@ -package pulse.problem.schemes; - -import static pulse.problem.schemes.Partition.Location.CORE_X; -import static pulse.problem.schemes.Partition.Location.CORE_Y; -import static pulse.problem.schemes.Partition.Location.FRONT_Y; -import static pulse.problem.schemes.Partition.Location.REAR_Y; -import static pulse.problem.schemes.Partition.Location.SIDE_X; -import static pulse.problem.schemes.Partition.Location.SIDE_Y; -import static pulse.properties.NumericProperties.def; -import static pulse.properties.NumericProperties.derive; -import static pulse.properties.NumericPropertyKeyword.SHELL_GRID_DENSITY; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import pulse.problem.schemes.Partition.Location; -import pulse.properties.NumericProperty; -import pulse.properties.NumericPropertyKeyword; -import static pulse.properties.NumericPropertyKeyword.NONLINEAR_PRECISION; -import pulse.properties.Property; - -public class LayeredGrid2D extends Grid2D { - - private Map h; - - public LayeredGrid2D(Map partitions, NumericProperty timeFactor) { - h = new HashMap<>(partitions); - setGridDensity(derive(CORE_Y.densityKeyword(), partitions.get(CORE_Y).getDensity())); - setTimeFactor(timeFactor); - } - - public Partition getPartition(Location location) { - return h.get(location); - } - - @Override - public Grid2D copy() { - return new LayeredGrid2D(h, getTimeFactor()); - } - - private void setDensity(Location location, NumericProperty density) { - h.get(location).setDensity((int) density.getValue()); - } - - @Override - public void setGridDensity(NumericProperty gridDensity) { - super.setGridDensity(gridDensity); - setDensity(CORE_X, gridDensity); - setDensity(CORE_Y, gridDensity); - } - - public NumericProperty getGridDensity(Location location) { - return derive(location.densityKeyword(), h.get(location).getDensity()); - } - - @Override - public NumericProperty getGridDensity() { - return getGridDensity(CORE_X); - } - - @Override - public Set listedKeywords() { - var set = super.listedKeywords(); - set.add(SHELL_GRID_DENSITY); - return set; - } - - @Override - public void set(NumericPropertyKeyword type, NumericProperty property) { - switch (type) { - case SHELL_GRID_DENSITY: - setDensity(FRONT_Y, property); - setDensity(REAR_Y, property); - setDensity(SIDE_X, property); - setDensity(SIDE_Y, property); - break; - default: - super.set(type, property); - } - } - -} diff --git a/src/main/java/pulse/problem/schemes/OneDimensionalScheme.java b/src/main/java/pulse/problem/schemes/OneDimensionalScheme.java index 54b3395..5483b5a 100644 --- a/src/main/java/pulse/problem/schemes/OneDimensionalScheme.java +++ b/src/main/java/pulse/problem/schemes/OneDimensionalScheme.java @@ -1,7 +1,6 @@ package pulse.problem.schemes; import pulse.problem.schemes.solvers.SolverException; -import pulse.problem.statements.Problem; import pulse.properties.NumericProperty; public abstract class OneDimensionalScheme extends DifferenceScheme { @@ -16,10 +15,10 @@ protected OneDimensionalScheme() { protected OneDimensionalScheme(NumericProperty timeLimit) { super(timeLimit); } - + + @Override - protected void prepare(Problem problem) { - super.prepare(problem); + public void clearArrays() { final int N = (int) getGrid().getGridDensity().getValue(); U = new double[N + 1]; V = new double[N + 1]; diff --git a/src/main/java/pulse/problem/schemes/Partition.java b/src/main/java/pulse/problem/schemes/Partition.java deleted file mode 100644 index 563fb9f..0000000 --- a/src/main/java/pulse/problem/schemes/Partition.java +++ /dev/null @@ -1,66 +0,0 @@ -package pulse.problem.schemes; - -import pulse.properties.NumericPropertyKeyword; - -public class Partition { - - private int density; - private double multiplier; - private double shift; - - public Partition(int value, double multiplier, double shift) { - this.setDensity(value); - this.setShift(shift); - this.setGridMultiplier(multiplier); - } - - public double evaluate() { - return multiplier / (density * (1.0 + shift)); - } - - public int getDensity() { - return density; - } - - public void setDensity(int density) { - this.density = density; - } - - public double getGridMultiplier() { - return multiplier; - } - - public void setGridMultiplier(double multiplier) { - this.multiplier = multiplier; - } - - public double getShift() { - return shift; - } - - public void setShift(double shift) { - this.shift = shift; - } - - public enum Location { - - FRONT_Y, REAR_Y, SIDE_Y, SIDE_X, CORE_X, CORE_Y; - - public NumericPropertyKeyword densityKeyword() { - switch (this) { - case FRONT_Y: - case REAR_Y: - case SIDE_Y: - case SIDE_X: - return NumericPropertyKeyword.SHELL_GRID_DENSITY; - case CORE_X: - case CORE_Y: - return NumericPropertyKeyword.GRID_DENSITY; - default: - throw new IllegalArgumentException("Type not recognized: " + this); - } - } - - } - -} diff --git a/src/main/java/pulse/problem/schemes/RadiativeTransferCoupling.java b/src/main/java/pulse/problem/schemes/RadiativeTransferCoupling.java index 73801b7..189548a 100644 --- a/src/main/java/pulse/problem/schemes/RadiativeTransferCoupling.java +++ b/src/main/java/pulse/problem/schemes/RadiativeTransferCoupling.java @@ -4,7 +4,10 @@ import pulse.problem.schemes.rte.RadiativeTransferSolver; import pulse.problem.schemes.rte.dom.DiscreteOrdinatesMethod; +import pulse.problem.statements.ClassicalProblem; import pulse.problem.statements.ParticipatingMedium; +import pulse.problem.statements.Problem; +import pulse.problem.statements.model.ThermoOpticalProperties; import pulse.properties.NumericProperty; import pulse.properties.NumericPropertyKeyword; import pulse.properties.Property; @@ -31,19 +34,24 @@ public void set(NumericPropertyKeyword type, NumericProperty property) { public void init(ParticipatingMedium problem, Grid grid) { if (rte == null) { + + if (!(problem.getProperties() instanceof ThermoOpticalProperties)) { + throw new IllegalArgumentException("Illegal problem type: " + problem); + } + newRTE(problem, grid); instanceDescriptor.addListener(() -> { newRTE(problem, grid); rte.init(problem, grid); }); - + } else { rte.init(problem, grid); } } - private void newRTE(ParticipatingMedium problem, Grid grid) { + private void newRTE(Problem problem, Grid grid) { rte = instanceDescriptor.newInstance(RadiativeTransferSolver.class, problem, grid); rte.setParent(this); } diff --git a/src/main/java/pulse/problem/schemes/TridiagonalMatrixAlgorithm.java b/src/main/java/pulse/problem/schemes/TridiagonalMatrixAlgorithm.java index 7426317..88c7c65 100644 --- a/src/main/java/pulse/problem/schemes/TridiagonalMatrixAlgorithm.java +++ b/src/main/java/pulse/problem/schemes/TridiagonalMatrixAlgorithm.java @@ -8,20 +8,23 @@ */ public class TridiagonalMatrixAlgorithm { - private Grid grid; + private final double tau; + private final double h; private double a; private double b; private double c; - private double[] alpha; - private double[] beta; + private final int N; + private final double[] alpha; + private final double[] beta; public TridiagonalMatrixAlgorithm(Grid grid) { - this.grid = grid; - final int N = grid.getGridDensityValue(); - alpha = new double[N + 2]; - beta = new double[N + 2]; + tau = grid.getTimeStep(); + N = grid.getGridDensityValue(); + h = grid.getXStep(); + alpha = new double[N + 2]; + beta = new double[N + 2]; } /** @@ -34,7 +37,7 @@ public TridiagonalMatrixAlgorithm(Grid grid) { * from the respective boundary condition */ public void sweep(double[] V) { - for (int j = grid.getGridDensityValue() - 1; j >= 0; j--) { + for (int j = N - 1; j >= 0; j--) { V[j] = alpha[j + 1] * V[j + 1] + beta[j + 1]; } } @@ -44,21 +47,23 @@ public void sweep(double[] V) { * matrix algorithm. */ public void evaluateAlpha() { - for (int i = 1, N = grid.getGridDensityValue(); i < N; i++) { + for (int i = 1; i < N; i++) { alpha[i + 1] = c / (b - a * alpha[i]); } } public void evaluateBeta(final double[] U) { - evaluateBeta(U, 2, grid.getGridDensityValue() + 1); + evaluateBeta(U, 2, N + 1); } /** * Calculates the {@code beta} coefficients as part of the tridiagonal * matrix algorithm. + * @param U + * @param start + * @param endExclusive */ public void evaluateBeta(final double[] U, final int start, final int endExclusive) { - final double tau = grid.getTimeStep(); for (int i = start; i < endExclusive; i++) { beta[i] = beta(U[i - 1] / tau, phi(i - 1), i); } @@ -111,9 +116,17 @@ protected double getCoefB() { protected double getCoefC() { return c; } - - public Grid getGrid() { - return grid; + + public final double getTimeStep() { + return tau; + } + + public final int getGridPoints() { + return N; + } + + public final double getGridStep() { + return h; } -} +} \ No newline at end of file diff --git a/src/main/java/pulse/problem/schemes/rte/BlackbodySpectrum.java b/src/main/java/pulse/problem/schemes/rte/BlackbodySpectrum.java index a0e7880..f395d00 100644 --- a/src/main/java/pulse/problem/schemes/rte/BlackbodySpectrum.java +++ b/src/main/java/pulse/problem/schemes/rte/BlackbodySpectrum.java @@ -1,12 +1,12 @@ package pulse.problem.schemes.rte; -import static pulse.math.MathUtils.fastPowLoop; import org.apache.commons.math3.analysis.UnivariateFunction; - +import static pulse.math.MathUtils.fastPowLoop; import pulse.problem.statements.NonlinearProblem; import pulse.problem.statements.Pulse2D; + /** * Contains methods for calculating the integral spectral characteristics of a * black body with a specific spatial temperature profile. The latter is managed @@ -16,8 +16,8 @@ */ public class BlackbodySpectrum { - private double reductionFactor; private UnivariateFunction interpolation; + private final double reductionFactor; /** * Creates a {@code BlackbodySpectrum}. Calculates the reduction factor @@ -32,6 +32,24 @@ public BlackbodySpectrum(NonlinearProblem p) { reductionFactor = maxHeating / ((double) p.getProperties().getTestTemperature().getValue()); } + @Override + public String toString() { + return "[" + getClass().getSimpleName() + ": Rel. heating = " + reductionFactor + "]"; + } + + /** + * Calculates the emissive power. This is equal to + * 0.25 T0Tm [1 + * +δTm /T0 θ (x) + * ]4, where θ is the reduced temperature. + * @param reducedTemperature the dimensionless reduced temperature + * @return the amount of emissive power + */ + + public double emissivePower(double reducedTemperature) { + return 0.25 / reductionFactor * fastPowLoop(1.0 + reducedTemperature * reductionFactor, 4); + } + /** * Calculates the spectral radiance, which is equal to the spectral power * divided by π, at the given coordinate. @@ -45,10 +63,7 @@ public double radianceAt(double x) { } /** - * Calculates the emissive power at the given coordinate. This is equal to - * 0.25 T0Tm [1 - * +δTm /T0 θ (x) - * ]4, where θ is the reduced temperature. + * Calculates the emissive power at the given coordinate. * * @param x the geometric coordinate inside the sample * @return the local emissive power value @@ -71,17 +86,8 @@ public UnivariateFunction getInterpolation() { return interpolation; } - @Override - public String toString() { - return "[" + getClass().getSimpleName() + ": Rel. heating = " + reductionFactor + "]"; - } - - private double emissivePower(double reducedTemperature) { - return 0.25 / reductionFactor * fastPowLoop(1.0 + reducedTemperature * reductionFactor, 4); - } - - private double radiance(double reducedTemperature) { + public final double radiance(double reducedTemperature) { return emissivePower(reducedTemperature) / Math.PI; } -} +} \ No newline at end of file diff --git a/src/main/java/pulse/problem/schemes/rte/RadiativeTransferSolver.java b/src/main/java/pulse/problem/schemes/rte/RadiativeTransferSolver.java index bb23d09..8013c33 100644 --- a/src/main/java/pulse/problem/schemes/rte/RadiativeTransferSolver.java +++ b/src/main/java/pulse/problem/schemes/rte/RadiativeTransferSolver.java @@ -27,7 +27,7 @@ public abstract class RadiativeTransferSolver extends PropertyHolder implements Reflexive, Descriptive { private Fluxes fluxes; - private List rteListeners; + private final List rteListeners; /** * Dummy constructor. @@ -47,17 +47,17 @@ public RadiativeTransferSolver() { /** * Retrieves the parameters from {@code p} and {@code grid} needed to run - * the calculations. Resets the flux arrays. + * the calculations.Resets the flux arrays. * - * @param p the problem statement + * @param p * @param grid the grid */ public void init(ParticipatingMedium p, Grid grid) { if (fluxes != null) { fluxes.setDensity(grid.getGridDensity()); fluxes.init(); - var properties = (ThermoOpticalProperties) p.getProperties(); - fluxes.setOpticalThickness(properties.getOpticalThickness()); + ThermoOpticalProperties top = (ThermoOpticalProperties) p.getProperties(); + fluxes.setOpticalThickness(top.getOpticalThickness()); } } @@ -128,11 +128,11 @@ public void fireStatusUpdate(RTECalculationStatus status) { } } - public Fluxes getFluxes() { + public final Fluxes getFluxes() { return fluxes; } - public void setFluxes(Fluxes fluxes) { + public final void setFluxes(Fluxes fluxes) { this.fluxes = fluxes; } diff --git a/src/main/java/pulse/problem/schemes/rte/dom/CornetteSchanksPF.java b/src/main/java/pulse/problem/schemes/rte/dom/CornetteSchanksPF.java new file mode 100644 index 0000000..f2a695e --- /dev/null +++ b/src/main/java/pulse/problem/schemes/rte/dom/CornetteSchanksPF.java @@ -0,0 +1,42 @@ +package pulse.problem.schemes.rte.dom; + +import static java.lang.Math.sqrt; + +import pulse.problem.statements.ParticipatingMedium; +import pulse.problem.statements.model.ThermoOpticalProperties; + +/** + * The single-parameter Cornette-Schanks scattering phase function. + * It converges to the Rayleigh phase function as 〈μ〉 → 0 and approaches + * the Henyey–Greenstein phase function as |〈μ〉| → 1 + * @see https://doi.org/10.1364/ao.31.003152 + * + */ +public class CornetteSchanksPF extends PhaseFunction { + + private double anisoFactor; + private double onePlusGSq; + private double g2; + + public CornetteSchanksPF(ThermoOpticalProperties top, Discretisation intensities) { + super(top, intensities); + } + + @Override + public void init(ThermoOpticalProperties top) { + super.init(top); + final double anisotropy = getAnisotropyFactor(); + g2 = 2.0 * anisotropy; + final double aSq = anisotropy * anisotropy; + onePlusGSq = 1.0 + aSq; + anisoFactor = 1.5*(1.0 - aSq)/(2.0 + aSq); + } + + @Override + public double function(final int i, final int k) { + double cosine = cosineTheta(i,k); + final double f = onePlusGSq - g2 * cosine; + return anisoFactor * (1.0 + cosine*cosine) / (f * sqrt(f)); + } + +} \ No newline at end of file diff --git a/src/main/java/pulse/problem/schemes/rte/dom/DiscreteOrdinatesMethod.java b/src/main/java/pulse/problem/schemes/rte/dom/DiscreteOrdinatesMethod.java index 5ad9d0d..af59449 100644 --- a/src/main/java/pulse/problem/schemes/rte/dom/DiscreteOrdinatesMethod.java +++ b/src/main/java/pulse/problem/schemes/rte/dom/DiscreteOrdinatesMethod.java @@ -6,6 +6,7 @@ import pulse.problem.schemes.rte.FluxesAndExplicitDerivatives; import pulse.problem.schemes.rte.RTECalculationStatus; import pulse.problem.schemes.rte.RadiativeTransferSolver; +import pulse.problem.statements.ClassicalProblem; import pulse.problem.statements.ParticipatingMedium; import pulse.problem.statements.model.ThermoOpticalProperties; import pulse.properties.NumericProperty; @@ -45,7 +46,7 @@ public DiscreteOrdinatesMethod(ParticipatingMedium problem, Grid grid) { var properties = (ThermoOpticalProperties) problem.getProperties(); setFluxes(new FluxesAndExplicitDerivatives(grid.getGridDensity(), properties.getOpticalThickness())); - var discrete = new Discretisation(problem); + var discrete = new Discretisation(properties); integratorDescriptor.setSelectedDescriptor(TRBDF2.class.getSimpleName()); setIntegrator(integratorDescriptor.newInstance(AdaptiveIntegrator.class, discrete)); @@ -54,8 +55,8 @@ public DiscreteOrdinatesMethod(ParticipatingMedium problem, Grid grid) { setIterativeSolver(iterativeSolverSelector.newInstance(IterativeSolver.class)); phaseFunctionSelector.setSelectedDescriptor(HenyeyGreensteinPF.class.getSimpleName()); - phaseFunctionSelector.addListener(() -> initPhaseFunction(problem, discrete)); - initPhaseFunction(problem, discrete); + phaseFunctionSelector.addListener(() -> initPhaseFunction(properties, discrete)); + initPhaseFunction(properties, discrete); init(problem, grid); @@ -83,7 +84,8 @@ public RTECalculationStatus compute(double[] tempArray) { } private void fluxesAndDerivatives(final int nExclusive) { - final var interpolation = integrator.getHermiteInterpolator().interpolateOnExternalGrid(nExclusive, integrator); + final var interpolation = integrator.getHermiteInterpolator() + .interpolateOnExternalGrid(nExclusive, integrator); final double DOUBLE_PI = 2.0 * Math.PI; final var discrete = integrator.getDiscretisation(); @@ -92,7 +94,8 @@ private void fluxesAndDerivatives(final int nExclusive) { for (int i = 0; i < nExclusive; i++) { double flux = DOUBLE_PI * discrete.firstMoment(interpolation[0], i); fluxes.setFlux(i, flux); - fluxes.setFluxDerivative(i, -DOUBLE_PI * discrete.firstMoment(interpolation[1], i)); + fluxes.setFluxDerivative(i, + -DOUBLE_PI * discrete.firstMoment(interpolation[1], i)); } } @@ -105,9 +108,11 @@ public String getDescriptor() { @Override public void init(ParticipatingMedium problem, Grid grid) { super.init(problem, grid); - initPhaseFunction(problem, integrator.getDiscretisation()); + var top = (ThermoOpticalProperties)problem.getProperties(); + initPhaseFunction(top, + integrator.getDiscretisation()); integrator.init(problem); - integrator.getPhaseFunction().init(problem); + integrator.getPhaseFunction().init(top); } @Override @@ -119,35 +124,35 @@ public List listedTypes() { return list; } - public AdaptiveIntegrator getIntegrator() { + public final AdaptiveIntegrator getIntegrator() { return integrator; } - public InstanceDescriptor getIntegratorDescriptor() { + public final InstanceDescriptor getIntegratorDescriptor() { return integratorDescriptor; } - public void setIntegrator(AdaptiveIntegrator integrator) { + public final void setIntegrator(AdaptiveIntegrator integrator) { this.integrator = integrator; integrator.setParent(this); firePropertyChanged(this, integratorDescriptor); } - public IterativeSolver getIterativeSolver() { + public final IterativeSolver getIterativeSolver() { return iterativeSolver; } - public InstanceDescriptor getIterativeSolverSelector() { + public final InstanceDescriptor getIterativeSolverSelector() { return iterativeSolverSelector; } - public void setIterativeSolver(IterativeSolver solver) { + public final void setIterativeSolver(IterativeSolver solver) { this.iterativeSolver = solver; solver.setParent(this); firePropertyChanged(this, iterativeSolverSelector); } - public InstanceDescriptor getPhaseFunctionSelector() { + public final InstanceDescriptor getPhaseFunctionSelector() { return phaseFunctionSelector; } @@ -161,10 +166,10 @@ public void set(NumericPropertyKeyword type, NumericProperty property) { // intentionally left blank } - private void initPhaseFunction(ParticipatingMedium problem, Discretisation discrete) { - var pf = phaseFunctionSelector.newInstance(PhaseFunction.class, problem, discrete); + private void initPhaseFunction(ThermoOpticalProperties top, Discretisation discrete) { + var pf = phaseFunctionSelector.newInstance(PhaseFunction.class, top, discrete); integrator.setPhaseFunction(pf); - pf.init(problem); + pf.init(top); firePropertyChanged(this, phaseFunctionSelector); } diff --git a/src/main/java/pulse/problem/schemes/rte/dom/DiscreteQuantities.java b/src/main/java/pulse/problem/schemes/rte/dom/DiscreteQuantities.java index 548e488..846dc1b 100644 --- a/src/main/java/pulse/problem/schemes/rte/dom/DiscreteQuantities.java +++ b/src/main/java/pulse/problem/schemes/rte/dom/DiscreteQuantities.java @@ -24,7 +24,7 @@ public DiscreteQuantities(int gridDensity, int ordinates) { init(gridDensity, ordinates); } - public void init(int gridDensity, int ordinates) { + protected final void init(int gridDensity, int ordinates) { I = new double[gridDensity + 1][ordinates]; f = new double[gridDensity + 1][ordinates]; Ik = new double[gridDensity + 1][ordinates]; @@ -32,7 +32,7 @@ public void init(int gridDensity, int ordinates) { qLast = new double[ordinates]; } - public void store() { + protected void store() { final int n = I.length; final int m = I[0].length; @@ -49,47 +49,47 @@ public void store() { } - public double[][] getIntensities() { + public final double[][] getIntensities() { return I; } - public double[][] getDerivatives() { + public final double[][] getDerivatives() { return f; } - public double getQLast(int i) { + protected final double getQLast(int i) { return qLast[i]; } - public void setQLast(int i, double q) { + protected final void setQLast(int i, double q) { this.qLast[i] = q; } - public double getDerivative(int i, int j) { + public final double getDerivative(int i, int j) { return f[i][j]; } - public void setDerivative(int i, int j, double f) { + public final void setDerivative(int i, int j, double f) { this.f[i][j] = f; } - public double getStoredIntensity(final int i, final int j) { + public final double getStoredIntensity(final int i, final int j) { return Ik[i][j]; } - public double getStoredDerivative(final int i, final int j) { + public final double getStoredDerivative(final int i, final int j) { return fk[i][j]; } - public void setStoredDerivative(final int i, final int j, final double f) { + public final void setStoredDerivative(final int i, final int j, final double f) { this.f[i][j] = f; } - public double getIntensity(int i, int j) { + public final double getIntensity(int i, int j) { return I[i][j]; } - public void setIntensity(int i, int j, double value) { + public final void setIntensity(int i, int j, double value) { I[i][j] = value; } diff --git a/src/main/java/pulse/problem/schemes/rte/dom/Discretisation.java b/src/main/java/pulse/problem/schemes/rte/dom/Discretisation.java index 20776d3..932d7d6 100644 --- a/src/main/java/pulse/problem/schemes/rte/dom/Discretisation.java +++ b/src/main/java/pulse/problem/schemes/rte/dom/Discretisation.java @@ -2,13 +2,10 @@ import static java.lang.Math.PI; -import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import pulse.io.readers.QuadratureReader; import pulse.problem.schemes.rte.BlackbodySpectrum; -import pulse.problem.statements.ParticipatingMedium; import pulse.problem.statements.model.ThermoOpticalProperties; import pulse.properties.NumericProperty; import pulse.properties.NumericPropertyKeyword; @@ -38,9 +35,9 @@ public class Discretisation extends PropertyHolder { * Constructs a {@code DiscreteIntensities} with the default * {@code OrdinateSet} and a new uniform grid. * - * @param problem the problem statement + * @param properties */ - public Discretisation(ParticipatingMedium problem) { + public Discretisation(ThermoOpticalProperties properties) { quadSelector = new DiscreteSelector<>(QuadratureReader.getInstance(), "/quadratures/", "Quadratures.list"); @@ -52,10 +49,9 @@ public Discretisation(ParticipatingMedium problem) { this.firePropertyChanged(this, quadSelector); }); - var properties = (ThermoOpticalProperties) problem.getProperties(); setGrid(new StretchedGrid((double) properties.getOpticalThickness().getValue())); quantities = new DiscreteQuantities(grid.getDensity(), ordinates.getTotalNodes()); - setEmissivity((double) problem.getProperties().getEmissivity().getValue()); + setEmissivity((double) properties.getEmissivity().getValue()); } /** @@ -224,7 +220,7 @@ public void intensitiesRightBoundary(final BlackbodySpectrum ef) { } - protected void setEmissivity(double emissivity) { + protected final void setEmissivity(double emissivity) { this.emissivity = emissivity; boundaryFluxFactor = (1.0 - emissivity) / (emissivity * PI); } @@ -267,7 +263,7 @@ public StretchedGrid getGrid() { return grid; } - public void setGrid(StretchedGrid grid) { + public final void setGrid(StretchedGrid grid) { this.grid = grid; this.grid.setParent(this); } diff --git a/src/main/java/pulse/problem/schemes/rte/dom/ExplicitRungeKutta.java b/src/main/java/pulse/problem/schemes/rte/dom/ExplicitRungeKutta.java index 246338a..648096e 100644 --- a/src/main/java/pulse/problem/schemes/rte/dom/ExplicitRungeKutta.java +++ b/src/main/java/pulse/problem/schemes/rte/dom/ExplicitRungeKutta.java @@ -115,7 +115,7 @@ public Vector[] step(final int j, final double sign) { for (int l = n1; l < n2; l++) { // find unknown intensities (sum over the outward intensities) /* - * OUTWARD + * OUTWARD */ sum = tableau.getMatrix().get(m, 0) * q[l - n1][0]; for (int k = 1; k < m; k++) { diff --git a/src/main/java/pulse/problem/schemes/rte/dom/HenyeyGreensteinPF.java b/src/main/java/pulse/problem/schemes/rte/dom/HenyeyGreensteinPF.java index 03c4477..f1e0d61 100644 --- a/src/main/java/pulse/problem/schemes/rte/dom/HenyeyGreensteinPF.java +++ b/src/main/java/pulse/problem/schemes/rte/dom/HenyeyGreensteinPF.java @@ -3,6 +3,7 @@ import static java.lang.Math.sqrt; import pulse.problem.statements.ParticipatingMedium; +import pulse.problem.statements.model.ThermoOpticalProperties; /** * The single-parameter Henyey-Greenstein scattering phase function. @@ -14,13 +15,13 @@ public class HenyeyGreensteinPF extends PhaseFunction { private double a2; private double b1; - public HenyeyGreensteinPF(ParticipatingMedium medium, Discretisation intensities) { - super(medium, intensities); + public HenyeyGreensteinPF(ThermoOpticalProperties properties, Discretisation intensities) { + super(properties, intensities); } @Override - public void init(ParticipatingMedium problem) { - super.init(problem); + public void init(ThermoOpticalProperties properties) { + super.init(properties); final double anisotropy = getAnisotropyFactor(); b1 = 2.0 * anisotropy; final double aSq = anisotropy * anisotropy; @@ -30,9 +31,7 @@ public void init(ParticipatingMedium problem) { @Override public double function(final int i, final int k) { - final var ordinates = getDiscreteIntensities().getOrdinates(); - final double theta = ordinates.getNode(k) * ordinates.getNode(i); - final double f = a2 - b1 * theta; + final double f = a2 - b1 * cosineTheta(i, k); return a1 / (f * sqrt(f)); } diff --git a/src/main/java/pulse/problem/schemes/rte/dom/LinearAnisotropicPF.java b/src/main/java/pulse/problem/schemes/rte/dom/LinearAnisotropicPF.java index dc27701..7c382c6 100644 --- a/src/main/java/pulse/problem/schemes/rte/dom/LinearAnisotropicPF.java +++ b/src/main/java/pulse/problem/schemes/rte/dom/LinearAnisotropicPF.java @@ -1,6 +1,7 @@ package pulse.problem.schemes.rte.dom; import pulse.problem.statements.ParticipatingMedium; +import pulse.problem.statements.model.ThermoOpticalProperties; /** * The linear-anisotropic scattering phase function. @@ -10,13 +11,13 @@ public class LinearAnisotropicPF extends PhaseFunction { private double g; - public LinearAnisotropicPF(ParticipatingMedium medium, Discretisation intensities) { - super(medium, intensities); + public LinearAnisotropicPF(ThermoOpticalProperties top, Discretisation intensities) { + super(top, intensities); } @Override - public void init(ParticipatingMedium medium) { - super.init(medium); + public void init(ThermoOpticalProperties top) { + super.init(top); g = 3.0 * getAnisotropyFactor(); } @@ -28,8 +29,7 @@ public double partialSum(final int i, final int j, final int n1, final int n2Exc @Override public double function(final int i, final int k) { - final var ordinates = getDiscreteIntensities().getOrdinates(); - return 1.0 + g * ordinates.getNode(i) * ordinates.getNode(k); + return 1.0 + g * cosineTheta(i,k); } } diff --git a/src/main/java/pulse/problem/schemes/rte/dom/ODEIntegrator.java b/src/main/java/pulse/problem/schemes/rte/dom/ODEIntegrator.java index 4c6680a..1785a2a 100644 --- a/src/main/java/pulse/problem/schemes/rte/dom/ODEIntegrator.java +++ b/src/main/java/pulse/problem/schemes/rte/dom/ODEIntegrator.java @@ -2,7 +2,7 @@ import pulse.problem.schemes.rte.BlackbodySpectrum; import pulse.problem.schemes.rte.RTECalculationStatus; -import pulse.problem.statements.ParticipatingMedium; +import pulse.problem.statements.NonlinearProblem; import pulse.problem.statements.model.ThermoOpticalProperties; import pulse.util.PropertyHolder; import pulse.util.Reflexive; @@ -19,11 +19,14 @@ public ODEIntegrator(Discretisation intensities) { public abstract RTECalculationStatus integrate(); - protected void init(ParticipatingMedium problem) { - discretisation.setEmissivity((double) problem.getProperties().getEmissivity().getValue()); - var properties = (ThermoOpticalProperties) problem.getProperties(); + protected void init(NonlinearProblem problem) { + extract((ThermoOpticalProperties) problem.getProperties()); + setEmissionFunction( new BlackbodySpectrum(problem) ); + } + + protected void extract(ThermoOpticalProperties properties) { + discretisation.setEmissivity((double) properties.getEmissivity().getValue()); discretisation.setGrid(new StretchedGrid((double) properties.getOpticalThickness().getValue())); - setEmissionFunction(new BlackbodySpectrum(problem)); } protected void treatZeroIndex() { @@ -100,11 +103,11 @@ public double emission(double t) { return (1.0 - 2.0 * pf.getHalfAlbedo()) * spectrum.radianceAt(t); } - public PhaseFunction getPhaseFunction() { + public final PhaseFunction getPhaseFunction() { return pf; } - protected void setPhaseFunction(PhaseFunction pf) { + protected final void setPhaseFunction(PhaseFunction pf) { this.pf = pf; } @@ -118,20 +121,20 @@ public String toString() { return getClass().getSimpleName(); } - public Discretisation getDiscretisation() { + public final Discretisation getDiscretisation() { return discretisation; } - public void setDiscretisation(Discretisation discretisation) { + public final void setDiscretisation(Discretisation discretisation) { this.discretisation = discretisation; discretisation.setParent(this); } - public BlackbodySpectrum getEmissionFunction() { + public final BlackbodySpectrum getEmissionFunction() { return spectrum; } - public void setEmissionFunction(BlackbodySpectrum emissionFunction) { + public final void setEmissionFunction(BlackbodySpectrum emissionFunction) { this.spectrum = emissionFunction; } diff --git a/src/main/java/pulse/problem/schemes/rte/dom/OrdinateSet.java b/src/main/java/pulse/problem/schemes/rte/dom/OrdinateSet.java index f6c922b..68662f6 100644 --- a/src/main/java/pulse/problem/schemes/rte/dom/OrdinateSet.java +++ b/src/main/java/pulse/problem/schemes/rte/dom/OrdinateSet.java @@ -59,7 +59,7 @@ public String printOrdinateSet() { } - public boolean hasZeroNode() { + public final boolean hasZeroNode() { return Arrays.stream(mu).anyMatch(Double.valueOf(0.0)::equals); } @@ -75,7 +75,7 @@ public String getName() { return name; } - public void setName(String name) { + public final void setName(String name) { this.name = name; } diff --git a/src/main/java/pulse/problem/schemes/rte/dom/PhaseFunction.java b/src/main/java/pulse/problem/schemes/rte/dom/PhaseFunction.java index 0892aab..e8123f8 100644 --- a/src/main/java/pulse/problem/schemes/rte/dom/PhaseFunction.java +++ b/src/main/java/pulse/problem/schemes/rte/dom/PhaseFunction.java @@ -10,9 +10,22 @@ public abstract class PhaseFunction implements Reflexive { private double anisotropy; private double halfAlbedo; - public PhaseFunction(ParticipatingMedium medium, Discretisation intensities) { + public PhaseFunction(ThermoOpticalProperties top, Discretisation intensities) { this.intensities = intensities; - init(medium); + init(top); + } + + /** + * Calculates the cosine of the scattering angle as the product + * of the two discrete cosine nodes. + * @param i + * @param k + * @return + */ + + public final double cosineTheta(int i, int k) { + final var ordinates = getDiscreteIntensities().getOrdinates(); + return ordinates.getNode(k) * ordinates.getNode(i); } public double fullSum(int i, int j) { @@ -55,8 +68,7 @@ protected Discretisation getDiscreteIntensities() { return intensities; } - public void init(ParticipatingMedium problem) { - var properties = (ThermoOpticalProperties) problem.getProperties(); + public void init(ThermoOpticalProperties properties) { this.anisotropy = (double) properties.getScatteringAnisostropy().getValue(); this.halfAlbedo = 0.5 * (double) properties.getScatteringAlbedo().getValue(); } diff --git a/src/main/java/pulse/problem/schemes/rte/exact/NonscatteringAnalyticalDerivatives.java b/src/main/java/pulse/problem/schemes/rte/exact/NonscatteringAnalyticalDerivatives.java index aaa2c3d..bbcf0d3 100644 --- a/src/main/java/pulse/problem/schemes/rte/exact/NonscatteringAnalyticalDerivatives.java +++ b/src/main/java/pulse/problem/schemes/rte/exact/NonscatteringAnalyticalDerivatives.java @@ -30,8 +30,9 @@ public NonscatteringAnalyticalDerivatives(ParticipatingMedium problem, Grid grid /** * Evaluates fluxes and their derivatives using analytical formulae and the - * selected numerical quadrature. Usually works best with the - * {@code ChandrasekharsQuadrature}. + * selected numerical quadrature.Usually works best with the + {@code ChandrasekharsQuadrature} + * @return */ @Override public RTECalculationStatus compute(double U[]) { diff --git a/src/main/java/pulse/problem/schemes/rte/exact/NonscatteringRadiativeTransfer.java b/src/main/java/pulse/problem/schemes/rte/exact/NonscatteringRadiativeTransfer.java index 7e72568..a4583b5 100644 --- a/src/main/java/pulse/problem/schemes/rte/exact/NonscatteringRadiativeTransfer.java +++ b/src/main/java/pulse/problem/schemes/rte/exact/NonscatteringRadiativeTransfer.java @@ -17,7 +17,7 @@ public abstract class NonscatteringRadiativeTransfer extends RadiativeTransferSolver { - private static FunctionWithInterpolation ei3 = ExponentialIntegrals.get(3); + private static final FunctionWithInterpolation ei3 = ExponentialIntegrals.get(3); private double emissivity; @@ -27,7 +27,8 @@ public abstract class NonscatteringRadiativeTransfer extends RadiativeTransferSo private double radiosityFront; private double radiosityRear; - private InstanceDescriptor instanceDescriptor = new InstanceDescriptor( + private InstanceDescriptor instanceDescriptor + = new InstanceDescriptor( "Quadrature Selector", CompositionProduct.class); protected NonscatteringRadiativeTransfer(ParticipatingMedium problem, Grid grid) { @@ -48,7 +49,8 @@ public void init(ParticipatingMedium p, Grid grid) { /** * The superclass method will update the interpolation that the blackbody * spectrum uses to evaluate the temperature profile and calculate the - * radiosities. A {@code NORMAL} status is always returned. + * radiosities.A {@code NORMAL}status is always returned. + * @param array */ @Override public RTECalculationStatus compute(double[] array) { @@ -180,7 +182,7 @@ public double getRadiosityRear() { */ private void radiosities() { final double doubleReflectivity = 2.0 * (1.0 - emissivity); - ; + final double b = b(doubleReflectivity); final double sq = 1.0 - b * b; final double a1 = a1(doubleReflectivity); diff --git a/src/main/java/pulse/problem/schemes/solvers/ADILayeredSolver.java b/src/main/java/pulse/problem/schemes/solvers/ADILayeredSolver.java deleted file mode 100644 index 1f7a589..0000000 --- a/src/main/java/pulse/problem/schemes/solvers/ADILayeredSolver.java +++ /dev/null @@ -1,89 +0,0 @@ -package pulse.problem.schemes.solvers; - -import static pulse.properties.NumericProperties.def; -import static pulse.properties.NumericPropertyKeyword.SHELL_GRID_DENSITY; - -import java.util.HashMap; - -import pulse.problem.schemes.ADIScheme; -import pulse.problem.schemes.DifferenceScheme; -import pulse.problem.schemes.LayeredGrid2D; -import pulse.problem.schemes.Partition; -import pulse.problem.schemes.Partition.Location; -import pulse.problem.statements.CoreShellProblem; -import pulse.problem.statements.Problem; -import pulse.properties.NumericProperty; - -public class ADILayeredSolver extends ADIScheme implements Solver { - - public ADILayeredSolver() { - super(); - initGrid(getGrid().getGridDensity(), def(SHELL_GRID_DENSITY), getGrid().getTimeFactor()); - } - - public ADILayeredSolver(NumericProperty nCore, NumericProperty nShell, NumericProperty timeFactor) { - initGrid(nCore, nShell, timeFactor); - } - - public ADILayeredSolver(NumericProperty nCore, NumericProperty nShell, NumericProperty timeFactor, - NumericProperty timeLimit) { - setTimeLimit(timeLimit); - initGrid(nCore, nShell, timeFactor); - } - - public void initGrid(NumericProperty nCore, NumericProperty nShell, NumericProperty timeFactor) { - var map = new HashMap(); - map.put(Location.CORE_X, new Partition((int) nCore.getValue(), 1.0, 0.5)); - map.put(Location.CORE_Y, new Partition((int) nCore.getValue(), 1.0, 0.0)); - map.put(Location.FRONT_Y, new Partition((int) nShell.getValue(), 1.0, 0.0)); - map.put(Location.REAR_Y, new Partition((int) nShell.getValue(), 1.0, 0.0)); - map.put(Location.SIDE_X, new Partition((int) nShell.getValue(), 1.0, 0.0)); - map.put(Location.SIDE_Y, new Partition((int) nShell.getValue(), 1.0, 0.0)); - setGrid(new LayeredGrid2D(map, timeFactor)); - getGrid().setTimeFactor(timeFactor); - } - - private void prepareGrid(CoreShellProblem problem) { - var layeredGrid = (LayeredGrid2D) getGrid(); // TODO - layeredGrid.getPartition(Location.FRONT_Y).setGridMultiplier(problem.axialFactor()); - layeredGrid.getPartition(Location.REAR_Y).setGridMultiplier(problem.axialFactor()); - layeredGrid.getPartition(Location.SIDE_X).setGridMultiplier(problem.radialFactor()); - } - - @Override - public void solve(CoreShellProblem problem) { - prepareGrid(problem); - - // TODO - } - - @Override - public DifferenceScheme copy() { - // TODO Auto-generated method stub - return null; - } - - @Override - public Class domain() { - return CoreShellProblem.class; - } - - @Override - public double signal() { - // TODO Auto-generated method stub - return 0; - } - - @Override - public void timeStep(int m) { - // TODO Auto-generated method stub - - } - - @Override - public void finaliseStep() { - // TODO Auto-generated method stub - - } - -} diff --git a/src/main/java/pulse/problem/schemes/solvers/ADILinearisedSolver.java b/src/main/java/pulse/problem/schemes/solvers/ADILinearisedSolver.java index 7ce79f9..d2472df 100644 --- a/src/main/java/pulse/problem/schemes/solvers/ADILinearisedSolver.java +++ b/src/main/java/pulse/problem/schemes/solvers/ADILinearisedSolver.java @@ -77,15 +77,28 @@ public ADILinearisedSolver(NumericProperty N, NumericProperty timeFactor) { public ADILinearisedSolver(NumericProperty N, NumericProperty timeFactor, NumericProperty timeLimit) { super(N, timeFactor, timeLimit); } + + @Override + public void clearArrays() { + N = (int) getGrid().getGridDensity().getValue(); + U1 = new double[N + 1][N + 1]; + U2 = new double[N + 1][N + 1]; + + U1_E = new double[N + 3][N + 3]; + U2_E = new double[N + 3][N + 3]; + + a1 = new double[N + 1]; + b1 = new double[N + 1]; + c1 = new double[N + 1]; + } - private void prepare(ClassicalProblem2D problem) { + @Override + public void prepare(Problem problem) throws SolverException { super.prepare(problem); var grid = getGrid(); tridiagonal = new TridiagonalMatrixAlgorithm(grid); - N = (int) grid.getGridDensity().getValue(); - hx = grid.getXStep(); hy = ((Grid2D) getGrid()).getYStep(); HX2 = hx * hx; @@ -104,15 +117,6 @@ private void prepare(ClassicalProblem2D problem) { l = (double) properties.getSampleThickness().getValue(); // end - U1 = new double[N + 1][N + 1]; - U2 = new double[N + 1][N + 1]; - - U1_E = new double[N + 3][N + 3]; - U2_E = new double[N + 3][N + 3]; - - a1 = new double[N + 1]; - b1 = new double[N + 1]; - c1 = new double[N + 1]; // a[i]*u[i-1] - b[i]*u[i] + c[i]*u[i+1] = F[i] lastIndex = (int) (fovOuter / d / hx); @@ -174,8 +178,8 @@ public DifferenceScheme copy() { } @Override - public Class domain() { - return ClassicalProblem2D.class; + public Class[] domain() { + return new Class[]{ClassicalProblem2D.class}; } @Override @@ -197,7 +201,6 @@ private void extendedU1(final int m) { for (int i = 0; i <= N; i++) { System.arraycopy(U1[i], 0, U1_E[i + 1], 1, N + 1); - U1_E[i + 1][0] = U1[i][1] + 2.0 * hy * pulse(m, i) - E_C_U1 * U1[i][0]; U1_E[i + 1][N + 2] = U1[i][N - 1] - E_C_U1 * U1[i][N]; } diff --git a/src/main/java/pulse/problem/schemes/solvers/ExplicitCoupledSolver.java b/src/main/java/pulse/problem/schemes/solvers/ExplicitCoupledSolver.java index 40927d7..9da5e21 100644 --- a/src/main/java/pulse/problem/schemes/solvers/ExplicitCoupledSolver.java +++ b/src/main/java/pulse/problem/schemes/solvers/ExplicitCoupledSolver.java @@ -1,62 +1,57 @@ package pulse.problem.schemes.solvers; -import static pulse.properties.NumericProperties.def; import static pulse.properties.NumericProperties.derive; import static pulse.properties.NumericPropertyKeyword.GRID_DENSITY; -import static pulse.properties.NumericPropertyKeyword.NONLINEAR_PRECISION; import static pulse.properties.NumericPropertyKeyword.TAU_FACTOR; import static pulse.ui.Messages.getString; -import pulse.problem.schemes.DifferenceScheme; import pulse.problem.schemes.ExplicitScheme; -import pulse.problem.schemes.FixedPointIterations; import pulse.problem.schemes.RadiativeTransferCoupling; import pulse.problem.schemes.rte.Fluxes; import pulse.problem.schemes.rte.RTECalculationStatus; -import pulse.problem.schemes.rte.RadiativeTransferSolver; import pulse.problem.statements.ParticipatingMedium; import pulse.problem.statements.Problem; import pulse.problem.statements.model.ThermoOpticalProperties; import pulse.properties.NumericProperty; -public class ExplicitCoupledSolver extends ExplicitScheme implements Solver, FixedPointIterations { +public abstract class ExplicitCoupledSolver extends ExplicitScheme + implements Solver { private RadiativeTransferCoupling coupling; - private RadiativeTransferSolver rte; private RTECalculationStatus status; private Fluxes fluxes; private double hx; private double a; - private double nonlinearPrecision; - private double pls; private int N; private double HX_NP; private double prefactor; + private double zeta; + private boolean autoUpdateFluxes = true; //should be false for nonlinear solvers + public ExplicitCoupledSolver() { this(derive(GRID_DENSITY, 80), derive(TAU_FACTOR, 0.5)); } public ExplicitCoupledSolver(NumericProperty N, NumericProperty timeFactor) { super(N, timeFactor); - nonlinearPrecision = (double) def(NONLINEAR_PRECISION).getValue(); setCoupling(new RadiativeTransferCoupling()); status = RTECalculationStatus.NORMAL; } - private void prepare(ParticipatingMedium problem) throws SolverException { + @Override + public void prepare(Problem problem) throws SolverException { super.prepare(problem); var grid = getGrid(); - coupling.init(problem, grid); - rte = coupling.getRadiativeTransferEquation(); + coupling.init((ParticipatingMedium)problem, grid); fluxes = coupling.getRadiativeTransferEquation().getFluxes(); setCalculationStatus(fluxes.checkArrays()); - + N = (int) grid.getGridDensity().getValue(); hx = grid.getXStep(); @@ -71,46 +66,52 @@ private void prepare(ParticipatingMedium problem) throws SolverException { HX_NP = hx / Np; prefactor = tau * opticalThickness / Np; + + zeta = (double) ((ParticipatingMedium)problem).getGeometricFactor().getValue(); } @Override - public void solve(ParticipatingMedium problem) throws SolverException { - this.prepare(problem); - setCalculationStatus(coupling.getRadiativeTransferEquation().compute(getPreviousSolution())); - runTimeSequence(problem); + public void timeStep(int m) throws SolverException { + explicitSolution(); } @Override - public boolean normalOperation() { - return super.normalOperation() && (status == RTECalculationStatus.NORMAL); + public void finaliseStep() throws SolverException { + super.finaliseStep(); + if (autoUpdateFluxes) { + var rte = this.getCoupling().getRadiativeTransferEquation(); + setCalculationStatus(rte.compute(getCurrentSolution())); + } + coupling.getRadiativeTransferEquation().getFluxes().store(); } @Override - public void timeStep(int m) throws SolverException { - pls = pulse(m); - doIterations(getCurrentSolution(), nonlinearPrecision, m); + public void solve(ParticipatingMedium problem) throws SolverException { + prepare(problem); + runTimeSequence(problem); } @Override - public void iteration(final int m) { + public void explicitSolution() { /* * Uses the heat equation explicitly to calculate the grid-function everywhere * except the boundaries */ - explicitSolution(); + super.explicitSolution(); var V = getCurrentSolution(); + double pls = getCurrentPulseValue(); + // Front face - V[0] = (V[1] + hx * pls - HX_NP * fluxes.getFlux(0)) * a; + V[0] = (V[1] + hx * zeta * pls - HX_NP * fluxes.getFlux(0)) * a; // Rear face - V[N] = (V[N - 1] + HX_NP * fluxes.getFlux(N)) * a; + V[N] = (V[N - 1] + hx * (1.0 - zeta) * pls + HX_NP * fluxes.getFlux(N)) * a; } @Override - public void finaliseIteration(double[] V) throws SolverException { - FixedPointIterations.super.finaliseIteration(V); - setCalculationStatus(rte.compute(V)); + public boolean normalOperation() { + return super.normalOperation() && (status == RTECalculationStatus.NORMAL); } @Override @@ -118,12 +119,6 @@ public double phi(final int i) { return prefactor * fluxes.fluxDerivative(i); } - @Override - public void finaliseStep() throws SolverException { - super.finaliseStep(); - coupling.getRadiativeTransferEquation().getFluxes().store(); - } - public RadiativeTransferCoupling getCoupling() { return coupling; } @@ -142,22 +137,25 @@ public final void setCoupling(RadiativeTransferCoupling coupling) { public String toString() { return getString("ExplicitScheme.4"); } - + @Override - public DifferenceScheme copy() { - var grid = getGrid(); - return new ExplicitCoupledSolver(grid.getGridDensity(), grid.getTimeFactor()); + public Class[] domain() { + return new Class[]{ParticipatingMedium.class}; } - @Override - public Class domain() { - return ParticipatingMedium.class; - } - public void setCalculationStatus(RTECalculationStatus calculationStatus) throws SolverException { this.status = calculationStatus; - if(status != RTECalculationStatus.NORMAL) + if (status != RTECalculationStatus.NORMAL) { throw new SolverException(status.toString()); + } + } + + public final boolean isAutoUpdateFluxes() { + return this.autoUpdateFluxes; + } + + public final void setAutoUpdateFluxes(boolean auto) { + this.autoUpdateFluxes = auto; } -} +} \ No newline at end of file diff --git a/src/main/java/pulse/problem/schemes/solvers/ExplicitCoupledSolverNL.java b/src/main/java/pulse/problem/schemes/solvers/ExplicitCoupledSolverNL.java new file mode 100644 index 0000000..07247ef --- /dev/null +++ b/src/main/java/pulse/problem/schemes/solvers/ExplicitCoupledSolverNL.java @@ -0,0 +1,95 @@ +/* + * Copyright 2022 Artem Lunev . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package pulse.problem.schemes.solvers; + +import pulse.problem.schemes.DifferenceScheme; +import pulse.problem.schemes.FixedPointIterations; +import pulse.problem.statements.ParticipatingMedium; +import pulse.problem.statements.Problem; +import static pulse.properties.NumericProperties.def; +import static pulse.properties.NumericProperties.derive; +import pulse.properties.NumericProperty; +import pulse.properties.NumericPropertyKeyword; +import static pulse.properties.NumericPropertyKeyword.NONLINEAR_PRECISION; +import pulse.ui.Messages; + +/** + * + * @author Artem Lunev + */ +public class ExplicitCoupledSolverNL extends ExplicitCoupledSolver + implements FixedPointIterations +{ + + private double nonlinearPrecision; + + public ExplicitCoupledSolverNL() { + super(); + nonlinearPrecision = (double) def(NONLINEAR_PRECISION).getValue(); + setAutoUpdateFluxes(false); + } + + public ExplicitCoupledSolverNL(NumericProperty N, NumericProperty timeFactor) { + super(N, timeFactor); + nonlinearPrecision = (double) def(NONLINEAR_PRECISION).getValue(); + setAutoUpdateFluxes(false); + } + + @Override + public void timeStep(int m) throws SolverException { + doIterations(getCurrentSolution(), nonlinearPrecision, m); + } + + @Override + public void iteration(final int m) { + explicitSolution(); + } + + @Override + public void finaliseIteration(double[] V) throws SolverException { + FixedPointIterations.super.finaliseIteration(V); + setCalculationStatus(this.getCoupling().getRadiativeTransferEquation().compute(V)); + } + + @Override + public DifferenceScheme copy() { + var grid = getGrid(); + return new ExplicitCoupledSolverNL(grid.getGridDensity(), grid.getTimeFactor()); + } + + public final NumericProperty getNonlinearPrecision() { + return derive(NONLINEAR_PRECISION, nonlinearPrecision); + } + + public final void setNonlinearPrecision(NumericProperty nonlinearPrecision) { + this.nonlinearPrecision = (double) nonlinearPrecision.getValue(); + } + + @Override + public void set(NumericPropertyKeyword type, NumericProperty property) { + if (type == NONLINEAR_PRECISION) { + setNonlinearPrecision(property); + } else { + super.set(type, property); + } + } + + @Override + public String toString() { + return Messages.getString("ExplicitScheme.5"); + } + +} \ No newline at end of file diff --git a/src/main/java/pulse/problem/schemes/solvers/ExplicitLinearisedSolver.java b/src/main/java/pulse/problem/schemes/solvers/ExplicitLinearisedSolver.java index 5d3a6f9..7b7f0ae 100644 --- a/src/main/java/pulse/problem/schemes/solvers/ExplicitLinearisedSolver.java +++ b/src/main/java/pulse/problem/schemes/solvers/ExplicitLinearisedSolver.java @@ -64,7 +64,7 @@ public ExplicitLinearisedSolver(NumericProperty N, NumericProperty timeFactor, N } @Override - public void prepare(Problem problem) { + public void prepare(Problem problem) throws SolverException { super.prepare(problem); zeta = (double) ( (ClassicalProblem) problem).getGeometricFactor().getValue(); @@ -86,7 +86,7 @@ public void solve(ClassicalProblem problem) throws SolverException { public void timeStep(int m) { explicitSolution(); var V = getCurrentSolution(); - double pulse = pulse(m); + double pulse = getCurrentPulseValue(); setSolutionAt(0, (V[1] + hx * zeta * pulse) * a); setSolutionAt(N, (V[N - 1] + hx * (1.0 - zeta) * pulse) * a); } @@ -98,8 +98,8 @@ public DifferenceScheme copy() { } @Override - public Class domain() { - return ClassicalProblem.class; + public Class[] domain() { + return new Class[]{ClassicalProblem.class}; } } diff --git a/src/main/java/pulse/problem/schemes/solvers/ExplicitNonlinearSolver.java b/src/main/java/pulse/problem/schemes/solvers/ExplicitNonlinearSolver.java index 18dcb95..ed8e7fa 100644 --- a/src/main/java/pulse/problem/schemes/solvers/ExplicitNonlinearSolver.java +++ b/src/main/java/pulse/problem/schemes/solvers/ExplicitNonlinearSolver.java @@ -21,7 +21,6 @@ public class ExplicitNonlinearSolver extends ExplicitScheme implements Solver domain() { - return NonlinearProblem.class; + public Class[] domain() { + return new Class[]{NonlinearProblem.class}; } public NumericProperty getNonlinearPrecision() { diff --git a/src/main/java/pulse/problem/schemes/solvers/ExplicitTranslucentSolver.java b/src/main/java/pulse/problem/schemes/solvers/ExplicitTranslucentSolver.java index d22a20d..56e747c 100644 --- a/src/main/java/pulse/problem/schemes/solvers/ExplicitTranslucentSolver.java +++ b/src/main/java/pulse/problem/schemes/solvers/ExplicitTranslucentSolver.java @@ -18,10 +18,6 @@ public class ExplicitTranslucentSolver extends ExplicitScheme implements Solver< private double tau; private double a; - private double pls; - - private final static double EPS = 1e-7; // a small value ensuring numeric stability - private AbsorptionModel model; public ExplicitTranslucentSolver() { @@ -32,11 +28,12 @@ public ExplicitTranslucentSolver(NumericProperty N, NumericProperty timeFactor, super(N, timeFactor, timeLimit); } - private void prepare(PenetrationProblem problem) { + @Override + public void prepare(Problem problem) throws SolverException { super.prepare(problem); var grid = getGrid(); - model = problem.getAbsorptionModel(); + model = ((PenetrationProblem)problem).getAbsorptionModel(); N = (int) grid.getGridDensity().getValue(); hx = grid.getXStep(); @@ -54,8 +51,6 @@ public void solve(PenetrationProblem problem) throws SolverException { @Override public void timeStep(final int m) { - pls = this.pulse(m); - /* * Uses the heat equation explicitly to calculate the grid-function everywhere * except the boundaries @@ -73,7 +68,7 @@ public void timeStep(final int m) { @Override public double phi(final int i) { - return tau * pls * model.absorption(LASER, (i - EPS) * hx); + return tau * getCurrentPulseValue() * model.absorption(LASER, i * hx); } @Override @@ -93,8 +88,8 @@ public String toString() { } @Override - public Class domain() { - return PenetrationProblem.class; + public Class[] domain() { + return new Class[]{PenetrationProblem.class}; } @Override diff --git a/src/main/java/pulse/problem/schemes/solvers/ImplicitCoupledSolver.java b/src/main/java/pulse/problem/schemes/solvers/ImplicitCoupledSolver.java index 5bd30cc..1301604 100644 --- a/src/main/java/pulse/problem/schemes/solvers/ImplicitCoupledSolver.java +++ b/src/main/java/pulse/problem/schemes/solvers/ImplicitCoupledSolver.java @@ -6,16 +6,18 @@ import static pulse.ui.Messages.getString; import pulse.problem.schemes.CoupledImplicitScheme; -import pulse.problem.schemes.DifferenceScheme; import pulse.problem.schemes.TridiagonalMatrixAlgorithm; import pulse.problem.schemes.rte.Fluxes; import pulse.problem.schemes.rte.RTECalculationStatus; import pulse.problem.schemes.rte.RadiativeTransferSolver; +import pulse.problem.statements.ClassicalProblem; import pulse.problem.statements.ParticipatingMedium; +import pulse.problem.statements.Problem; import pulse.problem.statements.model.ThermoOpticalProperties; import pulse.properties.NumericProperty; -public class ImplicitCoupledSolver extends CoupledImplicitScheme implements Solver { +public abstract class ImplicitCoupledSolver extends CoupledImplicitScheme + implements Solver { private RadiativeTransferSolver rte; private Fluxes fluxes; @@ -30,6 +32,7 @@ public class ImplicitCoupledSolver extends CoupledImplicitScheme implements Solv private double HX_NP; private double v1; + private double zeta; public ImplicitCoupledSolver() { super(derive(GRID_DENSITY, 20), derive(TAU_FACTOR, 0.66667)); @@ -39,13 +42,14 @@ public ImplicitCoupledSolver(NumericProperty gridDensity, NumericProperty timeFa super(gridDensity, timeFactor, timeLimit); } - private void prepare(ParticipatingMedium problem) throws SolverException { + @Override + public void prepare(Problem problem) throws SolverException { super.prepare(problem); final var grid = getGrid(); var coupling = getCoupling(); - coupling.init(problem, grid); + coupling.init( (ParticipatingMedium) problem, grid); rte = coupling.getRadiativeTransferEquation(); N = (int) getGrid().getGridDensity().getValue(); @@ -85,7 +89,8 @@ public double phi(int i) { tridiagonal.evaluateAlpha(); setTridiagonalMatrixAlgorithm(tridiagonal); - + + zeta = (double) ((ClassicalProblem)problem).getGeometricFactor().getValue(); } @Override @@ -108,30 +113,25 @@ public String toString() { } @Override - public DifferenceScheme copy() { - var grid = getGrid(); - return new ImplicitCoupledSolver(grid.getGridDensity(), grid.getTimeFactor(), getTimeLimit()); - } - - @Override - public double evalRightBoundary(final int m, final double alphaN, final double betaN) { + public double evalRightBoundary(final double alphaN, final double betaN) { /* * UNCOMMENT FOR A SIMPLIFIED CALCULATION * return (betaN + HX2_2TAU * getPreviousSolution()[N] + HX_2NP * (fluxes.getFlux(N - 1) + * fluxes.getFlux(N))) / (v1 - alphaN); */ - return (betaN + HX2_2TAU * getPreviousSolution()[N] + HX_NP * fluxes.getFlux(N) + return (betaN + HX2_2TAU * getPreviousSolution()[N] + hx * (1.0 - zeta) * getCurrentPulseValue() + + HX_NP * fluxes.getFlux(N) + HX2TAU0_2NP * fluxes.fluxDerivativeRear()) / (v1 - alphaN); } @Override - public double firstBeta(final int m) { + public double firstBeta() { /* * UNCOMMENT FOR A SIMPLIFIED CALCULATION * return (HX2_2TAU * getPreviousSolution()[0] + hx * getCurrentPulseValue() - HX_2NP * * (fluxes.getFlux(0) + fluxes.getFlux(1))) * alpha1; */ - return (HX2_2TAU * getPreviousSolution()[0] + hx * getCurrentPulseValue() + return (HX2_2TAU * getPreviousSolution()[0] + hx * zeta * getCurrentPulseValue() + HX2TAU0_2NP * fluxes.fluxDerivativeFront() - HX_NP * fluxes.getFlux(0)) * alpha1; } diff --git a/src/main/java/pulse/problem/schemes/solvers/ImplicitCoupledSolverNL.java b/src/main/java/pulse/problem/schemes/solvers/ImplicitCoupledSolverNL.java new file mode 100644 index 0000000..42c9c03 --- /dev/null +++ b/src/main/java/pulse/problem/schemes/solvers/ImplicitCoupledSolverNL.java @@ -0,0 +1,94 @@ +/* + * Copyright 2022 Artem Lunev . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package pulse.problem.schemes.solvers; + +import pulse.problem.schemes.DifferenceScheme; +import pulse.problem.schemes.FixedPointIterations; +import pulse.problem.statements.ParticipatingMedium; +import pulse.problem.statements.Problem; +import static pulse.properties.NumericProperties.def; +import static pulse.properties.NumericProperties.derive; +import pulse.properties.NumericProperty; +import pulse.properties.NumericPropertyKeyword; +import static pulse.properties.NumericPropertyKeyword.NONLINEAR_PRECISION; +import pulse.ui.Messages; + +/** + * + * @author Artem Lunev + */ +public class ImplicitCoupledSolverNL extends ImplicitCoupledSolver implements FixedPointIterations { + + private double nonlinearPrecision; + + public ImplicitCoupledSolverNL() { + super(); + nonlinearPrecision = (double) def(NONLINEAR_PRECISION).getValue(); + setAutoUpdateFluxes(false); + } + + public ImplicitCoupledSolverNL(NumericProperty N, NumericProperty timeFactor, NumericProperty timeLimit) { + super(N, timeFactor, timeLimit); + nonlinearPrecision = (double) def(NONLINEAR_PRECISION).getValue(); + setAutoUpdateFluxes(false); + } + + @Override + public void timeStep(final int m) throws SolverException { + doIterations(getCurrentSolution(), nonlinearPrecision, m); + } + + @Override + public void iteration(final int m) throws SolverException { + super.timeStep(m); + } + + @Override + public void finaliseIteration(double[] V) throws SolverException { + FixedPointIterations.super.finaliseIteration(V); + var rte = this.getCoupling().getRadiativeTransferEquation(); + setCalculationStatus(rte.compute(V)); + } + + public final NumericProperty getNonlinearPrecision() { + return derive(NONLINEAR_PRECISION, nonlinearPrecision); + } + + public final void setNonlinearPrecision(NumericProperty nonlinearPrecision) { + this.nonlinearPrecision = (double) nonlinearPrecision.getValue(); + } + + @Override + public void set(NumericPropertyKeyword type, NumericProperty property) { + if (type == NONLINEAR_PRECISION) { + setNonlinearPrecision(property); + } else { + super.set(type, property); + } + } + + @Override + public DifferenceScheme copy() { + var grid = getGrid(); + return new ImplicitCoupledSolverNL(grid.getGridDensity(), grid.getTimeFactor(), getTimeLimit()); + } + + @Override + public String toString() { + return Messages.getString("ImplicitScheme.5"); + } + +} \ No newline at end of file diff --git a/src/main/java/pulse/problem/schemes/solvers/ImplicitDiathermicSolver.java b/src/main/java/pulse/problem/schemes/solvers/ImplicitDiathermicSolver.java index 3e62848..3d36e22 100644 --- a/src/main/java/pulse/problem/schemes/solvers/ImplicitDiathermicSolver.java +++ b/src/main/java/pulse/problem/schemes/solvers/ImplicitDiathermicSolver.java @@ -3,6 +3,7 @@ import pulse.problem.schemes.BlockMatrixAlgorithm; import pulse.problem.schemes.DifferenceScheme; import pulse.problem.schemes.ImplicitScheme; +import pulse.problem.statements.ClassicalProblem; import pulse.problem.statements.DiathermicMedium; import pulse.problem.statements.Problem; import pulse.problem.statements.model.DiathermicProperties; @@ -16,8 +17,8 @@ public class ImplicitDiathermicSolver extends ImplicitScheme implements Solver domain() { - return DiathermicMedium.class; + public Class[] domain() { + return new Class[]{DiathermicMedium.class}; } } diff --git a/src/main/java/pulse/problem/schemes/solvers/ImplicitLinearisedSolver.java b/src/main/java/pulse/problem/schemes/solvers/ImplicitLinearisedSolver.java index 6b3b0f6..5020d2c 100644 --- a/src/main/java/pulse/problem/schemes/solvers/ImplicitLinearisedSolver.java +++ b/src/main/java/pulse/problem/schemes/solvers/ImplicitLinearisedSolver.java @@ -68,7 +68,7 @@ public ImplicitLinearisedSolver(NumericProperty N, NumericProperty timeFactor, N } @Override - public void prepare(Problem problem) { + public void prepare(Problem problem) throws SolverException { super.prepare(problem); var grid = getGrid(); @@ -76,6 +76,7 @@ public void prepare(Problem problem) { N = (int) grid.getGridDensity().getValue(); final double hx = grid.getXStep(); tau = grid.getTimeStep(); + zeta = (double) ((ClassicalProblem)problem).getGeometricFactor().getValue(); final double Bi1 = (double) problem.getProperties().getHeatLoss().getValue(); @@ -105,16 +106,14 @@ public void solve(ClassicalProblem problem) throws SolverException { } @Override - public double firstBeta(final int m) { - final double pls = super.pulse(m); - return (HH * getPreviousSolution()[0] + _2HTAU * pls * zeta) / (2. * Bi1HTAU + 2. * tau + HH); + public double firstBeta() { + return (HH * getPreviousSolution()[0] + _2HTAU * getCurrentPulseValue() * zeta) / (2. * Bi1HTAU + 2. * tau + HH); } @Override - public double evalRightBoundary(final int m, final double alphaN, final double betaN) { - final double pls = super.pulse(m); + public double evalRightBoundary(final double alphaN, final double betaN) { return (HH * getPreviousSolution()[N] + 2. * tau * betaN - + _2HTAU * (1.0 - zeta) * pls //additional term due to stray light + + _2HTAU * (1.0 - zeta) * getCurrentPulseValue() //additional term due to stray light ) / (2 * Bi1HTAU + HH - 2. * tau * (alphaN - 1)); } @@ -125,8 +124,8 @@ public DifferenceScheme copy() { } @Override - public Class domain() { - return ClassicalProblem.class; + public Class[] domain() { + return new Class[]{ClassicalProblem.class}; } -} +} \ No newline at end of file diff --git a/src/main/java/pulse/problem/schemes/solvers/ImplicitNonlinearSolver.java b/src/main/java/pulse/problem/schemes/solvers/ImplicitNonlinearSolver.java index f61fa05..0cc048a 100644 --- a/src/main/java/pulse/problem/schemes/solvers/ImplicitNonlinearSolver.java +++ b/src/main/java/pulse/problem/schemes/solvers/ImplicitNonlinearSolver.java @@ -3,9 +3,6 @@ import static pulse.math.MathUtils.fastPowLoop; import static pulse.properties.NumericProperties.def; import static pulse.properties.NumericProperties.derive; -import static pulse.properties.NumericPropertyKeyword.NONLINEAR_PRECISION; - -import java.util.List; import java.util.Set; import pulse.problem.schemes.DifferenceScheme; @@ -17,14 +14,12 @@ import pulse.properties.NumericProperty; import pulse.properties.NumericPropertyKeyword; import static pulse.properties.NumericPropertyKeyword.NONLINEAR_PRECISION; -import pulse.properties.Property; public class ImplicitNonlinearSolver extends ImplicitScheme implements Solver, FixedPointIterations { private int N; private double HH; private double tau; - private double pls; private double dT_T; @@ -51,7 +46,8 @@ public ImplicitNonlinearSolver(NumericProperty N, NumericProperty timeFactor, Nu nonlinearPrecision = (double) def(NONLINEAR_PRECISION).getValue(); } - private void prepare(NonlinearProblem problem) { + @Override + public void prepare(Problem problem) throws SolverException { super.prepare(problem); var grid = getGrid(); @@ -101,8 +97,8 @@ public DifferenceScheme copy() { } @Override - public Class domain() { - return NonlinearProblem.class; + public Class[] domain() { + return new Class[]{NonlinearProblem.class}; } public NumericProperty getNonlinearPrecision() { @@ -133,7 +129,6 @@ public void set(NumericPropertyKeyword type, NumericProperty property) { @Override public void timeStep(final int m) throws SolverException { - pls = pulse(m); doIterations(getCurrentSolution(), nonlinearPrecision, m); } @@ -143,14 +138,15 @@ public void iteration(int m) throws SolverException { } @Override - public double evalRightBoundary(int m, double alphaN, double betaN) { + public double evalRightBoundary(double alphaN, double betaN) { return c2 * (2. * betaN * tau + HH * getPreviousSolution()[N] + c1 * (fastPowLoop(getCurrentSolution()[N] * dT_T + 1, 4) - 1)); } @Override - public double firstBeta(int m) { - return b1 * getPreviousSolution()[0] + b2 * (pls - b3 * (fastPowLoop(getCurrentSolution()[0] * dT_T + 1, 4) - 1)); + public double firstBeta() { + return b1 * getPreviousSolution()[0] + b2 * (getCurrentPulseValue() + - b3 * (fastPowLoop(getCurrentSolution()[0] * dT_T + 1, 4) - 1)); } } diff --git a/src/main/java/pulse/problem/schemes/solvers/ImplicitTranslucentSolver.java b/src/main/java/pulse/problem/schemes/solvers/ImplicitTranslucentSolver.java index 5a769b7..d5005ba 100644 --- a/src/main/java/pulse/problem/schemes/solvers/ImplicitTranslucentSolver.java +++ b/src/main/java/pulse/problem/schemes/solvers/ImplicitTranslucentSolver.java @@ -5,29 +5,22 @@ import static pulse.ui.Messages.getString; import pulse.problem.schemes.DifferenceScheme; -import pulse.problem.schemes.Grid; import pulse.problem.schemes.ImplicitScheme; import pulse.problem.schemes.TridiagonalMatrixAlgorithm; import pulse.problem.statements.PenetrationProblem; import pulse.problem.statements.Problem; import pulse.problem.statements.model.AbsorptionModel; -import pulse.problem.statements.model.BeerLambertAbsorption; import pulse.properties.NumericProperty; public class ImplicitTranslucentSolver extends ImplicitScheme implements Solver { private AbsorptionModel absorption; - private Grid grid; - private double pls; private int N; private double HH; private double _2Bi1HTAU; private double b11; - private double frontAbsorption; - private double rearAbsorption; - public ImplicitTranslucentSolver() { super(); } @@ -36,31 +29,28 @@ public ImplicitTranslucentSolver(NumericProperty N, NumericProperty timeFactor, super(N, timeFactor, timeLimit); } - private void prepare(PenetrationProblem problem) { + @Override + public void prepare(Problem problem) throws SolverException { super.prepare(problem); - grid = getGrid(); + var grid = getGrid(); final double tau = grid.getTimeStep(); N = (int) grid.getGridDensity().getValue(); final double Bi1H = (double) problem.getProperties().getHeatLoss().getValue() * grid.getXStep(); final double hx = grid.getXStep(); - absorption = problem.getAbsorptionModel(); + absorption = ((PenetrationProblem)problem).getAbsorptionModel(); HH = hx * hx; _2Bi1HTAU = 2.0 * Bi1H * tau; b11 = 1.0 / (1.0 + 2.0 * tau / HH * (1 + Bi1H)); - - final double EPS = 1E-7; - rearAbsorption = tau * absorption.absorption(LASER, (N - EPS) * hx); - frontAbsorption = tau * absorption.absorption(LASER, 0.0) + 2.0*tau/hx; - + var tridiagonal = new TridiagonalMatrixAlgorithm(grid) { @Override public double phi(final int i) { - return pls * absorption.absorption(LASER, (i - EPS) * hx); + return getCurrentPulseValue() * absorption.absorption(LASER, i * hx); } }; @@ -81,28 +71,25 @@ public void solve(PenetrationProblem problem) throws SolverException { runTimeSequence(problem); } - @Override - public void timeStep(final int m) throws SolverException { - pls = pulse(m); - super.timeStep(m); - } - @Override public double signal() { - return evaluateSignal(absorption, grid, getCurrentSolution()); + return evaluateSignal(absorption, getGrid(), getCurrentSolution()); } @Override - public double evalRightBoundary(final int m, final double alphaN, final double betaN) { - final double tau = grid.getTimeStep(); + public double evalRightBoundary(final double alphaN, final double betaN) { + final double tau = getGrid().getTimeStep(); + var tridiagonal = this.getTridiagonalMatrixAlgorithm(); - return (HH * (getPreviousSolution()[N] + pls * rearAbsorption) + return (HH * getPreviousSolution()[N] + HH*tau*tridiagonal.phi(N) + 2. * tau * betaN) / (_2Bi1HTAU + HH + 2. * tau * (1 - alphaN)); } @Override - public double firstBeta(int m) { - return (getPreviousSolution()[0] + pls * frontAbsorption) * b11; + public double firstBeta() { + var tridiagonal = this.getTridiagonalMatrixAlgorithm(); + double tau = getGrid().getTimeStep(); + return (getPreviousSolution()[0] + tau*tridiagonal.phi(0))* b11; } @Override @@ -122,8 +109,8 @@ public String toString() { } @Override - public Class domain() { - return PenetrationProblem.class; + public Class[] domain() { + return new Class[]{PenetrationProblem.class}; } -} +} \ No newline at end of file diff --git a/src/main/java/pulse/problem/schemes/solvers/MixedCoupledSolver.java b/src/main/java/pulse/problem/schemes/solvers/MixedCoupledSolver.java index 463ee02..f5393da 100644 --- a/src/main/java/pulse/problem/schemes/solvers/MixedCoupledSolver.java +++ b/src/main/java/pulse/problem/schemes/solvers/MixedCoupledSolver.java @@ -10,16 +10,17 @@ import java.util.Set; import pulse.problem.schemes.CoupledImplicitScheme; -import pulse.problem.schemes.DifferenceScheme; import pulse.problem.schemes.TridiagonalMatrixAlgorithm; import pulse.problem.schemes.rte.RadiativeTransferSolver; +import pulse.problem.statements.ClassicalProblem; import pulse.problem.statements.ParticipatingMedium; +import pulse.problem.statements.Problem; import pulse.problem.statements.model.ThermoOpticalProperties; import pulse.properties.NumericProperty; import pulse.properties.NumericPropertyKeyword; -import pulse.ui.Messages; -public class MixedCoupledSolver extends CoupledImplicitScheme implements Solver { +public abstract class MixedCoupledSolver extends CoupledImplicitScheme + implements Solver { private RadiativeTransferSolver rte; @@ -47,6 +48,7 @@ public class MixedCoupledSolver extends CoupledImplicitScheme implements Solver< private double _2TAU_ONE_MINUS_SIGMA; private double BETA1_FACTOR; private double ONE_MINUS_SIGMA; + private double zeta; public MixedCoupledSolver() { super(derive(GRID_DENSITY, 16), derive(TAU_FACTOR, 0.25)); @@ -58,13 +60,14 @@ public MixedCoupledSolver(NumericProperty N, NumericProperty timeFactor, Numeric sigma = (double) def(SCHEME_WEIGHT).getValue(); } - private void prepare(ParticipatingMedium problem) throws SolverException { + @Override + public void prepare(Problem problem) throws SolverException { super.prepare(problem); var grid = getGrid(); var coupling = getCoupling(); - coupling.init(problem, grid); + coupling.init((ParticipatingMedium) problem, grid); rte = coupling.getRadiativeTransferEquation(); N = (int) grid.getGridDensity().getValue(); @@ -72,7 +75,9 @@ private void prepare(ParticipatingMedium problem) throws SolverException { tau = grid.getTimeStep(); Bi1 = (double) problem.getProperties().getHeatLoss().getValue(); - + + zeta = (double) ( (ClassicalProblem)problem ).getGeometricFactor().getValue(); + var tridiagonal = new TridiagonalMatrixAlgorithm(grid) { @Override @@ -107,7 +112,7 @@ public void evaluateBeta(final double[] U) { setTridiagonalMatrixAlgorithm(tridiagonal); } - private void initConst(ParticipatingMedium problem) { + private void initConst(ClassicalProblem problem) { var p = (ThermoOpticalProperties) problem.getProperties(); final double Np = (double) p.getPlanckNumber().getValue(); final double opticalThickness = (double) p.getOpticalThickness().getValue(); @@ -155,21 +160,23 @@ public double pulse(final int m) { } @Override - public double firstBeta(final int m) { + public double firstBeta() { var fluxes = rte.getFluxes(); var U = getPreviousSolution(); final double phi = TAU0_NP * fluxes.fluxDerivativeFront(); return (_2TAUHX - * (getCurrentPulseValue() - SIGMA_NP * fluxes.getFlux(0) - ONE_MINUS_SIGMA_NP * fluxes.getStoredFlux(0)) - + HX2 * (U[0] + phi * tau) + _2TAU_ONE_MINUS_SIGMA * (U[1] - U[0] * ONE_PLUS_Bi1_HX)) * BETA1_FACTOR; + * (getCurrentPulseValue() * zeta - SIGMA_NP * fluxes.getFlux(0) + - ONE_MINUS_SIGMA_NP * fluxes.getStoredFlux(0)) + + HX2 * (U[0] + phi * tau) + _2TAU_ONE_MINUS_SIGMA * + (U[1] - U[0] * ONE_PLUS_Bi1_HX)) * BETA1_FACTOR; } @Override - public double evalRightBoundary(final int m, final double alphaN, final double betaN) { + public double evalRightBoundary(final double alphaN, final double betaN) { var fluxes = rte.getFluxes(); final double phi = TAU0_NP * fluxes.fluxDerivativeRear(); final var U = getPreviousSolution(); - return (sigma * betaN + HX2_2TAU * U[N] + 0.5 * HX2 * phi + return (sigma * betaN + hx * getCurrentPulseValue() * (1.0 - zeta) + HX2_2TAU * U[N] + 0.5 * HX2 * phi + ONE_MINUS_SIGMA * (U[N - 1] - U[N] * ONE_PLUS_Bi1_HX) + HX_NP * (sigma * fluxes.getFlux(N) + ONE_MINUS_SIGMA * fluxes.getStoredFlux(N))) / (HX2_2TAU + sigma * (ONE_PLUS_Bi1_HX - alphaN)); @@ -205,15 +212,4 @@ public void set(NumericPropertyKeyword type, NumericProperty property) { } } - @Override - public String toString() { - return Messages.getString("MixedScheme2.4"); - } - - @Override - public DifferenceScheme copy() { - var grid = getGrid(); - return new MixedCoupledSolver(grid.getGridDensity(), grid.getTimeFactor(), getTimeLimit()); - } - } \ No newline at end of file diff --git a/src/main/java/pulse/problem/schemes/solvers/MixedCoupledSolverNL.java b/src/main/java/pulse/problem/schemes/solvers/MixedCoupledSolverNL.java new file mode 100644 index 0000000..77d7b19 --- /dev/null +++ b/src/main/java/pulse/problem/schemes/solvers/MixedCoupledSolverNL.java @@ -0,0 +1,92 @@ +/* + * Copyright 2022 Artem Lunev . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package pulse.problem.schemes.solvers; + +import pulse.problem.schemes.DifferenceScheme; +import pulse.problem.schemes.FixedPointIterations; +import static pulse.properties.NumericProperties.def; +import static pulse.properties.NumericProperties.derive; +import pulse.properties.NumericProperty; +import pulse.properties.NumericPropertyKeyword; +import static pulse.properties.NumericPropertyKeyword.NONLINEAR_PRECISION; +import pulse.ui.Messages; + +/** + * + * @author Artem Lunev + */ +public class MixedCoupledSolverNL extends MixedCoupledSolver implements FixedPointIterations { + + private double nonlinearPrecision; + + public MixedCoupledSolverNL() { + super(); + nonlinearPrecision = (double) def(NONLINEAR_PRECISION).getValue(); + setAutoUpdateFluxes(false); + } + + public MixedCoupledSolverNL(NumericProperty N, NumericProperty timeFactor, NumericProperty timeLimit) { + super(N, timeFactor, timeLimit); + nonlinearPrecision = (double) def(NONLINEAR_PRECISION).getValue(); + setAutoUpdateFluxes(false); + } + + @Override + public void timeStep(final int m) throws SolverException { + doIterations(getCurrentSolution(), nonlinearPrecision, m); + } + + @Override + public void iteration(final int m) throws SolverException { + super.timeStep(m); + } + + @Override + public void finaliseIteration(double[] V) throws SolverException { + FixedPointIterations.super.finaliseIteration(V); + var rte = this.getCoupling().getRadiativeTransferEquation(); + setCalculationStatus(rte.compute(V)); + } + + public final NumericProperty getNonlinearPrecision() { + return derive(NONLINEAR_PRECISION, nonlinearPrecision); + } + + public final void setNonlinearPrecision(NumericProperty nonlinearPrecision) { + this.nonlinearPrecision = (double) nonlinearPrecision.getValue(); + } + + @Override + public void set(NumericPropertyKeyword type, NumericProperty property) { + if (type == NONLINEAR_PRECISION) { + setNonlinearPrecision(property); + } else { + super.set(type, property); + } + } + + @Override + public DifferenceScheme copy() { + var grid = getGrid(); + return new MixedCoupledSolverNL(grid.getGridDensity(), grid.getTimeFactor(), getTimeLimit()); + } + + @Override + public String toString() { + return Messages.getString("MixedScheme2.5"); + } + +} \ No newline at end of file diff --git a/src/main/java/pulse/problem/schemes/solvers/MixedLinearisedSolver.java b/src/main/java/pulse/problem/schemes/solvers/MixedLinearisedSolver.java index 979d50f..56a74f0 100644 --- a/src/main/java/pulse/problem/schemes/solvers/MixedLinearisedSolver.java +++ b/src/main/java/pulse/problem/schemes/solvers/MixedLinearisedSolver.java @@ -55,6 +55,8 @@ public class MixedLinearisedSolver extends MixedScheme implements Solver domain() { - return ClassicalProblem.class; + public Class[] domain() { + return new Class[]{ClassicalProblem.class}; } } diff --git a/src/main/java/pulse/problem/statements/ClassicalProblem2D.java b/src/main/java/pulse/problem/statements/ClassicalProblem2D.java index d85462b..2a9ed2d 100644 --- a/src/main/java/pulse/problem/statements/ClassicalProblem2D.java +++ b/src/main/java/pulse/problem/statements/ClassicalProblem2D.java @@ -8,6 +8,8 @@ import pulse.math.ParameterVector; import pulse.math.Segment; import pulse.math.transforms.InvDiamTransform; +import pulse.math.transforms.StickTransform; +import pulse.math.transforms.Transformable; import pulse.problem.laser.DiscretePulse; import pulse.problem.laser.DiscretePulse2D; import pulse.problem.schemes.ADIScheme; @@ -18,7 +20,6 @@ import pulse.problem.statements.model.ExtendedThermalProperties; import pulse.problem.statements.model.ThermalProperties; import pulse.properties.Flag; -import static pulse.properties.NumericPropertyKeyword.HEAT_LOSS; import static pulse.properties.NumericPropertyKeyword.HEAT_LOSS_SIDE; import pulse.ui.Messages; @@ -67,6 +68,7 @@ public DiscretePulse discretePulseOn(Grid grid) { return grid instanceof Grid2D ? new DiscretePulse2D(this, (Grid2D) grid) : super.discretePulseOn(grid); } + @Override public void optimisationVector(ParameterVector output, List flags) { super.optimisationVector(output, flags); @@ -76,7 +78,9 @@ public void optimisationVector(ParameterVector output, List flags) { for (int i = 0, size = output.dimension(); i < size; i++) { var key = output.getIndex(i); - + Transformable transform = new InvDiamTransform(properties); + var bounds = Segment.boundsFrom(key); + switch (key) { case FOV_OUTER: value = (double) properties.getFOVOuter().getValue(); @@ -88,20 +92,20 @@ public void optimisationVector(ParameterVector output, List flags) { value = (double) ((Pulse2D) getPulse()).getSpotDiameter().getValue(); break; case HEAT_LOSS_SIDE: - final double Bi = (double) properties.getSideLosses().getValue(); - setHeatLossParameter(output, i, Bi); - continue; + value = (double) properties.getSideLosses().getValue(); + transform = new StickTransform(bounds); + break; case HEAT_LOSS_COMBINED: - final double combined = (double) properties.getHeatLoss().getValue(); - setHeatLossParameter(output, i, combined); - continue; + value = (double) properties.getHeatLoss().getValue(); + transform = new StickTransform(bounds); + break; default: continue; } - output.setTransform(i, new InvDiamTransform(properties)); + output.setTransform(i, transform); + output.setParameterBounds(i, bounds); output.set(i, value); - output.setParameterBounds(i, new Segment(0.5 * value, 1.5 * value)); } @@ -141,4 +145,4 @@ public Problem copy() { return new ClassicalProblem2D(this); } -} +} \ No newline at end of file diff --git a/src/main/java/pulse/problem/statements/CoreShellProblem.java b/src/main/java/pulse/problem/statements/CoreShellProblem.java deleted file mode 100644 index d575a39..0000000 --- a/src/main/java/pulse/problem/statements/CoreShellProblem.java +++ /dev/null @@ -1,164 +0,0 @@ -package pulse.problem.statements; - -import static pulse.properties.NumericProperties.def; -import static pulse.properties.NumericProperties.derive; -import static pulse.properties.NumericPropertyKeyword.AXIAL_COATING_THICKNESS; -import static pulse.properties.NumericPropertyKeyword.COATING_DIFFUSIVITY; -import static pulse.properties.NumericPropertyKeyword.RADIAL_COATING_THICKNESS; - -import java.util.ArrayList; -import java.util.List; -import java.util.Set; - -import pulse.math.ParameterVector; -import pulse.math.Segment; -import pulse.math.transforms.InvDiamTransform; -import pulse.math.transforms.InvLenSqTransform; -import pulse.math.transforms.InvLenTransform; -import pulse.problem.schemes.solvers.SolverException; -import pulse.problem.statements.model.ExtendedThermalProperties; -import pulse.properties.Flag; -import pulse.properties.NumericProperty; -import pulse.properties.NumericPropertyKeyword; -import static pulse.properties.NumericPropertyKeyword.NONLINEAR_PRECISION; -import pulse.properties.Property; -import pulse.ui.Messages; - -public class CoreShellProblem extends ClassicalProblem2D { - - private double tA; - private double tR; - private double coatingDiffusivity; - private final static boolean DEBUG = true; - - public CoreShellProblem() { - super(); - tA = (double) def(AXIAL_COATING_THICKNESS).getValue(); - tR = (double) def(RADIAL_COATING_THICKNESS).getValue(); - coatingDiffusivity = (double) def(COATING_DIFFUSIVITY).getValue(); - setComplexity(ProblemComplexity.HIGH); - } - - @Override - public String toString() { - return Messages.getString("UniformlyCoatedSample.Descriptor"); - } - - public NumericProperty getCoatingAxialThickness() { - return derive(AXIAL_COATING_THICKNESS, tA); - } - - public NumericProperty getCoatingRadialThickness() { - return derive(RADIAL_COATING_THICKNESS, tR); - } - - public double axialFactor() { - return tA / (double) getProperties().getSampleThickness().getValue(); - } - - public double radialFactor() { - return tR / (double) getProperties().getSampleThickness().getValue(); - } - - public void setCoatingAxialThickness(NumericProperty t) { - this.tA = (double) t.getValue(); - } - - public void setCoatingRadialThickness(NumericProperty t) { - this.tR = (double) t.getValue(); - } - - public NumericProperty getCoatingDiffusivity() { - return derive(COATING_DIFFUSIVITY, coatingDiffusivity); - } - - public void setCoatingDiffusivity(NumericProperty a) { - this.coatingDiffusivity = (double) a.getValue(); - } - - @Override - public Set listedKeywords() { - var set = super.listedKeywords(); - set.add(AXIAL_COATING_THICKNESS); - set.add(RADIAL_COATING_THICKNESS); - set.add(COATING_DIFFUSIVITY); - return set; - } - - @Override - public void set(NumericPropertyKeyword type, NumericProperty property) { - switch (type) { - case COATING_DIFFUSIVITY: - setCoatingDiffusivity(property); - break; - case AXIAL_COATING_THICKNESS: - setCoatingAxialThickness(property); - break; - case RADIAL_COATING_THICKNESS: - setCoatingRadialThickness(property); - break; - default: - super.set(type, property); - break; - } - } - - @Override - public boolean isEnabled() { - return !DEBUG; - } - - @Override - public void optimisationVector(ParameterVector output, List flags) { - super.optimisationVector(output, flags); - - var bounds = new Segment(0.1, 1.0); - var properties = (ExtendedThermalProperties) this.getProperties(); - - for (int i = 0, size = output.dimension(); i < size; i++) { - var key = output.getIndex(i); - switch (key) { - case AXIAL_COATING_THICKNESS: - output.setTransform(i, new InvLenTransform(properties)); - output.set(i, tA); - output.setParameterBounds(i, bounds); - break; - case RADIAL_COATING_THICKNESS: - output.setTransform(i, new InvDiamTransform(properties)); - output.set(i, tR); - output.setParameterBounds(i, bounds); - break; - case COATING_DIFFUSIVITY: - output.setTransform(i, new InvLenSqTransform(properties)); - output.set(i, coatingDiffusivity); - output.setParameterBounds(i, new Segment(0.5 * coatingDiffusivity, 1.5 * coatingDiffusivity)); - break; - default: - continue; - } - } - - } - - @Override - public void assign(ParameterVector params) throws SolverException { - super.assign(params); - - for (int i = 0, size = params.dimension(); i < size; i++) { - switch (params.getIndex(i)) { - case AXIAL_COATING_THICKNESS: - tA = params.inverseTransform(i); - break; - case RADIAL_COATING_THICKNESS: - tR = params.inverseTransform(i); - break; - case COATING_DIFFUSIVITY: - coatingDiffusivity = params.inverseTransform(i); - break; - default: - continue; - } - } - } - -} diff --git a/src/main/java/pulse/problem/statements/DiathermicMedium.java b/src/main/java/pulse/problem/statements/DiathermicMedium.java index b2aca28..0e8fcad 100644 --- a/src/main/java/pulse/problem/statements/DiathermicMedium.java +++ b/src/main/java/pulse/problem/statements/DiathermicMedium.java @@ -2,7 +2,6 @@ import static pulse.properties.NumericProperties.derive; import static pulse.properties.NumericPropertyKeyword.DIATHERMIC_COEFFICIENT; -import static pulse.properties.NumericPropertyKeyword.NUMPOINTS; import java.util.List; @@ -64,7 +63,7 @@ public void optimisationVector(ParameterVector output, List flags) { if (key == DIATHERMIC_COEFFICIENT) { - var bounds = new Segment(0.0, 1.0); + var bounds = Segment.boundsFrom(DIATHERMIC_COEFFICIENT); final double etta = (double) properties.getDiathermicCoefficient().getValue(); output.setTransform(i, new StickTransform(bounds)); @@ -91,14 +90,6 @@ public void assign(ParameterVector params) throws SolverException { case DIATHERMIC_COEFFICIENT: properties.setDiathermicCoefficient(derive(DIATHERMIC_COEFFICIENT, params.inverseTransform(i))); break; - case HEAT_LOSS: - if (properties.areThermalPropertiesLoaded()) { - properties.calculateEmissivity(); - final double emissivity = (double) properties.getEmissivity().getValue(); - properties - .setDiathermicCoefficient(derive(DIATHERMIC_COEFFICIENT, emissivity / (2.0 - emissivity))); - } - break; default: continue; @@ -123,4 +114,4 @@ public DiathermicMedium copy() { return new DiathermicMedium(this); } -} +} \ No newline at end of file diff --git a/src/main/java/pulse/problem/statements/NonlinearProblem.java b/src/main/java/pulse/problem/statements/NonlinearProblem.java index 5097ab3..28b2857 100644 --- a/src/main/java/pulse/problem/statements/NonlinearProblem.java +++ b/src/main/java/pulse/problem/statements/NonlinearProblem.java @@ -21,6 +21,7 @@ import pulse.properties.NumericProperty; import pulse.properties.NumericPropertyKeyword; import static pulse.properties.NumericPropertyKeyword.LASER_ENERGY; +import static pulse.properties.NumericPropertyKeyword.SOURCE_GEOMETRIC_FACTOR; import pulse.ui.Messages; public class NonlinearProblem extends ClassicalProblem { @@ -54,6 +55,7 @@ public Set listedKeywords() { set.add(SPECIFIC_HEAT); set.add(DENSITY); set.remove(SPOT_DIAMETER); + set.remove(SOURCE_GEOMETRIC_FACTOR); return set; } diff --git a/src/main/java/pulse/problem/statements/ParticipatingMedium.java b/src/main/java/pulse/problem/statements/ParticipatingMedium.java index 5674dcc..ad0d177 100644 --- a/src/main/java/pulse/problem/statements/ParticipatingMedium.java +++ b/src/main/java/pulse/problem/statements/ParticipatingMedium.java @@ -1,21 +1,18 @@ package pulse.problem.statements; -import static pulse.properties.NumericProperties.derive; import java.util.List; +import java.util.Set; import pulse.math.ParameterVector; -import pulse.math.Segment; -import pulse.math.transforms.StickTransform; -import pulse.math.transforms.Transformable; import pulse.problem.schemes.DifferenceScheme; import pulse.problem.schemes.solvers.MixedCoupledSolver; import pulse.problem.schemes.solvers.SolverException; import pulse.problem.statements.model.ThermalProperties; import pulse.problem.statements.model.ThermoOpticalProperties; import pulse.properties.Flag; -import static pulse.properties.NumericPropertyKeyword.OPTICAL_THICKNESS; -import static pulse.properties.NumericPropertyKeyword.PLANCK_NUMBER; +import pulse.properties.NumericPropertyKeyword; +import static pulse.properties.NumericPropertyKeyword.SOURCE_GEOMETRIC_FACTOR; import pulse.ui.Messages; public class ParticipatingMedium extends NonlinearProblem { @@ -39,72 +36,14 @@ public String toString() { public void optimisationVector(ParameterVector output, List flags) { super.optimisationVector(output, flags); var properties = (ThermoOpticalProperties) getProperties(); - - Segment bounds = null; - double value; - Transformable transform; - - for (int i = 0, size = output.dimension(); i < size; i++) { - - var key = output.getIndex(i); - - switch (key) { - case PLANCK_NUMBER: - final double lowerBound = Segment.boundsFrom(PLANCK_NUMBER).getMinimum(); - bounds = new Segment(lowerBound, properties.maxNp()); - value = (double) properties.getPlanckNumber().getValue(); - break; - case OPTICAL_THICKNESS: - value = (double) properties.getOpticalThickness().getValue(); - bounds = Segment.boundsFrom(OPTICAL_THICKNESS); - break; - case SCATTERING_ALBEDO: - value = (double) properties.getScatteringAlbedo().getValue(); - bounds = new Segment(0.0, 1.0); - break; - case SCATTERING_ANISOTROPY: - value = (double) properties.getScatteringAnisostropy().getValue(); - bounds = new Segment(-1.0, 1.0); - break; - default: - continue; - - } - - transform = new StickTransform(bounds); - output.setTransform(i, transform); - output.set(i, value); - output.setParameterBounds(i, bounds); - - } - + properties.optimisationVector(output, flags); } @Override public void assign(ParameterVector params) throws SolverException { super.assign(params); - var properties = (ThermoOpticalProperties) getProperties(); - - for (int i = 0, size = params.dimension(); i < size; i++) { - - var type = params.getIndex(i); - - switch (type) { - - case PLANCK_NUMBER: - case SCATTERING_ALBEDO: - case SCATTERING_ANISOTROPY: - case OPTICAL_THICKNESS: - properties.set(type, derive(type, params.inverseTransform(i))); - break; - default: - break; - - } - - } - + properties.assign(params); } @Override @@ -122,9 +61,16 @@ public void initProperties() { setProperties(new ThermoOpticalProperties()); } + @Override + public Set listedKeywords() { + var set = super.listedKeywords(); + set.add(SOURCE_GEOMETRIC_FACTOR); + return set; + } + @Override public Problem copy() { return new ParticipatingMedium(this); } -} +} \ No newline at end of file diff --git a/src/main/java/pulse/problem/statements/PenetrationProblem.java b/src/main/java/pulse/problem/statements/PenetrationProblem.java index 46a2b84..b93e68f 100644 --- a/src/main/java/pulse/problem/statements/PenetrationProblem.java +++ b/src/main/java/pulse/problem/statements/PenetrationProblem.java @@ -1,6 +1,7 @@ package pulse.problem.statements; import java.util.List; +import java.util.Set; import pulse.math.ParameterVector; import pulse.problem.schemes.DifferenceScheme; @@ -9,6 +10,8 @@ import pulse.problem.statements.model.AbsorptionModel; import pulse.problem.statements.model.BeerLambertAbsorption; import pulse.properties.Flag; +import pulse.properties.NumericPropertyKeyword; +import static pulse.properties.NumericPropertyKeyword.SOURCE_GEOMETRIC_FACTOR; import pulse.properties.Property; import pulse.ui.Messages; import pulse.util.InstanceDescriptor; @@ -16,9 +19,8 @@ public class PenetrationProblem extends ClassicalProblem { private InstanceDescriptor instanceDescriptor - = new InstanceDescriptor( + = new InstanceDescriptor<>( "Absorption Model Selector", AbsorptionModel.class); - private AbsorptionModel absorption = instanceDescriptor.newInstance(AbsorptionModel.class); public PenetrationProblem() { @@ -55,6 +57,13 @@ public List listedTypes() { list.add(instanceDescriptor); return list; } + + @Override + public Set listedKeywords() { + var set = super.listedKeywords(); + set.remove(SOURCE_GEOMETRIC_FACTOR); + return set; + } public InstanceDescriptor getAbsorptionSelector() { return instanceDescriptor; diff --git a/src/main/java/pulse/problem/statements/Problem.java b/src/main/java/pulse/problem/statements/Problem.java index d0e8adc..d2bde96 100644 --- a/src/main/java/pulse/problem/statements/Problem.java +++ b/src/main/java/pulse/problem/statements/Problem.java @@ -1,5 +1,6 @@ package pulse.problem.statements; +import java.util.Arrays; import static pulse.input.listeners.CurveEventType.RESCALED; import static pulse.properties.NumericProperties.derive; import static pulse.properties.NumericPropertyKeyword.TIME_SHIFT; @@ -15,8 +16,6 @@ import pulse.math.ParameterVector; import pulse.math.Segment; import pulse.math.transforms.InvLenSqTransform; -import pulse.math.transforms.StandardTransformations; -import static pulse.math.transforms.StandardTransformations.ABS; import pulse.math.transforms.StickTransform; import pulse.problem.laser.DiscretePulse; import pulse.problem.schemes.DifferenceScheme; @@ -58,7 +57,8 @@ public abstract class Problem extends PropertyHolder implements Reflexive, Optim private static boolean hideDetailedAdjustment = true; private ProblemComplexity complexity = ProblemComplexity.LOW; - private InstanceDescriptor instanceDescriptor = new InstanceDescriptor( + private InstanceDescriptor instanceDescriptor + = new InstanceDescriptor<>( "Baseline Selector", Baseline.class); /** @@ -73,7 +73,6 @@ public abstract class Problem extends PropertyHolder implements Reflexive, Optim */ protected Problem() { initProperties(); - setHeatingCurve(new HeatingCurve()); instanceDescriptor.attemptUpdate(LinearBaseline.class.getSimpleName()); @@ -100,7 +99,7 @@ public Problem(Problem p) { public abstract Problem copy(); - public void setHeatingCurve(HeatingCurve curve) { + public final void setHeatingCurve(HeatingCurve curve) { this.curve = curve; curve.setParent(this); } @@ -112,10 +111,7 @@ private void addListeners() { }); curve.addHeatingCurveListener(e -> { if (e.getType() == RESCALED) { - var c = e.getData(); - if (!c.isIncomplete()) { - curve.apply(getBaseline()); - } + curve.apply(getBaseline()); } }); } @@ -134,9 +130,10 @@ private void addListeners() { * @return a {@code List} of available {@code DifferenceScheme}s for solving * this {@code Problem}. */ - public List availableSolutions() { + public final List availableSolutions() { var allSchemes = Reflexive.instancesOf(DifferenceScheme.class); - return allSchemes.stream().filter(scheme -> scheme instanceof Solver).filter(s -> s.domain() == this.getClass()) + return allSchemes.stream().filter(scheme -> scheme instanceof Solver) + .filter(s -> Arrays.asList(s.domain()).contains(this.getClass()) ) .collect(Collectors.toList()); } @@ -152,7 +149,7 @@ public void set(NumericPropertyKeyword type, NumericProperty value) { properties.set(type, value); } - public HeatingCurve getHeatingCurve() { + public final HeatingCurve getHeatingCurve() { return curve; } @@ -228,48 +225,47 @@ public void optimisationVector(ParameterVector output, List flags) { var key = output.getIndex(i); + Segment bounds = Segment.boundsFrom(key); + double value = 0; + switch (key) { case THICKNESS: - final double l = (double) properties.getSampleThickness().getValue(); - var bounds = Segment.boundsFrom(THICKNESS); - output.setParameterBounds(i, bounds); - output.setTransform(i, new StickTransform(bounds)); - output.set(i, l); + value = (double) properties.getSampleThickness().getValue(); break; case DIFFUSIVITY: final double a = (double) properties.getDiffusivity().getValue(); output.setTransform(i, new InvLenSqTransform(properties)); - output.setParameterBounds(i, new Segment(0.33 * a, 3.0 * a)); + bounds = new Segment(0.01 * a, 20.0 * a); + output.setParameterBounds(i, bounds); output.set(i, a); - break; + //custom transform here -- skip assigning StickTransform + continue; case MAXTEMP: final double signalHeight = (double) properties.getMaximumTemperature().getValue(); - output.setTransform(i, ABS); - output.setParameterBounds(i, new Segment(0.5 * signalHeight, 1.5 * signalHeight)); - output.set(i, signalHeight); + bounds = new Segment(0.5 * signalHeight, 1.5 * signalHeight); + value = signalHeight; break; case HEAT_LOSS: - final double Bi = (double) properties.getHeatLoss().getValue(); - output.setParameterBounds(i, Segment.boundsFrom(HEAT_LOSS)); - setHeatLossParameter(output, i, Bi); + value = (double) properties.getHeatLoss().getValue(); + output.setTransform(i, new StickTransform(bounds)); break; case TIME_SHIFT: - output.set(i, (double) curve.getTimeShift().getValue()); double magnitude = 0.25 * properties.timeFactor(); - output.setParameterBounds(i, new Segment(-magnitude, magnitude)); + bounds = new Segment(-magnitude, magnitude); + value = (double) curve.getTimeShift().getValue(); break; default: + continue; } - + + output.setTransform(i, new StickTransform(bounds)); + output.setParameterBounds(i, bounds); + output.set(i, value); + } } - protected void setHeatLossParameter(ParameterVector output, int i, double Bi) { - output.setTransform(i, StandardTransformations.ABS); - output.set(i, Bi); - } - /** * Assigns parameter values of this {@code Problem} using the optimisation * vector {@code params}. Only those parameters will be updated, the types @@ -293,21 +289,21 @@ public void assign(ParameterVector params) throws SolverException { for (int i = 0, size = params.dimension(); i < size; i++) { - double value = params.get(i); + double value = params.inverseTransform(i); var key = params.getIndex(i); switch (key) { case THICKNESS: - properties.setSampleThickness(derive(THICKNESS, params.inverseTransform(i) )); + properties.setSampleThickness(derive(THICKNESS, value )); break; case DIFFUSIVITY: - properties.setDiffusivity(derive(DIFFUSIVITY, params.inverseTransform(i))); + properties.setDiffusivity(derive(DIFFUSIVITY, value)); break; case MAXTEMP: properties.setMaximumTemperature(derive(MAXTEMP, value)); break; case HEAT_LOSS: - properties.setHeatLoss(derive(HEAT_LOSS, params.inverseTransform(i))); + properties.setHeatLoss(derive(HEAT_LOSS, value)); break; case TIME_SHIFT: curve.set(TIME_SHIFT, derive(TIME_SHIFT, value)); @@ -337,14 +333,10 @@ public boolean areDetailsHidden() { * @param b {@code true} if the user does not want to see the details, * {@code false} otherwise. */ - public static void setDetailsHidden(boolean b) { + public final static void setDetailsHidden(boolean b) { Problem.hideDetailedAdjustment = b; } - public String shortName() { - return getClass().getSimpleName(); - } - /** * Used for debugging. Initially, the nonlinear and two-dimensional problem * statements are disabled, since they have not yet been thoroughly tested @@ -419,11 +411,10 @@ public Baseline getBaseline() { * @param baseline the new baseline. * @see pulse.baseline.Baseline.apply(Baseline) */ - public void setBaseline(Baseline baseline) { + public final void setBaseline(Baseline baseline) { this.baseline = baseline; - if (!curve.isIncomplete()) { - curve.apply(baseline); - } + curve.apply(baseline); + baseline.setParent(this); var searchTask = (SearchTask) this.specificAncestor(SearchTask.class); @@ -433,7 +424,7 @@ public void setBaseline(Baseline baseline) { } } - public InstanceDescriptor getBaselineDescriptor() { + public final InstanceDescriptor getBaselineDescriptor() { return instanceDescriptor; } @@ -443,7 +434,7 @@ private void initBaseline() { parameterListChanged(); } - public ThermalProperties getProperties() { + public final ThermalProperties getProperties() { return properties; } @@ -460,4 +451,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 cec8937..d2b5324 100644 --- a/src/main/java/pulse/problem/statements/Pulse.java +++ b/src/main/java/pulse/problem/statements/Pulse.java @@ -8,6 +8,7 @@ import java.util.List; import java.util.Set; +import pulse.input.ExperimentalData; import pulse.problem.laser.PulseTemporalShape; import pulse.problem.laser.RectangularPulse; @@ -90,9 +91,17 @@ private void addListeners() { NumericProperty pw = NumericProperties .derive(NumericPropertyKeyword.LOWER_BOUND, (Number) np.getValue()); + + var range = corrTask.getExperimentalCurve().getRange(); + + if( range.getLowerBound().compareTo(pw) < 0 ) { + //update lower bound of the range for that SearchTask corrTask.getExperimentalCurve().getRange() .setLowerBound(pw); + + } + } } @@ -123,21 +132,24 @@ public void setPulseWidth(NumericProperty pulseWidth) { requireType(pulseWidth, PULSE_WIDTH); double newValue = (double) pulseWidth.getValue(); + + double relChange = Math.abs((newValue - this.pulseWidth) / (this.pulseWidth + newValue)); final double EPS = 1E-3; - + //do not update -- if new value is the same as the previous one - if (Math.abs((newValue - this.pulseWidth) - / (this.pulseWidth + newValue)) < EPS) { - return; - } - - //validate -- do not update if the new pulse width is greater than 2 half-times - var task = (SearchTask) this.specificAncestor(SearchTask.class); - var data = task.getExperimentalCurve(); - if (newValue > 0 && newValue < 2.0 * data.getHalfTime()) { - this.pulseWidth = (double) pulseWidth.getValue(); - firePropertyChanged(this, pulseWidth); + if (relChange > EPS && newValue > 0) { + + //validate -- do not update if the new pulse width is greater than 2 half-times + SearchTask task = (SearchTask) this.specificAncestor(SearchTask.class); + ExperimentalData data = task.getExperimentalCurve(); + + if(newValue < 2.0 * data.getHalfTime()) { + this.pulseWidth = (double) pulseWidth.getValue(); + firePropertyChanged(this, pulseWidth); + } + } + } public NumericProperty getLaserEnergy() { @@ -205,7 +217,7 @@ 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/AbsorptionModel.java b/src/main/java/pulse/problem/statements/model/AbsorptionModel.java index 434db3c..3560faf 100644 --- a/src/main/java/pulse/problem/statements/model/AbsorptionModel.java +++ b/src/main/java/pulse/problem/statements/model/AbsorptionModel.java @@ -111,7 +111,6 @@ public void optimisationVector(ParameterVector output, List flags) { double value = 0; Transformable transform = ABS; - output.setParameterBounds(i, new Segment(1E-2, 1000.0)); switch (key) { case LASER_ABSORPTIVITY: @@ -128,6 +127,7 @@ public void optimisationVector(ParameterVector output, List flags) { } //do this for the listed key values + output.setParameterBounds(i, Segment.boundsFrom(key)); output.setTransform(i, transform); output.set(i, value); diff --git a/src/main/java/pulse/problem/statements/model/ExtendedThermalProperties.java b/src/main/java/pulse/problem/statements/model/ExtendedThermalProperties.java index a0790c3..df62001 100644 --- a/src/main/java/pulse/problem/statements/model/ExtendedThermalProperties.java +++ b/src/main/java/pulse/problem/statements/model/ExtendedThermalProperties.java @@ -7,8 +7,6 @@ import static pulse.properties.NumericPropertyKeyword.FOV_OUTER; import static pulse.properties.NumericPropertyKeyword.HEAT_LOSS_SIDE; -import java.util.ArrayList; -import java.util.List; import java.util.Set; import pulse.input.ExperimentalData; @@ -16,10 +14,8 @@ import static pulse.properties.NumericProperties.def; import pulse.properties.NumericProperty; import pulse.properties.NumericPropertyKeyword; -import static pulse.properties.NumericPropertyKeyword.DIATHERMIC_COEFFICIENT; import static pulse.properties.NumericPropertyKeyword.HEAT_LOSS; import static pulse.properties.NumericPropertyKeyword.HEAT_LOSS_COMBINED; -import pulse.properties.Property; public class ExtendedThermalProperties extends ThermalProperties { diff --git a/src/main/java/pulse/problem/statements/model/ThermalProperties.java b/src/main/java/pulse/problem/statements/model/ThermalProperties.java index 1039f25..75a7ee5 100644 --- a/src/main/java/pulse/problem/statements/model/ThermalProperties.java +++ b/src/main/java/pulse/problem/statements/model/ThermalProperties.java @@ -134,6 +134,7 @@ public boolean areDetailsHidden() { * allowed to use those types of {@code NumericPropery} that are listed by * the {@code listedParameters()}. * + * @param value * @see listedTypes() */ @Override @@ -222,6 +223,7 @@ public NumericProperty getSpecificHeat() { public void setSpecificHeat(NumericProperty cP) { requireType(cP, SPECIFIC_HEAT); this.cP = (double) cP.getValue(); + firePropertyChanged(this, cP); } public NumericProperty getDensity() { @@ -231,6 +233,7 @@ public NumericProperty getDensity() { public void setDensity(NumericProperty p) { requireType(p, DENSITY); this.rho = (double) (p.getValue()); + firePropertyChanged(this, p); } public NumericProperty getTestTemperature() { @@ -338,6 +341,7 @@ public NumericProperty getEmissivity() { public void setEmissivity(NumericProperty e) { requireType(e, EMISSIVITY); this.emissivity = (double) e.getValue(); + firePropertyChanged(this, e); } @Override @@ -355,4 +359,4 @@ public String toString() { return sb.toString(); } -} +} \ No newline at end of file diff --git a/src/main/java/pulse/problem/statements/model/ThermoOpticalProperties.java b/src/main/java/pulse/problem/statements/model/ThermoOpticalProperties.java index feafb90..6228287 100644 --- a/src/main/java/pulse/problem/statements/model/ThermoOpticalProperties.java +++ b/src/main/java/pulse/problem/statements/model/ThermoOpticalProperties.java @@ -1,21 +1,29 @@ package pulse.problem.statements.model; +import java.util.List; import static pulse.math.MathUtils.fastPowLoop; import static pulse.properties.NumericProperties.def; -import static pulse.properties.NumericProperties.derive; import static pulse.properties.NumericProperty.requireType; -import static pulse.properties.NumericPropertyKeyword.OPTICAL_THICKNESS; -import static pulse.properties.NumericPropertyKeyword.PLANCK_NUMBER; import static pulse.properties.NumericPropertyKeyword.SCATTERING_ALBEDO; import static pulse.properties.NumericPropertyKeyword.SCATTERING_ANISOTROPY; import java.util.Set; import pulse.input.ExperimentalData; +import pulse.math.ParameterVector; +import pulse.math.Segment; +import pulse.math.transforms.StickTransform; +import pulse.math.transforms.Transformable; +import pulse.problem.schemes.solvers.SolverException; +import pulse.properties.Flag; +import static pulse.properties.NumericProperties.derive; import pulse.properties.NumericProperty; import pulse.properties.NumericPropertyKeyword; +import static pulse.properties.NumericPropertyKeyword.OPTICAL_THICKNESS; +import static pulse.properties.NumericPropertyKeyword.PLANCK_NUMBER; +import pulse.search.Optimisable; -public class ThermoOpticalProperties extends ThermalProperties { +public class ThermoOpticalProperties extends ThermalProperties implements Optimisable { private double opticalThickness; private double planckNumber; @@ -159,5 +167,71 @@ public String toString() { sb.append(String.format("%n %-25s", this.getDensity())); return sb.toString(); } + + @Override + public void optimisationVector(ParameterVector output, List flags) { + Segment bounds = null; + double value; + Transformable transform; + + for (int i = 0, size = output.dimension(); i < size; i++) { + + var key = output.getIndex(i); + + switch (key) { + case PLANCK_NUMBER: + final double lowerBound = Segment.boundsFrom(PLANCK_NUMBER).getMinimum(); + bounds = new Segment(lowerBound, maxNp()); + value = (double) getPlanckNumber().getValue(); + break; + case OPTICAL_THICKNESS: + value = (double) getOpticalThickness().getValue(); + bounds = Segment.boundsFrom(OPTICAL_THICKNESS); + break; + case SCATTERING_ALBEDO: + value = (double) getScatteringAlbedo().getValue(); + bounds = new Segment(0.0, 1.0); + break; + case SCATTERING_ANISOTROPY: + value = (double) getScatteringAnisostropy().getValue(); + bounds = new Segment(-1.0, 1.0); + break; + default: + continue; + + } + + transform = new StickTransform(bounds); + output.setTransform(i, transform); + output.set(i, value); + output.setParameterBounds(i, bounds); + + } + + } + + @Override + public void assign(ParameterVector params) throws SolverException { + + for (int i = 0, size = params.dimension(); i < size; i++) { + + var type = params.getIndex(i); + + switch (type) { + + case PLANCK_NUMBER: + case SCATTERING_ALBEDO: + case SCATTERING_ANISOTROPY: + case OPTICAL_THICKNESS: + set(type, derive(type, params.inverseTransform(i))); + break; + default: + break; + + } + + } + + } } diff --git a/src/main/java/pulse/properties/SampleName.java b/src/main/java/pulse/properties/SampleName.java index 04cb0bc..5ef7419 100644 --- a/src/main/java/pulse/properties/SampleName.java +++ b/src/main/java/pulse/properties/SampleName.java @@ -7,7 +7,7 @@ public class SampleName implements Property { private String name; public SampleName() { - name = "Nameless"; + //null name } @Override diff --git a/src/main/java/pulse/search/direction/ComplexPath.java b/src/main/java/pulse/search/direction/ComplexPath.java index 9cf4d47..da33ddd 100644 --- a/src/main/java/pulse/search/direction/ComplexPath.java +++ b/src/main/java/pulse/search/direction/ComplexPath.java @@ -1,10 +1,8 @@ package pulse.search.direction; -import pulse.math.ParameterVector; import static pulse.math.linear.Matrices.createIdentityMatrix; import pulse.math.linear.SquareMatrix; -import pulse.problem.schemes.solvers.SolverException; import pulse.tasks.SearchTask; /** @@ -28,13 +26,13 @@ protected ComplexPath(SearchTask task) { * In addition to the superclass method, resets the Hessian to an Identity * matrix. * - * @throws SolverException + * @param task */ @Override public void configure(SearchTask task) { - super.configure(task); hessian = createIdentityMatrix(ActiveFlags.activeParameters(task).size()); inverseHessian = createIdentityMatrix(hessian.getData().length); + super.configure(task); } public SquareMatrix getHessian() { diff --git a/src/main/java/pulse/search/direction/CompositePathOptimiser.java b/src/main/java/pulse/search/direction/CompositePathOptimiser.java index 9b4c0bf..3b78913 100644 --- a/src/main/java/pulse/search/direction/CompositePathOptimiser.java +++ b/src/main/java/pulse/search/direction/CompositePathOptimiser.java @@ -1,6 +1,5 @@ package pulse.search.direction; -import java.util.Arrays; import static pulse.properties.NumericProperties.compare; import java.util.List; @@ -17,7 +16,8 @@ public abstract class CompositePathOptimiser extends GradientBasedOptimiser { - private InstanceDescriptor instanceDescriptor = new InstanceDescriptor( + private InstanceDescriptor instanceDescriptor + = new InstanceDescriptor( "Linear Optimiser Selector", LinearOptimiser.class); private LinearOptimiser linearSolver; @@ -45,6 +45,7 @@ private void initLinearOptimiser() { setLinearSolver(instanceDescriptor.newInstance(LinearOptimiser.class)); } + @Override public boolean iteration(SearchTask task) throws SolverException { var p = (GradientGuidedPath) task.getIterativeState(); // the previous state of the task @@ -71,11 +72,8 @@ public boolean iteration(SearchTask task) throws SolverException { // new set of parameters determined through search var candidateParams = parameters.sum(dir.multiply(step)); - if( Arrays.stream( candidateParams.getData() ).anyMatch(el -> !Double.isFinite(el) ) ) { - throw new SolverException("Illegal candidate parameters: not finite! " + p.getIteration()); - } - task.assign(new ParameterVector(parameters, candidateParams)); // assign new parameters + double newCost = task.solveProblemAndCalculateCost(); // calculate the sum of squared residuals if (newCost > initialCost - EPS diff --git a/src/main/java/pulse/search/direction/GradientGuidedPath.java b/src/main/java/pulse/search/direction/GradientGuidedPath.java index 22a0e05..9dcab90 100644 --- a/src/main/java/pulse/search/direction/GradientGuidedPath.java +++ b/src/main/java/pulse/search/direction/GradientGuidedPath.java @@ -1,8 +1,11 @@ package pulse.search.direction; +import java.util.logging.Level; +import java.util.logging.Logger; import pulse.math.linear.Vector; import pulse.problem.schemes.solvers.SolverException; import pulse.tasks.SearchTask; +import pulse.tasks.logs.Status; /** *

@@ -35,18 +38,18 @@ protected GradientGuidedPath(SearchTask t) { /** * Resets the {@code Path}: calculates the current gradient and the - * direction of search. Sets the minimum point to 0.0. + * direction of search.Sets the minimum point to 0.0. * * @param t the {@code SearchTask}, for which this {@code Path} is created. + * @throws pulse.problem.schemes.solvers.SolverException * @see pulse.search.direction.PathSolver.direction(Path) */ public void configure(SearchTask t) { super.reset(); try { this.gradient = ((GradientBasedOptimiser) PathOptimiser.getInstance()).gradient(t); - } catch (SolverException e) { - System.err.println("Failed on gradient calculation while resetting optimiser..."); - e.printStackTrace(); + } catch (SolverException ex) { + t.notifyFailedStatus(ex); } minimumPoint = 0.0; } diff --git a/src/main/java/pulse/search/direction/PathOptimiser.java b/src/main/java/pulse/search/direction/PathOptimiser.java index b11adc0..d357115 100644 --- a/src/main/java/pulse/search/direction/PathOptimiser.java +++ b/src/main/java/pulse/search/direction/PathOptimiser.java @@ -6,16 +6,13 @@ import static pulse.properties.NumericPropertyKeyword.ERROR_TOLERANCE; import static pulse.properties.NumericPropertyKeyword.ITERATION_LIMIT; -import java.util.ArrayList; import java.util.List; import java.util.Set; -import java.util.stream.Collectors; import pulse.problem.schemes.solvers.SolverException; import pulse.properties.Flag; import pulse.properties.NumericProperty; import pulse.properties.NumericPropertyKeyword; -import static pulse.properties.NumericPropertyKeyword.GRADIENT_RESOLUTION; import pulse.properties.Property; import pulse.search.statistics.OptimiserStatistic; import pulse.tasks.SearchTask; diff --git a/src/main/java/pulse/search/linear/LinearOptimiser.java b/src/main/java/pulse/search/linear/LinearOptimiser.java index 5588f78..891b42b 100644 --- a/src/main/java/pulse/search/linear/LinearOptimiser.java +++ b/src/main/java/pulse/search/linear/LinearOptimiser.java @@ -5,9 +5,6 @@ import static pulse.properties.NumericProperties.derive; import static pulse.properties.NumericPropertyKeyword.LINEAR_RESOLUTION; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; import java.util.Set; import pulse.math.ParameterVector; @@ -16,8 +13,6 @@ import pulse.problem.schemes.solvers.SolverException; import pulse.properties.NumericProperty; import pulse.properties.NumericPropertyKeyword; -import static pulse.properties.NumericPropertyKeyword.GRADIENT_RESOLUTION; -import pulse.properties.Property; import pulse.tasks.SearchTask; import pulse.util.PropertyHolder; import pulse.util.Reflexive; @@ -71,7 +66,7 @@ protected LinearOptimiser() { */ public static Segment domain(ParameterVector x, Vector p) { double alphaMax = Double.POSITIVE_INFINITY; - double alpha = 0.0; + double alpha; for (int i = 0; i < x.dimension(); i++) { @@ -94,7 +89,9 @@ public static Segment domain(ParameterVector x, Vector p) { } - return new Segment(0.0, alphaMax); + //check that alphaMax is not zero! otherwise the optimise will crash + return new Segment(0.0, + Math.max(alphaMax, 1E-10)); } diff --git a/src/main/java/pulse/search/linear/WolfeOptimiser.java b/src/main/java/pulse/search/linear/WolfeOptimiser.java index b4db6d7..751d7d6 100644 --- a/src/main/java/pulse/search/linear/WolfeOptimiser.java +++ b/src/main/java/pulse/search/linear/WolfeOptimiser.java @@ -24,7 +24,7 @@ * page */ public class WolfeOptimiser extends LinearOptimiser { - + private static WolfeOptimiser instance = new WolfeOptimiser(); /** @@ -37,7 +37,7 @@ public class WolfeOptimiser extends LinearOptimiser { * gradient projection, equal to {@value C2}. */ public final static double C2 = 0.8; - + private WolfeOptimiser() { super(); } @@ -65,32 +65,33 @@ private WolfeOptimiser() { */ @Override public double linearStep(SearchTask task) throws SolverException { - + GradientGuidedPath p = (GradientGuidedPath) task.getIterativeState(); - + final Vector direction = p.getDirection(); final Vector g1 = p.getGradient(); - + final double G1P = g1.dot(direction); final double G1P_ABS = abs(G1P); - + var params = task.searchVector(); Segment segment = domain(params, direction); - + double cost1 = task.solveProblemAndCalculateCost(); - + double randomConfinedValue = 0; double g2p; - - var instance = (GradientBasedOptimiser) PathOptimiser.getInstance(); - + + var optimiser = (GradientBasedOptimiser) PathOptimiser.getInstance(); + for (double initialLength = segment.length(); segment.length() / initialLength > searchResolution;) { - + randomConfinedValue = segment.randomValue(); - + final var newParams = params.sum(direction.multiply(randomConfinedValue)); + task.assign(new ParameterVector(params, newParams)); - + final double cost2 = task.solveProblemAndCalculateCost(); /** @@ -102,8 +103,8 @@ public double linearStep(SearchTask task) throws SolverException { segment.setMaximum(randomConfinedValue); continue; } - - final var g2 = instance.gradient(task); + + final var g2 = optimiser.gradient(task); g2p = g2.dot(direction); /** @@ -118,16 +119,16 @@ public double linearStep(SearchTask task) throws SolverException { * if( g2p >= C2*G1P ) break; */ segment.setMinimum(randomConfinedValue); - + } - + task.assign(params); p.setGradient(g1); - + return randomConfinedValue; - + } - + @Override public String toString() { return Messages.getString("WolfeSolver.Descriptor"); //$NON-NLS-1$ @@ -142,5 +143,5 @@ public String toString() { public static WolfeOptimiser getInstance() { return instance; } - + } diff --git a/src/main/java/pulse/search/statistics/CorrelationTest.java b/src/main/java/pulse/search/statistics/CorrelationTest.java index 67191bc..c977544 100644 --- a/src/main/java/pulse/search/statistics/CorrelationTest.java +++ b/src/main/java/pulse/search/statistics/CorrelationTest.java @@ -15,8 +15,8 @@ public abstract class CorrelationTest extends PropertyHolder implements Reflexiv private static double threshold = (double) def(CORRELATION_THRESHOLD).getValue(); - private static InstanceDescriptor instanceDescriptor - = new InstanceDescriptor( + private static final InstanceDescriptor instanceDescriptor + = new InstanceDescriptor<>( "Correlation Test Selector", CorrelationTest.class); static { diff --git a/src/main/java/pulse/tasks/Calculation.java b/src/main/java/pulse/tasks/Calculation.java index 7943c11..af33470 100644 --- a/src/main/java/pulse/tasks/Calculation.java +++ b/src/main/java/pulse/tasks/Calculation.java @@ -160,10 +160,11 @@ public void setScheme(DifferenceScheme scheme, ExperimentalData curve) { @SuppressWarnings({"unchecked", "rawtypes"}) public void process() throws SolverException { var list = problem.getProperties().findMalformedProperties(); - if(!list.isEmpty()) { + if (!list.isEmpty()) { StringBuilder sb = new StringBuilder("Illegal values:"); - for(NumericProperty np : list) - sb.append(String.format("%n %-25s", np)); + list.forEach(np + -> sb.append(String.format("%n %-25s", np)) + ); throw new SolverException(sb.toString()); } ((Solver) scheme).solve(problem); @@ -208,9 +209,10 @@ public boolean setStatus(Status status) { default: } - if(changeStatus) + if (changeStatus) { this.status = status; - + } + return changeStatus; } diff --git a/src/main/java/pulse/tasks/SearchTask.java b/src/main/java/pulse/tasks/SearchTask.java index 717b7bc..b17a6db 100644 --- a/src/main/java/pulse/tasks/SearchTask.java +++ b/src/main/java/pulse/tasks/SearchTask.java @@ -21,7 +21,6 @@ import static pulse.tasks.logs.Status.INCOMPLETE; import static pulse.tasks.logs.Status.IN_PROGRESS; import static pulse.tasks.logs.Status.READY; -import static pulse.tasks.logs.Status.TERMINATED; import static pulse.tasks.processing.Buffer.getSize; import static pulse.util.Reflexive.instantiate; @@ -31,8 +30,6 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Executors; -import java.util.logging.Level; -import java.util.logging.Logger; import java.util.stream.Collectors; import pulse.input.ExperimentalData; @@ -51,7 +48,6 @@ import pulse.tasks.logs.CorrelationLogEntry; import pulse.tasks.logs.DataLogEntry; import pulse.tasks.logs.Details; -import static pulse.tasks.logs.Details.SOLVER_ERROR; import pulse.tasks.logs.Log; import pulse.tasks.logs.LogEntry; import pulse.tasks.logs.StateEntry; @@ -59,6 +55,8 @@ import pulse.tasks.processing.Buffer; import pulse.tasks.processing.CorrelationBuffer; import pulse.util.Accessible; +import static pulse.tasks.logs.Status.AWAITING_TERMINATION; +import static pulse.tasks.logs.Status.TERMINATED; /** * A {@code SearchTask} is the most important class in {@code PULsE}. It @@ -81,7 +79,7 @@ public class SearchTask extends Accessible implements Runnable { private Buffer buffer; private Log log; - private CorrelationBuffer correlationBuffer; + private final CorrelationBuffer correlationBuffer; private CorrelationTest correlationTest; private NormalityTest normalityTest; @@ -171,7 +169,7 @@ private void addListeners() { *

*/ public void clear() { - stored = new ArrayList(); + stored = new ArrayList<>(); curve.resetRanges(); buffer = new Buffer(); correlationBuffer.clear(); @@ -233,15 +231,12 @@ public ParameterVector searchVector() { * * @param searchParameters an {@code IndexedVector} with relevant search * parameters + * @throws pulse.problem.schemes.solvers.SolverException * @see pulse.problem.statements.Problem.assign(IndexedVector) */ - public void assign(ParameterVector searchParameters) { - try { - current.getProblem().assign(searchParameters); - curve.getRange().assign(searchParameters); - } catch (SolverException e) { - notifyFailedStatus(e); - } + public void assign(ParameterVector searchParameters) throws SolverException { + current.getProblem().assign(searchParameters); + curve.getRange().assign(searchParameters); } /** @@ -284,8 +279,7 @@ public void run() { correlationBuffer.clear(); /* search cycle */ - - /* sets an independent thread for manipulating the buffer */ + /* sets an independent thread for manipulating the buffer */ List> bufferFutures = new ArrayList<>(bufferSize); var singleThreadExecutor = Executors.newSingleThreadExecutor(); @@ -295,8 +289,6 @@ public void run() { notifyFailedStatus(e1); } - final int maxIterations = (int) getInstance().getMaxIterations().getValue(); - outer: do { @@ -304,14 +296,8 @@ public void run() { for (var i = 0; i < bufferSize; i++) { - if (current.getStatus() != IN_PROGRESS) { - break outer; - } - - int iter = 0; - try { - for (boolean finished = false; !finished && iter < maxIterations; iter++) { + for (boolean finished = false; !finished;) { finished = optimiser.iteration(this); } } catch (SolverException e) { @@ -319,18 +305,12 @@ public void run() { break outer; } - if (iter >= maxIterations) { - var fail = FAILED; - fail.setDetails(MAX_ITERATIONS_REACHED); - setStatus(fail); - } - //if global best is better than the converged value if (best != null && best.getCost() < path.getCost()) { - //assign the global best parameters - assign(path.getParameters()); - //and try to re-calculate try { + //assign the global best parameters + assign(path.getParameters()); + //and try to re-calculate solveProblemAndCalculateCost(); } catch (SolverException ex) { notifyFailedStatus(ex); @@ -338,7 +318,7 @@ public void run() { } final var j = i; - + bufferFutures.add(CompletableFuture.runAsync(() -> { buffer.fill(this, j); correlationBuffer.inflate(this); @@ -349,13 +329,14 @@ public void run() { bufferFutures.forEach(future -> future.join()); - } while (buffer.isErrorTooHigh(errorTolerance)); + } while (buffer.isErrorTooHigh(errorTolerance) + && current.getStatus() == IN_PROGRESS); singleThreadExecutor.shutdown(); if (current.getStatus() == IN_PROGRESS) { runChecks(); - } + } } @@ -393,13 +374,13 @@ private void runChecks() { } } - } - - private void notifyFailedStatus(SolverException e1) { + + public void notifyFailedStatus(SolverException e1) { var status = Status.FAILED; status.setDetails(Details.SOLVER_ERROR); status.setDetailedMessage(e1.getMessage()); + e1.printStackTrace(); setStatus(status); } @@ -544,9 +525,9 @@ public String describe() { sb.append("_Task_"); var extId = curve.getMetadata().getExternalID(); if (extId < 0) { - sb.append("IntID_" + identifier.getValue()); + sb.append("IntID_").append(identifier.getValue()); } else { - sb.append("ExtID_" + extId); + sb.append("ExtID_").append(extId); } return sb.toString(); @@ -564,10 +545,9 @@ public void terminate() { case IN_PROGRESS: case QUEUED: case READY: - setStatus(TERMINATED); + setStatus(AWAITING_TERMINATION); break; default: - return; } } diff --git a/src/main/java/pulse/tasks/TaskManager.java b/src/main/java/pulse/tasks/TaskManager.java index 03aa334..f8801c2 100644 --- a/src/main/java/pulse/tasks/TaskManager.java +++ b/src/main/java/pulse/tasks/TaskManager.java @@ -26,11 +26,11 @@ import java.util.concurrent.Executors; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.TimeUnit; -import java.util.function.Consumer; import java.util.logging.Level; import java.util.logging.Logger; -import javax.swing.SwingUtilities; import pulse.input.ExperimentalData; +import pulse.input.listeners.DataEvent; +import pulse.input.listeners.DataEventType; import pulse.properties.SampleName; import pulse.search.direction.PathOptimiser; @@ -39,6 +39,7 @@ import pulse.tasks.listeners.TaskSelectionEvent; import pulse.tasks.listeners.TaskSelectionListener; import pulse.tasks.logs.Status; +import static pulse.tasks.logs.Status.AWAITING_TERMINATION; import pulse.tasks.processing.Result; import pulse.tasks.processing.ResultFormat; import pulse.util.Group; @@ -58,19 +59,19 @@ */ public class TaskManager extends UpwardsNavigable { - private static TaskManager instance = new TaskManager(); + private static final TaskManager instance = new TaskManager(); private List tasks; private SearchTask selectedTask; private boolean singleStatement = true; - private ForkJoinPool taskPool; + private final ForkJoinPool taskPool; - private List selectionListeners; - private List taskRepositoryListeners; + private final List selectionListeners; + private final List taskRepositoryListeners; - private final static String DEFAULT_NAME = "Project 1 - " + now().format(ISO_WEEK_DATE); + private final static String DEFAULT_NAME = "Measurement_" + now().format(ISO_WEEK_DATE); private final HierarchyListener statementListener = e -> { @@ -96,10 +97,11 @@ private TaskManager() { addHierarchyListener(statementListener); /* Calculate the half-time once data is loaded. - */ + */ addTaskRepositoryListener((TaskRepositoryEvent e) -> { - if(e.getState() == TaskRepositoryEvent.State.TASK_ADDED) + if (e.getState() == TaskRepositoryEvent.State.TASK_ADDED) { getTask(e.getId()).getExperimentalCurve().calculateHalfTime(); + } }); } @@ -125,10 +127,10 @@ public void execute(SearchTask t) { //try to start cmputation // notify listeners computation is about to start - - if( ! t.setStatus(QUEUED) ) - return; - + if (!t.setStatus(QUEUED)) { + return; + } + // notify listeners calculation started notifyListeners(new TaskRepositoryEvent(TASK_SUBMITTED, t.getIdentifier())); @@ -141,9 +143,13 @@ public void execute(SearchTask t) { //notify listeners before the task is re-assigned notifyListeners(e); t.storeCalculation(); + } + else if(current.getStatus() == AWAITING_TERMINATION) { + t.setStatus(Status.TERMINATED); } - else + else { notifyListeners(e); + } }); } @@ -312,12 +318,12 @@ public SearchTask getTask(int externalId) { * Generates a {@code SearchTask} assuming that the {@code ExperimentalData} * is stored in the {@code file}. This will make the {@code ReaderManager} * attempt to read that {@code file}. If successful, invokes - * {@code addTask(...)} on the created {@code SearchTask}. After the - * task is generated, checks whether the acquisition time recorded by the experimental setup - * has been chosen appropriately. + * {@code addTask(...)} on the created {@code SearchTask}. After the task is + * generated, checks whether the acquisition time recorded by the + * experimental setup has been chosen appropriately. * * @see pulse.input.ExperimentalData.isAcquisitionTimeSensible() - + * *

* * @param file the file to load the experimental data from @@ -325,17 +331,24 @@ public SearchTask getTask(int externalId) { * @see pulse.io.readers.ReaderManager.extract(File) */ public void generateTask(File file) { - read(curveReaders(), file).stream().forEach((ExperimentalData curve) -> { + var curves = read(curveReaders(), file); + //notify curves have been loaded + curves.stream().forEach(c -> c.fireDataChanged(new DataEvent( + DataEventType.DATA_LOADED, c + ))); + //create tasks + curves.stream().forEach((ExperimentalData curve) -> { var task = new SearchTask(curve); addTask(task); var data = task.getExperimentalCurve(); - if(!data.isAcquisitionTimeSensible()) + if (!data.isAcquisitionTimeSensible()) { data.truncate(); + } }); } /** - * Generates multiple tasks from multiple {@code files}. + * Generates multiple tasks from multiple {@code files}. * * @param files a list of {@code File}s that can be parsed down to * {@code ExperimentalData}. @@ -344,22 +357,22 @@ public void generateTasks(List files) { requireNonNull(files, "Null list of files passed to generatesTasks(...)"); //this is the loader runnable submitted to the executor service - Runnable loader = () -> { - var pool = Executors.newSingleThreadExecutor(); + Runnable loader = () -> { + var pool = Executors.newSingleThreadExecutor(); files.stream().forEach(f -> pool.submit(() -> generateTask(f))); pool.shutdown(); - + try { pool.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS); } catch (InterruptedException ex) { Logger.getLogger(TaskManager.class.getName()).log(Level.SEVERE, null, ex); - } - + } + //when pool has been shutdown selectFirstTask(); - + }; - + Executors.newSingleThreadExecutor().submit(loader); } @@ -488,10 +501,13 @@ public List getTaskRepositoryListeners() { /** * This {@code TaskManager} will be described by the sample name for the * experiment. + * + * @return the string descriptor */ @Override public String describe() { - return tasks.size() > 0 ? getSampleName().toString() : DEFAULT_NAME; + var name = getSampleName(); + return name == null || name.getValue() == null ? DEFAULT_NAME : name.toString(); } public void evaluate() { diff --git a/src/main/java/pulse/tasks/logs/Status.java b/src/main/java/pulse/tasks/logs/Status.java index 080f6ed..87e226d 100644 --- a/src/main/java/pulse/tasks/logs/Status.java +++ b/src/main/java/pulse/tasks/logs/Status.java @@ -31,9 +31,16 @@ public enum Status { */ EXECUTION_ERROR(Color.red), /** - * The task has been terminated by the user. + * Termination requested. */ + AWAITING_TERMINATION(Color.DARK_GRAY), + + /** + * Task terminated + */ + TERMINATED(Color.DARK_GRAY), + /** * Task has been queued and is waiting to be executed. */ diff --git a/src/main/java/pulse/ui/Launcher.java b/src/main/java/pulse/ui/Launcher.java index 6843a20..acadc0b 100644 --- a/src/main/java/pulse/ui/Launcher.java +++ b/src/main/java/pulse/ui/Launcher.java @@ -36,7 +36,7 @@ public class Launcher { private PrintStream errStream; private File errorLog; - private final static boolean DEBUG = false; + private final static boolean DEBUG = true; private static final File LOCK = new File("pulse.lock"); @@ -147,6 +147,10 @@ private void createShutdownHook() { if (errorLog != null && errorLog.exists() && errorLog.length() < 1) { errorLog.delete(); } + //delete lock explicitly on abnormal termination + if(LOCK.exists()) { + LOCK.delete(); + } }; Runtime.getRuntime().addShutdownHook(new Thread(r)); diff --git a/src/main/java/pulse/ui/components/CalculationTable.java b/src/main/java/pulse/ui/components/CalculationTable.java index 42c9c9b..9d966b4 100644 --- a/src/main/java/pulse/ui/components/CalculationTable.java +++ b/src/main/java/pulse/ui/components/CalculationTable.java @@ -81,8 +81,10 @@ public void initListeners() { var task = TaskManager.getManagerInstance().getSelectedTask(); var id = convertRowIndexToModel(this.getSelectedRow()); if (!lsm.getValueIsAdjusting() && id > -1 && id < task.getStoredCalculations().size()) { + task.switchTo(task.getStoredCalculations().get(id)); getChart().plot(task, true); + } }); diff --git a/src/main/java/pulse/ui/components/Chart.java b/src/main/java/pulse/ui/components/Chart.java index 2158758..753b878 100644 --- a/src/main/java/pulse/ui/components/Chart.java +++ b/src/main/java/pulse/ui/components/Chart.java @@ -217,8 +217,6 @@ private void adjustAxisLabel(double maximum) { public void plot(SearchTask task, boolean extendedCurve) { requireNonNull(task); - var plot = chart.getXYPlot(); - for (int i = 0; i < 6; i++) { plot.setDataset(i, null); } @@ -261,7 +259,7 @@ public void plot(SearchTask task, boolean extendedCurve) { var solution = problem.getHeatingCurve(); var scheme = calc.getScheme(); - if (solution != null && scheme != null && !solution.isIncomplete()) { + if (solution != null && scheme != null) { var solutionDataset = new XYSeriesCollection(); var displayedCurve = extendedCurve ? solution.extendedTo(rawData, problem.getBaseline()) : solution; diff --git a/src/main/java/pulse/ui/components/DataLoader.java b/src/main/java/pulse/ui/components/DataLoader.java index ec13f1b..d6b6539 100644 --- a/src/main/java/pulse/ui/components/DataLoader.java +++ b/src/main/java/pulse/ui/components/DataLoader.java @@ -21,7 +21,6 @@ import pulse.io.readers.MetaFilePopulator; import pulse.io.readers.ReaderManager; import pulse.problem.laser.NumericPulse; -import pulse.problem.laser.NumericPulseData; import pulse.tasks.SearchTask; import pulse.tasks.TaskManager; import pulse.tasks.listeners.TaskRepositoryEvent; @@ -66,6 +65,7 @@ public static void loadDataDialog() { var instance = TaskManager.getManagerInstance(); if (files != null) { + progressFrame.trackProgress(files.size()); instance.generateTasks(files); } @@ -151,13 +151,15 @@ public static void loadPulseDialog() { metadata.getPulseDescriptor() .setSelectedDescriptor( NumericPulse.class.getSimpleName()); - }); + progressFrame.incrementProgress(); + } + ); } }); }; - + Executors.newSingleThreadExecutor().submit(loader); } diff --git a/src/main/java/pulse/ui/components/ResultTable.java b/src/main/java/pulse/ui/components/ResultTable.java index 141d0f4..520aa0e 100644 --- a/src/main/java/pulse/ui/components/ResultTable.java +++ b/src/main/java/pulse/ui/components/ResultTable.java @@ -178,7 +178,7 @@ public String getToolTipText(MouseEvent e) { @Override public String describe() { - return "SummaryTable"; + return "Summary_" + TaskManager.getManagerInstance().describe(); } public boolean isSelectionEmpty() { diff --git a/src/main/java/pulse/ui/components/buttons/LoaderButton.java b/src/main/java/pulse/ui/components/buttons/LoaderButton.java index 5b93f01..46c9b43 100644 --- a/src/main/java/pulse/ui/components/buttons/LoaderButton.java +++ b/src/main/java/pulse/ui/components/buttons/LoaderButton.java @@ -24,8 +24,10 @@ import javax.swing.JFileChooser; import javax.swing.UIManager; import javax.swing.filechooser.FileNameExtensionFilter; +import org.apache.commons.math3.exception.OutOfRangeException; import pulse.input.InterpolationDataset; +import pulse.ui.Messages; import pulse.util.ImageUtils; @SuppressWarnings("serial") @@ -84,7 +86,18 @@ public void init() { showMessageDialog(getWindowAncestor((Component) arg0.getSource()), getString("LoaderButton.ReadError"), //$NON-NLS-1$ getString("LoaderButton.IOError"), //$NON-NLS-1$ ERROR_MESSAGE); - e.printStackTrace(); + } + catch(OutOfRangeException ofre) { + getDefaultToolkit().beep(); + StringBuilder sb = new StringBuilder(getString("TextWrap.0")); + 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(), + getString("LoaderButton.OFRError"), //$NON-NLS-1$ + ERROR_MESSAGE); } var size = getDataset(dataType).getData().size(); var label = ""; diff --git a/src/main/java/pulse/ui/components/controllers/InstanceCellEditor.java b/src/main/java/pulse/ui/components/controllers/InstanceCellEditor.java index 2c5d4fd..ac0c3bd 100644 --- a/src/main/java/pulse/ui/components/controllers/InstanceCellEditor.java +++ b/src/main/java/pulse/ui/components/controllers/InstanceCellEditor.java @@ -27,9 +27,14 @@ public Component getTableCellEditorComponent(JTable table, Object value, boolean combobox = new JComboBox<>(((InstanceDescriptor) value).getAllDescriptors().toArray()); combobox.setSelectedItem(descriptor.getValue()); - combobox.addItemListener(e -> { + combobox.addItemListener((ItemEvent e) -> { if (e.getStateChange() == ItemEvent.SELECTED) { - descriptor.attemptUpdate(e.getItem()); + try { + descriptor.attemptUpdate(e.getItem()); + } catch(NullPointerException npe) { + System.out.println("Error updating " + descriptor.getDescriptor(false) + + ". Cannot be set to " + e.getItem()); + } } }); diff --git a/src/main/java/pulse/ui/components/models/ResultTableModel.java b/src/main/java/pulse/ui/components/models/ResultTableModel.java index 5910a00..ffd35db 100644 --- a/src/main/java/pulse/ui/components/models/ResultTableModel.java +++ b/src/main/java/pulse/ui/components/models/ResultTableModel.java @@ -135,7 +135,8 @@ public void merge(double temperatureDelta) { avgResults.addAll(group); } else { //add and average result - avgResults.add(new AverageResult(group, fmt)); + var result = new AverageResult(group, fmt); + avgResults.add(result); } //ignore processed results later on @@ -218,47 +219,53 @@ private List tooltips() { public void addRow(AbstractResult result) { Objects.requireNonNull(result, "Entry added to the results table must not be null"); - //result must have a valid ancestor! - var ancestor = Objects.requireNonNull( - result.specificAncestor(SearchTask.class), - "Result " + result.toString() + " does not belong a SearchTask!"); - - //the ancestor then has the SearchTask type - SearchTask parentTask = (SearchTask) ancestor; - - //any old result asssociated withis this task - var oldResult = results.stream().filter(r - -> r.specificAncestor( - SearchTask.class) == parentTask).findAny(); - //ignore average results - if (result instanceof Result && oldResult.isPresent()) { - AbstractResult oldResultExisting = oldResult.get(); - Optional oldCalculation = parentTask.getStoredCalculations().stream() - .filter(c -> c.getResult().equals(oldResultExisting)).findAny(); - - //old calculation found - if (oldCalculation.isPresent()) { - - //since the task has already been completed anyway - Status status = Status.DONE; + if (result instanceof Result) { + + //result must have a valid ancestor! + var ancestor = Objects.requireNonNull( + result.specificAncestor(SearchTask.class), + "Result " + result.toString() + " does not belong a SearchTask!"); + + //the ancestor then has the SearchTask type + SearchTask parentTask = (SearchTask) ancestor; + + //any old result asssociated withis this task + var oldResult = results.stream().filter(r + -> r.specificAncestor( + SearchTask.class) == parentTask).findAny(); + + //check the following only if the old result is present + if (oldResult.isPresent()) { + + AbstractResult oldResultExisting = oldResult.get(); + Optional oldCalculation = parentTask.getStoredCalculations().stream() + .filter(c -> c.getResult().equals(oldResultExisting)).findAny(); + + //old calculation found + if (oldCalculation.isPresent()) { + + //since the task has already been completed anyway + Status status = Status.DONE; + + //better result than already present -- update table + if (parentTask.getCurrentCalculation().isBetterThan(oldCalculation.get())) { + remove(oldResultExisting); + status.setDetails(Details.BETTER_CALCULATION_RESULTS_THAN_PREVIOUSLY_OBTAINED); + parentTask.setStatus(status); + } else { + //do not remove result and do not add new result + status.setDetails(Details.CALCULATION_RESULTS_WORSE_THAN_PREVIOUSLY_OBTAINED); + parentTask.setStatus(status); + return; + } - //better result than already present -- update table - if (parentTask.getCurrentCalculation().isBetterThan(oldCalculation.get())) { - remove(oldResultExisting); - status.setDetails(Details.BETTER_CALCULATION_RESULTS_THAN_PREVIOUSLY_OBTAINED); - parentTask.setStatus(status); } else { - //do not remove result and do not add new result - status.setDetails(Details.CALCULATION_RESULTS_WORSE_THAN_PREVIOUSLY_OBTAINED); - parentTask.setStatus(status); - return; - } + //calculation has been purged -- delete previous result - } else { - //calculation has been purged -- delete previous result + remove(oldResultExisting); - remove(oldResultExisting); + } } diff --git a/src/main/java/pulse/ui/components/panels/ProblemToolbar.java b/src/main/java/pulse/ui/components/panels/ProblemToolbar.java index aa85aed..9515642 100644 --- a/src/main/java/pulse/ui/components/panels/ProblemToolbar.java +++ b/src/main/java/pulse/ui/components/panels/ProblemToolbar.java @@ -49,8 +49,7 @@ public ProblemToolbar() { add(btnLoadDensity); btnSimulate.addActionListener((ActionEvent e) - -> Executors.newSingleThreadExecutor().submit(() - -> ProblemToolbar.plot(e))); + -> plot(e)); } public static void plot(ActionEvent e) { @@ -76,7 +75,8 @@ public static void plot(ActionEvent e) { } else { try { - ((Solver) calc.getScheme()).solve(calc.getProblem()); + Solver solver = (Solver) calc.getScheme(); + solver.solve(calc.getProblem()); } catch (SolverException se) { err.println("Solver of " + t + " has encountered an error. Details: "); se.printStackTrace(); diff --git a/src/main/java/pulse/util/PropertyHolder.java b/src/main/java/pulse/util/PropertyHolder.java index 8238a33..05cef73 100644 --- a/src/main/java/pulse/util/PropertyHolder.java +++ b/src/main/java/pulse/util/PropertyHolder.java @@ -289,6 +289,7 @@ public String getPrefix() { * * @return the descriptor */ + @Override public String getDescriptor() { return prefix != null ? getPrefix() : super.getDescriptor(); } diff --git a/src/main/resources/NumericProperty.xml b/src/main/resources/NumericProperty.xml index 3e3180a..ea01316 100644 --- a/src/main/resources/NumericProperty.xml +++ b/src/main/resources/NumericProperty.xml @@ -212,7 +212,7 @@ default-search-variable="false">
@@ -301,9 +301,9 @@ dimensionfactor="1000.0" keyword="DIAMETER" maximum="0.1" minimum="1.0E-6" value="0.01" primitive-type="double" discreet="true"/> diff --git a/src/main/resources/Version.txt b/src/main/resources/Version.txt index b7e420c..06373ae 100644 --- a/src/main/resources/Version.txt +++ b/src/main/resources/Version.txt @@ -1 +1 @@ -1.94 \ No newline at end of file +1.94F \ No newline at end of file diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index f88018b..e09393d 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -43,7 +43,8 @@ SearchFlags.Label=SearchFlags SearchFlags.Descriptor=Search flags LinearizedProblem.Descriptor=Classical 1D Problem Statement
  • Linearized heat losses (front and rear)
  • One-dimensional heat flow
  • Dimensionless formulation
  • Cp and ρ assumed constant
DistributedProblem.Descriptor=Penetration (1D) Problem Statement
  • Based on 1D formulation, except:
  • Distributed radiation absorption (finite penetration depth) is considered
  • Detector measures an integral signal from the bulk of the sample
  • Laser and thermal radiation absorption are considered separately
-ParticipatingMedium.Descriptor=Participating Medium (1D) Problem Statement
  • Based on a coupled radiative-conductive heat transfer model with distributed absorption, emission, and scattering;
  • Describes a sample with opaque coatings on front and rear faces and a semi-transparent bulk;
  • Sample material acts a continuous medium in terms of absorption, emission, and scattering.
+ParticipatingMedium.Descriptor=Participating Medium (1D) Problem Statement
  • Based on a coupled radiative-conductive heat transfer model with distributed absorption, emission, and scattering;
  • Describes a sample with opaque coatings on front and rear faces and a semi-transparent bulk;
  • Sample material acts a continuous medium in terms of absorption, emission, and scattering.
  • Allows a nonlinear emission function -- hence requires Cp and ρ data
+LinearisedParticipatingMedium.Descriptor=Linearised Participating Medium (1D) Problem Statement
  • Based on a coupled radiative-conductive heat transfer model with distributed absorption, emission, and scattering;
  • Describes a sample with opaque coatings on front and rear faces and a semi-transparent bulk;
  • Sample material acts a continuous medium in terms of absorption, emission, and scattering.
  • Assumes heating is small to suppress nonlinear emission
DiathermicProblem.Descriptor=Diathermic Sample with Grey Walls (1D) Problem Statement
  • Based on 1D formulation, except:
  • Laser radiation is absorbed at the front surface and then re-radiated to the rear surface in the form of thermal radiation
  • Sample material considered fully transparent to any kind of radiation
LinearizedProblem2D.Descriptor=Classical 2D Problem Statement
  • Based on 1D formulation, except:
  • Allows heat losses from side surface
  • Allows radial heat flow
UniformlyCoatedSample.Descriptor=Core-Shell 2D Problem Statement
  • Based on the classical 2D problem, except:
  • Explicitly accounts for a coating that covers front, rear, and side surfaces
  • Allows for axial, radial, and circumferential heat fluxes
@@ -91,6 +92,9 @@ LoaderButton.5=Specific heat, (CP) LoaderButton.6=Density, (ρ) LoaderButton.IOError=I/O Error LoaderButton.ReadError=Unable to read data from file\! +LoaderButton.OFRError=Out of Range +LoaderButton.OFRErrorDescriptor=Data file does not cover the test temperature of current measurement: +LoaderButton.OFRErrorDescriptor2=. Please try expanding the temperature range in the input file! LoaderButton.SupportedExtensionsDescriptor=Supported thermal properties files LogPane.Init=Initializing... LogPane.InsertError=Could not insert log entry to log panel @@ -273,13 +277,16 @@ complexity.warning=

You have selected a high ExplicitScheme.2=Time interval too small: ExplicitScheme.3=Problem not supported or unknown: ExplicitScheme.4=Forward Time, Centred Space (FTCS) Scheme

  • Order of approximation O(h2 + τ)
  • Conditionally stable
  • Faster than other schemes
+ExplicitScheme.5=Forward Time, Centred Space (FTCS) Scheme (NL)
  • Order of approximation O(h2 + τ)
  • Conditionally stable
  • Faster than other schemes
ImplicitScheme.2=Time interval too small: ImplicitScheme.3=Problem not supported or unknown: ImplicitScheme.4=Fully Implicit Scheme
  • Order of approximation O(h2 + &tau)
  • Unconditionally stable
  • Intermediate computational cost
  • Finite-difference representation of boundary conditions uses a Taylor expansion with three terms
+ImplicitScheme.5=Fully Implicit Scheme (NL)
  • Order of approximation O(h2 + &tau)
  • Unconditionally stable
  • Intermediate computational cost
  • Heat equation and BC are linear while RTE has a nonlinear emission term processed with a fixed iteration algorithm
MixedScheme.2=Time interval too small: MixedScheme.3=Problem not supported or unknown: MixedScheme.4=Symmetric Semi-Implicit Scheme
  • Order of approximation O(h2 + &tau2)
  • Unconditionally stable
  • Steps are computationally more expensive but their number is fewer compared to other schemes
  • Finite-difference representation of boundary conditions uses a Taylor expansion with three terms
  • Weight is set to 0.5
MixedScheme2.4=Increased Accuracy Semi-implicit Scheme
  • Order of approximation O(h4 + &tau2)
  • Unconditionally stable
  • Steps are computationally more expensive but their number is fewer compared to other schemes
  • Finite-difference representation of boundary conditions uses a Taylor expansion with three terms
  • Auto-adjusts its weight and discrete representation of the flux derivative based on accuracy.
+MixedScheme2.5=Increased Accuracy Semi-implicit Scheme (NL)
  • Order of approximation O(h4 + &tau2)
  • Unconditionally stable
  • Steps are computationally more expensive but their number is fewer compared to other schemes
  • Heat equation and BC are linear while RTE has a nonlinear emission term processed with a fixed iteration algorithm
TextWrap.0=

TextWrap.1=

TextWrap.2=

\ No newline at end of file diff --git a/src/test/java/test/NonscatteringSetup.java b/src/test/java/test/NonscatteringSetup.java index d56f41c..5de9622 100644 --- a/src/test/java/test/NonscatteringSetup.java +++ b/src/test/java/test/NonscatteringSetup.java @@ -16,6 +16,7 @@ import pulse.problem.schemes.DifferenceScheme; import pulse.problem.schemes.rte.RadiativeTransferSolver; import pulse.problem.schemes.solvers.ImplicitCoupledSolver; +import pulse.problem.schemes.solvers.ImplicitCoupledSolverNL; import pulse.problem.statements.ParticipatingMedium; import pulse.problem.statements.Pulse2D; import pulse.problem.statements.model.ThermoOpticalProperties; @@ -38,7 +39,7 @@ public NonscatteringSetup(final int testProfileSize, final double maxHeating) { properties.setTestTemperature(derive(TEST_TEMPERATURE, 800.0)); properties.setScatteringAlbedo(derive(SCATTERING_ALBEDO, 0.0)); - testScheme = new ImplicitCoupledSolver(); + testScheme = new ImplicitCoupledSolverNL(); var grid = testScheme.getGrid(); grid.setGridDensity(derive(GRID_DENSITY, testProfileSize - 1)); From 1ce1f8e6963aad84cf075d067fa6ba998eef165b Mon Sep 17 00:00:00 2001 From: kotik-coder Date: Tue, 7 Jun 2022 14:10:37 +0300 Subject: [PATCH 2/3] Minor Fixes - Fixed pulse visualisation. Now it takes parameters directly from the PulseShape, which is initialised by the DiscretePulse. Moreover, it only plots points at discrete time steps defined by the Grid. Therefore, the user should have an idea as to how detailed the pulse correction is. - Adjusted the pulse resolution allowed for 2D models. This had to be done since 2D calculations are more demanding in terms of the number of pulse points. Previously these resulted in incorrect Tinf values. --- .../pulse/problem/laser/DiscretePulse.java | 31 +++++++---- .../pulse/problem/laser/DiscretePulse2D.java | 54 ++++++++++++++----- .../statements/ClassicalProblem2D.java | 1 - .../java/pulse/ui/components/PulseChart.java | 39 +++++++------- .../ui/components/panels/ProblemToolbar.java | 2 +- .../pulse/ui/frames/TaskControlFrame.java | 7 +-- src/main/resources/NumericProperty.xml | 2 +- 7 files changed, 88 insertions(+), 48 deletions(-) diff --git a/src/main/java/pulse/problem/laser/DiscretePulse.java b/src/main/java/pulse/problem/laser/DiscretePulse.java index 8160344..b27e4eb 100644 --- a/src/main/java/pulse/problem/laser/DiscretePulse.java +++ b/src/main/java/pulse/problem/laser/DiscretePulse.java @@ -7,6 +7,8 @@ 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.PULSE_WIDTH; import pulse.tasks.SearchTask; /** @@ -34,7 +36,7 @@ public class DiscretePulse { * tc * is the time factor defined in the {@code Problem} class. */ - public final static int WIDTH_TOLERANCE_FACTOR = 1000; + private final static int WIDTH_TOLERANCE_FACTOR = 1000; /** * This creates a one-dimensional discrete pulse on a {@code grid}. @@ -74,7 +76,7 @@ private void init(ExperimentalData data) { widthOnGrid = 0; recalculate(); pulse.getPulseShape().init(data, this); - normalise(); + invTotalEnergy = 1.0/totalEnergy(); } /** @@ -96,7 +98,7 @@ public double laserPowerAt(double time) { */ public final void recalculate() { final double nominalWidth = ((Number) pulse.getPulseWidth().getValue()).doubleValue(); - final double resolvedWidth = timeConversionFactor / WIDTH_TOLERANCE_FACTOR; + final double resolvedWidth = timeConversionFactor / getWidthToleranceFactor(); final double EPS = 1E-10; @@ -112,6 +114,7 @@ public final void recalculate() { //change pulse width setDiscreteWidth(resolvedWidth); shape.init(null, this); + //adjust the pulse object to update the visualised pulse } else if(nominalWidth > resolvedWidth + EPS) { setDiscreteWidth(nominalWidth); } @@ -119,12 +122,12 @@ public final void recalculate() { } /** - * Calculates the total pulse energy using a numerical integrator. The + * 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 void normalise() { - invTotalEnergy = 1.0; + public final double totalEnergy() { var pulseShape = pulse.getPulseShape(); var integrator = new MidpointIntegrator(new Segment(0, widthOnGrid)) { @@ -136,8 +139,7 @@ public double integrand(double... vars) { }; - invTotalEnergy = 1.0 / integrator.integrate(); - + return integrator.integrate(); } /** @@ -191,7 +193,18 @@ public double getConversionFactor() { */ public double resolvedPulseWidth() { - return timeConversionFactor / WIDTH_TOLERANCE_FACTOR; + return timeConversionFactor / 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. + */ + + 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 a34102f..ab9fd0e 100644 --- a/src/main/java/pulse/problem/laser/DiscretePulse2D.java +++ b/src/main/java/pulse/problem/laser/DiscretePulse2D.java @@ -19,7 +19,14 @@ public class DiscretePulse2D extends DiscretePulse { private double discretePulseSpot; - private double radialFactor; + private double sampleRadius; + private double normFactor; + + /** + * This had to be decreased for the 2d pulses. + */ + + private final static int WIDTH_TOLERANCE_FACTOR = 200; /** * The constructor for {@code DiscretePulse2D}. @@ -35,9 +42,8 @@ public class DiscretePulse2D extends DiscretePulse { public DiscretePulse2D(ClassicalProblem2D problem, Grid2D grid) { super(problem, grid); var properties = (ExtendedThermalProperties) problem.getProperties(); - init(properties); - - properties.addListener(e -> init(properties) ); + calcPulseSpot(properties); + properties.addListener(e -> calcPulseSpot(properties) ); } /** @@ -47,7 +53,7 @@ public DiscretePulse2D(ClassicalProblem2D problem, Grid2D grid) { * It uses a Heaviside function to determine whether the {@code radialCoord} * lies within the {@code 0 <= radialCoord <= discretePulseSpot} interval. * It uses the {@code time} parameter to determine the discrete pulse - * function using {@code evaluateAt(time)}. + * function using {@code evaluateAt(time)}.

* * @param time the time for calculation * @param radialCoord - the radial coordinate [length dimension] @@ -55,12 +61,26 @@ public DiscretePulse2D(ClassicalProblem2D problem, Grid2D grid) { * {@code coord > spotDiameter}. * @see pulse.problem.laser.PulseTemporalShape.laserPowerAt(double) */ + public double evaluateAt(double time, double radialCoord) { - return laserPowerAt(time) * (0.5 + 0.5 * signum(discretePulseSpot - radialCoord)); + return laserPowerAt(time) + * (0.5 + 0.5 * signum(discretePulseSpot - radialCoord)); } - private void init(ExtendedThermalProperties properties) { - radialFactor = (double) properties.getSampleDiameter().getValue() / 2.0; + /** + * Calculates the laser power at a give moment in time. The total laser + * energy is normalised over a beam partially illuminating the sample surface. + * @param time a moment in time (in dimensionless units) + * @return the laser power in arbitrary units + */ + + @Override + public double laserPowerAt(double time) { + return normFactor * super.laserPowerAt(time); + } + + private void calcPulseSpot(ExtendedThermalProperties properties) { + sampleRadius = (double) properties.getSampleDiameter().getValue() / 2.0; evalPulseSpot(); } @@ -72,9 +92,10 @@ private void init(ExtendedThermalProperties properties) { public final void evalPulseSpot() { var pulse = (Pulse2D) getPulse(); var grid2d = (Grid2D) getGrid(); - final double radius = (double) pulse.getSpotDiameter().getValue() / 2.0; - discretePulseSpot = grid2d.gridRadialDistance(radius, radialFactor); + final double spotRadius = (double) pulse.getSpotDiameter().getValue() / 2.0; + discretePulseSpot = grid2d.gridRadialDistance(spotRadius, sampleRadius); grid2d.adjustStepSize(this); + normFactor = sampleRadius * sampleRadius / spotRadius / spotRadius; } public final double getDiscretePulseSpot() { @@ -82,7 +103,16 @@ public final double getDiscretePulseSpot() { } public final double getRadialConversionFactor() { - return radialFactor; + return sampleRadius; + } + + /** + * A smaller tolerance factor is set for 2D calculations + */ + + @Override + public int getWidthToleranceFactor() { + return WIDTH_TOLERANCE_FACTOR; } -} +} \ No newline at end of file diff --git a/src/main/java/pulse/problem/statements/ClassicalProblem2D.java b/src/main/java/pulse/problem/statements/ClassicalProblem2D.java index 2a9ed2d..278fe8e 100644 --- a/src/main/java/pulse/problem/statements/ClassicalProblem2D.java +++ b/src/main/java/pulse/problem/statements/ClassicalProblem2D.java @@ -67,7 +67,6 @@ public String toString() { public DiscretePulse discretePulseOn(Grid grid) { return grid instanceof Grid2D ? new DiscretePulse2D(this, (Grid2D) grid) : super.discretePulseOn(grid); } - @Override public void optimisationVector(ParameterVector output, List flags) { diff --git a/src/main/java/pulse/ui/components/PulseChart.java b/src/main/java/pulse/ui/components/PulseChart.java index 418ec70..bb33646 100644 --- a/src/main/java/pulse/ui/components/PulseChart.java +++ b/src/main/java/pulse/ui/components/PulseChart.java @@ -22,10 +22,10 @@ import pulse.problem.statements.Problem; import pulse.problem.statements.Pulse; +import pulse.tasks.Calculation; -public class PulseChart extends AuxPlotter { +public class PulseChart extends AuxPlotter { - private final static int NUM_PULSE_POINTS = 600; private final static double TO_MILLIS = 1E3; public PulseChart(String xLabel, String yLabel) { @@ -59,36 +59,33 @@ private void setLegendTitle() { } @Override - public void plot(Pulse pulse) { - requireNonNull(pulse); + public void plot(Calculation c) { + requireNonNull(c); + + Problem problem = c.getProblem(); - var problem = (Problem) pulse.getParent(); double startTime = (double) problem.getHeatingCurve().getTimeShift().getValue(); var pulseDataset = new XYSeriesCollection(); - pulseDataset.addSeries(series(problem, startTime)); + + pulseDataset.addSeries(series(problem.getPulse(), c.getScheme().getGrid().getTimeStep(), + problem.getProperties().timeFactor(), startTime)); getPlot().setDataset(0, pulseDataset); } - private static XYSeries series(Problem problem, double startTime) { - var pulse = problem.getPulse(); - + private static XYSeries series(Pulse pulse, double dx, double timeFactor, double startTime) { var series = new XYSeries(pulse.getPulseShape().toString()); - - double timeLimit = (double) pulse.getPulseWidth().getValue(); - final double timeFactor = problem.getProperties().timeFactor(); - - double dx = timeLimit / (NUM_PULSE_POINTS - 1); - double x = startTime; - - series.add(TO_MILLIS * (startTime - dx / 10.), 0.0); - series.add(TO_MILLIS * (startTime + timeLimit + dx / 10.), 0.0); - var pulseShape = pulse.getPulseShape(); + + 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); - for (var i = 0; i < NUM_PULSE_POINTS; i++) { - series.add(x * TO_MILLIS, pulseShape.evaluateAt((x - startTime) / timeFactor)); + for (int i = 0, numPoints = (int) (timeLimit/dx); i < numPoints; i++) { + series.add(x * timeFactor * TO_MILLIS, pulseShape.evaluateAt(x - startTime/timeFactor)); x += dx; } diff --git a/src/main/java/pulse/ui/components/panels/ProblemToolbar.java b/src/main/java/pulse/ui/components/panels/ProblemToolbar.java index 9515642..0aeedc8 100644 --- a/src/main/java/pulse/ui/components/panels/ProblemToolbar.java +++ b/src/main/java/pulse/ui/components/panels/ProblemToolbar.java @@ -82,7 +82,7 @@ public static void plot(ActionEvent e) { se.printStackTrace(); } MainGraphFrame.getInstance().plot(); - TaskControlFrame.getInstance().getPulseFrame().plot(calc.getProblem().getPulse()); + TaskControlFrame.getInstance().getPulseFrame().plot(calc); } } diff --git a/src/main/java/pulse/ui/frames/TaskControlFrame.java b/src/main/java/pulse/ui/frames/TaskControlFrame.java index 8a32dc3..3bcccea 100644 --- a/src/main/java/pulse/ui/frames/TaskControlFrame.java +++ b/src/main/java/pulse/ui/frames/TaskControlFrame.java @@ -26,6 +26,7 @@ import javax.swing.event.InternalFrameEvent; import pulse.problem.statements.Pulse; +import pulse.tasks.Calculation; import pulse.tasks.TaskManager; import pulse.ui.Version; import pulse.ui.components.PulseChart; @@ -51,7 +52,7 @@ public class TaskControlFrame extends JFrame { private ResultFrame resultsFrame; private MainGraphFrame graphFrame; private LogFrame logFrame; - private InternalGraphFrame pulseFrame; + private InternalGraphFrame pulseFrame; private PulseMainMenu mainMenu; @@ -201,7 +202,7 @@ private void initComponents() { searchOptionsFrame = new SearchOptionsFrame(); searchOptionsFrame.setFrameIcon(loadIcon("optimiser.png", 20, Color.white)); - pulseFrame = new InternalGraphFrame("Pulse Shape", new PulseChart("Time (ms)", "Laser Power (a. u.)")); + pulseFrame = new InternalGraphFrame("Pulse Shape", new PulseChart("Time (ms)", "Laser Power (a. u.)")); pulseFrame.setFrameIcon(loadIcon("pulse.png", 20, Color.white)); pulseFrame.setVisible(false); @@ -453,7 +454,7 @@ public Mode getMode() { return mode; } - public InternalGraphFrame getPulseFrame() { + public InternalGraphFrame getPulseFrame() { return pulseFrame; } diff --git a/src/main/resources/NumericProperty.xml b/src/main/resources/NumericProperty.xml index ea01316..a4293ab 100644 --- a/src/main/resources/NumericProperty.xml +++ b/src/main/resources/NumericProperty.xml @@ -351,7 +351,7 @@ abbreviation="<i>d</i><sub>las</sub> (mm)" visible="true" descriptor="Laser spot diameter, <i>d</i><sub>las</sub> (mm)" - dimensionfactor="1000.0" keyword="SPOT_DIAMETER" maximum="0.05" + dimensionfactor="1000.0" keyword="SPOT_DIAMETER" maximum="0.2" minimum="1.0E-4" value="0.01" primitive-type="double" discreet="true">
Date: Wed, 8 Jun 2022 10:47:54 +0300 Subject: [PATCH 3/3] Model improvements - Convective heat losses now introduced in diathermic and participating medium models - Emissivity can now be output in the result format dialog --- pom.xml | 2 +- .../pulse/input/InterpolationDataset.java | 11 +-- .../solvers/ExplicitCoupledSolver.java | 3 +- .../solvers/ExplicitCoupledSolverNL.java | 2 - .../solvers/ImplicitCoupledSolver.java | 3 +- .../solvers/ImplicitCoupledSolverNL.java | 2 - .../solvers/ImplicitDiathermicSolver.java | 9 +-- .../schemes/solvers/MixedCoupledSolver.java | 5 +- .../problem/statements/DiathermicMedium.java | 38 +++++++--- .../model/DiathermicProperties.java | 32 +++++++-- .../model/ExtendedThermalProperties.java | 2 +- .../statements/model/ThermalProperties.java | 27 +++++++- .../model/ThermoOpticalProperties.java | 65 +++++++++++++----- .../properties/NumericPropertyKeyword.java | 7 ++ .../pulse/tasks/processing/ResultFormat.java | 13 ++-- .../components/models/SelectedKeysModel.java | 4 +- .../ui/frames/dialogs/ResultChangeDialog.java | 11 --- src/main/resources/NumericProperty.xml | 11 ++- src/main/resources/Version.txt | 2 +- src/main/resources/images/splash.png | Bin 67179 -> 67897 bytes 20 files changed, 169 insertions(+), 80 deletions(-) diff --git a/pom.xml b/pom.xml index 3dc51b9..ecbe713 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ kotik-coder PULsE - 1.94F + 1.95 PULsE Processing Unit for Laser flash Experiments diff --git a/src/main/java/pulse/input/InterpolationDataset.java b/src/main/java/pulse/input/InterpolationDataset.java index 3db3182..45f244e 100644 --- a/src/main/java/pulse/input/InterpolationDataset.java +++ b/src/main/java/pulse/input/InterpolationDataset.java @@ -5,7 +5,7 @@ import static pulse.properties.NumericPropertyKeyword.SPECIFIC_HEAT; import java.util.ArrayList; -import java.util.HashMap; +import java.util.EnumMap; import java.util.List; import java.util.Map; @@ -14,6 +14,7 @@ import pulse.input.listeners.ExternalDatasetListener; import pulse.properties.NumericPropertyKeyword; +import static pulse.properties.NumericPropertyKeyword.EMISSIVITY; import pulse.util.ImmutableDataEntry; /** @@ -29,9 +30,10 @@ public class InterpolationDataset { private UnivariateFunction interpolation; - private List> dataset; - private static Map standartDatasets = new HashMap(); - private static List listeners = new ArrayList<>(); + private final List> dataset; + private static final Map standartDatasets + = new EnumMap(StandartType.class); + private static final List listeners = new ArrayList<>(); /** * Creates an empty {@code InterpolationDataset}. @@ -121,6 +123,7 @@ public static List derivableProperties() { } if (list.contains(SPECIFIC_HEAT) && list.contains(DENSITY)) { list.add(CONDUCTIVITY); + list.add(EMISSIVITY); } return list; } diff --git a/src/main/java/pulse/problem/schemes/solvers/ExplicitCoupledSolver.java b/src/main/java/pulse/problem/schemes/solvers/ExplicitCoupledSolver.java index 9da5e21..d50b442 100644 --- a/src/main/java/pulse/problem/schemes/solvers/ExplicitCoupledSolver.java +++ b/src/main/java/pulse/problem/schemes/solvers/ExplicitCoupledSolver.java @@ -56,7 +56,8 @@ public void prepare(Problem problem) throws SolverException { hx = grid.getXStep(); var p = (ThermoOpticalProperties) problem.getProperties(); - double Bi = (double) p.getHeatLoss().getValue(); + //combined Biot + double Bi = (double) p.getHeatLoss().getValue() + (double) p.getConvectiveLosses().getValue(); a = 1. / (1. + Bi * hx); diff --git a/src/main/java/pulse/problem/schemes/solvers/ExplicitCoupledSolverNL.java b/src/main/java/pulse/problem/schemes/solvers/ExplicitCoupledSolverNL.java index 07247ef..ab30eeb 100644 --- a/src/main/java/pulse/problem/schemes/solvers/ExplicitCoupledSolverNL.java +++ b/src/main/java/pulse/problem/schemes/solvers/ExplicitCoupledSolverNL.java @@ -17,8 +17,6 @@ import pulse.problem.schemes.DifferenceScheme; import pulse.problem.schemes.FixedPointIterations; -import pulse.problem.statements.ParticipatingMedium; -import pulse.problem.statements.Problem; import static pulse.properties.NumericProperties.def; import static pulse.properties.NumericProperties.derive; import pulse.properties.NumericProperty; diff --git a/src/main/java/pulse/problem/schemes/solvers/ImplicitCoupledSolver.java b/src/main/java/pulse/problem/schemes/solvers/ImplicitCoupledSolver.java index 1301604..98c3dbb 100644 --- a/src/main/java/pulse/problem/schemes/solvers/ImplicitCoupledSolver.java +++ b/src/main/java/pulse/problem/schemes/solvers/ImplicitCoupledSolver.java @@ -58,7 +58,8 @@ public void prepare(Problem problem) throws SolverException { final double tau = grid.getTimeStep(); var p = (ThermoOpticalProperties) problem.getProperties(); - final double Bi1 = (double) p.getHeatLoss().getValue(); + //combined Biot + final double Bi1 = (double) p.getHeatLoss().getValue() + (double) p.getConvectiveLosses().getValue(); final double Np = (double) p.getPlanckNumber().getValue(); final double tau0 = (double) p.getOpticalThickness().getValue(); diff --git a/src/main/java/pulse/problem/schemes/solvers/ImplicitCoupledSolverNL.java b/src/main/java/pulse/problem/schemes/solvers/ImplicitCoupledSolverNL.java index 42c9c03..389d148 100644 --- a/src/main/java/pulse/problem/schemes/solvers/ImplicitCoupledSolverNL.java +++ b/src/main/java/pulse/problem/schemes/solvers/ImplicitCoupledSolverNL.java @@ -17,8 +17,6 @@ import pulse.problem.schemes.DifferenceScheme; import pulse.problem.schemes.FixedPointIterations; -import pulse.problem.statements.ParticipatingMedium; -import pulse.problem.statements.Problem; import static pulse.properties.NumericProperties.def; import static pulse.properties.NumericProperties.derive; import pulse.properties.NumericProperty; diff --git a/src/main/java/pulse/problem/schemes/solvers/ImplicitDiathermicSolver.java b/src/main/java/pulse/problem/schemes/solvers/ImplicitDiathermicSolver.java index 3d36e22..920e4fb 100644 --- a/src/main/java/pulse/problem/schemes/solvers/ImplicitDiathermicSolver.java +++ b/src/main/java/pulse/problem/schemes/solvers/ImplicitDiathermicSolver.java @@ -47,11 +47,12 @@ public void prepare(Problem problem) throws SolverException { /* Constants */ var properties = (DiathermicProperties) problem.getProperties(); - final double Bi1 = (double) properties.getHeatLoss().getValue(); + final double BiR = (double) properties.getHeatLoss().getValue(); + final double BiC = (double) properties.getConvectiveLosses().getValue(); final double eta = (double) properties.getDiathermicCoefficient().getValue(); - z0 = 1.0 + HX2_2TAU + hx * Bi1 * (1.0 + eta); - zN_1 = -hx * eta * Bi1; + z0 = 1.0 + HX2_2TAU + hx * BiR * (1.0 + eta) + hx * BiC; + zN_1 = -hx * eta * BiR; /* End of constants */ var tridiagonal = new BlockMatrixAlgorithm(grid); @@ -106,4 +107,4 @@ public Class[] domain() { return new Class[]{DiathermicMedium.class}; } -} +} \ No newline at end of file diff --git a/src/main/java/pulse/problem/schemes/solvers/MixedCoupledSolver.java b/src/main/java/pulse/problem/schemes/solvers/MixedCoupledSolver.java index f5393da..fc3e516 100644 --- a/src/main/java/pulse/problem/schemes/solvers/MixedCoupledSolver.java +++ b/src/main/java/pulse/problem/schemes/solvers/MixedCoupledSolver.java @@ -74,7 +74,10 @@ public void prepare(Problem problem) throws SolverException { hx = grid.getXStep(); tau = grid.getTimeStep(); - Bi1 = (double) problem.getProperties().getHeatLoss().getValue(); + var properties = (ThermoOpticalProperties)problem.getProperties(); + //combined biot + Bi1 = (double) properties.getHeatLoss().getValue() + + (double) properties.getConvectiveLosses().getValue(); zeta = (double) ( (ClassicalProblem)problem ).getGeometricFactor().getValue(); diff --git a/src/main/java/pulse/problem/statements/DiathermicMedium.java b/src/main/java/pulse/problem/statements/DiathermicMedium.java index 0e8fcad..488eafd 100644 --- a/src/main/java/pulse/problem/statements/DiathermicMedium.java +++ b/src/main/java/pulse/problem/statements/DiathermicMedium.java @@ -14,6 +14,7 @@ import pulse.problem.statements.model.DiathermicProperties; import pulse.problem.statements.model.ThermalProperties; import pulse.properties.Flag; +import static pulse.properties.NumericPropertyKeyword.HEAT_LOSS_CONVECTIVE; import pulse.ui.Messages; /** @@ -60,17 +61,31 @@ public void optimisationVector(ParameterVector output, List flags) { for (int i = 0, size = output.dimension(); i < size; i++) { var key = output.getIndex(i); + Segment bounds = null; + double value = 0; - if (key == DIATHERMIC_COEFFICIENT) { - - var bounds = Segment.boundsFrom(DIATHERMIC_COEFFICIENT); - final double etta = (double) properties.getDiathermicCoefficient().getValue(); - - output.setTransform(i, new StickTransform(bounds)); - output.set(i, etta); - output.setParameterBounds(i, bounds); - + switch (key) { + case DIATHERMIC_COEFFICIENT: + bounds = Segment.boundsFrom(DIATHERMIC_COEFFICIENT); + value = (double) properties.getDiathermicCoefficient().getValue(); + break; + case HEAT_LOSS_CONVECTIVE: + bounds = Segment.boundsFrom(HEAT_LOSS_CONVECTIVE); + value = (double) properties.getConvectiveLosses().getValue(); + break; + case HEAT_LOSS: + if(properties.areThermalPropertiesLoaded()) { + value = (double) properties.getHeatLoss().getValue(); + bounds = new Segment(0.0, properties.maxRadiationBiot() ); + break; + } + default: + continue; } + + output.setTransform(i, new StickTransform(bounds)); + output.set(i, value); + output.setParameterBounds(i, bounds); } @@ -90,9 +105,10 @@ public void assign(ParameterVector params) throws SolverException { case DIATHERMIC_COEFFICIENT: properties.setDiathermicCoefficient(derive(DIATHERMIC_COEFFICIENT, params.inverseTransform(i))); break; + case HEAT_LOSS_CONVECTIVE: + properties.setConvectiveLosses(derive(HEAT_LOSS_CONVECTIVE, params.inverseTransform(i))); + break; default: - continue; - } } diff --git a/src/main/java/pulse/problem/statements/model/DiathermicProperties.java b/src/main/java/pulse/problem/statements/model/DiathermicProperties.java index 2315390..bc0a9a5 100644 --- a/src/main/java/pulse/problem/statements/model/DiathermicProperties.java +++ b/src/main/java/pulse/problem/statements/model/DiathermicProperties.java @@ -7,14 +7,17 @@ import static pulse.properties.NumericProperty.requireType; import pulse.properties.NumericPropertyKeyword; import static pulse.properties.NumericPropertyKeyword.DIATHERMIC_COEFFICIENT; +import static pulse.properties.NumericPropertyKeyword.HEAT_LOSS_CONVECTIVE; public class DiathermicProperties extends ThermalProperties { private double diathermicCoefficient; + private double convectiveLosses; public DiathermicProperties() { super(); this.diathermicCoefficient = (double) def(DIATHERMIC_COEFFICIENT).getValue(); + this.convectiveLosses = (double) def(HEAT_LOSS_CONVECTIVE).getValue(); } public DiathermicProperties(ThermalProperties p) { @@ -23,8 +26,10 @@ public DiathermicProperties(ThermalProperties p) { ? ((DiathermicProperties) p).getDiathermicCoefficient() : def(DIATHERMIC_COEFFICIENT); this.diathermicCoefficient = (double) property.getValue(); + this.convectiveLosses = (double) property.getValue(); } + @Override public ThermalProperties copy() { return new ThermalProperties(this); } @@ -37,13 +42,29 @@ public void setDiathermicCoefficient(NumericProperty diathermicCoefficient) { requireType(diathermicCoefficient, DIATHERMIC_COEFFICIENT); this.diathermicCoefficient = (double) diathermicCoefficient.getValue(); } + + public NumericProperty getConvectiveLosses() { + return derive(HEAT_LOSS_CONVECTIVE, convectiveLosses); + } + + public void setConvectiveLosses(NumericProperty convectiveLosses) { + requireType(convectiveLosses, HEAT_LOSS_CONVECTIVE); + this.convectiveLosses = (double) convectiveLosses.getValue(); + } @Override public void set(NumericPropertyKeyword type, NumericProperty property) { - if (type == DIATHERMIC_COEFFICIENT) { - diathermicCoefficient = ((Number) property.getValue()).doubleValue(); - } else { - super.set(type, property); + double value = ((Number) property.getValue()).doubleValue(); + switch (type) { + case DIATHERMIC_COEFFICIENT: + diathermicCoefficient = value; + break; + case HEAT_LOSS_CONVECTIVE: + convectiveLosses = value; + break; + default: + super.set(type, property); + break; } } @@ -51,7 +72,8 @@ public void set(NumericPropertyKeyword type, NumericProperty property) { public Set listedKeywords() { var set = super.listedKeywords(); set.add(DIATHERMIC_COEFFICIENT); + set.add(HEAT_LOSS_CONVECTIVE); return set; } -} +} \ No newline at end of file diff --git a/src/main/java/pulse/problem/statements/model/ExtendedThermalProperties.java b/src/main/java/pulse/problem/statements/model/ExtendedThermalProperties.java index df62001..4ff6ff7 100644 --- a/src/main/java/pulse/problem/statements/model/ExtendedThermalProperties.java +++ b/src/main/java/pulse/problem/statements/model/ExtendedThermalProperties.java @@ -62,7 +62,7 @@ public ThermalProperties copy() { public void useTheoreticalEstimates(ExperimentalData c) { super.useTheoreticalEstimates(c); if (areThermalPropertiesLoaded()) { - Bi3 = biot(); + Bi3 = radiationBiot(); } } diff --git a/src/main/java/pulse/problem/statements/model/ThermalProperties.java b/src/main/java/pulse/problem/statements/model/ThermalProperties.java index 75a7ee5..c3691eb 100644 --- a/src/main/java/pulse/problem/statements/model/ThermalProperties.java +++ b/src/main/java/pulse/problem/statements/model/ThermalProperties.java @@ -169,6 +169,9 @@ public void set(NumericPropertyKeyword type, NumericProperty value) { public void setHeatLoss(NumericProperty Bi) { requireType(Bi, HEAT_LOSS); this.Bi = (double) Bi.getValue(); + if(areThermalPropertiesLoaded()) { + calculateEmissivity(); + } firePropertyChanged(this, Bi); } @@ -272,6 +275,7 @@ public Set listedKeywords() { set.add(HEAT_LOSS); set.add(DENSITY); set.add(SPECIFIC_HEAT); + set.add(EMISSIVITY); return set; } @@ -285,16 +289,33 @@ public NumericProperty getThermalConductivity() { public void calculateEmissivity() { double newEmissivity = Bi * thermalConductivity() / (4. * Math.pow(T, 3) * l * STEFAN_BOTLZMAN); - var transform = new StickTransform(new Segment(0.01, 1.0)); + var transform = new StickTransform(Segment.boundsFrom(EMISSIVITY)); setEmissivity(derive(EMISSIVITY, transform.transform(newEmissivity)) ); } - public double biot() { + /** + * Calculates the radiative Biot number. + * @return the radiative Biot number. + */ + + public double radiationBiot() { double lambda = thermalConductivity(); return 4.0 * emissivity * STEFAN_BOTLZMAN * Math.pow(T, 3) * l / lambda; } + + /** + * Calculates the maximum Biot number at these conditions, which + * corresponds to an emissivity of unity. If emissivity is non-positive, + * returns the maximum Biot number defined in the XML file. + * @return the maximum Biot number + */ + + public double maxRadiationBiot() { + double absMax = Segment.boundsFrom(HEAT_LOSS).getMaximum(); + return emissivity > 0 ? radiationBiot() / emissivity : absMax; + } /** * Performs simple calculation of the l2/a @@ -320,7 +341,7 @@ public void useTheoreticalEstimates(ExperimentalData c) { final double t0 = c.getHalfTime(); this.a = PARKERS_COEFFICIENT * l * l / t0; if (areThermalPropertiesLoaded()) { - Bi = biot(); + Bi = radiationBiot(); } } diff --git a/src/main/java/pulse/problem/statements/model/ThermoOpticalProperties.java b/src/main/java/pulse/problem/statements/model/ThermoOpticalProperties.java index 6228287..67db88b 100644 --- a/src/main/java/pulse/problem/statements/model/ThermoOpticalProperties.java +++ b/src/main/java/pulse/problem/statements/model/ThermoOpticalProperties.java @@ -19,6 +19,7 @@ import static pulse.properties.NumericProperties.derive; import pulse.properties.NumericProperty; import pulse.properties.NumericPropertyKeyword; +import static pulse.properties.NumericPropertyKeyword.HEAT_LOSS_CONVECTIVE; import static pulse.properties.NumericPropertyKeyword.OPTICAL_THICKNESS; import static pulse.properties.NumericPropertyKeyword.PLANCK_NUMBER; import pulse.search.Optimisable; @@ -29,29 +30,33 @@ public class ThermoOpticalProperties extends ThermalProperties implements Optimi private double planckNumber; private double scatteringAlbedo; private double scatteringAnisotropy; + private double convectiveLosses; public ThermoOpticalProperties() { super(); - this.opticalThickness = (double) def(OPTICAL_THICKNESS).getValue(); - this.planckNumber = (double) def(PLANCK_NUMBER).getValue(); - scatteringAnisotropy = (double) def(SCATTERING_ANISOTROPY).getValue(); - scatteringAlbedo = (double) def(SCATTERING_ALBEDO).getValue(); + this.opticalThickness = (double) def(OPTICAL_THICKNESS).getValue(); + this.planckNumber = (double) def(PLANCK_NUMBER).getValue(); + scatteringAnisotropy = (double) def(SCATTERING_ANISOTROPY).getValue(); + scatteringAlbedo = (double) def(SCATTERING_ALBEDO).getValue(); + convectiveLosses = (double) def(HEAT_LOSS_CONVECTIVE).getValue(); } public ThermoOpticalProperties(ThermalProperties p) { super(p); - this.opticalThickness = (double) def(OPTICAL_THICKNESS).getValue(); - this.planckNumber = (double) def(PLANCK_NUMBER).getValue(); - scatteringAlbedo = (double) def(SCATTERING_ALBEDO).getValue(); - scatteringAnisotropy = (double) def(SCATTERING_ANISOTROPY).getValue(); + opticalThickness = (double) def(OPTICAL_THICKNESS).getValue(); + planckNumber = (double) def(PLANCK_NUMBER).getValue(); + scatteringAlbedo = (double) def(SCATTERING_ALBEDO).getValue(); + scatteringAnisotropy = (double) def(SCATTERING_ANISOTROPY).getValue(); + convectiveLosses = (double) def(HEAT_LOSS_CONVECTIVE).getValue(); } public ThermoOpticalProperties(ThermoOpticalProperties p) { super(p); - this.opticalThickness = p.opticalThickness; - this.planckNumber = p.planckNumber; - this.scatteringAlbedo = p.scatteringAlbedo; - this.scatteringAnisotropy = p.scatteringAnisotropy; + this.opticalThickness = p.opticalThickness; + this.planckNumber = p.planckNumber; + this.scatteringAlbedo = p.scatteringAlbedo; + this.scatteringAnisotropy = p.scatteringAnisotropy; + this.convectiveLosses = p.convectiveLosses; } @Override @@ -76,6 +81,9 @@ public void set(NumericPropertyKeyword type, NumericProperty value) { case SCATTERING_ANISOTROPY: setScatteringAnisotropy(value); break; + case HEAT_LOSS_CONVECTIVE: + setConvectiveLosses(value); + break; default: break; } @@ -89,6 +97,7 @@ public Set listedKeywords() { set.add(OPTICAL_THICKNESS); set.add(SCATTERING_ALBEDO); set.add(SCATTERING_ANISOTROPY); + set.add(HEAT_LOSS_CONVECTIVE); return set; } @@ -127,7 +136,17 @@ public void setScatteringAnisotropy(NumericProperty A1) { this.scatteringAnisotropy = (double) A1.getValue(); firePropertyChanged(this, A1); } + + public void setConvectiveLosses(NumericProperty losses) { + requireType(losses, HEAT_LOSS_CONVECTIVE); + this.convectiveLosses = (double) losses.getValue(); + firePropertyChanged(this, losses); + } + public NumericProperty getConvectiveLosses() { + return derive(HEAT_LOSS_CONVECTIVE, convectiveLosses); + } + public NumericProperty getScatteringAlbedo() { return derive(SCATTERING_ALBEDO, scatteringAlbedo); } @@ -159,6 +178,7 @@ public String getDescriptor() { @Override public String toString() { StringBuilder sb = new StringBuilder(super.toString()); + sb.append(String.format("%n %-25s", this.getConvectiveLosses())); sb.append(String.format("%n %-25s", this.getOpticalThickness())); sb.append(String.format("%n %-25s", this.getPlanckNumber())); sb.append(String.format("%n %-25s", this.getScatteringAlbedo())); @@ -182,20 +202,28 @@ public void optimisationVector(ParameterVector output, List flags) { case PLANCK_NUMBER: final double lowerBound = Segment.boundsFrom(PLANCK_NUMBER).getMinimum(); bounds = new Segment(lowerBound, maxNp()); - value = (double) getPlanckNumber().getValue(); + value = planckNumber; break; case OPTICAL_THICKNESS: - value = (double) getOpticalThickness().getValue(); + value = opticalThickness; bounds = Segment.boundsFrom(OPTICAL_THICKNESS); break; case SCATTERING_ALBEDO: - value = (double) getScatteringAlbedo().getValue(); - bounds = new Segment(0.0, 1.0); + value = scatteringAlbedo; + bounds = Segment.boundsFrom(SCATTERING_ALBEDO); break; case SCATTERING_ANISOTROPY: - value = (double) getScatteringAnisostropy().getValue(); - bounds = new Segment(-1.0, 1.0); + value = scatteringAnisotropy; + bounds = Segment.boundsFrom(SCATTERING_ANISOTROPY); + break; + case HEAT_LOSS_CONVECTIVE: + value = convectiveLosses; + bounds = Segment.boundsFrom(HEAT_LOSS_CONVECTIVE); break; + case HEAT_LOSS: + value = (double) getHeatLoss().getValue(); + bounds = new Segment(0.0, maxRadiationBiot() ); + break; default: continue; @@ -223,6 +251,7 @@ public void assign(ParameterVector params) throws SolverException { case SCATTERING_ALBEDO: case SCATTERING_ANISOTROPY: case OPTICAL_THICKNESS: + case HEAT_LOSS_CONVECTIVE: set(type, derive(type, params.inverseTransform(i))); break; default: diff --git a/src/main/java/pulse/properties/NumericPropertyKeyword.java b/src/main/java/pulse/properties/NumericPropertyKeyword.java index c9e63cb..539a832 100644 --- a/src/main/java/pulse/properties/NumericPropertyKeyword.java +++ b/src/main/java/pulse/properties/NumericPropertyKeyword.java @@ -157,6 +157,13 @@ public enum NumericPropertyKeyword { * sample (1D and 2D problems). */ HEAT_LOSS, + + /** + * The convective heat loss in diathermic and participating medium problems. + */ + + HEAT_LOSS_CONVECTIVE, + /** * A directive for the optimiser to maintain equal heat losses on all * surfaces of the sample. Note that the dimensionless heat losses, i.e. diff --git a/src/main/java/pulse/tasks/processing/ResultFormat.java b/src/main/java/pulse/tasks/processing/ResultFormat.java index 63c7679..6611a84 100644 --- a/src/main/java/pulse/tasks/processing/ResultFormat.java +++ b/src/main/java/pulse/tasks/processing/ResultFormat.java @@ -45,16 +45,11 @@ private ResultFormat() { private ResultFormat(List keys) { nameMap = new ArrayList<>(); - for (var key : keys) { - nameMap.add(key); - } - } - - private ResultFormat(ResultFormat fmt) { - nameMap = new ArrayList<>(fmt.nameMap.size()); - nameMap.addAll(fmt.nameMap); + keys.forEach(key -> + nameMap.add(key) + ); } - + public static void addResultFormatListener(ResultFormatListener rfl) { listeners.add(rfl); } diff --git a/src/main/java/pulse/ui/components/models/SelectedKeysModel.java b/src/main/java/pulse/ui/components/models/SelectedKeysModel.java index 3234193..98491bf 100644 --- a/src/main/java/pulse/ui/components/models/SelectedKeysModel.java +++ b/src/main/java/pulse/ui/components/models/SelectedKeysModel.java @@ -16,7 +16,7 @@ public class SelectedKeysModel extends DefaultTableModel { * */ private static final long serialVersionUID = 1L; - private List elements; + private final List elements; private final List referenceList; private final NumericPropertyKeyword[] mandatorySelection; @@ -28,7 +28,7 @@ public SelectedKeysModel(List keys, NumericPropertyKeywo update(); } - public void update() { + public final void update() { update(referenceList); } diff --git a/src/main/java/pulse/ui/frames/dialogs/ResultChangeDialog.java b/src/main/java/pulse/ui/frames/dialogs/ResultChangeDialog.java index ee1c7d9..b0554c3 100644 --- a/src/main/java/pulse/ui/frames/dialogs/ResultChangeDialog.java +++ b/src/main/java/pulse/ui/frames/dialogs/ResultChangeDialog.java @@ -3,19 +3,11 @@ import static javax.swing.SwingConstants.BOTTOM; import java.awt.BorderLayout; -import java.awt.Component; import javax.swing.JDialog; -import javax.swing.JTable; -import javax.swing.JTextArea; import javax.swing.SwingConstants; -import static javax.swing.SwingConstants.SOUTH; -import static javax.swing.SwingConstants.TOP; -import javax.swing.table.DefaultTableCellRenderer; -import javax.swing.table.TableCellRenderer; import pulse.tasks.processing.ResultFormat; -import pulse.ui.Messages; import pulse.ui.components.models.ParameterTableModel; import pulse.ui.components.models.SelectedKeysModel; import pulse.ui.components.panels.DoubleTablePanel; @@ -30,7 +22,6 @@ public class ResultChangeDialog extends JDialog { private final static int HEIGHT = 600; public ResultChangeDialog() { - setTitle("Result output formatting"); initComponents(); setSize(WIDTH, HEIGHT); @@ -47,8 +38,6 @@ public void setVisible(boolean value) { } private void initComponents() { - java.awt.GridBagConstraints gridBagConstraints; - MainToolbar = new javax.swing.JToolBar(); filler1 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 0), new java.awt.Dimension(32767, 0)); diff --git a/src/main/resources/NumericProperty.xml b/src/main/resources/NumericProperty.xml index a4293ab..3d4b61a 100644 --- a/src/main/resources/NumericProperty.xml +++ b/src/main/resources/NumericProperty.xml @@ -180,7 +180,7 @@ visible="true" descriptor="Optical thickness, <i>&tau;</i><sub>0</sub>" dimensionfactor="1" discreet="false" keyword="OPTICAL_THICKNESS" - maximum="100000" minimum="1e-4" primitive-type="double" value="0.1" + maximum="100000" minimum="1e-3" primitive-type="double" value="0.1" default-search-variable="true"> @@ -375,7 +375,7 @@ minimum="1.0" value="0.0" primitive-type="double" discreet="false"/> HEAT_LOSS_COMBINED + + @<2ndw$-}eXPFCZY`Ba_}3AYdpU2@yeMH{eSh@Lc6T7~j`hZaPj6 zr=kMbap+%f|4eZ8u&sGwg%LS!-mNWgzk!NExrudi)%M~=T{Hltd)TwkteQvJPM zuP4)3SavqkSWFe^B#>LFenXaOD5ghg_evGTCWdJf($YPYz^Y^x)-Dc|b{5lgs?hs$ zW?A;2Y7E^dPbQ#%43N|MbqOQ_#lNa?n8hQE6?T`SK2+3J#{lTdVWV9)@`HFwh`>-W z#G`VkJ;9|5sflK1{<=^gG1CVEc$ZI5k;1OQHiw{qd%%%BeYhlT-Iu9iB?|y1hfEY4 z&-wOtOp{fh3G@rsibm#Wo0T8GZ5Hj1jVX4~v4C^Hk-hDkv2ojBxY#OI`w$4r`t2HyQFI5kuJQR`3NMe5T)Y;C$_04;W_{s}jpUKk4pf3C z9_(2eftzVBBMifA{EgZ7+!>z2E}*r}WTEmU0qaB;fBF*^+xsr%M_nB0Xu#N_E0vcM z7fSp7Cz5w+v=iI;hK!M+6`{BS!Z`hWN3HL!ucT{!XBr>>pFQsP064B%GqKNiiWo?$%nH+yVoghc;Wf-s8(m8}0&g zUMIx%%pbgjm6F%1?vDGvpE@0%k;6+J8ShW+ORB@T#>9oaUZNQ1srea}}2g7|l$_&riT{MSTEfvv3q`8kqcc!YDEA$(m zW5~S8!K@2$q9{U|#+uuY##S0K-i8Hf*PSGO4I-_Q5ikrQTOv9?p{IV?Ir9WsX8eeJ zEx)YpoXBAju2=+*hRVGl*ZiV9TFq)yVvY!9A*7Ag#%H#}2@DD_ zEq=fJ#mRIWgcDT|NXIf6wS>O>mx|5su6?oix*pUJ=Alr%Sl$|B}4Nq zEG&XYMb@;4M$!rGkget_xw7} zVH4FYG7UW-`g82I(-dg`B2PH2*GUF-?Ms%Dz9(+5STa$wSM%-PNKrA?lRD#=POfKw z_qN(?BgSEpBLT9$qu0cQh=<4`ML-e8DKy+G60aj+DJ7 zNh_NmD!;Vp?uR%xwL}Mb!}cEPdtGz0Uh_qFU9KVJpwPn~?>BNL_5zX*1SmrQ>PSd<5W8z|T*$0`-J-%*h z)4JOWJ%-QINLu3V)T;b5z)NCw!2&(Z3>#Q7~|zCr&ZPxGGE1*f(}9(e#MfyJzn*}WHSdo zmg&?(ucSj({?}dJ<}&`cRWx^8^J){O%+^vG3SE8J59-?gbtj_@xP#%bfikYBK)gg-3-`W{Db^&@*kSRYIGV*k;#UNYxy4KRbK3BNBSPyN z#D%`4ZV;u^ySf1*i)B1GX9hnUv^#5azkW9R`kN9h`0O%~2=9Ba)i?Y!QA0ag=3`hT~2^EGbB)PQIKexDEj`t7%cc<}^;VS%^mFLau7M zQfA8xG+d#X;5U4h~s$On$2h58h88dnDC0>s;pvo$uEP%y;kyy&@E^l1O$G zz#Bf#YI}&DA}6t01Z4R#CcBnRnUYfz;bV=Rt=1sVxeNJ5X4QeP3_mwEI9ciHx>+KP z?>Hko*Suvw4+g?AmJoK1$p7wng;j*N8vd<_93rPH&1~kEl~XXpMo;dZlU9$&LIG<} z3Nw0LCx999;EN~xri!j%n7b`$G1Yz_>qU7!3h<2qPZXCMj27T|=RprH;A!}|agLDg z-MR+sjot(TCxxYdnbD3*88Gs%w5r3TXY}j;?m~xr==}UZPdu9s&@f=dJ~XxY!PRR7 zL25I0fC+igAS`}0Y;Cn*8F#kCLx!xTFYGVXR(M#Noc98U z0Xyi6@&)@u4~+r`Wj!weg!-E1Pf5(NeC;8LyT7CTY0Mmrfwc#xEVx0eC0MwmZn1_e zm@aqdKrC<6%+zSX2@pNlAJF?E%54qiWXd-VH)X$eba3#7(t%h@dZ_xM#~9rxSch+Q z*t@LCS|E<)p@t}Z`;P5bQMBmhD2zpel!`7$E+=(o`!NHi`l?q`B&sKk-tO3{;D5P| z1j5->rd`xnP<1rVpB&q{4NG*F*$(Ipjd|w#(}8F;0du(f8G`_ZiYA_wQ!zgag~R<| zwHVZ5O_xIRXUbT`j99WaZo5RM>$L;o-wYu)tc;wXO0OEg4A)F#tD_?eD?IZ)m`n$k ztdeOnT;8+op$GIfk?aQNrdFI+5}RKPLyt6jZ*_1&5o{a^TDTyKumh}xV{PyQZ4^v1 zSQt=wkhke{VG4(lCvF?)%wAJ7YqW#`abM=fLHvS_r03!|VH--6s>1Y`qLwU?H+4Q- zomSVM=7POO{vtZs2aTcY3*QKEXd=rL&PdB&N}+6?uY(C_jG5q`KHr z9aD+XYP`k)Gb6{yi=87zx+>ZRJigYm*t;Qmw0ZfRd6|pmz*kRDrG)|N{TE*;I5v82 z73A`^=9;sSUD;O&-zlALvoxp4q%9!?2p2A0u8Veq<-wVNNeg^VH^6V2Dt2xsXLwRD zte*d1NHyO}(6ZxqQ3zUaR0|+^N*el56to^XO3NSh8UStN`d-V2s>z z`788MxW*mOOgxK^Y(k7s1BQwkDvf)XgDX!PQH$4CU)R{Swyl$+0ikh1rr_dpN%qUm zFF3amExV@`)3gP|e)DFJ&SK!kR?zY0LOvbF1_xGF6$kJbUqRp!2$93pq)FPE_g+GOc(&jHf^z^deiWU%+%V$cjoM9T!@hou~D%9?l;j?3srcNPCGU!ezLHaXRTb2hAbO(}oUZ#H1H_o-%g zDvx#_06UPnJ`UvM^kzsr zp6B|-t7$yhLbFgauLdEGtyX)nW(o}xe+Ev2CyVUAFglY;wT8(9znK78%F6M3T=3j> z>%U#`I(LRkz)&&G$;E!W2$_9!o0gnm|B%-b5}g!pXm8{1yn4}3%w|3wyAlo&svX)5 zo6gBGN)q-WxKI?DD@f z&f!FbtZM+UxtVzLba11MK0VJ6?X9@dx}`=sar-5>!pk;_O3m>hOIVgjd6!}v_V5Z; z?3p{)GzmmHDax0^T2f0cx1rp?S677|^?jMjxA+)yXpS6@Oexvq&^2vrF|g9hhVEE> zQSPU7(>Tide(Q?(Hm)1do(Y@tH0DI*(dO>W8^2#<0&j)LZDdqFyhMqmj$j5jcDlG> zNS8(xkc#tX*8^GgxWidaOe~+c=)65_lg9xbfTK2Y}X3^axi)35>BBwbK_$a!Hyp=ZFs z@QUwfPBPY6=PD#_mrQ5Xk!~#po^7YgcBgv2*LYObPPxZMZI|IPeK_Q3l&=38LUVQt%c41Fapzlb zp>@DbH$g<|bX)5>M+fDDw+S5tXk~mMNF>;lh0k0p1V)C(4IF&N3pVo|U3%$x%3$;b zyR685k>u%YRtz!ElA74%-KIn*!KkenTOL&JO^Jt}_>Ngx-0f!yRB=bS`hd8ypTppY z3FcPs8v__@WvK6flllMB5liH^jEXqq{gYCE#gG>Du>FU}bO=4Aly;A~(C~+Zkp`0j>iv^zD$5KO_E$nNrk2_)7+wCD`aX%NtiW z^fn8ls#*A>Ocz*yRBF*zT zyq}P}<ElDn^(KQdcMCVSu|DlQBz@yUp%dT5B@0_MK-s#q}#3C}}9>{yVnDhyQkc(zs&> zIuW?!1afm6GV*%s)LA2Fl{%zAF+By#lwwYFdo!%HAp*($h`>zRoRwKPLG|*GoH;-f zCV?CKY)FfwXk6egDQkhzOhCMgdm7yI2fCR88d` zX7F>c=xmhSlwq?Ul?gy%b;GKiaJYbOE2b%0f`VlKlvMTsJ z4i8Zod!DJ3r6}r7;^wdnI;8wt80ae#SuzDYwc7$VaKPIrdXM_kyXP}q;mt?e>OHIg zqs0*nM5y|AOXltdo80KXWpMKLZ_t9EKX(;bw zM_HA6AhVtEjAgT5BHGy9v9db5=>jrg2A<$YMu#1B`kdw_Nfz5 zy|}=c#p=N0S@ zhQ|)fiwIF3yzE;i|H%!`bSzh~EEmtSU+uT7&z{_6LDv-FmgH@Y1TgMrA>dn5R1JVqKDcr~C4?_3ACvs+{MJ zQz^El8qvBpXoFb}7uZ8t)a;i)AP{}=@QPF0X+epOHmIlb?}=PjiVJ<9sjEu$76j3; zekq-1A`||#UY{Q_$0ydAzgjy}=xXDx>0Fr!yJ38_iN{vXR&HuWi_s1?t5%)uP4>E> z=8i4A6m#L4f?9pg*n>*>DjhIK5rz|{y2Ba?rnK5vELLDDkzNzxF)mQ`99nBMT(}&y zyS^}YYT<`ST53LhoiycyODC#iU+Z|Aw1RpT(QImN= zI!}aE$5PyE74|>FnY0OYG;X+2SJ}VuxPs?wc~#&clXPB5aJ zfWK{JpTZoOSgqq@yr~$r1ZSk{{3#{FZe!Ek8W-el^%=HQyB-DwbhQ`W4eYKP3Ernj z&Ks+`TiG=Vd|1CoPVoCLk&;;pFSTiGQ*IlXMxy*VAeeOV_YFF&lOp2QZ?`3ln5_$F z)gpi^+YeLm_SMjjsr%*HB8BGTC!9)y_u^ zv=@#pruAy#X2}VY9X6oEw@=lzF9+BPqB~DaFYhCW+9;>g*5|-38iQ z=Q*|79_oLJ5FkiZR6RK!)zB+U_zZ>@CE^IOe$1 zD^Gw(d!EYT|24PiY`ktGw zA}BDM8m<-^yX}q%x~Q^8;?j&qzMzkPKU88|tc3;EzE%>FsYOq4g$??*rONm1$+WR~ zt>eG1UW@_5vf3J~b_v?=J~Amw@Hv-~Dm4=wESby_ti$uZ0hY{uO*Qbl9a0P?Kc~iw ze)arP5%j_X98dvgYYNFZP0CQ+&<{Z5N5CYkGNWy2DvuzH05)$d2@M7;n(|w>aEMmK z$+<39ot_+{W}fg23-dptyv;*tE^>FgRS~bA9Jx+d!0`aW0@h$zZbBzaZ56EWBHLAI z1s8-4$3$rYq4e-gY1e?J?7S>*eV%%;d9=kFTYp5Y{-`jKyY?!TJQk?7J;+y~UQ_U~ zVzh>IM^7&R!m8YSn{SC^8s?KnyA>zE#y7eFj7k-5{>@0XZz0p}hLTRHNeUk46P^|v>B-@C| zf_WL46>o1qxt0$)+)Nc6SD4De5+xchNsKqJqxpwBOBViTCx-vvqXAh_si6#kU;KI| zWX`QmIX{bS$w3vN14LIDM`C7i!a&_CQq656_ZW}Jp&CB-WIqwc~T*U&wHRJQg#wB5ivBDk*8bc$XIJb%8dv{ zEyD+5QRD&U?WF#A5wOwOLA6w%j$D2CoC#rek*iAv56b|>=jCHx)8!A4X8gy#kYZSyzndm6^`Q$W+dDWJ#hzo!r4KUI9ibRq)(KQI?Y`x5}iR~cEd>Ku)+|h zK5}i+^k##ahhqJO67HWohuYu&LOGJ~Y`ZR0%f`HJjfX|~E$*oY!;JXa_r7C=ztMxE zz*iWvzM(4~?3~C2D`3(!JJ{#o#wXCEYZjv11)(=_Nb%CD9^2W$g_6{^8DT6Lm#Dmb zsXUGb!t-s@!z-)Gfsw=`e--ZXv!xn8e!+lHd;8;DlH*fb#ly!4eGs(pmnZ6^yNKO!{)lBBT$`==T$7tE$O8l$b3 zt2;apD~7)1(3*R8Gw8i~{AwBCGPl!~?+e)T(Lj6}ex!4yV=v1UW8dtTb0RpK_a|r> z%oDFj)OfY$YW+c}rTQkRm+yLzV3@_^W&y^hRRv8pDg-e8#Gmr6A@Xv>>ueTyN#1 z_N7|AB}1voy_+a;8)eYD4oA@hQWA*_M7${T2ntlWXQFJaq=aet+etVC7yRZcqntVx z9HR`8h5k<9(N;Uml{1=5=StM$uEpOJ&@5D&%J!dWPZ~*7+EVEf&8NsU-Tmzv$Y)gO z-a%_8-E%wnp09sJ(`W97FyllXmHl^gvlo&+j)Sl+dR&%0SD|5+msoaw|J{@H7{{oA zOQt|PiU0U?rM>Gonh?w>+bqV*x6w@=(7Lyn%b?W}&Iy}BksCq_8uCfzt9FRlgz%3h zc4n>o)27kzOPLzUJ|tRRxTu-n{z6vU`RrUi*x65-f{2Q6I%-egs?5LZ}nRI|;>%c?yjYFO#W^R$h~ZB%sk)~hu= zG(?A*hDSLEi<~#LZfM}&?Khy?bSk`kZ!8t_Ft)(nC1DbG;|o$wNb8Kz^DWb@neP>$ zX%ogaM@YcKo{Wv$ev&-xQB1ZA=*!gU!6_Gi9Y zADIf8Mo82&;5-Y##!#LX-oDxbgE^=OTD_^2vDIc3*6_Jv8-F;Bt&I0m4=pkH26g+8 za7lo(?ivIJ^4k*q2U_6XF{(;8s-Yj_cRQm<#<)i|=hsK8nMDQ%7gS4sAZqAb!@vno z14wqk{bSV-SN=VRTKdwe#B{REVYmHoK3ogdpMMbD^%;U5S$U#Fpq*8txH*^K=@ZSw zjn18<5jk-E0|a`@0w^^DQSO!J=YvY%N-!;a_*eiRu4Ou zqDsOXm|Q#?j5p@N9YknhHr4n`14$)L_ZnZDQU)hyf8tO@ky9_)PYlfJy|pXAtq?ki z+AERBY_EnvG@v>e|S@) z+PyR^42##^?lTAy7+!xb9uVRQAP!3E+QE9 z2*7T@YhRqCi&uR^R&O$q#pY%vlkB4S$12CB_ps%|DN68>)VtT)#FY!tgyI!17%ZcW zu4(?m1n_cHU&xa`o$w23&f(9=gZMw#cPGiakP;|1oFI7ugehnuLRCjgTkuYHGMF3Z z<>f*xzn#A_MF97dfYW;Ut(PKWeh8|w;ht>3*(!7n1xxx_?}g?BvVGwTm1OYC_({6x z_cYRNkl%gHlJKeKM7jLLbq9Z;`ETU%{}cL^#xMSZM!T8u2URB!l}pb9p=@(5Kq3XOS}QnSi~A@!8*kbUa&# zi8uuopI}=SPhiq|9R5UK!D6ab&fa+Oq#+-fcc(LWTp*H&kmUi3+H1Y0N3$*-2!>a{ z(|1Zmh~X;&&!#G;=}laSWfMtS1s;DXf*LOJ^zLfKRLNB(e9D4_Ulz*s|2agE5YY~t zs_TGEaa4&{bjag#9m(@lw9+l+`yAL(|Fe<#k%l0o*vqlhjVydMz7ZcKrTw)6wQ6^QaJEMibUlz?DK8of@ z0d{+hx|+});>7(hzj_tbFfADWtCOqzx_GRor+B?fhLtwsOP9D`kK5W6W|??UC7>9a zF823x3Qk05gKtM0_<_C_^cCGDEcYDaG`GBI!B>DRhSQctPM0fhwx)xwiP46QegVSy zyi5uh*Y?TvzOnC<>jWLirpU=Vd?5{~gV7|G8q@yP5_81MDdxg{HV?6^!f)A?-?jee z8E?spqpj&OOb30M>=ZR>Y^5j}hR?OQ+@5|wbm7$EV?X5c?+C8bRv_{$ZeblE1Q;p_ z2EFH*(EX!W@b9h?@tqFia%Ml<=i3kZJ~hx1RKrVa%Gyf=C+$Z*c7FC%E9FEMqMDNL z>qixD(5~hK)NKy62a48AQfDVjUhf|7sFq_bXY2V&ntN7{{=d;wVqXLGJXL)NbkN1M zxf+!l789)tCLpZxE%JR^uvOwwxEcl5Sqs<)9wlg6W(_c!+uFb{9O!D7M(6vG~O%|T&7lBE0a8QQ;K_q_k&nqTw_OhMEU^uc-Sxk{Dduw9d;Kd+7g ztd#6P=S%QPR5;=#nC}(kF98hShIO$%9uW7<2hYko8uhuuZtX42vezzS0CK)W&9qhb za3K;kjs+21&9LTd9vy8V^LwXUpel$fvMQ&BoUIPv+dJ{+gLW&;yqKar@{%>C-F(n> zg(s3~9i9yO6ChUN5bL5m0qdq?T^l9S{Igo~##_eav=5`Ks z$|7>^4=D^3?0mYP$9;v4vhh(V>ec>J=N}SD!arxcv(ax0VY115_>b5)aRyILX6gLW zp0)*6vcUWR!!UejiiB+tt6o?g5ZX64qU6@rHC>~v4X_L~@tn(p)bDrwL`aOen=_(Q z48`kYkozL}8TWEvQLpA1s^uSMvc$HEiUh4(1{R8WaP9=3p~IvT#yZbGT(9x&Qi!4) z{^yVNUU?lrH51ni-K?0-Mh=IN13_Ox3KCwbh(tVT&#@h*GCI}O9R&y&c@|oKV`{>0 zIpmJz=k|r=IvpWP!P7JekZiLZoC3^Wm`kyrTpS*r@~byNf)lhh#Tz-Ih4ea$GdykZ zz??~sW#p5)H#3L1eI0z;`_9C;+8BD8Mzb#mMO_Vra@L<+Gsse7j8EcoC|hsRZLs<_ zL-ZsZo+u?6O{7|GsyroE^Wm?lhWDzz*>2xse)LnDUh`=dMI4`)tY)<|$|XH3!5njJ zf{=19;puf#$CPOcj~^=`zW zSoSzY3=>=3!ge%}hRt?uVH?y~H+Q_%n-RQ22Xk!FIF0e^^#S@OyuhC>Fh=ERbqE2; zd(*TlHf^Vuh@%;~O}@AdJztQvAy@0yxh0_yGa)0-7yfajMs?vAd8 zi@9W;Z8Bjq z(8!KfY}h$>Q2 zJz)m-^q^6BvV(X-KunIX0bPtrU#IS#Uq17B8#2V3Oc=jRBo-+wXB2Ki3h(wJdfHjZ zr7gr8_=DPFn^%OWgFgIHNO@B)SZRLB_ghggJrZL zT*=}8P$iL7dh!3(;2G9XT&nSe`xHexOrVo+YyT6#8-SrIzdp3zO zBSDaef??DlBAK@7Z;jI!SwKKkcUJ{hoFIzwK$~x>k}p1b!L_qF{`DN5ctU*oHW&|E zra@sU=Zv*F3s?=+t~Yjse^Uyr4sB!D2}D}>c^ut17&(iZUyIw~;Io=%rhPR5N9S~d zI;WiU>8uWvvRjVXdCRy}{VsZ;Cow$Us5lLO5@~_dp*fLa6}Dl~eIQVUf652%l;k+! z#-{MF#_F7{X8mrt=YZ8p6DE^?aojG{<$`akeXAs7YNc2_0PSWEp5xSR>F^{58TVQ_A zQ=81-)Aemk;(6-*l=UHg-2Z~rQNs&8dt203=jOZ4`z%TTVBTIL!rDQ-_fH6GYj5gZ zIG^J>;cvCIb^LQ={FqK}`S_yKN&f5~N-cup_OB8Hpen~A;Y5C4Qd6~s2F!`Ufl&)q+m z-#RuiQE#Ta!UO#T9S;~N<2=qcm;SB)kP(D`FHLBlQq>(w#FADdO;!a0+egD3B*6n< zQbwHeV0a=m4*aWzCGU!9ol5Zd0%OZ83-T34tQQ7^?;n;4`#q3GVl&a)b?f5De*La> zH=3lS@4$GYop2ycVxB#p(sc;h0&U1yzeQpV&th``=_dM*s=HepLblO;nw0RbaNo#j zvl!b-uJJ@NdB@-p`1scJ3?Ed2a73%gQoz@m(Ir;2Gic;#U(K#qnvi$%uAb2WTsHtQKOo#P!8sp)(fc@};#B^^u>#%vF^YThee zH)dHfS?p0^Mqd{SmMw4tIbIMFWEYD?EcsJq^RgG$ zu5kZb0&~hvE{hR$#P=O*CnqU3J)Lf$x)^HQ0!_~F!4~57Di5}k+lF#nmp|0Bn>##s z>w36$cxczsL_(717`0jP9UOAVFjmCUG&!5OKn^WD70C2zWrIIUV4^`ZC|N5W$& zM;$^pj|bMpcPs78$stlCkT?)=!~^`-=k85pE_` z!*S%x*i7xAk=Z*h2}FLN%qZswUAdRZ7FWHO7t4a;wE;@>D3j0cHqh*}*BWi2U?s~* zb_Aw}D+!im@22_hY0YpoGacu2o64+kY}a=C8)Dv((RDR}M~-%u}z; z*sCzsp@xTQBl_HJ6a6!G_}r)o1MXi3$j=cUSY!igwW{#XHfs}inmZP(9loFsx>oR8 z{!x>lo^s{kB1ih>&Own~JZXpMphztq%?UW3Oj)ZCDp5R}o8DZ1yFL>CrM{m^H}k5S5TC)KG#f)pNN z^plsa4qhZ_+|RerB$700&!+o@E%!^GFFFVOK{%Sxf|4hi+NbB5JUnmXs^GAYczu9b&DH%cq^ zI`pMQ192HI0J``zqf_4~n^+izDeksTBcbL}2Fu1{$i}CyABfr*C=`XO;nA&g!J0IXxH@1O3O%A8zv40I&_IBbD z2!joD=`;Z{U0QOYbmDIIM`)cpS)w#}le702ckh!)t??pSe&;-HsK6C7(EZoYfAJ(% zyoy@CrGSz!WtSD9A`|fHCZ(GWcH@v73OGIA- zTy6h!^Ua)Q`%JFdIk5QE6-R=Bbuf$S%0^Fwyt#LncVcK!0{?*7dNb z8l{EA+m1zxf37^ag%+{g#PTc6jdC|y$LFY4T;QI(7tH+ARTMvtOjq0b4o;I0(XTl! zuS9QOSAiXpQLtNjw(750dVFX`*ErPj?h{xF&E;#E-mTpNZO*W z6vI>D%PW=|1?Gp}@2j_xHc1EVlP)o_r@x%WUj}zNluhj=uttE-iH|5xlTllz`y%s) z!s#0Oifksn8_X1X6y^P+N}2_AiZtX69*ARip!C$kUHeUC?O2(5PQCe%)L zGuUoW6!YFg3i;@EP*y%H2xHtQl5d=lmLRRRTXNgif z+epmZ(u}Mu-~ixZCQ(3+5#1|hfD(AA%?N+C=4x1t3h|jPj z)MNTf%VwkgxYvGq(0+KGyvSshZ>Qv775xNr65F-sckv8&nf0$HAuW&kx0tn<XmkyC~iLvHWY$;@u_Xaqexzd2=lKMNa2hUT{@0~7?ME3{c>bzi4~8j}pc zUW_PzstYB%oh1Iv`y)<*1vXuQoskZG+MzvmSCSH9-yh0D<|-4MWBT)3Li>ac zNqW2;b2S~`!{+@Lf*I4O;@xUTyQbqL4g~II7sd?(Pk=iEhs&uh*M(E|D;kX&zNwSI zmfe1li?sj8W<<+dl(;1$dd2~6G@Txoo}{dYiTu&uVdra6m&piR~LsZ1#63Kw~fVv_(Y3#Lvw&^pA!A3d}uuK9P^iJwp%!$xOUZaz`rMsteWeyx(( zj`m*!pN^mq%auZRt&k!rl_BI`v>O%!D!r`f;4!Xy2Ukt*Gwg02!;&INHBl81%?tJs zFAF|*Ma*v`^@H43bfpPkRLl7+@hz3WIOSPf-bCl~$3^?=f2A|q@R6LgkPy(cPjow( zU2e_hZT&Wf?vrawp7t3n?ul9Rnhd{>ZE#nQ(^Txi4X8MuPLPz%)E0~k6(QgSHVJ&^ zjk8p8B`ATnqEKU;z>nOHXL!NUns{}!Rz{9*bDgvOx^{-XH?7Ut(vq+!JVM$aA{fpv z;YVyWPwAlFDU4{;CCbO-DER0f2-yGKHzxnYK+S3s4IN~>1=c(t`4dL>r{gZzD{UAX zpTD$ll8wL?3nZr}2VLy%7pQpQ-wYp^1Y{=9DHG7b`GHjs{WbS84)_J?!e%_By(C9G z5m`=P!~^k3S;;*MsUSxh4UGJ5LjywJlfxaIzWn%7w1{tjPdG2Kvl;2%vCqGqxEdz&5KbmIXgX(GZ0P}V+R68ssSI^sA%j2I8yKN}~R*#Bld zBJXkviWq}Gsn<)bIt(bXyyuX`->~)<7^Mz{tCE882`1u!S!!w9lXiYrf7lB(=X-^CKX?&~-|5G?``( zmWec)0Bsl{RG4Irp<0=ur(!BsFv&7NG#mGuOKRRKu{#IkfnEbUh5rkBxO~ajaBa$! z@p=MjlnumPQxI3lv^+5sX5uG4YxN90ErlWX@VhH?^_HU%VakK?`Kb54pIKuli?Q-RaPmF=WvvC2(cMtNAALJR@P%Aj= zM`D-B0SuY}!~xv>C9L_&Y=-M*1a;eCpK8Js%uKj2?N3>AmP^Xa-NPv-2rh6mkXUXg ziN2cPzZx~f(_KrX8E!c~F+Y-Cq+Lj`h~DNkVek$h`@j2*Myqpy8(jbUW4HNFuXFW4 z0Z<#+Y~BWpO0cqs?p9E$V-F#{I}51}IURgK!}%B`@7Fp8`sJyRiAX)<-kf-ZO=#mi zZ>&N!O629$9R_!(!B*9?IWCgnEdXSwXERdK3J~%*Az>1>^gYUmUTj*Sg$6SulzH zN`}A5{yD`@5iC3i2*`0 zCihj{a%Ng{5v&bmyu*xt{nxhw{zUSnV4-WVQG?cn=GxyQ)#jWC@<;d-%NrQ01Pa0^ zJ9PrmXpm&*v%J1Z`tSu|w$7YEp@v*Y#w7yGCPIbo=u}rA(oQ$ou^z~ZF4QuWf5-Z# zj8W+*tPt6`dQeS=oV|}ez-8>0?Axyl)5Y={?FarXfE^e#5(q%ZPegYkX#9uwSAi~t zFbcGca5B=mcza1HfifJvzW@kG{xZcQbxwD3nQhHRoVjyKq7U$KV1xnRJ*bYD>(s&D z?i|X@&b7_XR4-f=HCBJgs=hbT8DyraO#f4pA#YfZvYQMiiQ zSPB&=?}?~9l}$7izzW%Csg%Q^9zhLRmy~Pqmj7uKSG!?OmH>aO6fk@K~N2Tx2jG}BH&xQv&8)MEH&xbcZe8-6Q1z=6= zU5TvQt8XRAA^AQ%5ifs$tzL4^Ze>Oqhx_k3kLrQltM~=&tO5q3zWO{C`XJ%iX^YC2 zr-)e_nRMe6m#|E&oJzk;Vf>zj!M0C| z^KwJQL%&oP5Ea0OxX>q>f=q6?+lRY&VOQjU^w=Oa7AaV9;hi%$P#?yWgp$zDJIH|_ z7fuxo-t$;s&(y?a6!2*}T#7!6#fXCc9H_TkF;uUA`2k0X8L7W|(5Rx#9`qm) zEqZxKix63w!RL`d>a;baS@&ROQuO(R<$>r|id_LaT-yH&zpmWiMy*L*i?ssdI#J+NcCpmJH>H4}!RArTfx{+RC* zs1qp+3TY1?{K6PpI2v+sE9%Ar{{A%Wz!<%6{O(gr^&}p{_5b29kvNtDM1&bj-ciG7 zBFG*Hr4zdfz1$!}f`&4W^(*{Td8u8f{lGreb<)`~a!sV!{LDxnBeMM31MR$+1nq9p zk(gZEV>&Qh)jqA;2Em&d>Nh~N(;kG)w>;zkA@+88m>PCy?7zpWz`y*YwqIyFsSTD+TURJD=o%qPnh%J7=bPGda-sd z*S{65!Cw)zD3y{gSD4(rC423m8tWWq zcRgo|f=~XSIPPfrO|4B^^`tVRcDsJ6pEkTyk0?n^^MKp@D}xnX9G~0#UDUC|B5bDz z)kmc**y$#z#ds#KX6wI(poTzRDvSw5LQb0~hC__gI8^dQ!}W?5>HNkqaXWD*?Rj`3 zgCD+6M90o@Jfj&Nh2iwfsZICtco8iKcc7DXbyK2R)4K>)_UuZA?rf+uiLWXJWuCnd zr0i$LOrL))XHA$RTfumS9rdd`E`w0yS zq?+95{*0POp|#q~*%XAEp;o}h?MZynBk1+z3Pb@;JA)mU2l*EMX;YZ60!hh@p)12= zax4_$SZvr}5uSNi_5J*OVkz{~f#tpRLJpz|vSEa&pRdU)9g2mgf$jskIoXX6?=DTK z%KAH8!U>qw((>t5@BWBBXSb|^lXOG*+r}5?nk{G+khZKs>L>C2Nq(cvHXuc z*JI%rjNP)QFH1@U#epOT<@*s zG)zFW_FuZFA!RDV{=gz`Qe*34_aJQk=0J@RWiD4nx?9d46*U{^Ct-eWp5uXx@*~Pp zj+r3A8mrJL@rl1tF!;gYF71{%8^A=g)C7uWG|+)r3wE34M z>bln#yJ^`G52|wNEWNBd_ml)C`xysJS8iYD80cp+z7(Hs=uWj3cNrbT)5vJMrllA0 z@%*fDHjX3orFA#vje;9zao*JI_v<`&V~hEoB5cm@**Tn%tNy5C{VcEWVJUGi@wTSL z3b~GI&XM!@rCjgOp+CbBt^gf)$u#gKMQEwd0G83)C*+(rPx zlaeFBRb$B>-avH@>d}CHy!>0rH|N&LiaZ6G+#4Rx+4iU^C0hIpyWGN6RvYjD1+5Ep z!#y0fk>XFgEZk^2+W*n?4UB-id$SE{>uSbRp6oZ0d>UmN!PL>+)*kBDq{VEgE9vmUzf zdCZTzhclYqYjWr`#L9k*tV1R2qO=MN%x3<YU)cNjOojC3QFgn{RfBvhP}=w?8D~?LL6?KJ{?b2P;A{tbgg0Yx}v` zTT`d69Fg#3jPY-Xcrd=7csu6kZ&Nkky(Izq9Ju|zUOOsi>%!*TI-|eaPtqG#hp*<` zuY@WklY2o1&krq6k#6$5EK(yOG1egVQhCd>sVlrZR(9vGEWO+(C4^=|6?1NeD#@`u zqCES=okn5;ZeC@O2pHACkp$4@q;HBd3Zp(DoSlGzVa&sRElCk5u^4^(AEbUy;1%k3 zgsSi85P-{A@1P=cCEM+4*)8`e8C)C&+4p4Fp`>6PyvedS)h)hhbN|0W}7ao+xF1S!~Nil^LW|)4db+!G1_2A z-~W(jOW3K3?-B5++&FakWRjJol(8eIRrSSS1?7WAI6GXu68hEJqeOV|r!DVtLr}F2 zvfKi0FR)LHd~XagIk@k*KRb-IJb#8aO0mbOwZJj2UV2QtcB&nydMEWgKyD-U1l*W+ z>Nn;ourAAir&BtQ#&r`nQzoBtnC@*iZ(U7+diTZ#9}5C&OU-~)kB;BoJReL(t-PG@ z>-doZr(S?#3H`oKc#&VwX#J`BUbwDwgkz*r3h9>FxlBuZiF*bllx6PY>eoSyk-g zBSf2|y1EtQRe}DTjC?A#yz1H;1K!uj6fXk!z&1Ytu5DoLU57+4gXq$hge5JE@(lnL ztG^2bB(`iQHV6_(|1LeD*JEftY4>p@&EVB5l%#Bqle{&dJ)$ZV3N>Dg*kk#=e}@&3 z)Df>C7Ax|0JTl;dRwJKDdlU!flIbVCyE!Wq&^iXlD~`CZ;^gI4BPjm2pBSu(}mp_{yvz*PiGHOKYRuKRxB`)8M+PQH~KpK${u~#lJz~**J0Am_kLS~ zYm4Ep1R`^l=pAg5WYFK1=6&*5bS$Z7&`a9V07)O@KH4kT6i3s7b7iptczQ4?EF5UH z9+K1!m_B$ADO{Y&j1#7ueMIH71^orBwHNV$K~-7S7T=Dzr~d zGK(YqY(Gn%V|0^pZmUzYSGDX!Vax&z1h?JJ zs9@;yf)?c-Bv$zxcj8P zr=Zp!Gy=pDwmBsYQ~btUHQ@z+5D7laN8OnI&M>O;q60a`^M(05-xlmBB2l4my1wIH zpB9!)7~1xtc)~ObDqAUYAN~j0vg+7vtX5l!rHg}L?QQNu(F9jLl_TpOOCRHWIzzSze> zHLFd;Vuk#^C~qecu_SDPE|Wx9p)q#Q7c+tehWpe&DbcUQNuNJQIdwiYl?bG>)@+~& zG-umzBngd99qczw`?0-Gp2@4j=|U5x7Kzx2?~{4Y01cN?Kw2es%H7e-M%{r^_`aD~LS}9UOg+DxY2Li8b>Wg~(Bz2`4bt00`;PSLs5a$g0&@Fs3G4}>RhYKbf z#E=kP3~zrfjdwOyARh|TW530RZJ#fP>mdhULC#cSjT$UH-zt*5Pk8m-CoiME8rkHF zzc-lHU#|F{)|uw(9C;vk*RJeZDlnb8qkHk}&AFfwFRxuJAceIm5$aPJi|@PT zRQlj#Y%_5VTi~jvU8yotU--B!chhVTh}XJY&Jo=V^q$yne^FXSAIYEk7@GN1TsH%5 zN6eJWgq;fRjtG`pp*eCM5a07*hdyHDzv#06my4&1cK=%*E!8^^aNRdRxSNx57EPfC zCJ%do=tT9v^m`k*X#4@zQ^jKdVwRs z*YLkkjROLNmY)XLog>C;BdUUNQMn{IeD(LXb5hAnVet5Z(=~<+IQy)KyEc2W3qej8dt>d3?v#bYUJnRjUX3A7?GzbP5?tN*$UQsYc|yisf6cFi z(kryNH3gw#Bj270EjMQHM(%V8nTuzskafv$@sZBXlgw`Bn;sZ)xQpD1H1tRUr_4^- z>BL`hQ?k|;kdzmaA^Sy2~B8V=jvw-yXa@)1GrN*XL1VmMKmX>iWDq*1cWsDx#5bPB~4D z?qSTr(r}5}+fYX9U9INcHb%_%CUm&V3bFC!wlu%D^_@Zr9 z<(ivXrl=c8nGl@cX>RDfJHxt~Zby!1g?2JP4;YOVnrM$Ru(S2&JqIID?}(=A#>}bV zpzaNq@!+f8EgMAW%m#ipNo~%7q@qVBQnUo-C3E_B{oyQ9j|Y3jtwCP@cs>m4%g)vEUK_WQ0eG8S5BGL|%vUqsCVtgg z1}AFBV{XGZ+inoAi!I5Z*lx)WLy(4Wp>#Ny@#{QmYVCXYX0KR^F8khhePX{ zH4$Dd6#VggZhq(%`vL`QoJ6s_wS#oTt$~j&-c__Hatd>WxbgeB2KdNa>hBREjwwKo zFmzMg05EI)*9E@PvrbNNz~|lGb`5qb@;w#luVpKji~gQhzqm(S0&~FrRlOT_L{mPD zOZ$dot45D@F|`*5tp1!r(n@!e$c^vd>nrk8H`En*&KM$<{$=YcDkd$9+xfZt^)9i%M^SJd2Jb-`6G$mD;tPV`zD6xpkkYA zJlsJJl>)8DFI)9nx#xMNA4VNHKKQdGvBl0gOfl<4t2cUd#!r8T`tRLGx#r|ne#>(-cOq0$<>H7+rI)ed}ucfzBPlR-@ivP6^lCG zDCM|~CC_5=V-7RC*PKADT5-nKZG9U=j*o8K=97m%-En7RwF3P=xpNIAVZXk-fxo!Y z1Yc3lPVFQ79*{O88f0nQ?_BJ!`En7Hy&I9aW(kAUdfK4jiSk{f=~#4rg1OY*<9!*I z=Tin0>frlp!r`eHYM%gC-tfYeI?{+;NJNxJU`*t^Pzz(i;UH;9*?IYK6#`LWXf7l? zOk#~ed+SF!4JB|iG7me!mgg-DJFOrOD+{RfXbPeu#0?hDIwgB91_Uu<@^w*Tp3`** zs#$DZs;>7q?0xRtf}xeCPS(#CbYHM|r>6W4M@4tjhHFA&^4T|*d-%Fxfg!TB8ktni zm(gvwv@ZHS(T)V!^P5k3edbnEL*&a@_si-|Znt<|+-K@F;DE_p71YN&rUlJBA$;r5 z)tqv>tYmq z_IbzQ?ug$e(cg&QLUbEz2+^7K->gmKF+sO%(xo9xa#_q%|3Hs|gg#woJJlYfjAnDw z76MJ%-QhH{;3R*30G#5=pFG5=#04YK@iS#rHx=2?vHKum9Peertq64AINBA_VcjU5 zb4I#-<3p@7h%Bthl{p=6-I{@WW=^X8L7aH33RRw?nCg@@tp*Lv^HSZsyrbDq@hLQ) zpD-U45z844)e$OLn4dfL$66>O9`!0O@ggwHHGIg(hAk}O^|4i8$A6Qp(2Tk~d0X(0 zOZkXjhhVz7jI89Y+)~XT_Vp0mb@}}_a5bD|Wxt%?E%?l3Xp|YNCC9276e57;$bT;M z?FPPdh9X0Ooq=$^1vNj#7WNcGOR`4zDZ$%d9yOm6j(;bzP@?k9aKCTEF1@<`arb&k zGPmHehL1NP2HX?4G`6k5lO%X%RhIWzuuU$_^l^dB9f+?sEl5WkmkCUcx>0C2iFuqB zh!BwgNH$T|T@0Y<)fniX;(g z)}p7&aq))bekQH<54B95UY<8llLMnxx;kjLd*5@aZ(IBQ*lVX$D0uhNCw<-0?bm3q z4T>UTT)2&w6Lcyxc*$#=&MhR_>C;d|9l^fMTZiuYAgf-CLve$0S+y#tkjd-uN83?WR;8r^`1NR ztb}F>{DGIMm)v-?YQa7&Kg$ggNe753#{8k#gyCYoQnb} zKH-%C`4uOIwf@}$?3Q?4gOaw_Y75cTU7j}@ou{xvKw3}ZIh@U4!cLCh$q1N{1Zw%b z655ttD~gac?4);H8>=OAvOYt;tGjek&f^mhPWcKV{(N^#Z=^Xop3=A^my0*`uZ8Km zgbFo-H~)PS1nr5-Z8AU~9LQAmJ3eU70cV(uH}K&Fq`&2j0JG0N=&e^9c8dbm?~-`# zJ`n3e338;SN=OGh-q)B-4$j!|Gsp45mPV6P1`j0ku7xCXw^kcD$9&U;@Ud}|sPd#yM$`nbf zq0}9T1p0{;^u@Jhcy!*S9EbJxM^z=;nTY#vPnA<~8X>H;>2XiQJYLPYo zs;_NBlN0Bf)R~*1^IO1Hc%lch$n^Eo`}SgKOO%Vp@OzY8MP(CtXa<2hR!Xl7UB@0g z(FuSE%6cjg;kt2X2ep|kH5Gdfep~e!QaEqE^2<}C74d$-T*T$-2ud&?=}1hL9r1NTWUwK$ zu?5c)vB-RL!EJ<+yX#7dlaihiiJw4F(=Z`vrqh5kPUB{nDL2HZ!5=CmJ~3p_I9};~ z&5X`Hn?ttkQd@$G5rI7tQpqE%^90`xBX!!F6VXz{u=>Q2|9HjrQD=vOkZK<2QmMm; zF~mVfuYMnP)k>Ms3DmDnW&AG~Jz7YRR#zxH^<{#oC3kIR53ye(qV7V5=u<7W7)(s3 zBk~saeS4X2b?>m=7?8o}HSe3sU6$gV{dglKj&_rFs}p;aywUG`I)8-5zR=Og+SQ~dGfW_HwOg9DZ0rHkd1j}7 z6=jpcnsHWM-nZ=p3AJ5s*kuCS&%_9?X&B=?og;W|3LzXFh05oYgp9VpGX?+2>Ed{j zutk>6hC~R~KM#;6YSY$N$=fOuoZ#$q{)`mhWplr%#=VhT~(k^|#hVrR7+B@rMd4JYTz0WxT8cbfpUf>I4u)!LK?}VQ}ieNJn zm1maCHS_+P_Mwcj%SMD`d|>yL;vAcwNv>y04B@8sgK+Q%5+c|EftbhDUq zDfc8-)>H)lCqZt$vJ?Hx$)5punazwJ0LUmf2$aZ=4VjiJV4kpDgV#4G{8|Ojdh+DR zfer*pC2*+_D875&k9u!)c*9@tvbbWw z-f^$0?ZVfR8f1Y@IUr8_dRJnBm~No2?;By7U>5QtkRl%f1rBf@K<~H<-tTi#jClXV zE3%c|Y>x3%^yF3~q!Pqbb#7D42O+*ED28=ftW!QMJU2b~VOyKVJ^ECD& zj@^Mk!35lghzy5=oXp+kdS8olI^)^*eZ#Bkq4AIPO0bcjd6^2PVzU)Y*e=_;7Wdfc zn z7D!l3RlkqDt7Re_8b_KN73l~uRP|4#-DUCJWK@Dbl+cD2wM&|PGBWk()FU0&>``xkwc(eNV(}y8w+1ieWQBV@*V6LLFC|cY>#uIK^HIEcGOCMk?NbA~;XE6Ws zxSx7_k=#*8ga$fXQ+OHS&7d5MP^tofrYHd@IlL<-tXMlQxbUnmtg@nxU8Mm^0g*6) zRXeT!*|9kS1rv}+N?Nc(iCMwnohA!rb%Q>bs(GwRDuO%GPOkXmYf@;(2YEd8)6m&D zJt0prG*S?P1Ji%H#}X*KfI>92GM=dCPH(Fl%$v@uHU)vS>5vUDb(#vmzz(n$s~V|W z_Jn4zifJapoW!%kPye%@dtiSdT7ky05e1(S=2KzF88|Z$_a?&HLhGO^+bj^2 zsPDT<1uYf3Y17% zuyPfwbRAe|3=E;Mky{6b=iP}3t&F(wATTie^;oe~uF~%n2bUq)9*>N-3JyB2+=wkr z;ny>EqoM=$H0x^)DSjKZEnfP+%_I=UzgK%UR`j>2$OCQ?8*gB!RfceLe|VHo87q~< z5Vnvx2TVB>2}gfKkWCQsf`WuL;sjg4KyA99Nq|nfN~rp?;Q9k-R>C8FB$u&7G8Qu^ z`S~V&X`BZH`Hdc&xuoNM@IN^yKp;>meiDnPYHK{9YMQtZV>kg41Ik7+?hwxSff^n7 zAY7#EzG}>IqeehQ`?>(1B)V9OBffxeKClaq#thms2jjBt6-v>0>pRc8PVf2#P-d_H ze`p#YP%;4!V>HXk4seN{#EWb$WSsIB5s0hpAdP%I|L6IjT8z?jSkQA=f4c*SLg&Vi zqjkMh**0!#(aBc7bgS$BIyo!DNDA#*eYlvcpXs( z=@2dAZU-qeb9RL?Tz%jOIbr@#`Xbu0rK-jTv*hnL`SVQ>lHrF~Ofkn+gmtPb9E%hNa&?~iod63d$l(+f7=2|W@<&2}sIfV1dj79P0fEc%yCaKFiQgDt3 zaEg}jk{Kw1x|_#2k?d&%{3o=v2lD(^jRBN{hV;0U&=kM6f$a(IY@j%36gm9pKY#rr z`rqZKW3}wQm~fT^AJ$`97#B~t-wB5o5mQbuxxso-I?=%2+=0v{tHna2y-g_2i#_yZZ}tL&hqB z?})xM`34~2-6tIu-T1A3QbSmCR_hBVoa1f>G&Yv-*wcPK(-lU1$1e*>TeAk0w9w54 z_E?^RLHI@o-~HWjDuJw8gPko^lFRL1F0t!A!f9{siiH>jc<3Gb@1mCe<&Dnx=YxITDX4#$XwwDLw-^mGq^g$ZozLRMP}90+$KtXg2E!1F+bknv%Ld{w+7 zK1&-M?3ZsCE?p+f9IIOo%OuSLOF=_^{BN!Lh<>imgVu`$<62l^`NVuctd>dP9>t6P!n)}>!w>Ct#?U`x0$@oK_K<*^ub4xSL+DZtY2^K}$! zfJNcB{Tg38dmQUm`daR2mA(lRO<~7oW$9f3#R~3?1Z6Q^p!TUs3p23Xr>8eB*lVC?Fw%B3~erQtu zp~NEvvLO%v$w+Llg`@Df+l5~T*|kMUT+-p{v{w9>_o)H66%^mh(y<{IB>$Ft1tfdC zm;3jiydY9UL!wNu_&d>WeSb~d`c2O>Atpe5NA~u;yUuy5cu`CsD%<>LvO*C*0%BW< zcLbBXxN@_WWhP5AX2;qdEzI0QquTcr3=TFJ+}0XPdR&mwZMlRW5w1!gBH-;W`x=y0 z&j6gfkcU}oHqe16_J$ji9XiMlq#|6CVjZ#mcjTY|5uy_mPi9bbomBa$>y}Zq{CT^! z7c}j+B}i9ErcyTc`k=2V3_hHQ?Y@S3+|98_v&F1&dcDfCWWYFv=C)XQi}!n)9n8^) ztZ2#(6bX@W7(g6#1c5fVNaA)xd(K z8bUwAp_KI+1(jIyz8NTlw%*jkMuWTiT61qFl3Ma&ks%TAx>{l`)&V?j3GrBM7a|b> zF>jX(v9l+KVV9$#RY%%IqWXNMk#p76gJ8pu!eBHs?0qn#X-KkM>8}XLAlg*+G$|Cb z8s{RHVR^jnuk=sK+U)F#Os@rygDwW8J(hP_^y4kmR>gTBcX=3Q!r$U8q#a%5&*VX= z%f5y6+gXF{xLV|e^1o#$))`dBpDkOm&Pv6~qfq3V0_7ETtN(EUrAj0)@2s?z2+u>Vyxvt3Z z*t^rXRdjpl3JF#dVD;T^vcQ|V+GfJg=bp+V+8g+(rdsXRAm)yhK!2?CBpd1e?rNgR zUAxDKI4U=Bk4i$(gWa*r4H*M=bu+vgjq2zoDK?pUNBl&(KtzOfpZanvpUU1=^X@=I zIiZ&_6u6$s{WOh(Cpw761V}WGyT@pD)_M=n2=A=Ib2X4lEOkFty1@f}wo0Y7bjU9x z%tZVCd5>2r{w4foEBIVSbEe9Q1$5BEOjxA%lmkdDsw=Vf01y62sOCi(<0p^Xp>0lsq^Iq(Jzc&P;C&|{oATs;EI7=^kD>3= zihymS6hL8mFa=BLR`5HMH`AK&#GJuMhp^S9pVK*fTZl{`{N@9ngx6UZ1qxxA5bgRC z-}^72^q(D=2vW;=1~ryR%`+bhOn<^OHbI{a@N;qBqCvDjcsyDA6`1uP!t*;e!xz4eWV#TwA>*v@0@?1cpO?~ zaUmCMexDzD9)D|l_hOZVq9MNGb>SGUN2$D0{?vTmwPuIr@4+&w?kHeC6m>nhqY5gI z>$-7>ysS2K)!FYWwMp_c{eqost_h&mgmr3+Uotl;uwEv5gps`X&1RCfdWU~n%zNan zo|K;t<*f|0sHZkIa!EB8Ur*ofg5-CjqNZ6xE z@PaC+$Lwu^u|P*i$Vrvr=2x1z;n4F_5Gww{pdAbL!jmzXte#&38T8x$J)EFt$lCXL z)yDp#P11+mXx6ho?Z_oexr?<$i)Ul3Ku-iy6X8~V5Kghvii^3>Bqb;AT410=3=LF= z@eiJM^Gub&)GVP;^%fL?$a-IADd#aXX)Xjwq)v)Eb!HB-3o4C?>$8kD)(0P6_PGT$ zcwC#wxuwN#z}IbSWgn{}jBH!_`V(g*z32Q~wRMNJ=tvbW`FJc{McH9bt|5LAbK&;y zqJHe&0r8|vdvu%rS7aLt4%6AAe<@0FpgrJhs&bsk702?`B7+}D>1E)AJ@NjO5RB=2YqFWkO;%Hhh#jYlBG zL_8j`q}yt8%G;3?a(zNE?I3qTaBkxLb(el~9?|G(&&G7Q?LqHwjR&knBr5)O;=s-8 zqkgU;I5)j~a)GaPBX^3md&^dK4q7wM_OC^_^}wmAN6l&(98OJ;QU8Dxzu=@RlCz&% zTA`axv_Ty*3ZV40HnLVbw##Sfeo+Tix-WmZ_x6i=73Pm zT&e|E4VJ=M520e67oHUop$IlE)&&>w>?AKndNShh5K$f&hVyX=DFj0sz{BdazQw96 z;+6pRAZ@N|wp1lLJg=aw1>LODWmbWt^J~sUt7w762@(>qM1MK6#V((hB@7UXBToZ& zowyEpO=UR#izxmK^QOS;cF6baNriu6|A)mv@-ya`%({*T;%?Ypy_;f+rzJ|z4}wY7#9 zr595kcCf6TX$k9s>Z};cW0(De6Yzk!qX_|-YX$^qrD+u(rt^_NaE*f!N4_Bj-^UQR~M>gUuM zCr~UW7gX#KGrCb(iy{uzw&ugej>ba=+IPk(J|t+OtEU(aO&c;n<1pv-snOAZI!ayn zmLuF;NUxLyL!}#TIU6w{T1~zmA?~9_I*7)+K9u>E8^L9W)nONb7NSL$6E6qB#{L_#^(5~eQqhC8ziC?! zrr%DNin*vzB1Gn!tX{ky)hc-{tH4=4Ui%O6e;Ts+kItz--~yFEbeU|04PQh^#w;emt|P zRN*~_%lf!Mkd_NX(t<0`gNn{cw$nf~(u3HmqRyRADk5|p)!xI}iyM}43jR?JSx(V& zjwMQA{|+O6O6eu;O3zb$G!k^6O*J52zG@ct@+IAIo|ZecR7?>9h4RQ=R~mCewU)~| zUf!Bi53v>&1i~vTvVWLi2iMYE>WRmSn9&OCODv0E{EPym*qe{XDwX4V-Q$vA5s}1f zAe+G~xS-_hC#}{3o^&hBQ)ZJ&qzekcyYvr{!E`ekcR0o5yz}_A3^<9UIuq8MtEW?s ztHDGx{RusmTOvaVZ6;+6pTs7GX%C-7r>A#HbyABE4A58~ssnn!y~j9uNfjnSD8&VK?xec>_6wD}jv_XNRC}K0 zF24EDbXQWbJv;9;GR3!tYGHakRSNssD63y`O#Jcz=sY~i8~%VZXfh+khWO2RX;4%m z3EK%+@25XVJ{wS!Ws-mHMDMk+`julpJnt6jj!(UkTxjk*7wM^>R;qK}YC$8a6U*iz)i2OUFUnt2B-=Ml398sYP7Wdp8TYNry{-WNGX zbMY^bIuX^FJ4i-rAh}#+uvK(~Ovd>*+WyV2S-zg>idv%&S$$rRCBGQL!#Dy(_A~H@ z^D_o2jA5g;D6W>gQOH=X-|M?Y)nR?8DumFa^Is+E+&Y8YR}ep zzkUlj5h1B&*sTwmlU8;U?S0Mv`Sx>{L2LwwGdCiCy7U;z09Qq1bUIeumi2>Lv_ihv zf?tRxWhf#YctO-fdfde+wQOpk}|5NWm5-YzHY!y=N(L)I-d;op`6gd}f|33Dwj?P|SjWjuA*2H=WB< zd5mp zKP_hX%Sk)Ns=sQB?{>OX7dgZ8@`%zu7ash?))2*5oWUUusys(WPx>)9+>)BNSFoIi z-&=bN_fi9etd9?boR#tXY0F)AwaRLe3Zhb}0ru?Di9AK6iDcOwyPy~ zPP6}45*lq}`pj>BFM1o)2wuz2WI;)okUB!7hKZ6QiC`~oWF*XdQxQu?O(&HFs<3dK`F>xyPV{u+(z1(#(I_p%zspRNs4+cQp z)xqqxxCP9e&=2ZlUIx3TQS|JAat;jRAxgP0OZ7oA`rlotBBD(q~NRLTSPdyKtg zt}BgB4E1C`Hl$`9qw{4GfD@>J4%vzwe=wbP#GZ74R+VY$4Rm7G_8#FiG@3^y$!Phz zlFzybb#}N51LdI(k&mkujcumoHnZ!Atk@-g8Xa@tb<0Iizxuc5S{1{m;+(sW@Vo1v z0(}@)^GV}YiDm$jizh=e#BVumE2zWO7y}|@3^H>-C72V7*J{;}M`X;2=JLKSpHhb# z+QGy?l_FGKoF>g*zsLad^x5Fj51|!y9)OxQmVqGls7QcmuTXtFK=*HLdCdHxEwKFZ~O0+E0V zm^c|alD^r)A$zV~Fa2N>ZuQ1AhD+rf-NzZag%u&(&gN9#G;!&*FBkf?=OV?zSU-Hy zi-CJh_u|qHp{oVXYn0IELL{_@w-Sh{1zdHIcbqiP&~4USkBOKcwJ)(*bB=0xMy?b< z$weE?mt~j3YS4%MnQid}%qf(Lx9i{&QnNrLm$$!$gas^<092=*+Wh3)6=cmUD`S+_ zD+MtQ-+K58^qxV@!F^+iMN-A(+$myufs5`=$x7+*gLh3s<($p%L#cOy8JSU!kv9nB zZUB}Xdjv`R(V>VFt6*Uvsk-KTgE8pl$km8x;j7``MD>6U2y>gRDhWLWAnAR*L2`JM zCa(8?F_swj>$7iVzPrrm1NH~DRmmS@VxWTY%wwBbI6UPF<6m7CvE-_CGFu3CrX?6? zOQ=;0;1Wvt`w50o(U=5$IPr{wz9mjXkbPuXmyHRDkS5kXeG8!&Ua!>_zDgliu{7c# zgZW1v*>f~QA7_}|-OYs9U2ej**Ob5=^DV5(dyrH%81+GDgSA^Nz&{&w{XifQS7Kf| zw`Ldxk*<_bk$B+mIOc=;>C|jQplgW?@+E0A3{jU_FU+(Ui-lJQat z)PkaQTVe8QG!i4vBypJE95mstap#Ue+}T7h2&jb zx%@&z4maK@yGXu?eM-<_WEZo*hZ>*S`qoIk8}}zxY_Oxq@3SJX#1dEMf}|aeyHyC` z1WYxfXPF^d>_MM@Dw0ziy*HYnf{4(|r#8wdYLpINxEUOsfX+}rhp!1K&prVYw07WM z?CF$rWadf>h=a=+JQ2=ycS2KfUiaK9pG^xs3=AA+D4|3q$TgN-1&pwa5;Ft|N|B*a z#jo0b+SdrdCe?3bR`9bVA? z2yqo_7D-e-FzmOD@ErjJzX~9&_64rGVF8O{OUrAAIhMcu*zyRsThmXWnf?Q|B(xd} zsi<0-(ygh7=V?Ag_krU*jMe3rrz(sQS0b~vDYJ400-!xWZ*ZMRIGu%ngykAXRp(!| zM{0O~zpw0OD}btP{_glZzEjH=*2Gm4*ObYiM^DZfFub@lY8x%E)W-GRlaEKUi~h)R`Of2G07vm0umxyS|^N7Ejw znU?tRE}r!2OWX?uxL*S^MUDt?KF<1D-yZ=FI8Tdux0j1szF-<)5);m1P^a|5rM3gy zSaGRXIavOtnO&~i&Afgg4>fYJgZd39@aW+V9qi!mVoRUE%~k}THS$R2F;7_VPvZX0g& z%SZ%>&+UkhV$CQowG6+1eXIR|bIH=Hb*~(M9OniMm)p?#orx0;)I5Pnehr(I+C%$b-nZ?R1B+oKv9DNt6VhH$_s&FMNjbnD!Lhi|?*@cQaoD-u@TZ#dafP$QOU`2quM* zU_3N;Ip4Vg3}d~GEek}OfzxU==*izHuGZqyh354HLuoS_r)wb+JyQHPtgfr^0@;xHCZ`7G*c zmq&~E4A{_TiF#yjWSDTA#~BXayFGNY-U??(UYDFJn{8;^)&=VL-Je|0P|Y%cJSi&M zQpaG@nVm(JG#jq8o*z`wxZ5#67Q9QqHTT`^T`X5c`37Pz?ZoE2EyKc37)M^v?Mu9r z;tCKuT|#~Cj#kX2F_NEHzkV-&X|5{eBIO9pa(TeQ*kF=Ry z@swxy`QOv1SKiL|kp2_Xc`{(8^7uS%Hzidf06iiPo>_k%z@lYhd+W7uw@1u#5Hv>Q zMgYXpmWsj#YNY~7k)b1Q8l$TY!s^fQ-*_Z7$VSCQjlrh2!tb~5cm+u5&q1xGZ6i); z8msVN{=X*8z1pfrZntqjS$~=vRYMd9y)eh~16}_Q6!Jm*Q-@-Baghjt&^)UfO1Z~R zN?43Qy;Sme*v5TBl}b|AthxS*&2acz%Y7Kj*{q;k13zCt*!mI<6@ECEeCv~#i{I*; zt9^iH7&r)>9ILs&pHdVWyY=Jt58)51zuiw`F#hu>Y4R*9LJQ7f56gff7@3$bgjJTQe#1D(ukg%k(1$YqK-1#bE#1)%1(a zNnM3l?N~N5wrzI+Yag=xA@U`G%Ijs2jlrcWEtG*8a!>$u-zWLOg$U${{mix>tGs`6 z!!+aKa6$(P%r`9BBM#oGkXTG7N}-yK7L^Eaf1q|xaADonu(JUA3en;qVE%lFY!8-& zrnpfiarQMPN%=pZLJK31JG}r#tKRVY1CHym1l3vGiJ(wmp;hdvWo2u;&1TU25`jYd zN7J5l^5E?dMyZ*#GF#%%eK|=FXd!AlU?1*33{#ZAERaZd&!|4M#B8mnaN%DPh`XYD zbP6s?jWfNZPKi{1HI*=McEN@66l~8pNL0|(I=|t+Dki>PEd3*1*TW-Y{?o7Vkx;A% zC4L`4|4WI5{EQh$i*9-%`O4gA2CH-q>uW;zoD%6F;dGrif1oCm1rQv0H>(x{1mY9| zfJ_jsR>#Q4tv29fiLauHm0``6Q!vO9-zyAkhw8sa;UQ>m*_B(&uYq z+qOM%GO=c2+t{&fo0Exc&cwED^W?#KzxO-m=Vn)TuUb{rtM2R0&6_c0WknaZ5NOdr z)}soOYx_VPi2Btc6VFVKZ)gSL|4G!*Z_BkP9btAT);#RWfV^v~z{@&w*{S|D>OLL! zz@sLX$EP!tohM@AzPqp`%~p;6VT%0&F7t%97LA6LW#n}JW)f8^jHmmOd%0$~wfO(j!pcNA&bopVpK z8|%gHUY9$nf!jmYI8G%aLG$WsL`vY(BGyrKxNm#O0E6(*H2$6u48B!7gAt>09j zUbXHEbLdbUD7Mp7TjBA%Z&XB#2uuaalKJqPCc7a>mRJ_Ge%1?&<)@;LNOnH zcYd#HJ0gpkO4u-gx>6c7hI^+?Cz`b+15Hxl9h-vZrjkpK$bnxiR0UyWGs_2UJ~Zb9 zF#qB-V|YZ&U%R!UkBnU;{=Jo83;m+oJZ9zy!KAqBCm_^Ptl_IJR&`O?rDl|s7wxa^ z$~-lb4}*`#hy{)yXZ>jjIzA&IiA9%ocW(F{!i#ORy%FE}?Xfj%p?UXqZeDCsNhfjY z9eF;1GuPCk^}OjqD?NqzJmCc!td(M}!bDOmfgPe&jIlw&g^Aj=KimC@UEsk&lU2Yv zF%mY)(l~X4an72_HCin|FDbgxL3Y2B7t%PkW%vP>17QIsA{z*qG7mV9*4z{pk`)Y% zwOIo8lD>l2?hk$L`hq;#)|H)S@d3VxI`TY~*1k1vigyZAElF62AY>dT9oF|J!wN4= zvJ--|n)9K2W39(b#2AzJaj%;m5x$2xWUnht`qqejKqqg0pVf5=E#VcefIT(ht(@2z zeEw3>KAy{<2;axdr&UA~3F1d5j>_lm?h9#IQN@V%t9gwKLot%*mrKXBu>SQ0`c=86 zBZQ=x&{I~d`+16ORl@xp9Vxd`Cf94Bh)I?rqsLuzrG*jigFhPW(Fn?W9A}ee2O?H- zR^e9!A+_b}Yao42w3qwUDp-Q`!I`1dwja^)^lh6}o>dk6&M8b*%QU*S{AdGna^B-2 zW=)-``ZMY@$(1)glc1gTLE8H$7f<@Z@21ITF1cY{CQIUy-{ zpek~s`qj&|5V7hg#f>Ce^h>y7iBd$Qp_;UV>@|b3O46!_(GMTd$oa5oj`naD`y3*C z!Pm*@`{oi`e|D^Pv+(U?{HkZPUc7$*;d!ZQ)pM)018Y<7Z5@CbQHSGQI1R+oAT>YL z1m+s|&?7wE3qE}MFD_7@#-6K{)@{JWekuD}1uv)bU^Fpx{b_mc|7dynTyUm|Ch*2b z-Ilb{l-xJs+bc683fe|_yqT<;7_yA*3Z+~HOTLsItdIA%~X!RZQ z?z;L}UR4+w%hlR1>L6|$);EAXlRn~De@JE{u`rL5VTXXg4-gabHR z9P{Bq4QcDwm_{fXHNGJq_7QkBk;=6gEw-y&#;gQh9gT#~%>jIh7OaBQVdqx9ABC!q zK^9|=S|(@esm%Fo>u)yb7-q`uGgItf1ZTlEY9yOuf5Ou4$XP^AHmxi>q z_jW^($&*n!-~c*(iuj!5gF5Xpy&gr(=A(}t(PUqMFZ)-7})2aY%^zs4?7k}CTprU=_hyhnVs!Ca5wx|V8eKk_ zj7tZ&{CfXL%P2JX=Wa=5=HX}>7~+=z5~>Lei#=ra%K(m;c>x@R;q*%)xudUIa0&LB zTn3$PD9@-+=zrAQ#zHKI1iPWj@g08U!TMTIzFkS2o!UeL^9anO%_ZM4SisX=EP;#y z%MkeKE1~(FPj7f6-y0KmET+pq$gck+rjy3wc=IrTkWw%V&g_Kd+aE~83M@u{y^b*T zF;VzOpV6Bdy>~frFowKdKneM^LF#Ae^{-Od=mv8$n zuAnU62MWHj@g)RHO;e<~d|eC8>Fo^0lLW_K)sp!YOL32xZ&hcfs{q%RD~2p`3suc}9cPCZv#ATp$WogTv8!4qLfKQ?i^INlW3P4R+evhs zZFN6z4Venna&UkQ|D0e^7NZ}wLV{>yr$HS&0F$VnsA}mA_~b}%CiS2%Mv*^7|IF0@ z8ua^YvgQdUL<{wB842fv&O~Cqv9cly{=_Hb{gioNDCKc9JRE;2pVT@!DB=9zNr~sJ zC!}T?*X4d7{e_!|DLSP3HSK`1dB*7zVBPt=mPL4qU|Tdo!ELNGqT(=6FO_)Q&}5iDkB|?&O|l_`7ol0`IDN zwvKoI5r4kAjZ8XPFrOWLN3Um9brk@zJrZcybzGqRTW4^{qygvn|LOe=MPLGCvGJwfx8`(ngk6_`GO9#VpF-wO8R=)uV~3Q=-6=HR zkWRu@`<}cIL{S*pfwJN0+4OsCXpfSTYhE|eE zY%PTF4>L@@J^#2E*AE{TA=K-T`Z;6*+!Uy?ExNzw@PtedR8vK^p}PAuGU$&1T3j$3aHbza%3%K7nhyM*LA9WtVw`XVwT zl4{5wBL6sL+zM_@vL9u;GsW12*RdYhi;`Ond_q0{T~oNYW8Y5Of1awCjA@}b-gd8M z`GjVP97aI~Dh3eO#eSxd@nTay^5MXmX32fIHVzWJs%I&+C~0}(%cATrMB)S?j$O8A znozX_h3yNw`GlrMOO3qDHV<9SF0R|@d+oWiCwm@FwsNhbqiFwrZ^n+Rv@#eVEYO)2 zBm)g1PFTPGmYB|tTpamTT&RwP*^3j?u!Ar_skL&0$!yu$`ggu*o37vEz-XM1crX9k z`w)Isa~2?0<&RmXLKIsz^dw34Rzl?AkEO{(3Vp!tYOe%H{eI3))P@JfqKXEU^k~sd@xga7OqY(iE-tsAjqj6q@iF+L%iLhSKfB)ddCT`|JU_xo z%u639j4No15MQWin&Q=66=Ev4pkNz9&fOTAMpFk*{~_D0wad6?HuJ=0&r=-Id7u@- zmqdn?NY8dK)*NapMzJfDK{w(So!L^qPXb#_QKj@{$v`$ zn+7#{{oLdVI9qjw#R8I_Z`#87EEr{UQ!*9*%`_ML$R{GQt`E>*Q1k(FXSM7u|00$7 zJ*?is=0|yCn@2Yqs;iFiCiM-g;F4d^e%7TA(pd3);-~Az{mI6ps|q6tr!gW-Oxa(w z2Ft7_5E#+#Oq-S=`r$tw-&^H=1I@Z>o-O2gV553 zzWc~)`)Uk}jhHNa#x2x0+$FVjO?S7V2VN~NLu$%hj91iYzLK>EK=>O|0tX%$ZOEwG zDOSB;j{jxJw@d87%uKmH4OsF7MTMrAl2VwV3FQQv9C)bM8Ri|js2F-FEA;7riIlx} z7~aPWe6z>(*Q(T43&&}t!>P-ntW@lDD#^(Uh7@?s6`hI({q?&S>mAt?-vIhhck^F=?@p8f|M9ir<+VQ`9)7M zqttC5q}kU;6J8v~X&D+Zlp(o_+_JgMj1R?uL@C=9qTkVM=aJ>c>M(Y3!{Ug1B9TlTW?}dHW2EkHB)B5gp zOy4f34YLsk7qaLOO`4+E3qgad5!(AgX=WbMoIb3fdCDrt8>`K>L<##$j0lm6 z&4{8A3Xi46(yfyteRK-G580a2M=$Tu!@$f}Xt|xK{dQjKWHrriG8fb^x9drGV&#*2KpEqyALgNq&Bm@;zT6pyDOgZzuo9v_sLWhdEDp?-%aysi|h} z)6b6S^|H6FAC2%B6g1?x6OvDS(wV@lzZ{61L)g$h>??kIJd@^gD&9ME!PuK{6Jb1G zi^EbJGXw6Zm$HQ)+8tgA1H^tktiMeC_b(Wf#@`_kR1bxtKPp`w9g#fd(EkMTRHK6=6Mb3__D&dSwZLf=n@kO9o7*g{=C6@ zb+I?uxQe1&&wV~#&*;!&GI&ZPOUH9|%1d{uuhQQ1>@;TLm>a=#4=rWqsNAYf@Ulh~455JwkwmI`f z6o`tlb59Hvxk^-kg#{Cy1=ayTP6|emJr!jxPj=Lq^}{H8)#U8~MGeE}R2vJdj@XSIg4DIG6n7_E8R@t{}kPCSt16(EO9qz8XUxo#iHrpi%PjM-MrseGrhj$OlD9t z?wDYr|Ms~7%jOUl%}NVL3-oiPoKNu%a@%;w9u^~hO{lm=LCr0Gs!~qCAokyPU%#`z zkbzFnHPPG)`(oJAb=}KGX0su-U|dWE8F<&=l<^Mn%@;lHqVoVvp)yfw2Ywcps!+0t zm%Q30`VW{BA$RwR4W$}Wa?scDjymS+X^6K^p!o{D^H8(XOq3$I^K?Fjds?SP_c z(bFtkL&THU4!x3gZRY(eS#;dU>jI6Rk`SY>Y}L^@{wj=!2?w2E{$HD{xC^cL^95dG zdH9k;l+@xv;7TzqUbw)vT-zpD3gbh%pjEx;Tr{*rhKfsn+Se^xQ6+m`f(t$Yc4 zKc4AxLvmnX-fGxhlhQ*<-5rmP)#KQnpjCt{Mq~)^l{EcK0rjQM^4dgIAEd-P8eg%W z+6uPSc>?%mKQ-%r?&Viyu$Hg;bNv6-tXm>+Qnbf7K;462{((s3p%8OA;rJ3rD6-qW zBxFVRwc%!=^bb7n&zT6FgxL0SM$RI8;|lpk_O&}NyG`EjA5@PpFuyFJ%Nk4aw``iQ zK;3DDpy|S@M$JR!niv|HL8V(b8G9w^n zZ_P!b!LXMO?EMo8gXFza+$_=?xaRo#9}GT~)L)wWTKKW_;eJ&Z!^qzijV6Hd*MCd+z+JS^QgAxND`#$Yb4afmOaBHnNZ%oOaHCFo+LQY+cC5 zj8WE?z3WJDerfl&0N4*er3qp*;pFnd!1X+bGV69tEM-{+0WQ83sa}Q9w}*5v$#SS3 z%<9jl_SyPw>j$QBy++p^$2`>M&Ao6&*W$Y z4QW_l9(wukwbi(Jm1Fe&OHzG+yC^ZmntfB;dyLPvJ)c z9JcCd=ir@r={cIbzKQ&BNcO z4%nc<+J1|*@r!zxPU*JuBgUn#?_Yy17FBj3U{*#9%k_}V_0z!1Y($~GY(uM>{}wq? zkwBxtfRGqV^WOo#v)kX@*yag(!lIWb3NJQJFr`h1gMXK0S;|IKU4v|XUOky)Z=H$% zMUn)Tt@(G&&_Fo%5E>AQGNWD|K%3(O!_1CtpX2Sfi2mt1cA}&y4uhMlGBu!$?dmEr z?O4;RS)AF9x`9Dh1&!mv4)2TzIx?bONpJb7r576)6$oc}0 zrQLWXalF9a!w5n7SBQL8Q8_@HlD@S3iikgm{zv1=2xw>y;DO+Ra-+U_3tqHOIX-UlACOi!om6P zVg7pSuLl49l1LCx8)LvN;d1_6`_A|v;hahKl?hQ&xA`xP@qc~qFC!bJ5I?GJ-2#zb zf(0!HN{HR@y;-KO4DWsn*SW~KmXqi1$wHqL`eBi*>~^F#Pa#8$)yZe zhrK>1kJX6({}9m7AjSGj{Cx$W+fk|sJm^#ThWVVf=l`hi|8t@KX<4o5g=LrdG0%jb zTVJJRhm3`UpiMXd=xHp-h*ijv945VtnFR#Dm@&oMG%*efQTn0fyzp#^NKqSPk^n@x zRcmHkZ&H+i4^%X<_&LEJ5>4&iij;f|TR-a6FWpoI2JKK0U(-K1&$zig- zgQ_?&?iN5CIiR$j55mXMPtvc2^gp-Zvw8j`A$}t!h*6*_{ny?v)J2Xb} z8oBb^@Yc&PL8du@%dz0XIfy;6kV#sKi%jA{@2mr2^F)ggtR@UKzq1L}d*knUT3T(0 zP%O3xSJcSb#$1rigyE#|#lzvK$(J<`*~#-_${_W;ys`3WNo46@#Lz->!|b*v^#7j= z6wUz}ZYC7&f)U|!oK|lzt{|yCik_JCn}l!g3&}K+aDoD?#GrdA{D@qE-%`G^Cd+BH z2Gbm=eq;qGDdC-!s|g0{Tcn@AnNfZ9h-8(e)@Ts>X^oW$fAEP9OQM1_!%|A8Eo@3cE;`Sisgk819;{kO>=So~+7@p#M(Q{m!n2q98d2EfW|&=R zQ%kOkLVq!8CL=9^YLaXbHa0bp7e)j5OF~w*Emfr#Jpq7;YW{!OAO8gU0<;|j*#ZD^ z%r99e4IaS4KzcsdRcK))EH+JG;iLiLo$f5r*PAhcS-Wv&V7eEntpPMMAZe^WLZyB9vL~jsTg(y5Wi&-7JdcFbpBlus@01@*=L+H8; zUXG%my@!|OjEY2h3)oqYu^c(mAlRq3G+aWqrZQDKXI?lRcbzPsnk{Ow%q^y;8>M1} zjcM(-AemUA6>m07<;*#e$`}L;t^hOvq5ukCw5{UUctAnJV z#bp0a-}l!+2dVbktv5kf!UoE(L_D%aSZdAj09!MzuNvjHYTq2NA&{~|p@(IgB0WB3 z@+8qdSCm~4vs;Cc4PxI11e(((A!(Ku87|vou4~b_e*9MwvY`ZT21OCG%!q{SUtJ8^ z9y%a79#mhJt1gImr;B_vyCV8RPbO><9QVd-Lb^T8la8+y>Oh0w#5`yJbF@#YVlsYxQZx?2sp$69hQ`N!vD zT<2-xL7c0HOuLOR()1Ri>KhuyqZFd`AnA}1>hsM8#vA+`RFgmC%$Q4LV zSn__5d8XJE>J75vlVYipv_VPNDCwR+FS;3HOWWzBxtVN)SRBJmL5Yy-kJ=J34X||a z*0N0eAGFhy`C+${Q)Y4&wh{j4lzBTrOTs<*OG3obpS8HU{+ss^SpIGhOT$OOi|oItD6#RD?&ywlbf`S-d zWm#Nmgz_&ai4-pq^$AaGl2{5|u_~5|*^z-SQpWe-!!h4Jykx&! z*bcjnfV<0fUh?2{p`YA+!RZ{0gc}C$7sx&48Br$?~lKjB~V`d6?`Doz_ zNgEcO14gO&Myl#kM+wsmLP;JWXo-Q{6MaI@5UDeaqPhGWDAZIALmL>4v3yG`d?ssR zy;o0EF2_11Nf!QvG%-X)Up=P}puzrUGVo8|%LFu^p#%ISjtdS|y0=3Gg-u4krJvQ} zrJV$+fOIjL2A;7SNcPDnn-V2jLe^QaLKkHS3;-wEV?hTJsr;|W z=N-0EoWb_T+6+1y(3_jF4P?TjC(={lqEtv2r}E!O3{yDsUz4J!@RdNZX~1njg;TPx!@Omq%=AorHEt+T)(h0WY6S?7h(>kBWPibOS@L=OSGWG0mVT~GO8lgrzoPLr!4D{m2lDJx zR=T0xPPJIMhz=y5Om@QOS;<1rod+tlV}LK~5EW$xJZ-B(TXlgu_qPy(i)p)HD7WXFlAeMPKtwHH=3@jf^J|Jd-sZ6xqzgu`{sI#TS}+Z;4GBWG@#W zA^RfVAK&m1{D#A?fV*P3Aq7jF8j1-<>05#wHpVoKQWl^jv8#oxBDSsQ6^K#@)U9Cy z=lmf6VJvne0!^lf@?S#|o`1XeX0nD5nK4JN0)u!FDr@d&r$RW?8Sb!-9KVCP3>5FV zx>PA-orG=rUUSMlUo1)w$B!3gk_?2SS8BP8Vh~S(K>G*t2J?K-v!p}WE}#BTg>Tz~ zFd+WPl zh{{BKb!9$1)#S?Bqf#CJVXFKaWJ*9lvoks%o&9r)5HamZNz8ZmW!x#z#)p<5ISmm( zsgi;k|3S1=L2y}&V0;_`uH^g`MDAm{GgdUhKrX*XOJ_aw`_NFV%m$3w8u7IcGBI5> z!24S;8FhoSK9ElQ@d3R2A@(5#y5u9|cu_?vrO{uHB`t^2st&ay)*TFHs8i@P7`<8f zM!N7TwiIRhlZFHwOge-iayqd|bgX7X1dHtYi>hYuZK*EJM;$g3KkMSfti#*~$$ z2~6q_q~P-RzS$>wWG4`Ah{6t}@_okM(r`bs3p#h{xT##I-ygD`gzrVh`hCP|=itsW z=9afzJk&hmw@!2=a-q-Sez{Tp<@;b?UZ$um_6x>{&a&XBkFewHdqz@>hRv-1>z?OFcheX7Ni`t1EFRe&nN%zRe%f|-d$>KB z>?Tx5fdFXM5zxxi-&ia!zD5-xtiC7W6~fYy6`&GZ2=#uadgU z)UH(+X9=L4tKOZq40KCX?N2TXNd1~0F`AXnpP^N>>e5di!xQ*It+-AA!x?zv=c?vN5KBJUQ{tMX&I%+o)(73x zwcT$xyu+@l=Y%QvYALq8KlA1vyw2ZEB=SY3s)vQnxC2V9$Hc#^3(x+NC*a-83z_zb zS1W@AT;L(pIq)72V{Q)k%_^(U$N`yoAxNy{I8T1^6R?imqL9vv$18|DpFl`Cr6((O z@J@vDiOBvchx|n?FuRB$<74Hn{;&hzZmZ2X6W3#3l0x)+ zERk8xiaZ#eMO52n^uI5kQa>_PJS(%Cj|pyeGv@2A=r?;Yu$HQl-7UtK%BXw}RfF_a!e;I$g;et;2nGkW{<7WtPo5dp z0S`|E&HvpDRNxgt1_Z4C9$Bj)2N-6&V}dt`M@gB>H-HXi{3c$k*2-#cIY%QiLmVdPgrO=dL4| z#!)fTLFZlyOueNL(_8n`UNW{TyD~9JvQYqpi{nUS{{y`IXHsIL_-X12N+NA0wz?Xl zva*qTK!CILr>)nBCQ3&3g$HFt)Oq1YgCq&_1qmFvd3qW0*s8E40n|)R1GB)6On1u3 zl%XP%o}f7XaT$~C3#dqer1TRGtgyf!=S5RfyNVjyw&14BI4jF5TYp;$+|}V zz#R+Kvsnr_z;0UuP`$!JN74L@Y9?=z58}A{Dn$C?k5SBbqFl)m#8ETpP~mCe*K*mn zlP;-dgexTI^y zb#%C%?u4`4N)7lkQ(wt99ltcq&3uDH^U8^#aUu{{p+*=)!FTNvxGL*Ip852HytrBs z=4e_@7%fk|B6sdPGAh2m5lkJG1`Riy}&5#XnzaZ)*##g?H$f=O&xbH3Uv`f(rY}s#ch7CoFYmm-I{{K9NN0 zK9v~Zm;v=p3TdI7BfyiAKL&m0tc%{KG{4O!xns$8+Ohrb%aW@#5#@2!hSm^d3W5f4 zi%?u)QaE4zIn_75oLuh~QLC87rwBD)GR*Q@aoiv~`*UPmq{FHmr};_V>Rlh-CC#ZO zg_!Y|y9AW?;m$555vSxUdGecto&QvtJ44u+ds8BOV%NYobeT@2PEc4}=D`i|jyRgF z6n6T6UOOew`7PSt)PkDfZ}ei};RU&q4KZm|RKhT02+rvASPQxQX^y)6B1j$qv+!eD z4!w(!%;`&;UAzULKi6CkjAfeD?&;bYk8f8*Y>!Z{n#N;o1^SFO_(oY9)9sN~QV1DT zg=fA}Ap9h{0uP-1*!4S*C%CmKpUSm`G#-k6D6A+RzReddxckjV!-0Qc&kQ%VqE5(9la58IoVf)P!V5y!mmzkoNc6J zoDw#qED46|*ARvHH$)9y>1c7GkC8wqr6sz!;ysI0+6KSxXJ4f>?or=C8tVxjTY2hG z&@CK2M!<4Jm`ryhI!a3E8M;aH^ibW}Iug`VeYU_mGh_vEz^PW6*|WR4P{MDld74&I zx^FVz!0Qpu2g4nIpA~7ABR+G$GM0-5v@LJOcAa%f|dn4{BsTDesPi_~dzjB7(O~z2QhI~axhpeyK1BjyL z5x6lHp4lq-4Q@*eq|Y~IRbhK9LtwaeLg2lC5Pf1x<4C%BJ+*bjC`TlxW2>bBW~zzL z)-|G1dZsC4<;0VbKitHrL8Cun$cRyxi6e-Vtgy1Ru|byn#`}0L+X}L=d>7d0BFQk< zt5(Ur@k{1TkQxm4)ZkXEdSvfneu0SaExAxqQ3Rd!*MujmYUztQCaIfiu*b7tF6A8EUFU-V6{W=F zaY@zK@NAtBv%#xsr{C^&Xm2VnaRkR$epN+f!S44)`GBDKO1aZjXZVVQ8zrMoWgR4Z zEt-NP)cMXYif6~QN+fI>k(q}^-rxj9FywJR$ zpFh+$@A%P9=NnL#@eqLL5ibY*q=N2XzH;46k*HQg zbu6*4K^!30#~9(GeKW{t4X5PzqozOu4bvpdzmM&}zT)qCG2&cX{*l#vHxmn2L#^{s9#ZM4Cn>aDPq zam87b#5Jq5PIP6akx86_o1rE7D9zpOah8aGJQye?C1rD4OZU(aYLEXnXs3b;s8BgA z@P;GJM)DP1cFEiR{xJTn1K*>7drgxIEBX%s`hPWrD zET?K^X}2dtui-5tImtjxqbdmN`fkd3}ogR^j84)Vd>`SJ1$M0WtvzqG6{g=2MrYD@ab z93D&OsY(fgKCP?}_sfNPpe>y}-jxn?%;Yc)ed#ct@^S>>=mfVmOeUeRFK%X$2A0#o z^+0_fj!a;h_J|(2l)O1)i0tG#BIorD?p(F+Jsx~|EMf1(vz>OaL0|E68=vR))aGfb z*tWP~jB#f5pEo!-1}ucAL~4y(`FSdF%4Np_N9wXOE$Yy_mS0&IY5fb!&c(P;$_S$i{66%CQ-T@m>>F|$5xa{L3L3Gjx=DNHXf zkKz1hLh=XXdei|n+`+N>`KofSuG z?Y7NU@=?h))kjD}60P@<`-r+9LW55UtHqFDZW9|<_Wa^8FvpwYNeHYBzWs33< zCY(QzEE_M_4_HdzuXND`Z*43pZI5sb`-{xqN_S{UxoUS01+*2^>fMmFlfNQY6xG95 zwno3_&&ITHP{fagb+=be7+{^mM<$XCJ*dMJH{FV=i9%roDANxkE0PDDl%g1lKPLy})+zG~1-RA`@sm$ED!e%1+n%^n7nFHj~!6HGn z^gsW@!8#Dckmoc-z4zs{Cbl7ImtJ`3H!F@@k%}l;zUJITvO=F1ma<8ZZOm3In6dwu z=g2o9L%S1rbk&RmSO+2-GUo!+F=7QTf^wi?LtVGNWr#nP0M>P4m(b#?#$i1J4Fk;7 z%www=W#7SW$}sl5q?E!cRmX2Mwab!7k^RBCGL3LDgY5G&wO|q9X6-U6?`P%q*AZ!5 zYngI|i;|zAn5~c=-9vg{n3QpR4wR$EXwq{TK9k13Q0~?2P!I zX9DZKx8HGfQe&4)G!9#0dMJ@l@UX=O`!N&@8f*pYU)@1;V2>!f?|%{C#*;BVTz+Mq z)$enaRT)1;jkE)MsZjL5*!(_S(LWf-w8bxG$a;)B$ef%=iv|vKnpkFLYmksTtpo0a`>i%Wb|F55OQV& zmuf6^wPaRNH?f73Skg!uW#0R{0MV8iu#Yfcvu``p$w49rh^O{vis=wT z^zOv5-!7ppuNREuq~Wr?8Dp;pOc{?qhUfxsS|C(zR{}O^tlD6t5$`686YdG(fC)Zw z*Vq6AXGnkAeEY;!k-gUo`tpbq#kZ4sbrj#0pTT-1l9R){AbLZ>Ks;W(y6E2zQ>Nso}}6Ev`WG}#)Ej%>XdJryBU3WP5_uWmTDgU@;W(!w~9w# zLfONx)mx++@uN0T2l%6bc$o-HMQLbqgG<83j+Q^>&${AZ_4ufnZ$?(TD3krN!Fbri zS2jN@(u8Wgb%#gZbu^me>61(DcWhlaMo|q7<@&PEjSV#vU?R+07zKKuQn1q$3E$xQ z2G(}3Gjvx&kb*Y%ORQMa(8%~;c>XX!7xnv5$z5{HYN|*5R)V?e<^=q?polR>GZZj0 zI*1q20u7ClJVhgA1=4>6$swO4;i0G}ho`!4?8fBXoco8z`X_7m)Ue}%@J~Nn^k!7d z#BI!`i}U(Sl)Twx$QDz#uchX9UbhKyvNKtaro?omr&SEqHx~y+0Re4gTY1+Q!Vt+2CytR+ zKj4-OoxIsI4@P2~P{QkJ6FIQ&I@egYjz|>EWK` zc#P=IK>9j*djPQ$C0)JB$d4bYtyXby-z1#k8%#3c3j(If|CLFajKpO^5Ul1C_diSui-SZ6A!~aej=bf_@C47HRgfhy z7N1QfBOWX+CEFIwx-g2{^uQDxl7B0qm=-?tvXVDys7)Z)gbmU&fj$1iGLS6zYsiE? zgXI0=Rct)w%8K>1Nx zkffx^)&=i`>ydK`;!YpGp_@%}=$wOR%PpwD`yNZGOPnQ2N5R$P>-k`dg+~W#CL95O zorN&Q`|X;zV+NeZgNxTsv;(M^fs2w*ZMI=s0HHT=<9jWPevV~-afDIe;UTZo&vGAT zg#IsOBUG(V#=Fh$sjRW{;IvXh*OjuGb=Lq6-D;+kPq-18r0eF=&bL&j2ewrVLvcg) zs6%jH9draUrjx8^*1~BL7Ca>k>p@Q@5)J-+dtfK>D8@N(da>GVdxA*`&C&7*HJ0I& z!d8L|CM^5Qok2Jc0iH1->Yi9Oe!z}FF20shZo_kB7b%Z(fm|c}p>%HnPYYzoY6PGp zer%mU)FQSYH;rcnn_EN)sU83M#GEK|MdESx>HCsD6zzymKEp*r71a?l%%088NRdTF z=ef(R#FnJ=tFP0FFrHeo=FB$u14Hx8J2MvMWY|FyHBprl!?UEZiwNvGkM80PLj=dr!; zAmYjNEC*r_8^uQ*?@se$^zE@OdKDoeOW%x$8~Z;Q{E4fQ-0nw)X8p;ZKla`AHXO0m zfi%AuV)q9n6=>7CB$T&~%UdmCPBw?Wt%)e|ysrXt-g#=6r-$=)m>n3)j#h_Zwd01N zVA_h_AIGlVy=7%7F}JzSTiDs*(<}%|6lcVioUiGSQ!$Vfd7#eJ+ThZbUzvCOc?J9p z2XYLC1(2S)kz%D;NWkww17<2fDLG0M$spqN!2H6U#IG75z$8p$u}kuv&xK1v zHjw6k8=xiyGnr&Xl%g_%h|J%y#JX(ybAJi)WA-rOl@mV*eOBT69fGZM7iHDrkuH8# zC}GEg2UgfR8tngB^z)-20)?~1518tyYHV1ZT>c%a7S-u^u$i=3^6u~tW zFg3YY3bQ@IYCsi_cs$x+;%!D3YQzQ~64*pr#$C3>aDd z`=c1jba|9_I3|cRsBD9eWl`UNHss`RyrsEI@J{_e~$+GgsBl{pXi^T znlT3RgT^+k%c{93o@7{OJ!I=xnoHgrQRK_rz#v)39eZ@m`w->uzJLD#+7D6wRHHbz z&|f+BlrXW2KiGug0jF@uhewK=o?1YmOV_+7 zOY|A#@G!#M2%+dpc8i3z$m5M3QcrI=0z`-Gb}B^{Yhm2XQ$e_?hc1^!(F)PD7Cs>J z@=_Y|iEureej4X6XB=VxsX3#15a$#MHDix=x5+l%e zX@|nyqg)t_p9`4oK4kt*vQ8jgs6WMSwd4i=BigheH=3;1NHL7V;qD@Ux!~F0yU3UJ z@`G?)6+c>Mg!i&9=?gP*iOf`Z6_Vuws&l&V>{{Cwma%L9osf<-t(P%?t6aRzw6o0 zTD8`kRl91;F~=wnnhd=E)=rK`9}Ew} zFrLeAD0_3fB~@YUYJ%l?0dzE+ABu~VC(+=0Ivaw{wbP1cNwqq$5z5hbiGsW`gGVAJ zsltSx$C)29QfMpQvM?w3P=Jq2o06~oag7S9ic*Kr`W-!^^gqB0dJoa5_|3+(6YHSxC z!jy7zHSsv&P(HPW;%j_$)alTwnu`B$Uv{R$J+;`EQzocf3@3K$ zLVIQ%cWsH$wr@((5Xg;C$VSPL%SCl$GX+Cuyh;NvDYl`e+@lRG0@=1nYNR<Lvzn&?vzkCSa6cP|$BAMcSsic7Vqh zI`T$YX?y(4F4^}m*~+?$ZCxse_JCiLS&J&?=3jIo^_D`#k& z^niQ(XMGewU-;!{1!?2THtr&Z@e0!vDj4ecH#o;*E5s???${)A7DxU#UQBj&P58}v zc%SDUTSWmjOj3*Kuu@!@(f!mq7w>AWTNx`_V@h$-S~)rytGgT~oH_ccffKwWAVp?M ziU5Jg4k>In<6oF~2e)^`UsLR3GV2&e>p9+sAfhen3R5vcmec0S#LZ`8zYYPS4#%N& z@7lA^(4H$amIlUS3k%9?i4VLmllIrO;VcDm;HW0 z41~>=C`>wMe=wk&{)pYNDCR<*Vv0yB%<`D-ygGOzfjxc;iI9BWMA2bf={2)bC$v8f z)u4DXrfE5Q%z9K+{)%{ zeSS&%<7mh(0=eU{Gzsx$pA4((M2I6PnFvyTjFws*jtLnPHHpk3>W1j-{YyP>A6fE! z)N*aWf^?UmdEZzk`?y{sOOtbSfu>-f9VAzPbd*3^YogXE?3DdjBh6 zVBOEi%T~$*W~;k#H?-VgM9VKjZ$*~jE*Ao}W-R621wKd@KUzXVSp!&dj&k5D1uQLZ z>O6Env>%~(xcX@<<7NCb3WHDM0~Ai#^|7?e4J3#V*D`U0#^Rub1sw$R(3Wm&EN&R$jaX2^%owT{#58|fxOB+q`3(-PAg@-QhYjV-LPyV6sFGd^nZ8h$ zInlG8RHrwCC?F+28ccVpw;{`3hEpIXa)4+f%a2e=-%#>A8YDx&0@$$!71^nS?P1v z!HqbdT=Sf0{n5SE`0EjybHp?BmHv;_l932vE@=#bzB=2-zY5Cw$(}3A1oiZYtBXvx zpN?rce`+BX^7vTKkW1@m>4bD(v4#+< zy5OV2TW2-Xz<9|Y=!+R~+z>73)nvIG6qU71N(2Mt2N&P@xShD(h``Mhswc+n`PC(w z6WhMKI^4Ca+}o|qk$6NHHgdokRNkc|ip$HA$Jv8UZf2~pAWO;N4$;bX|6NH$?s|m( zog9DP%ptq4XA|rCH#j(oaaCiWn#@Hk5}j@wlKhO9r8>dZA7f-aZ2;`0c&i_9$7r2{ z6v1C7!O+f6g5HJ0|C*&*J|}nxJ!<$TdA`|9K>qhc4s`SyJeum!$$Ktha&M z-tnt+uJX$q8Zsw#JHTAx++~=$PaC7D5;SLJVNw3BCIsK))8x570xMnx8?Sye!c2>!TLFzdwZ*yuq+< z|JI|f&i^W5VPT^)LQn6CB<9G*)V89w{sksg0I5Y*BA=8ECNc<^3ZM!oHxi47&g*tQ z*vSqi^*FCok(^`Gt*1Lpa6U#|tSvk~Q{P8k6REq}U`j)tM;9_wbL3!7cpWq5iQ8I^ zp^eOq0Cu4yeR++A8%B6s+wtjrkYKnru~y&^74G#LcHX594CfX!;0gzIA|IW@A(^wW zT6tMol#2hz7ju-Nt~O6#2~e(IvOLKqdu$`M!#D3q3c~E2v>}=&Zmp~_1|W;9I@0lo z5irQnpl1m)x`VA;rt4U%l-}tfW^lk6|6w<-j7TjWpC^4X#PZauDD)yP19!O1j`&hy zzqfRig@4w(-S_%6$%fCjNOSYRYR9dIb2vv~@kUFN4wpauJ_l?v0^rrD4 z_1>M*>YClGAx=zgJMIchsYEnf2^ULbNfanI<1nyo{+{(YD^X8<( zfA6}~E~@?!B6hj}6B#iew`;p zRT*cEYau97cwSp#dBY%5SnL4}ht>-g1=wH%?)W#7fF3i>ml>EPZ@9zl8J~{Mc#*<$ z)0Uxo{T{VW#t>P4k5S@_^6X(|CW^JG>@uALD_%-`AMPg1|NcGLtWV!oWdv1B*%SxP za!o|B>IK?14L#HM$tq(62DYSxTvs-f=3GkB@EUm}=|z1_^9Iwb!AM-L&g~t8wOaL) zlgQLM=-f^na$X|=OsL}c-MOUK8kPIV`wyMN>aa*P%KP=rw+O0k7gWOdzSCnP(_K=n zGjgUbz3uB(e^AJPTPu^lGdlyep9L)E;UDiIfHUoO*xX#2DuvcG7oiHefdy8#G@Zu% zWS8mp-Nf3bgB%#!Ylh122)5guPSnjdl48v(9QuMsSEAANw)W*Axbh1qNiK)B*OF{0 zMF}QWOD`%1w#0F6uni*yk$mjhWV+8E=Lnj)_QLs@OGXO}@5MnL55+<1`xvY!9(!#l zfUT=%>0Lf2<_}ysA))l(yx&D}cf?Zy{$Q7#uKgA4ndT%KBMA|jos0x1?2(wAuhD~< zb?>#OrQe@+zQj|K)7U-gm1B4LxHEDkQ`kB=2-^67M$fE0$+2%m1EspUSFB6&O25EK zawahg+uLkDtCTz9QS1W~6kFbpAFhM9ihx!#yppMj#CztfU1;WizCW~2kfB&ANa;%@ z%*Fj89svVYoc4$WWAxJ6^fBXMabS0mwT)4&zKePCHd{Vw@;YK52zrScA%c_;9#U=C z5pU^ajRD9pB?dw^7#4ulJyj3EmhSm8kqf*LkDiLZvSj-hg4=UAPBR?Q(xNQ*)VHg|l1 z{I%u-_IElp?dXA{$46*C+U}Yujg1`>9IVY9S4L931aIhO;=oY@q6KD+^(2(3rTB1l zoQ;VNG)|#27K?o^4XwNh;g|A~cJfjGxvv#dolgN`OL#zc1&YZI3E8uLgid$LYf00wTX@#~N>3$NPci$(Qp>)382*7+J?c9=;1hcokMM_1J(Ujsi7Gu0TQH+r0#m-~u`3PO^j) zTND}U;7p^c5xZ=?ITlRYl9YDwR#ZQCuc(pSmxY(ES}OtQI_2-d5j1rA=7mVoV+h~h zcHJ?E4eap)5s9cKd&xvSt#~2D+NwwJ;?wqET$_rOY7UOBp{ZfGKEAxjCznuI9nmPi zX*ix|H?AlSs;$xOdZG}M^k$%8#UM@6IzDz>&YS)zV9rG^E z>4~^wVkaLm>RFJ1JBB&G+!w#EaR{3=ffrA$9E6%9VYx*u4`wq^Bd_V*uH_km<4?;O z8+<6*UyFW@^NF5yxA~%NWl!q6QvpVC8I7U1`UqI15b=R{PBf!oF6B!T(12f($q_?Z zv--rxW2JFI7+lCFnfgT_VY95Ws_N(l`j{o+RPnf`*Kka7dk8XhgKyW(2~P}>_vI>> zWP-cf5io8p;~%tZmXSz%`-j%N==AAWl*%*HBUp=ud0cdOKyH{B_l1FJq`I>*=SnvJ zigjLO0wD=&_5$#w^&R+dXTNr(m;9sQ>PSsw%*Vo@!4C_U?(*!sNNKR}mBjkF6KqY6 z7aN`~%Xwbv-^Ac=Jt9}nNZ5M~|_(|HY zK@dm4FPihi&hjkRNuq+2yto${=vpafs>FgOU|*VGKp!k$T>tr3Z_3dcOd}_FKa3y{ zMMxS|SQS&{haptwSFzYKZbJafiJi$AUXr}drVs>2a$QyoJ~)<)IOEx$NPN(XLJOQ_LeyLN0pgBCR1%+X_;fRLjOBCy1-mR=e08tIq`SGmx*=(F@WqAWCEZ)+YT1`DD z-sfu5l#thOm(f&%^!%Ba>EeqZkgUx~Oiy6Xo&=f}TnuR3_c&Rwiw`SV7+< z1Z}Y%AntHiB?rdqb<&tY&>&TZp3t=A+fo<|8^m!f<-PngipJ9hFU|Z=tomtc%7f#N zWxrPZ5NTpUS!a-OrmIBbTK_0Yz3jpDuOU!H362;D0^Uo>@=NH3$5X|vG>t42m?em4 z`O7<1nDkL0QPCgig1%U+5J>u!br=H$`LRdP)VuI)0nO9d@j&>6eKej?+!ffe;Z20e zWa#7=LSgts*}lDKevy=%C25vV^*E5}|MZ>yHwiQk4*1ht(8|p)Wvz(=RSoV^xaApJ z_}%nfJwwoT_uZ^QQ&=0WcAwUwguXuGiUY?D`Y~9@g*dco{z4CDw-S9{MDtl+wWPS; z@`D9Ai1dtpNfQK8AW3Fajoul&;K{o{7R{6TU$5u@kr5>r#X?js>8P+TEe1<_P@zim zJ5(q6Ti;wyn`UF-VK|K`6QArk#H!Nixg8Cp;ruzJE6q)Kainz7&MyOgDP5VXOSlA` z+wna$8R~Yu(mC9PI+Mg-oZo%YRlA`6?iEjvVA|+tf<`&g7o)n`ntAu3GC$|TEQPQXMB(Fl^bC>Q$Um8hsqq0f>|44a52 z`+!1A16E{8O>%Ip_FCD>V#eNDP+Y zKE?Rws`50H2RhEImr@Smef_Z8PRh*b;F+iKbS&c2h$ch$aOE-3cl8R(PP~6LyKp0c zeeL{@z`X=HTcTiSo9%ze8o8t>s#`@z8LsU#E%GEXFSOWDHz;k<=@yI?7A5-j}PqO0^AHcx3_WAC?`OKzpMSZ(=TIqT7$uIorqtCe9F~q3;?xXDd%x zc7A*qW|b0gZ@mk`>KF5pDq z^o9Vq|G0I}!G~*N4w*Y6JeV_e+!Ol>6WBiq30H#J|Qj3i7$nj#!BqJb<1 z=oHR#zO5g&D!F#M>A(Y0AR92{-W5?~H2|>9^O`$IkDMWSJh|G{n0xc1OUPLch=k!-q=uU{Eh#+wL z$PJZx67XXWKqux_muJ zAo=Dkmlz(7BQCZ` za=wRc!=DkG98I#9a%?GIDb>FdbFtoGupDne7Q)TZNh&w3&y^5}5O)0h+Xy{AP5JyJ zG@hOqz)FRQX56)H0p9ol>KSK8&CU>@~u)@Q$0#T5r6t4&-_`XH3VS z%#bf`PvyWDefFLCHIlvm(R?z>*fMT)!wzK$BZ`MxjA00nf`ZP%p&2KxCYK6VAY$E> zVqTihDtaOdy&W<5vNHxE)C7wBwg6ojKw71*Q$(2^M*I3pMn+zXM?OSaNS9Tm-atiD zR<+|@;w-NW_bW%m_HN!OZ@`cQtUy1;xdll(2<18MsFQd;<8(R)cP=Su`HmnB&9#J* zOXE3KE`c}^nn{+WW{dYxwI4SlNx=@pRjp2`!kWHU?oyeZa=m_k_NOQ zly@RaxL=GMZq^Jk8%-2a$oCP7KdZ7Zd1Y<364;-OhQ*$ov)EDPbRLU$rFdd%zMkrJ zJ}(L>Tf+|qLQ@!7DI!3^Cg-e02+*}fD=&UZk=A+$A-`4prBlP)o&Okk+8I&Pq_JeY zD$(+2&5f#?ob;`py(WXL3pJ!xBT9L>)2xO{GDGMu5DVHCm-|TrSLc6@=+o}Zfq2Fn z4&;~~9`MbsWMtjjPDNVWUa}@jbbS~J_Efpj$->epii*^!cGDT4zJ z&ueNFA~;>idB0fKS&|r;{~j2bbE-S@Kdq&~y^_a?<1h=+Y)dCCn9j<)9>c@N;`lYV z>4rY*wT2dzOAxqG(n;$HV|$@v-inKTG`v9`<-3c;1y&U#7^Om@jm4m<%nNlm$76BN zyn;~M@M*KHG8fyr-Ro5rCL6MaWn>;Xrq-+zD9GJ-kb}#Ab6U;s+0tPnU-E*DBAuC1Irop$W8YvtBU@FlKLN^gD|+ zgPZP{hJRalW_+(bqOTb-qkpYuUN7=%Q_lBkJ`F<1UUgsVMUds=FJv;=1@v-u)U1$8+>8Wr8-%0<29QBjjq&paMCe&f0lpJ z&yR+)eY6>)Lat}jShOU;_zYt-8W{0(p9Jav7X88NWXJ}4YoV-Dt8>EWJ+a`qhYi@% zCLeGi;#S8Hin}OA1$1sOv8C7nwl-%v=<~c~v7XN9g=2}ojWC=rl`yCYpXiTKdx{@OVElpX z;`=Ue`G*aar6YqiH;2cI8%tbHLzkl4V;8qx-;yN7d~4JcHlj(ZxzO2FqksBc+-;8Ka1;=Y&>fJ=qBYf=Y;$IB&Az_TruX*&YNL4d z^(~lmh8?-=5!=C~A&uCkTAz%!W3aD*C~E$TX;+&j7t!8`-tLQ=cfyjj))30w&V$I1 zyV9uY_W}p+y(F|bcFunSoa8T%Lq1`~&c;>uhkf>HTB`L5;rs~9IcW7T5`d?C5XNJP zvq)-J#cTrFq}@TcfTUaQt;?>kGeH|-*nLn%13*%ocPp&M7k|r~(!qr5DS147q9U6X z$P51!5@*n~o2sZ-f28PjP|oDl$xKZtP%Lf@SoEwJfsvlkBM-XAGS@%sOV;YY-_*gxciGK z2phb5R&sbhUgFb)eY$S*2Bxx7fQPZrwwLIH{zs7Q400l1&8I%0^1E6!TO`Y_l>^xD~4~<Kd$$ayy`la|VJUK2=oIT}YM&{%Pkc!^+LoK0JUxXRH9;zRzyIRr`&-=o zTqroNS4@{}rc_>qJc>OJG5c*ZjIlNAm ziBez}rtupDL~t}9j`G{$Ru%a3)Bd`?gW(R$8XqeR|8PxebmUs4W|I$tx;}|Hy1ZYS z?RbH0g zm8&Ji5V!*RA(xUEvUi9BXV?*dr3q*PHu#KuR7+~g>xC&^Wd^jZO-eAPUjjv;bH5Iw zzdH>IkQhBD0D-2X?D%dHw}k&R)Fui ziRy_7r?u6WH-a07%jQPghGN?rw?v*T$qWrVOi;g|_zt;L!?w>e1O(=gj*t($=vf4{ zME?7>EvLW}*3GzK_BU0?Oam1qX2{VbK2wUvutawdPhJ-N zr~OVkRli$%$-nA?5*2c`#7ykUy2;R?&l_o~HQb3z( zY2(nO$4U$Gr~^|+2yH~rKG-hvsbLA0inS{{UMHBE3mRd+bRhi17=CpPlGnpo)X*)C zLzql6!q?CGM&@W8k3DGE3AkcgHdbTaq?K1(-NYm{VTL!E(a?4}g?J;RlnW1wu%8SP z3AD_o4d?P%p0?J(M-A;fEYBTp?}|X8@=e0|(Mi2p2cn@U>~+vCyD-p3JLVHa%eW zE_~a=BU}OF)>2{pVanC}VJM?W%G$3^XhfQ{5m@Au=tA_o3hdAneIX64{r-BILgvr< z>WS|BR(1oZtTA%|1w7DVWD8EkOj|aXi@nIZp`1lN(P+^YeqRR${7LBo782B#?G~5i zpYt#$7*T(ZYuRA;bgg>BgLtgRv2a|Z~YpBBYTRclwfo9|i z0Qt%APW(iEfNpTBF@vQu;ZF#J`F)Bz%_#S$$8sZUy(Xb7S-C5_ho8y8hr>B3@i)o{ z1p6&4?X9m23f3VoxuSl%5F)7CJA>~P2v3ubWT@Tq1ypC%)CVcYYZ-~vc@T019rjZP z!mz8PFjxy6Jad9!BDY=++0*IRZWJr%w-tIK-IRV!j~kfg)4ys@tSLof@*4nlUx1y| zT^JS`Z<3+!OZo1Tj~ETTsv>7e_50Npr$8k+sPq&$6ilwdcaHBKa(JNLC|_?0VS@Jq zlMetHp|aKSOlyI$=x^Rw`Y{eUy7?YwBh)So&vf5l!QjHqs}Oqq<;O9a_a-MS6cE%`iB13HGj zB&M~UTeo7x?DPGC1=ExiORy+Uhu0AzSvUaAQ<`{1ew;fMzmJ#dk%iv+6t;h8m9xygouu_GBCFEh=-wnzfMCt2bLHY;(Xjb|8<-1Xyw9PB7?7L87n>9gPP-;>GV26 zMBB105+44XVqTC`H;o_B*vJ6}q9wu28|QdW7h)ftk4>2k%43)%xmLl1tckiulXG$j^`TwD?qN8duFI zgDGAnwU0MQ7yWK2cKW^81ZTp?Z99X8J(GH){U%AFA}I1!PvMRWO3Bxj_-|IwaxgdW z>Wps0*$h(GQQy{rD zj&?=26DU~?zK@kl=K4!OgwKMLh|L__Xg`};^U-RK`QKME$iajEcNG)rOO4KeLkvl!`w5@}*Q3e?mt&oX*3fk`X_K&V2iHEk^WsfUn`4TLW zOvrl`B}SnNj(T(VXnZTKG=uZ0`m+$ro@^l3RBD~czT=q!tx&o>@5h}a35ktnKV-tf zaV!WeAl^&Y;w+G_Rk=-Biyx7IH|pKI!I8WaU5w6wI3|h*lJ9Jl4Fde-v44~biIqYY zSqw~%Mq<2sSJRpGA3RP;Xm@veH|VHUFO8`Jy5IIrU9DweGNoIJtNdvxEX*Mza1oj! zW+jSWao}uig@?t{3C?pxzE0WZd$$tZd^!*#( zM`}OzTuspD#4AI=jNDE`VbNZUOv*ylU9g|Al&>ZpZaMv$07GPs&X3n(K0O}4aj`=M zLtfQud<|11*ZuJ-<5=1K$%=ky$NFE;8h5Kjm&aQ)LnF>mL!PiEfvhx{sbWoix^P0^ zWDQrXs08`Lz}fjmlSp6*I$QOK7@0Cw!at9b-xS<2vbyuZ9A^5114%My688Qm14ae4 z7p>Y}QcweKm~lh}#S)RmCXm`5yTWfaMEb~xzgFh2tJS?5gz7tmDx#oPK1^m0FrD)w z&==x6>j7U*pjShG20W}ciDCTep4LghQEX9nI_~!5-wel0lM}5Mga0phb}5rG#M$)8 z*!|>)@lzTHzS9zu;NnoZ3a)76Ip*|w7%|dUWcmS{16^SQJu=|Loc5Nv_F<{ZLAbRR?)_b#6mmXaB~<=VtSK zSy!h?m)wGvs@?UAXg7kv-c0k|?e3`s6Auh@487*95H6qh@ZQgQ(UfbKS z9R(FuWOAdUF_lA}o}#S=mU2x3{Mdjyo7QMR=;j&|+9N9aOQOv${bqy-cKf03b71Rz zomAS{4}|DC|A%`7igTIb$GbWm1d2%<7vm3AbfeEz{HMmLw}@;eBtnIWq599NfeIU( zhN5c^WA*TrPMePmB_4;jq977Q`*$m}cQO)bEP|*r34Mv)N~D2jCmMLTjziOT6Pd33 zBKXy~4WMXfAH4#v#I>;Sd6-~nP)Qz0qvy~N;X~n=j0FhjunmA})a+rPCa-?eVakU! z0IkO+5Cr8^#J&smopw34?pvphYy<$qhh*n!S=VdT@=LzJ)4cA$L|>G0@vKFr)rkqW zxX}Sl&oC|CCFt3JEqp5Hh{Xzi17ND0d!4Mx>SjNDI_LwK&u={)y9TJ3*Nm*xd^=d< zLjq^78J_sjaJ^gVjZ*KyX;oRG>q|;Zt+6Aj$M|uhV;{VhM|Y~eeA9E@k3q$q5c$6F z8(LIBw$+#$^MUb`S7tA3->4469!Z0n)l5{+HcVe!7l}}HMyJlpN2ybs%eo+`V{6ka^$nPEQD#mXI)tuR6fR+smz(jC;86$ zJBW{$7gst>R+L@ZxLy0)9KrTIV1m2+5Z^2UqvZSu$>xT~eYGBAao<7?5a2&ScgH-H z50jeyC`hU;(}e%(v1u7ZH1E)?@cr^7eUm zCnmpPB#$WSK%xEnZE2geI3L7w2x54^Gu+ZoZppzE{vuqb9Dl-Y!gmBaX!fMP%KNs$ znyCm-_rp>WTuDSze+sOUp%@)+25oji76sG>pU>KTLY z1uqIx6=&T6=}hZSn$$WN4Aq6iL;%ICgxD&HIfoZ3;`&G$MfseUlM!HXQ*;|r>4(N7 zJ&Ges$+qR8t%v=R*4t@~dCVw`eD%@lcK}lY@zGr6w;d7P$q3`sI{tqE!kW2uS)c1+ zj;8`@*$Ebhz53umJ=ZpPl!W(o($jeQbYrT#Z#O z0!HN9xT=x3)Z-sy7{OP-Ub%6Q1S@f7bISH@OA-pGre60 z4dR>1k=BTh8Qj`$H6WYag;$rOtUfm%h;*KZJaqZ&Wnf0vXJ3TSA9M0i=cp)iEx0~( zMnrissxWWOSVSZ7an8r0EKf-=`IEneK>55N`rGt+AX?iQ#e2{Lt%(YP=v?HhfVj9G z_<0N1__G=;Y~$VfN_-;UrwM@van`N_RXGBaIxHvK0}L*r5Ucdy$}H2=W)f2cSX&AN ze!LGh4&ibzm64G3N%i%_Jy-DXNu^1wdymLqV73I#n3n_bZXqt7v}KB!goj99HGk27 zS-sE;rq+k~)0xM!$@Dl1lwE8qWGb#YH2=42=18d=WPkz<_hX!xNy>+s944BALQ|?; z){MOWTM@N~x$-@^RMf9ZEl%HU9QkZ6;3LO5xi`3x=KMyQ$Dzp@=GfiJu{#@I@kwHU z2EkE~A`bwDvgth(ce2T_6*TxceZ959XOw*lQ?}TM_FKIpFB^ENiQw^M=w4-MEW)S)@aa|U7^${;Ii zB18dnhAiu$W*b|1)$hIxxB14(4gJLnFD%?1MU@iN%hbsfjt$lm+1thFlF0qXzXD*k zIiQBf6TtjY@14sT=-@u@=Tz}a%~({rqlq!K%Sa4Ho4-({VsXMO{-EcQty%(q8pFqn z0>!dW@tf&M7c~Z)-Q*!uX)wm2V?{4^YkHCA#jfqe4n>=i?zCz8W7jPWGWbu*zPz}9 z>NUOGb0EhjIG&Nvv(|f(;7ZaIpjN6Dh#~~GyRs1&55pH2nV4K?hGB8+8SNs4&AhG{ z9+kR@d_IBDwPiuRH%%<3F5o(g;ArAX!IPrQT0nYw7LHZ$VR9y?3Oykp+88}+Kp)!cg_dMk>8B3U&X*@Cmb+x zyIK%V`Cm~apkju;v2 z8x=21U>2v;j{U253{s#P{;L+mi$eV21BgsyZlkERs@Es-w;A}vQyo_qqzYj zP&*nJlOmnuq@X0M8zs$2K*d`j6OYh}CSfy?)%_X^CQYyXC`2beDZr*;v{=nbqa!MS zh*{gOZV^o#aPfl|$#)EV@fEsxD}sgZYNlWtaHLdf+K6cN2Wi{=(tNi%6Ey*S&%{L2 z)D{J?`Y%uc5wlnDMA+9nwl^5(n;SbJ&sTW%yJ4rU7*`XesJa~>OJ#y)m*m8YH>6+k)v~OVJv3tx7#VEbm)-?Q$ZLHjz+Jg z^{#HhBTF&(_wKaX<7p_v`H!UYA46!fTtuL;-w9WPEx z(W%j3%)I`d#~A2v7l1YgoQs%pV+;CLlH;GA;U`V_{vZ1!ACKY`P*b^~VQ8|A$R?nC zrISMg36RTO&J21i?b}tsKijqu;0H&KrP9O@Q9Dl~X1QvphHp`g46g`+r97z^JVoC`vY3F#gTH4{V35BgYm z4-;MHA6igmL{DNwYs`hVk7XJ`f=NSip>=(!g(2d{%$FmE4|XBV7!t<`5;#Id14P3@ zGUfD?Ny{|o>`DiIlC?e#?qFHoj#PgcD)QJ3IPi=k`xsw`rP8&m>*++y1=v4Q{WcAY zv2BDSTrDTK&-|-2EtQVpO3n7@GkWzl*)69giJJ6beC6hU>vQlU#)M<%APyx-V%9LH z=2$;AGYDaO{_aVYsg5wty6C=wzV)&#q_rl2u1M#&^|7+1xI<->hZ_epzRLZ#JWtJbuSmO?Oxd_Whe;Xq4f(f9H1yGl?%FE zcNfAy6WK&as^YiG1mH;5VfzF$R_Y*BzW0z;{Q;*iMw9%b#B0WUL*Vn%1w%L-W4pY8 z#9=dPb9w##3#J>mVD)P2kPVzycQg=tiPD#B5jo%=TQ z?Gp)OV7Tv4pP}#mh*){7LOszH`Qn#Lgi#p4PX}tCJ~tmH3u4kZPB@!2oSm>@3!QgV z!2RQL-<8)OW`-Ij-cKkN*BB4cu0whI88tT=SgH`Um0P z=8bMQ_O;HoFZf814eUGvl<5IThS)x;H|lY(`^2CRwyE$T(I+mYyuC$iCL0o~b8+H+ z8T8R>7`_KPeBAsazs-{$LZ6T`h5&p3F7mr_!s*t}s~sLVFxmdTGoHv1dC>=vX<;&o z;E~58s=|raf297Gg9MFF>>rXRa{n>x(S`;-Ob4j$9*EK?i1m1am>N^`9SYP8xK1NV za#sx~u~nEU5s74V!MyA1iR@>72F=}3E6U}<4{!Hxg5v0aV^BnJg5JuP1<`aF`fR%sz_ z+Vh8`rOz7aHCV0of4bp8q-oI3uA@QT+LP9(w+Jm2S1RrFVaLU?40lrgYjDe72Ub%- zS;M5xP9>*PU8?qDw^saR@afB*;M08H_@)#c8Y4J#ftGL(WBN1+THQwis@8M6WQ8=i zvAKz~R2BkE;(s_b|8vKqK^O<9L6a+N6fGtj*72}d=3%63?H4{?KV2|2d3Lf$uc z3pyx;n2HK~+>R1j{A1=Tagqe>pKF%M{^@_+>7QVn5cOima|hJsM%Ri8B1aOCVRb+9 zgOnz#IOx}*6a3&1q$2r&!mNF0c8VEr3Xz%?iQ!!MgNX=s=-Wk6PY|)Uhg)FIExpsf z1eXOy`?6SE>$`Msg^V_`(e=qkNJQ_-xTh8;cMQAKEG zc*~+=#m1tasc`u~6LSR~%@!^se!BnTP4M6O0{p`HS%}mN>{MHbyZqkE97prPQ!r+- z_+c-nH-c!HNU3N;fZnt9sFTgPlT^nJPJ!&)h|e6G!1{OCc}U4My2&GEGl4&xGI!%2oAsNs8DD6GyWZB_#C6 zWkP|U|8XMw@A&7zgJpp#L5-{`LnuK;kW{C}1DSaY7zMNaAnkB923+TGTC)Yd@p;bg z@HbfzTi2lt%W}e!xbX8+W%CBbrq2xBh04lG${AH?e64ivcmS7DT%envtRJMIrxi>l`=4SjG< z5!uJ98;Xb%$jP_EJbF41Dr6GuBtSaqRGMaJb{z_HxpD*!0{jKROh`y!qUU#9Zq#Bv zW1^QQAtH(yR>vk971k%Nf5#X2Q|v2ezDK!tt)>ARw)Vm0I*}rGUzOGp!lH$c$c^~3 zq9XI!onWgwntRhp8Y)?8gykG1MUAB4Rh7+^ca3C&BBj_wLA^9Mj)dFeUsQQ%LgNCP z-M>7*tATjng6P&U!;>qqUNg7f{%30bCtIrmzIu|iLGMZ`&?CYz$M!3M_(FX%h&V<* z!9#dJ`ER9bI{#-YF6leZE#rJutXW_;n``+Wn}#K7N`@M%UfodXah!OoF*{-L>1eg1 z3%;E?lJdgSwQHrHz^{C>?H z==ZhuY+>6Eyx^O*sNl8pZi)U=OA<@xsj{`+`QOB(l6B&oi%NcsPu3#fS^PYwMOPFR zFF1F$Ex1y0fw_a(i{ARg3m1!pSiY?>y!vNt@SdyoouUi=tE%+|Tog&x3P|Zcm}A6q zrEYFL1CK~%od2<3ljP@a{x{*(6g>-(P3z|eb~z^TbZ?E^mu0BEA7yVrgYpG7LEx-| zw9J%e=A9>sg94e3uDful@x>3e?n$c5E=f)a3ev)n{1f+-8urIBS$laazW8D$eT4f+ zfycYN33E-3luWQ`Hk5xN>a)zu^J))A|H_LGCZKKFuwn(~l9wWfWA;xz6T4x}&P3J3 zG=Ha6{$A>OI|HNwsu!GgOe*yUSYk;RviWLaq?s zI3DO>4D4o#R0Yn+KuarR`6FA~S;eU=&`79UPh<*kj N@O1TaS?83{1OQJbFdhH^ literal 67179 zcmV*XKv=(tP)n;IoSFIN&YY<;qn0EI+!P)J)GR5@MlR%~Qb@`~E)bTM1yhD9 zPEtt9QB+>v72YbBK%6eLUyjV;cPr+}34vh~7Q$X2N&t!fvl}!?M`Ti8Rf_pl*x1KH z7f}W$yKPs(p3@KliVLL1PnEp_CQ_9U=w~v!lL+YvU|~=6IYUIEI{|lbLcnicFJSu)EY)UIn-6zKEN3oO#&^xJ5?5XMsds{==;> zLPwvI1DZCz3RU}QUSZUXI3gEN)~xKBRqXIbkW?YKRr4vY;qG(~p9NBO+Xy>8>S9%( z9&5!fNt0?PJqwy^>DPEHkcd;i+v5)#ch+uqwP+vx6}ov3$j+HlsZe~-GNMo_G)Bja_R=2t>FMw+|{~znNVeytT_+s)0M=3!D#ItOZp55+Hfa-Z!a5?Y<0^Ug}b}?=3y+}dPV-)9f4i;$s#PX zlB=t?z??^`+XG+Uwhr}M-)DbcFlB&Ts~I9AKHE>c8laS8chXMDEF#XCfGVu8L@oZ& z0j)y3*CwZ+))%(|sr5y57_B84I@L3{>Eo&(!s;hEG;Itrn0uG0njKc++5|xOKs{Nxn?%5H2__CmilvfbMsw*ry$ z-bfwY1u(j_=c_t8+U=Qd+=a0B_CZy=hD;OrF%_JHQ5J_o07t8)9Kx($9&>K}Kz8%O z-d$^y4m zqc4$*e0JEhFwMRKVb4qhnyt zbe}~6+SOF<)B_7Txw<-o&3wJ_bPwt5;uy}+Zlhv_!Hg$A*@OQ0>tQiEw*rBp8bUSO zVCnMOBJ=vR@=F_D4UzM$g`zMHuRL#K54QqQ+MJlF2jR;Y|M?l}Ru9Te8>5r^NM`DS zh$_3AukeBT*_shwUuYmavSucsnfw2=8Vg@yLFU;G4 znIoDzaAQ?g>+o)|4x6wTk5vc0IVE5Y6$mrUYg!6p*u)iBFuAvdFo=KM3PfX2!C;~< z*@sDfh*Igth><0c+PelEyEbwyVXInlPmU@J)s|oyRy$aMu)-KWV;SBV+Yh5AQDIDc zP!_f=;#3$magp5C9?58qI^nk1wHQs^)malQvWeA>cg7FJ_;(hAEiEuJYSLoNpJWmE z%%2VP^1`?k$f;RJQE?anNhbOJ>WKM`WiZ<$k(&}DRj3V9*3WLFDcx(@R~paGJLKk{ zQwLj9B}7=##|nd)*)(0jjLn}Wg*+NtVK7e{?Sd(m0%+?Hq8@!iHh%}_pjq7tL|$pi zu<)0^Xosj$+EYQfIy(3}S~EYJ=J;y2(>#FP4wZZK*`B^_$EXt1@WZcBDqED&`He7_ z1RfDdd~_mWXHS+{CpNE+xE+>SV#;s;GnWykcYidO9I}JY*qwz2k(uaRGcMcFhwab( zSJ0_pS$7IC20MRTfoA_=1V5vAT$ z2T}Wu0O`R?RN|-!cK$iBd_ZkJGZAsINKZ<`L6wNk6Z&K9jMbPiwx^w?WApOTyNgl& zp8Ifc{a;id9%O(y8KFIf;>D%skbGzx=F=c?_~fM|g9h07Ia*xfVv_f{DT^>`!VpKY zfb1R%gmJj|=LwW=WsxNY>aC>ItSe8+2cwQ$`sYMIiS$J52(+WWV_`VDGyDJEw8#Y9 z>s}a+{5~2`nW)(hUJHZyxyHp>(A*RT@* zZu}$Qw?J$x7@!e9Ekf-36Kzh4o@tMcK=QM%DWc^>eq@L%KS1+Bv zy>nUwa!2I=HP;U*TqULEx*>(EKv;4dN2yAZSM&E4kkS&ok_zD^as2*WUj4a;X9U;; zr(&VbnzowD<7H0%rcb1d>#_<0BR<~y3~3?Qwin33`5zw$C@PRTtE$q}37b0eY}ACe zZ7-KAMa>meXdz0#vEFB0z4*Ha1pF2VQ?@a>1l0Cf2C-~2Cs#*C(zX52{g^%DYBoz2 zrJcU#oWk%IcLW@9bn|u9n$q@O3b9-Z7`|@<@!_p*^fm{fR6{T|1nJfZ=oWXjIrBoX zBjMfRwLoetvrIqRf`*RC{<<|(7+IfHM(9(tMxqY4k+?mDVm0J~VYD{G_@3yO&CBM_ zpB4l>7D&Y9^-i0pJ?VdFs30;wt&5QEpFynI!zZ4sFqs~S&BPu042ku|*k1Y50RfK% zQg;0aJBc^F4;8B&r0=0yIR@&xcR|x>l0A$kOW4>m$edP1_A370biW01?$2LjeGeNi zK@6i^AAKxaIvsX=u&yHOw%eXIdT&qL+PTf&waYK1(Ke#U>NanW&!=cRk)FDmu3=$J z#;_^B$YEUll%~(EK;rh!vMcL|OtsdRv94Je|NR=Ox_wE|H}F9%(6j)lB3OHCWjBt& zmG+dU7wkUS?48Qc7Ea&3&5^4zTOY4T4k@nzUXXz zZUy3~?_ncyeHwmy7n=LugsKFoK3}G$%W53;Mr>}bPbK^v!`QyAcg6({MkIY#w(yR? zJK7!NV)i)>%^`aqUQ5Fs_T7*2itZuX(`cZy`1&VSe_glfQmu4`^Tb1;+hrASIlH=Dz2h{6m{Pf~>9I%Wj#82?&cT z0TJB2j^!|y%E_uh=;zZ=|vSQXE^d3#?Jr1VL&;fYzi-V54 z$sBy$t+w>C_>zz9N9FWX(hoTy6EZy!NA(2v&@N3@sqLTHH$sPwS>}meb_?nHDza8j z1GW+%)+WnFtUq&D-X*JU*d1x}s{;GiLGv;-PgbMqH5t+dOM%4yIRRJu&9QkYdb)mv zHUL;)$FGyQzd+kaQ_L2&GpsupUFjaBW~hCXg=Y0t3B4^=r@y8v`|Pq|3F$hnOSEL# zR>-!eBX3)}M%J>h`gi|XzlFglooYzj)~{f;ZG;xQZwpj+ehSi0A6jDa@YnF?0_t@Q zlQ&goouV4_%S>o`ykMnd%03nuCDPa9&3M!IVDz@kI)?S+Hq)UHY_y)}{P{VC88mhX z2H65wpN{^Hc1JRA?v6Cf?o=SMZiwEM#LK%6x~xT0QErW&lc9; zh4&ro>{K8(+P{FfU*5OCVKUO`08ZZ7Fwq_@MS3KX)=ro5NHs)%cZzny)9zktff(te zpkNu+Y?Q2XH$E6#TM^5{2hX8y{mO2%j?Isor`$eUYPY*za9o@azt#G2#4i6p=A8E8 z{fPYd509~=4JCDGKZ>C&py5ga{dLZOZsxQ0ChOt@;}PO9hMYNGKe`7d)W&fV`kzc$ z2&Qjd!`kw){ka`TH$BFh;}t(`!StouWc`oI4-du^k~dzP74uEfohU9RYWtEO&|=U) zd}QUHvy^%dB-F>37>JLSihL zv^(U&Vgr+dZIOL_|+$WPjLq7#_Ho7 zYj^i?>(jPbniU2!uKRD>%C43_9s2b^hkgzYvej10Ezj8w-Dy|K+`hXN$n_T%pya#5 zWWLGmNnvN>$?Q0+dL!8mf!PdaF4_YmJvz>A57aW1cD|3ZYCHJ~yKUhT%p*mT`D5>f z5a%pgn7W!xA&1EgW}Iq;3u8&2LMqVFF0R?HV^=3&M$aXXAo*eg ze$%&$(e6>}j(98ea8h}zE=qWV3f!if$mGQfW13+2%r$g)h8eABxA;Gi^{sK8mE{oP zI(X1sbW4I?2e$%YAui4P8|4SLlLIFk}Yg{DnDZ1pHf68o}brmQN;Qr<-fvZ`5g=>Nw z3Z%3n_q-J1#zgBy-b5)x%R;88Kx`as;Ku!LD#Dk1ZF6>eX7(O{TW?NvbLS5J5eTS2 zZ0r(W8VB~{Cin=bcg`6qtpD$P|KPLc90Nsw%Hl^^KjiD*4!tpJ#ol)VWx(k<0=F{) zqkA`cQx+k%%HcRD&C86llW4pr zN*m>q-pNxH3RV3_0Qxy4lr9S}ecc!ahx^7H!&Ug}LHRYk@`JeC^ixMKZs!ODgn-j` zr!=HWmx!vrH1bMrxlr#o`LneU2SZ~Lc3+#CF5FXlxNy&b(zjJj6^HJBQ(37DbL+yS zPj?Fwaqef-px^n<>!6FVZa{aV;tm`E?+653L29pbzA4g?wW>r-Xoh;;2rSdlz8R*t zp7k-ELO?%eLLT`OLb2+yJ*R&(Pad`$@xFEpjK@~Hv8lB7KB(0Kj`Th2=Eog40=^Ik zsDf1gKHhq~hO=Z)sZ`L8J>@GNC%@3A4v=npL)k5BL1+1?X+>()ZC-Yw3-m?Z?K7~S z{u8Ridh51JD-(l(?z*D%pBPGq;P}%j4m9FBfZn%yM&1IDk(7wP_FY1omh}*4yJ9bI zqqK8ZTtTXT9d8fhXwi0k530t_+hQI4J=#N}p^mNup1J6R_KXl-Zp#se@)7GCPI4Ke5G z2KM_enX9LDCfojQwz*Qi)6C(V1W)vVr8HaJvf(|AQ9Hu9VYBh-*2Z}wrp?{|-fU;D+sg^ z*X7qK3hwT-h4b}PR6(lGt4iZ*cW^_WSr4&9HDCO_`e~lr1jE~wUAT@!x|Od1^b3FU z7|n2L9lZjwi&gGH*h`0z@m@W*aMkBhC3*6Un|n?idbX=?57x6oL+B4~##2kr!+2#s zxi_%;<%Pvoxmntl~t~ZB6mq=cI$}K$0V?-wIM{<7=eq zWNj85qR@1CO>yNgEgJ)|>qH1qC9^qDH%Cn)2X80)XH2ODRcIta-aL>UCQzFu93cbe zLAP*(Q%IJZ%0G0&xq)k)+;j5c?IWE;`g3_|j92XJo3(C&r@Q%<0Y8X7n-(D`R454tLjJHr|+Jk)|F0jC{YR8 zS2oJNs+wJ4*t5og(%EXH)pX-)bgkg*9n(Yd6I|^NO zna#50-Z&PXCvN3R&`Q68Ydx!%v(3qQ;^M(gUDp2LoIiCI!%6wnx>H9cc1@SBJAXZJ zw>Xg!keO_@6Og>ovAEbrYDOJd=V$E7%R#v)jeYd4hN&Jh6+~7!5;m{Jn}6JtNtgt2 zG!v}KyRJAm(kQX}tRNv5|AX1!&PMar-|minTr`h-3DYYzp}%q-`WLH06B+?k)h2XU zou7c@oksMLA7xjPaH=rr&>7)vI~0h3#rizn&*G;$WMqa?J*zW2Y<;?1@Uzh zj;928Y6(5jF*l{Wy9^(QE-p->%T%;W6`NOXTF^kkhSX|Bal1D{wshE@lN>ZQZEm)w zYkaQn5X0T_XAA52bPMO`8KB*#1kv8OkbOQ=fQ&toYs2Wo6FQH{eMsYx+|@B-bRC4x zIwCs@k;S1l)4j0PGqke~?LyW_x`KN8b_mfWT!UJURdtP*&Lrf9Bj5`GpA{sFcFbxn z?DG{qU_VLccIcd99!9;ME*nm!+n-}BVTx6Qbn%2!_|)fGKwG0RLdPw0a%b_n8YSI3 z*D(D3&O*Rv1-a4ZJ(QjHfEyyFWB)-_>uxu9e-8RpU&7Oe>Mxw5zwzry(2SlTJ4xrL z%f-6kE=GTF52UyHAnfhsvX7ZnA`cZL_3nW#Aq)PM^`C8B1ZJQAQd2DbW;a@l84PyD zPRW=Mlx)!fW7`zRNE(JLodIG*RLy1&t4&Y#V^U(U{A~M(=+>@+`mVOn zbnfNgnr+Q`g^my3`%kt*kZc)~o>k1rbS)A3c^|pavqtyEnAvXbce;aIIS7OWlZ)0z zoR{V1w}P<9&wYIq75h|o>uRMKUjq0>C{mc)b#NWq%PDF zh#HlB>|x7}<57B2l2)$3X%fb)%PptDQkefr^Xw?hG~lL+V)zgxZ>#xs@Mv_r(L1UbAVuarEBOjv3cUDxPq|Q&(p_M?y(0T&{3q0 zhP5cSYSw^CB_ig?F>0vP_CtOh=?c=W2@A*BwfhP(32vu6v%}(TW^9vCn-vk|Ya@c* zoGJ&s+xq=BLPzPr+-#>pv&zBD+fMg#kQB03E*l@)*h40!Kb|VzBQFfWBQ^qu=cCm` zJnB+e*`fN(FzLaJEgn`mm>KrEWjy39U$*o(pFC|FH_0sW{ZBv@gr)BCH^0hPOO$U> z%Pp}cn&e~0#!^E<_uSH1&$I=+Fqre3_CgiTJ~?i3gMj0?#kt{Al)?sFLFCNQ$IniQ z6)v7fx!&%xK}@2sv#$!;Twkyh`UZ`2v@fWj)6U~B90=sBf>_foRj4Fa6cb%^6yK_@ zyz{f5v&E#>`OW(whK3b%Qh5Bg0Rp+IAh!Ig)Ed}M(pO(Ni?V&X zk({Ign((l}!JoGg0zs)Dj(8X=#@j0ka}V9I6{W{KL8_>%;}smEq3NirQ)eQx%LBn- z$hmL?ydV(t3gQ}1R`WJEx8u00GtI#paP#0^RNM70!Vm3-kd|tvk!V1jS)&e4w7v%= z8q%CP9TUV2N1z}OaH=4R_TU6Pm9(`Xx;_0|7+5Dr zN1&)7AWM*DBNqm5fKb#B;OXTEctL<&X5`ex@~xX`4Y_p^}wy17)tR zK;)G_5S~t3Q%GS)vYrkhh#J~6twCm3Riu@>50^@l;!|vyyh;k^%MtL1fNeZ(uOJ?& zg5^khA*J?DLF}1tEq8o7@Re8H9p7muE9nLyBy$f!ZtX$wgCEGgf~Z1j`5w5^YzA#Q zVjI@E;RxhC0(n|Nw5g|1{jW}Ru!3cXlbeB#WHur4!Yb6DA3KQpOuIG6imjLT2=N&1 zXaw@4g0Pl9ep4^ntSAeEpaX2A3Rm&Z2O!dd;e}QUkWsc{(Bt3?IRXrU;8qYCN7Pzf z4`!n-U$&Z=rbalC+ileDzt*wVHzD~ zU4fjfHN!^W@RZW7!2I`{FrC||NVg>V*C~t9_o#Xr=?NYpsaKvEFAW8Qv?O zYa01GN5B&T%D+|*p=+(ELM^-R{*b2eNT^zmgXxba1F9Tu(a|)ox(nSPML*M?FJajB zo*RkHfu*VDa#l&s^4p|rpzvpo5Xf-_slB2yOg@wb)nQRk4?Ad2l&bS=7`~_s%~RB} z(+xqnF{;K-(XCcb!SqcN7}9QfWW{#Z*C9!Aw9h$@{@jrxpag*&Rgju%8u+Xn+D;!r ztUbUveyS8g@fyhbyb4{jHWwmwxpHe%kL`wVeiQVoAN9z}#45?p)o|oN+7-+VM<53X z1YAK%|MvH;Zkp}O% zH8h1BI_z&`&Ar!+6AedDe&aA)YQMnEk30BAU_KpPx-~Nj3numSO_)c%+jbp;P8Eg@ zjVt9*4Bpu)t{^do=X>O$uGkb}nT8(yyg91wrNzHL-?n?3Xj(Sw!+3(20gI)`>bB?U!`ApsQwm*8WX+X4yrVjZNLa zMxUcgH(e6sc%QqAm;Ke>Ew_wtE1l~*kgkx(jWMTR!-x<5kXd|4$CwuP+!1k#;z_#Z zflaj7%y%@5jH%faGajyGb8>#t*~Y^rt&-hrcsdgV{-&scl>B!h?f-CR&K3YQgO|B` z_;FD+83z23T|pQ*!$JQ6Ns`yj_zCUbTits;%pX=EDaE)tn~H1u<3hLGPYy7y^;$JV z&2UReUr)G(Er+hik&UG@af4Mr&Z*D&+kmc3JO&nHOL%m5b?h3a;EQ$*WPN|0@JOZa zXAU(IBbD4eLgB8^4YsWK*m0@0n5DFf7(Q(ieTuc@+jL!{W0j**6%l`)@$j*_5-WUH zR6$}7f8%-Z`}hil(YTON%~DggjV>RsiK>bIgKAVsPPqgK{8ck*8%&?Jatj!8X{Vc8 zP95qrtB(gt*ou$No+U;$3GgeOKJDn2(>Q#GGvVn;OXM4&7+vFLcj4Kd^*sjZ*2XgK z^XTcr(RkWBUPsrg#I)_{9vcIOgPC+frsq}J%|<{nIk;};B6L0mgk!HePWn$?)T zJ;jOPo}K$JjDEqCUeA4V9AZh&m9BR8UcmN+*qjEmjdVhU8=P*P(*wuHh`z3swm?eY@C+KDRG^D@c_eyLns`)QniAEN-@>&H7?4 zM(jR~Bb<~bri_A|txRICL^h(}T5Qkeqhc(K(%E2;Z zs~nD%VcDmP@auI6ul#xqA3jjZDc?59=Hy5(j^SPX*n1Wg--l!F^5iAEv6B7j!zE~I zy$2E)N`9!xqbg7na1pAuMb`ML2z}>tHWh6?^u#-xfLf5Z>m%%)GmcI$?Og#?qux$# zzFyRoTflI7yBmr2=5gqY*K~8wt%INr$NUM((wCdlSQkw0Pd7OIC@1&TceA?{@r~}r zgg&+1vf)osxCJ1G-OGNWzrJg_Wd`#Aej&-#qa@LqE#1C%?v);l+N;-uks>2JWJfxg&fRsKfNXZS8=;|zYfHVU>4JaDT18+gU z=_{naRu$n>=$O$EYlqbXZ%4+g{n9NSi;%%H?9P%TLib*4guJoM?&iy)edKEx-g2+d zCD*-%OM_SV3hLJ{kf~U@Kng8GompU3{+{dUL}b%?G%ywDGV)rl^*gCmTH;&iNoWto<3Cyp0G}YW(8lM#e zx-7TpU0F5O!=T$=I@F_of^J-6nJnY|E{J$@gWoQeji+vEJA{neWP63W?UT@Ln2WR* zt0D6JL$pIO%UFBl6h&GSWl|89&XBg@s@l0Q{GE zQ+{s|ZgCFe@6wMKVeL)HE)}7>jA2tZ;Qfh1QOYU=uF56~xTeDOfY&V8pPTjfRA<7|R9gR$l3EcW~h9+iJLW0U@MONb|Nbp{C~> z(5;^<+tXjIhluG%X&;!@D-$uIIFMxNg4l>$vSo7)`csIkhN|BLWe{-yUWBVyKjYw3#hc(({$E9Wb; zLT)o&^B+~vJ~J4Wj*ojNf9Yf)20T9m16+c>^y&~77yqyA9vzH!{>cix@6^4s2jUivfFA?`uOJ3bwZ=~@R&Oz;bgn+stj|Xybj+7BHzBT!8!uye zCiK6|g0{zKrv#aATy~>%BH%9^0WS#ntRS8i@(kyWxoz9_!rRT+xaTj$Ggoeb$-%sP zeYP{rqDv06OE|0X?%(#ss;28m*6~t2)OSpB-GbaVE=AG+u)snkyU-u%+ z^e=aScKQZ;io`RAa}}?hQ65{&w~k=M%-!}_lnid|!~s}Nrbx6#;6A?e#Nrv6ahgW@ zEi|D7re87-t?_oJVos!zJ_Uj{8ZS*;g#S&>9$2%j?QDyCQ8qp+h{`O`Mn$>zZ_lII zlxepIYGGZV+!)7{NSW!h%I}$5)}`}KQcg-qaa%0R!BURr$b#%D#?+Fxa;NUzfKiLi zz;Jvor9A@+#@~IT{_Qqj6_w{i!>~=4PH7*8U zD@#)938%1?(k?+L7KKFGJF}d0G5U~B1_PY6U+?{N3QaTjW8EzcZYAn5e_|hee>?#j ze@~FtbAk+C(AoK{ASP`Lq>N;zNaaSG9osH45R@Ckj&-yuudLNl!Zma5c%S?X%RYgo z-*Yl6I@pAabKIiJXV#S@Ax~(`w3U0Fr z?C-3lc#Rg+;;BfUrHb+xY3xN;s)3m!n#=T452j)ATcqXam}Oe=Z8fkL+7QLauTQ{q zdXI&l;UaV>CWrI-$0h8SH1}e`SFlzN_HNPTc%yGMTHU{i`7bwz?$8$If&HgdMWS-B zv^qk@-knTRi-CywV~&#>doi6l09B2<+}wRRWPb3BFYkc;jDP>)wutG-M8Ax-%TooB zQj|smS{6q8tpFiw5LlM19vLpp`Fy#@Lez_qd^Xs`BE>B z735a`3GR#wvCI6ox?{xD9$AZO!JG)V+e-#R{W~ zhF+iwF?+@m9F@Y5oa#M;GX__~=nsEJ>j&GQU#)Dq2_%`=O8EEWB#0rHPn-WF>fL%W zYC*!wYp!B?cbctqFhk2<#+$R&;A$eN0M&Q!C<0cW6@=Xt$U2g)pQ7RVRYh6Tul^bt zv)@rftAt2y@u(F_e{HZ^nq0%nX(n3z<%_Of^1G=J-g_t(t3%@t=j1M!GT7pviN(Be zeX>2ZahN~Z>PN0$TBU@Iz;S?IXY3%Gv;2Izu8N-3?QU}(uZg)cmSWZ`1CY39BQ40b zxBD@RBQN$YL^y|KZp_SaNy9vAm$2iIUT~DxFEd{jW>t z2vKL?9f>lhj)88=T1Tf`+cnhpP3>O8Z3Cq@#)jRSJjBpo!6yWOXU;7Hi z{Wt=41pHPIF){`sb?04Xd*x46w`3kd2GK|8Vob*lvah+ojpRWQgpEOUwVD=4+E`!S zwY!p=ggEY!9YfQ(pG{dp{Jl$c7MGNM5}+9JE5-9EJtDUB9Pw-!Xmx#_$0*5ndBCK#wYJ1WZ-b>lsdzmIsa1{M7xm}3Wv0= zLkmSZSR%gt><8@Ahaf2{6!YKghLJQy8ygh@F}^9@?-wr*j%l1Nk1qt8{$2XbZnPLP zSf0*h;oWDvn5*UJAyy>wXiUIL##WKuW4aaezGcZuEIevi|A?nyrp>tWV^hX#R_tZc z`<_mVg$oW`!TvSWZ7p{fW-%9*p!kPkXta?6zy^QoFPnHi_0eUa5tC z&keTRwq?zMg7z?c1G*}BereSu|zF<;!xobpA z5>Cdo#b=(|m;8VhgDe$~y{oG=$NX`?;l2AYeZ?`%f3>kg_sV%p=ZRmja^-PO2h=L7`yVLOrnxaiTg_qtvgWlCO z_~`GOIJAB(ipPW@qGCNX>DNf^iiE0wsk90n>%zy|YxACM@a&8=h$vqh*ADHX;}#zz zeevZo`D3!gI_oW{lLRT&qBD}VuLql|oi%L{>XPzOacvSN&f0{qx~+(Z3a)D}UKu-h zH0d|MXTF-%i@~VLi_wJkiuqf+abTNgR>c*BrQpoEW2n%srpLsj3><)%-`CM)omHTT zD*@fjWRG+O<#=x0zJiL{9@ex=z5}?_q!LwX~Yd%8V1e!`g$zm&O<_$8XvO z=%0?QU?r(0Kb`ovtbf>eu{D?lQ|j1WEzj~Ow}Ef8S*P)9?xxyKu{_OLq{r63Kw^tK-r-W zK|Fpqe*vpl8JCu?&)+zCY2QaNY!IJtIjCvZ6fcQ0zx)kBP4}V)l{06?5pYHz zPb!El3XyiEU0S_KepQ`6i!#p*hd@X2WN%5cQTl!g%)4Z1Bz*QYj8&`4#Il?3VBD7@ zkZT0;w1POM&QvKLmsb8MJF(L(G7>JKaC|wSjt=i&h5FJ@@J4Ybj5XiR*;+P}0r;B~~8SK~P0Y@(Knbr`_dq{5(3207rl$z!50o2-t@p z%|-`XPqaXBft^h2yg^)ionh}Lj}74ab)78G3@BU?HmD)07rl$aK|Fxy4K-Q zfZP`7(CvjPLkhyLZAH|DpAnO^4U#T{Hiyw!^<=E~ROBkF?C3&fM6w8>kw}efh}7~u zkY276jN0Plar?q+$vOYa5#R{i$q3vQEu1Rr24c@HLEPEzq0UNx+eM#L!p1x(Wt$K( zF-t}&!ZNlZj06_;WJji=)h3fR7RluwL}HEMFho=$PurfeY&ve^2yg^A0!0mhA}B!U zD!q$aQ0?esXi`toh{Ad4#xlMrJjEbi5S*2Wm{Z@#KbA5!VlhUP#nt+6A+_B7bS^#@ zD21oQcuF_|908>W6bS($>XT4m`;!Q}zQgGXA9){{(wr6|Ix~$}#I(yO`|r~zOX;FP z3Jg*4INxS5jN#=9QA&AII0762j(~Rr3atRqq@G5le;$V_?VNqRqNoZC@9W42ZPOBBfq}iSFGKQcgq4 zN}+|7Oj)?O%%Ww7&4EqMFU47LV_*5oM^j97oNc?5)=?;J+;9Xq0vv%tgg~JXAmP`y zqVm5_z^K>B&;(nP@n=N_Vgl@ z-us3;+Q`i=DYgVHYC<5?9t2gh=V)X>BM8B3Q=d0jiz*1wm7r?*vixI_u-j=RlMd!R zbAbc1X~2?VpN@0udlFXBMH% z-Z$kPR5=}OuoD(ll{-S!VLA}zJiTK&vKaX19SB{%gjlg{Pyq{+nV<@Rs_7Vjer$vz z>C+%>84uI(pJ{{=_?|H~T^4if3&bA(0*Q5=!}U5Z5?3Zdx#0+K1ULfui$J~#kkFfZ z5Wl5ASZ_Kff`d&R6)kHruoN_!2HUmk)s z5<0$imT}TJMhP>T3fJCtAt=Pk#pxDvRaG})#WR&R;Kxj_N5#R`L1agl+ zz6cPLL5JGwS^%UE5qO`Qrh>x4A@=)&ridyi&aqgoB{YLK(lzewVYqP$__{fmQlHRp zIMi+4Qp_h$2~}FC@0kS9PoqnL>4(lRq+O+9it|A=JTRx^ z2yg^A0=YmS&jm=-rL`#c?^A9He@fDovF%M#VA(;ZA#eelfbR-KCB)>cf)9Lk!2PACZQf%Lch&i_cr@O2p z!Rv2dnLA$Gg(JWb;0P#1AWsB{WHO-UipG`&;{Xk8?v zCt?0?o?Bzg7P3vtp8&gHRCUkmP&XZ^h-aP?F+tLEKGd{}%k)iq=#xm`DKz5EQ>gh< zJ)C@qgtsSm*q1__u>-EKUj zvwy>Q?YPIVW|JA_4e!FZ@m;7JKL&ND$sXzQ-I49$8ukxDPOXPw#R#WSl&`dyOw>i8nf7r%3V{({Myk^_!QAjQr>gYQF^8lE>z%zJkswq-f9{c_4s_}2Kb z&}Z%6glCprbPC*h=pc-!l|6FvlgI)GL?Tw}jF7R%Vf?ZcEwtbC zntn$Ql8K1_r8|yyTMd+~=jc^r?2<7Hi@*CBi&={4`kGmj1|lq|jX;rsqEILF;HnkY zscHc;3s87$TCoP->`Ag>CCJ}OW&oQ~lz}Yag^7!hV&yBSBk;~^y-+qJJ9NU){TR3C z0M;#ALqFi|etj^yVPG3vK74l(j${Qr@!5$Cf-PtQL^5O`etAdQqN_Z^ni5r^esX=j zDg6l%B_XzW4)}Y9U+Adqg?atEG%EQ3>H%LvtkTwRm^>$7yS7H}0+v1j!^w?`qGKbg zn!i4X)BQHn3BctPg~=}>e?9q|hqvREB`33!BN;K8D(R5zGy+;I3$Yg6yJ1v|;&SLolNaK=_x@r)G1!R5Ps$b^L&gq4 z=Q#HsO_|yaF@H)!Y+bk%^N$(u-?HVHA2I;LYbn?eWK6{HDZk3o?UA)xp|5c}zB$QS zq{3%{Qr2c#N2u{*JD7EBDCZ8Qr);Y$H--9N$4?!wI+kA`HZGo8& zrQ?NFR~)h>VcvcNo^7b0B07XBY}TE>ti=~Ql#L|IHfWBi{c9AqEZEcY>)hp*?@&-- zLc18dyI+er5yK|^Y9SLem_K=d-_~Q|J)yE`}`8@q~s~T6Q|u9*$SnC^4aETy1g?u zVEBZkPGwfUL1RqnUspba{eT17jvjw}oTdwr$&XCbm7XZQHgdwv#u>L=#&R z+xE>l=evKPy1Q!k?&`gsz3SPmocu}y;k=*ewdZw7X@SAkY2F2XfTq zl^TACoz)6c#B9I|Z1v?-!P6_Iqw!%OJs)j!uFX?Fe4T4-w zy1`d6XgQ(xL^XIlWBwFI`DA8bQe0v*XNg5;vsA?^96o)~*g6F)>0@l>)98yo(y3Svhjhy8kJXZxG%%xbIoo?Zyza7ik(e1_TmoWwtru&OHyE zBD~MABoEgE>sZHgfqdDIBz%M~2!mY@g`B^A@pNhk@@tmWtk>V9Y8MI3BQ#c}yA?x~r+%23cp3a+%eWs}IhjVyx+(Pe0?R&mQ%TRD3Hpmp@kMROXn^ zn=b>-I1dN#C>?%B2RB46wZh#8GUPRYjjHn~z z^D%4##SRbO2W#&y3STu60qj(?j$zIcOy+h> zLbi)*OWKUUC;R3N@-9YVm8~yU{O7mg91(2vb(NsNA@ci~pG=hRsy2B+8*8fFX;NgB&zi}iB@$T!pQa3_T|BrvJ@du7Cs1`;{@@F&MnVo&vAUoHF z6!1#j!#%bnjPx_TXE^nm4tmOk#+$gA?XYi^TQCX#p6h%SI)gR$P~-HfQf+ITCPxAn z##$xNJLG#!eB#8q=X+o*Enbg;D$A_7|79zwlG~&!Ye$!vf6;JrIg|3O@xSH zqD@o6X)VO-Mh}={-=G3Zg;?Ws7v^jspBtU$Ym(BWMTJ9}qP5!Jl~4mHoLQ*lye^_I7@T z??mV?1ul1q6ds9-Vx+L5$p7fI;9TCwq@TR036}NTb#PL=A&H@j;LdIX^mu1l0w|WM z-z}*XKDyD0MB(^ifJJ)={TlL;_pczoX;vnLDMiTSm zrw@51d$P#7vI|?}2&R_OdkB}d$%z2MYpUw`hswv44O`D*7J^v}nQ&vw5`%TpbwQch z&O&xy$gH}`jPDA1)~JQ3USBjX_W|#ItsW8uCpkU>kCGDs`xaPKmA!=}VX?9_QXC%g zl{a|Rl|(W^ocH}NhO&D$v5k%R$vJtwo~RA)4!0*#zdey0SE`Bq9$aSI{D7U}*CP22 zq;$T_>qX3ohL@9D(aJdt06mt zmp}iO7Bq4z9hE+Gh?ko^8sBtUqLowAfU2IAo(As|FKwZ_T8v= zvF_o3D?V7ys$)f4(}<|8R9$!?1VWY{^O`qmv?YW7FfLt=^-yUw<3AH?o;XM>o@Quc zD9l>(k#r~szH{PGShYHm%XqOi*L}|`m#jUrlTrycR1Zlk;5-6q^L|;kE*jkk_V}eqz1cp4JSx${fxwq@K$x5j&X%%i_T8 zlQh7NdQ@)Z+@6-r_G^J1aL>0n&*l=^wiun220~3!`uY z8i|m!3i?2cirLu#v*s~Lc%^HQ?$-@v|)^KBRt5i*_r7juoJ#pFcs z9+YOGDh@JRo#cZI^z*}lur*JnAw2Sz__wcuIV@t=sGXtm5gQ`12 z#}UXUj0EAI;3#A_@P+tI85Fxi8-uUTNSYZdQT?f?pHgQ24~ySl*C-fe-CPV!rqa+H zT@x=7jM}ZpzIffI_Fkn%AMX2;H8}cR(mO%#k}Ia{xCKLsJ9Cg;?H1UtTzR)w0^7(c zwVv;y>n4bLku54AM}Ck`7SqkN7&kC+i*}*fjHE5iq;CUtn13^@mKFqe9>Wksq2|oq z7=D;<9(ITTfaG9K>)m0eul?sanLv5|R7%|sct*hu6z0&%W7|mBUDi<$vin`XED#mV zs^Mw~=CN_j%JJpE$XaKzZ6ypMDK)t=^N;wrbq{|^yCb)&xk{b;P|soSnR4>3;R-=l zpsPQI$zlyxF+2i% z7eWGfkgskzcg-6X4|C0UymA)C`9pr?Dn5-BRAU=ui-NQ#GLLI)G1b6ezV;)4RsWqR zlaNvW@$F5@5aw#SCRxhm>#`IlGySPyKnal$ZXm+~C z`DVu~d?fhp9SddU*hDmr7BUJ5LD=F zc_^cs{|*mC{I^g279?4qn{6TUsQa0RH`_ta-R*Zd=Uq#5Gdxp1iF0JwnA}IrEYzDV zp}cs9+(Uz&hO$|}8K$mCN8f2(PWVgaE1B2|$Bu_P2qfm+1MN7t6fm@F_`VbE_piMY z0Qq+V(Cn-hTb0c?7!*S;4OT|$XNgA^JtK;xoNV~Z9CQS_<$8Uz(UUG!u06|4O2$dc zA2#ww)ZF2NUM>d<-+jViK52vKY+k6qN|!$>4WGf+E_$ckXeuVXu3+E(6_T9{<3CtY z6bS$Ta!4XI%b0k=Iz1_8S8qz%@9^sR1dUkf7~kXTr8ONfT{A11DA*Gn)a$uD*X#5{ zK7_SEyF#0Y4yk{D`$$WL!oRO#^B%1N*>fDPOsoZW#dqNeV1Z7RCT`l1aYP@=~vmtMSC40@yafTuxx^!xVN z4$sc@5A7YCUA)=y4eqIK3$klt4!z9&MA=661J zQU{;-P%VXE)*vffy2}RK^#9-yq9^^^f=z+z;xMm=m2V%YRMU)Vuy>g1Qc(HTXc(A* zV&c^VnLBugc?AX4tglYOAwofgtOFmvup#fxckFnp3schA#^kex(2T!^ski`_XjP`< zAXx2*+3oF54Xe6|&QrGc-2xAhrf2%Rl@iL-dKTBVHaz~hj`0=of=O%iNlVXkksB)I zFpHktm6GsLQbUXNc?ofbPi1bch8awqoQg?bgIKE*kMHQd7irEc@V@((3nXz)ac;+V z{pPP~@Jw}1>BC2ky&Io$AE`N?CI(}Y5Y^@darY99O5A>YU7GO?HfV3b-W)x5GXVeC z!2gCPR_J);UdX!cWlcCHqlM5wj=)%PuZ@s?kYn(0g3@}&Kjej(7^Z34i%IH85G?C$ zJy~EB1b~MEtv8m1#E7pR64RWB(>_W$*TLw?2UFO^$YRir=(U8)2$mnUuFUANShwSE za=c3qkv*{%0YbLga9#jG_MZH(x?n4IH&3VSp)sNu6BGDLEEW%thkuZ3%1TLCTuc$C z)hKtSwP?KzNLS4oUp|yMX{#V5`s-MI4aDvvN9(7}S$%C-C^B9I$Ns#1wYU@7g(nJ5D(ue|F-4P6)lmCf-pI_eaNVPs@mw6 zNQ)euwAeK@Ht<{Bu;oBT)Cz@kwo+0CZ0S^{g#9F&rR3-+758B3V~BSj-HoKpTr6(z zb*%~Bvz%--LC#qM>H$PI#tDPA*1ennEx2su6bE>F80y*P^ko06BKJRI?{eMQ_UZrq zijZIeJh-xT%!zC!fOD>_HNmdUuSZJj!;W64IFGT!$1e3x`R-Pk?Ok-rz z>V}R@&wh`+7v7(cR7O^!fpa(9#FWbor=Hr$5^KN6cU-r9Epv4|K;u0qx+gshAl8cm z+qeu1z*2?1)3-i7yqpz|5oWJapZD*Hdg@9~lP-yYz4er;<19LqhXT%i$a=gplk={~ zIrleQl$qp{fIRJ4gK=#xft9fk_^2}=r}ZYy`#4s-uTfZ^oBv}n8S|0x@B{Jw)Z|9s zYZ)izeVbttUwynff47-6E#(vHRjQXiL0qFgY&_BS`BFpHRR1%se0HwgmGgSAWEg9u z4-_YD;cwkG$KLe(Ns0k7Dvvf;PWoT{96RH&=(cF*C*aun-M!73Km_LI*C`zrETa}3 zr^1~ebtZ!o)ju6$RaT62NILHJ*HfNBB=!G z9c((B4wwZ?QzOYiQeQD0tY4J-S^TbsM%vc|Y7YL8cT==X{;cvh=D_NvbSznJ)2=v| zU=X)C?`Z2e-A8z^$cxe^wVlaF?a+VD;6*&di^kl%=ZE_SM0?I9e|576Wd9jdi&Zo@ zw{DR5L9%0nN{3M{gWqMMFB!d=p1+3^QO?%pYs$2yz4BMJx~%&F9iN~Vaa;a%*bk}T zyJecrTTz3qee}`X6T!C|Q`dQyQNNrWsR7G~1CUucpZ&LFh6qR z-FVo*DMsiQ7*zDb8Y*G&JK!w^e^>GzoAE(%AJJQ?ju?~H;FZo)9vgg9Ut1Q+w1{n# z>41(1m1xb|du|}kfY@ehp#X+!=m2(R&P8)68nnQjFLzo}MLbJJ%EJ~%&qj7r*Z!TP zU_V4na^P>e5i)Ut3WFiwzK_^a6iM2Gw{#}ceJG#s+>u!aT7qu9jZ{IrgzJtivjc-D z>{i{edh{+r@3WyuoUxb~^u!m3)CW6PK~^#w(pt_Uj(9IK?=~9j)K4 z3&JLUn?-3Yb-9Z-NWQCBBr_tbsqPwu@2YmhWcsQp6r#o~Dpw9cztPu;LL{kRiVZ$M z;Z8dFtdr2m3#Cvhk4v`hnp(G%VUKLrPpQ}7A8<~f7siKet@P2KTSH~*i3&S!hhMs7 zA|QL+KGKh^GXxq7amB^d+KX@rH$e^u2dsR_Fnyd*T$blbHL=_n&F|@wlmE84W)wgn zVH5M@?Sa_Q$NE3pXZYsa>_?O-=f^F?xf7o!*A-e_Ho_c9SvGw&V&~ZN*L8-m{$uHv1Ygg#n_uI?cg9-FqPp1@yMF z^gy>hym0k>yYbarFrTlnyQGf{>wQP3+gu2TFJ7o(80zDZy+a&Bp#$_e$85}Bao3?c#NFR8NDNcOB%*DE{ z6Ot6?8_sx{!r4X@>-1u+!lROfZ0Vc4Z7CG)3n(hb{C#isDe1%xVZ4_Ry0p7E==Ttv^aABKOn` z26Hc!o*927?>#aIfeFj=Q0*Y_<^_~M|p-{;N znJfz6k0dd~2>N0!!8|XmBRMjn5==rF-7cILZ#M4L5zzb{tcAOc8E}vpkG{UCz}zd# z-_^mCRG7xUr>p=8*(obphn!~I3S}ww7WLgNFs5?l{R%ErrWc!h$2&#H-5iuIxnU?Z zpS>Z}2aC~(;9VD{QpU09U5Ba-QuT4LC{a;mNjPQ+MMois!-v%v)I4PPW<{A|IO9wf z8lsu7=+11m{e&fF?vEqgw=1keC5ytGkgH}=Rz5(lMp!zpElDn@Vp{#;%9lQ(wijYS zf|9J-(`hgcm}B-12V=Ki70$-BqHpfhSt>umnyFCbJhNdjGT@9m$szu8g5_TYf{mV z9h<|eB!I;-F6)W7&$%_5d&7Gtgg9)3PQ1Liy^5zD6f~K81I>)z+Vsr+N#rgrn@et8 z!{C;;O1#;rUTJK(2lY)8<-M?>PK)b5bwM4c<3(3BP4FIHk-RrV5XL8v^98065*-Ll z;lKquxgMpikYw|qk-`kdhZIRGbqv&qFVu?j${^5iiiuIja=S$2gUE6)>K9xqNoEr^ z)T#+e?#A0{X<{y0iB%I5dRVR~v8~2$%W2k23+jCp6>|oK@oZ zvQb?(EK9mf7fraqBv5Dk+LX5Qd58Arn z{biN0k8yA7FP-j72EWLS8_8X22j}e@`1>S5koW0b?!O&M{pHCZWSiN;?Me~isH*Ju z7c>EdVLb<(f2uhk@8#y`R#7>CH$o~VZeTc{QzYuAzDG|m(O<2DZ&?=xD$oa&h|VA7 z#9LRl2b!i6w)h+fg$&ze1&5PcM5shhSmPwa&bOz_2`vwD_dgw2oX7w?g1(__?%=26 zynY2ko@j^grqR-gt^WH7BrtkzQFeU*EP^1Dqr*2KLPUr}h4dXl>6Id8x+_jdzsJQg zjX*8euVw?5q-=dIL&}wUNf>sN=b4YikFLeSYxaLv6}7}RZ+$r0opV)4*Ru?vWw!q(;D^EoickvK z0!5b0=H1>$-^O=@etuBOeD|||SMhMi?CusqLjI08*Adr#4L#bmhqfBp<1Tng{KHIa zkSLk1Rn~a(?!1riJNe1GXqw!Q6Drw$9wpf8Gpd|X+**>5z*vD2j7%AoxH{mQ?Y9zn zdi+b!By2;WVFT|%#Y7=M%aw2OysgH#9FQ3MYX=?YrwdDUigS`-bT@uCFY3$`xy!Uc zCN8-%-QO(klWN&oxp5bb9Zu%|%iX=U0J8dH>4#&td_!{wryGrofGU%N@YF9?yUnZ>N6-`39GW`%S@Bxdi*bqkJKjGK)OBcpym>oxT;j)C!FQL8fzkgS|C>%Sf_m@zqC2EpKTWaW z1m4~E#_hp!J((%p63eXlfRg5cvRlKpJjS%{M3|Yqaq;Ws<49haAAkbM)f7+mE0_-? zwk}Xb4<_y=fm2pb#9jwl9>NuYV+6MIh!Lf-|L29|0%;QjX{!Z7mdk{a<`0dSI79Zn zp|pHhK2EBlF)MUr(YrTb8$p})`4EJ-@-qdJ8aUJA+{MB(zqP6D$8ksv_PDSH)YY}z}W8?tB~xLJYku!N2J9p5E<VqQ`vemxV=fa> z7~v<;JPxm())BE~!J>pBIk9}duLTK2O3&a>E$zyf8pVG;mksC4X4G3k!8&v)hFE&c zgwW)N*vs^cHEjA~bl4@dTH%h8I1&H+8^5=pfgjEQ-XuwjqW#Ee>CtJmV~o+eBEHpy zv;;5c)HVbWr(uEixnq76Cg`!~3NFQevkDR#7)&k@a-)6GYg#W-()51hWf3!0z+?0|j3 zD)SGT77Qpx@CIYt0&7Gla$KRAB)4`fkH-^JI-Ku4EHpIwUG1Jyt~UG~IFBLS2Z@~5 z8a@V~FlyCQB(ujMv1ngG*YZ|SR=`PPi;wN!+y#XL64+KC9KALkvBXPb=L=@E+y-tJ zj-DN=VxyK2#Thd56H5(U>SqWB>=ptghe+XUlx8b7e%K-$N%sC+sZh5Y&|EL$NluNw z;R1GW<6;Kn4fv-c2GO0-$JXsoeAXF zeX+_F(i!}3=pgZeey|9`NwL`2CtxcLr%hFhAW?K&#KKKTB-sVpYyD0n6ma#PLZKdF zBzuD3=0q3GUsvI)DP2Q90Qa&L9iId_$IZYa^^M?f2iGg#uR!^P*`|R|1RZFDIpgjWMH9^2Ohko;UBR?Wgfyj*_7`3W zuB21TACm>MIjIs@g^ezV!-{e6Ol|_s?>YY&PMUB90uY<$ZQ*d8PQ5lX7KV!rTk+yk zYk8Xp8u}4eC80QYucw-!@CDN_4+oVcQg1Fq`y!$XU-9+@aW`Cx^oawjy0(a?xSrXh zqU?Jo%C+c;Dz}EEv-FbuZwlF`1$m(rvJ6*wp!bzBy7Y--rLJ>UJB4yxX0$Bb2rk0E zs6pxS1s_uE38bwpJZ-{>pyuOuO~kk#miz>6?FBvgyUcX(Q(JaiPHf7RzP|6yenuH`k1n**Gme$%Tl)e(YwA8cKCtvI=hTt-AcErRR678q$72p>=TC$o=mk z;2HBJ9_S zP|61|idL0FL<03dRXxsu{@cWXm>zQ^zutv8S^R=@T=Nk5m=OQV7fumEd9X;X<{-Hh zZzNL>ka;4hQqc!ba$!{iCV`M;NLAijVK1Wuw|1d1fy}g^nj63(;U`Lq$C;#gp6C(u zpG~1|^A4oP7^0#=v-N?pm87f*>W%O$GHmcvbd-nxejB;JQ9ba8)o&gio-I~P(g)0X zfd>6y@<=%53d>T7w@0Z;01z?9RIcCBh3%@PRO_7+=s24w95mur`%xrw{i1hjk zEbXb(M7a?}2Awyg;azaZv3p&x`HqgSpgt$#zcm317(n2I_lBoXex9nfW#!oB(#kucmsnP-f2 zBv5ERd2uk;{nW3qcc}l_#n^j;-O2?-a3YXgK5qnE-73xUgr9yg1{3konNgi2Z-se21Hb6C+c_F z5e;zB|Ci{n0{M`MK;$B}xx;V--CV+NLmgV9rOw&ljaz;JTL3hG+zN{BpVT-}I4EZG zUof>#FRCFvb^l5ha8W4H&4bN-xO}}$4}tQ?PKe3Tyz_&E6K;q;Dc@AmNy>Hn6SGhZ zAf6;b#&N!g>T|0BY_*vg%AAGC&S%Nyo(Zv?J4%KV-b^0KYx4$z@KbHMg!c0{0 zQMU(w=-G2hf$j^mRMx7Ji@g~W>`B5&LLu^x z;$dqjPutb5EhnBOPX11vw|Kxq>aF=Yc0oCm`8v%V8lRK0g)mE+bUf3E@$pkqkP$M)s@wgC3O- z*EJ(TB7o$Vt&kY5!gda=N)Kc}bVW-2^G8>I(ZU7>6^04oP`hvZhp%l}9;;bBtQ(7=>T6Jr1e#UtR_4 znm|u8Sf2NX74rJ@#H|vpHvr;NO^3qjWyFU9^FqqM-$`JqfD$e5ktO*Jth>-~6EE6v z>Ql<%iqOKxpbNi?7Zq%UF0;v37Y&VHoSOv%!XjjB9F)r7f^c^1Kj7bi=gxmH;lxJ0 zgl}*K%qk1omAu6aW#j}{P|y$V--s$H?X&Kp8doo!kWrMNmOE z42khP?$ZyeYMp;3HsVU`Op5v^cFOL|x{pEYhjYG1j+MPJH8_vufE^j#q5-2KXoouu zXS0vbXZSFuH`*IbdiN2}uLZ)#!ifH`E~_KrzU}I;NKU@CocYv`xzJ?JPB#U_U<>XF zq1cfWQO8GKy@$Lczd_XJLNU$ri)`mDSlrQ-v=%@kC0;_s^C*s{LNdK7Ndl4xAn=uw z6eTtf!KEmd=6+FpA>$-{{}5|D3G?4N;Cx}YA6j{|l{EgTQiyuZEQo_KVRnb6SH?FZUHMFW`dXG-1rH9c|& z-~ysw){wo+%M`;^fJ5L2tOn!yt`8YC&f&cIHeh>+eHw{wv-4orH-U}rcj zK}w!4zl7&hImHTrjPQ+jKEGxq37g5DLzU^2lgk=NjQ+^o$xcAQNiPRny$XFiGp4MF z%mkOK`ml?p4RCdV);e-cZFx^2s%FuBY_sWJ@@><~2YNsm9k2fC9j$nCvy-?q?|yhR z)^!>?45wxIo9u_s!yk`#sU$hrtdq_2lK0~O#bp|+Di&Su6R#h~^k5$Kj@CT;Y^tj$ zHgg}AS>;e4`!eqL))`emeZatnN8+>lvbDnE*s(#Dr`1PbxxPHG-1U&oEl473FA3f) z*eIZ%<&q7H)I^LrtnewsdEp5;yeO$2rBGD6HM_{|uq|z!8MO$;aQOo-{^r)ma29DT zwdSe$9Y=De5)ItuUnnODZ-Z7JCn3rT8Aw{_<6T-5Ynly!-iBRN z?cedH&TD>?C1bFTTc*Tb_Xot5SM{$3J+3<4tF=jl2px9<%^xAT!i#bA7jE$wD92Aq zWgJcFKXF3wN?n#8=R=MeFGg>pG5B!#o{4tR40o9?eVpKAo&*oiwKEzUrRHcR=ykX> z9xV8c&F%*PA~kaehp;Xpbs}Xk3jXH2CEFvS^B?@7g|}&Pn-_Y2kFWF}v~^85X9vB< zJ`DbP-7V|&z&v%8v8_N_S1K%(WF^74XQh_>$%wMp{=_~J5EzUB;AT86(o7bgBuG0T zh?OFd$G`@RZ`3cGhDi$`(W>jnpX}4+s~@ygb_`C=QcdQZN@L4Se_M6 z&qlm2-{{Q_(+Tg3$M@#?iyMyON7gJumxFP@j6pOIx63okOnoDL`}$qDqJOg=CP-P| z+MlUDCZ7FX_QKjOV$L|)txiBfIp1}I%=$TQ#x+Yp+IWR=f=2*lmIFr4rjPNi0M>=<80=7X9 zdlyDFnpA$k%38j9%fmeZ@1aJG4zkM$L*&TWiyRa#;Vh{j#g>eI2bB#uzk&cJVp%ve1Vw# z@hFw=Xie$YXMYmPNjTFrqo}val4t|0a{k-66D$(Qwf49bOE*vCTa=&R_~|$`sv(cH zU^H8Dj+FsqM9qc`$24^X|FGhJVm{RGr8DrS5J?N=@OGgfXS+0leH|aO$${b5Hm^Fe zuMrVX>!W+yw2V%Vn~{`u-2C;KaIWf#H{pro;7|uGE{YCJzQS>sQS$Pd)szc*GqU^v zHGfxe^{@--kQ6ryQZg^O2sEx{wW5SxhY`{2nw2CQ+lTlRb-c)@0(mZjS3M%$A6%0K8}sI0xF%=OFLcFD&-Y_S*{Dkq(n;}AF8 zacKqXMqgW*nijZk+lB5YuJ)g?yr)@~=vUAdN>&5wi*v2t+uf#mT2<<=rWLBFXlH&Q zmWwWP_6e~0LKaAbSoHZs8p@9_1ACC>ZN9mJ2^hm+?xZ z23uV{P7R^?ma4I})fv+?W)GpaRdJuzUIaA9+;f{yJGB=|l>&qGOIC5mJm78RbC;Gj zCzmB{RfRzas!JX3ra2+kv=;l4aAc=-LIzUW(v8=2K$Qkd$=Q{1eY_4Oq}arzv70ES z@r(CGb?WI?Nle%HHxy?Tntdj!gT8HKYKcNXrB_1ziLmd%5`v2ntEqeW)z+X0(YWtW zc4g|ihH6*WtVq_tygGa?UXk}yvRP^zDZkQatt;^Ik0tXO>es8#k z+IqtYP%c)>NWPbqFjoc*sL>#vTCMzxD2%abgU*RTN0$71i`&1(@XQ^gi9@?ADeb?B z^5Yu2m`-Dp?RgIc;=9Ar^@vYRUPopX%`7rd{oF>RA`ZA%>(syk+0iZ3<#U_Cc)oS? zZQo7_g@}SV9!jb4{%FCRp||uJAY{8}R(Z6x5#nnK#h1@Md4UC& z+~8%ReM0V!f>_%Q2p7NeiP9=gnFWZW==-5v#seOz@1+jbXQd=tq3&Kq-*zSH2BU5` zeLtYoKm`I5B!k2@O9&_Z<${$5*f1vS0HIUj3IPjN^AL3|u}^wL;s6b@kMd<+-H|t3 zI{313)9gy*SKm#+TScYNzo`wrO1x`5Pp23h28dp4yX`F|`0FK!Iw~NJRI`orA=`dQ zZ-o}0YceqIPrTp;T`p}OA2-CV=Z`^65gFW`NECfc=2(Q&K?w+z%)9oC88gaW6xG?DqZoS;wDMV z(c8?QX2bh6xCOUV3JMWR5HYCQ_((?6xUU&tI{QPEm^JwiYynPPzZAKY$No(NdDgGG zBzu69N_}ytYfq%t7iq!81BV(t1aEo5MFGn^W|hnHKHl1iy!IFKWUSY`+7B&C_x0$k zCFy%I(|ZGifR#sSOTq#+2v5=~DOIa7dV|%X`~c zmmL`Lp0Ka|Y}J!3Im~ZUXvg0}8g^*)}`n(BRyDXcMZq_0`=eL%++7#H0@w)U5e#0vXn)8Uj+5)u0J;F z7`+x3-MxoBUPTn@qqrJxYjr6$L6FzDLGS%&x>_(!^J&GBwPh5@D+~E(p0Mu=9lSUN zCIr5l`6eDquo&hZ_11S2uFzPJi>-h5ITR5+!zhyzt6PadCJ>_(8gm96 z63jxBD+PT7r!$Ey?wGY}`}c{lNK91vq2dBp)Jj9J$jNu?C9MyX1JYhSP` zAsrS&Nr$xZ&GR!}a#&zIX`|mZR^LZaUM<|*sO~$}V2;Hqo;8=SGsFenGp3yQf_{+x0=lh^mJ*~}(e7Pg-UHx%|0kh+R(|)ao zi;VIJrN~R3^)CylZ`9*5K2X>dAuiC`7In4hpt(@=2 z(7X>cdo;Z9$Mz@$7UI!ym#Jn;yGdY*0;ZL&b%s^Ak0y)s-+JELFe`9C?fxQzKa^xnkmQ{M-~6G5fVYO zAiGWB5YOM}hxkK0uZabPso5Hd^fr}m9-ardf9V=FgVQX+nKC3~gr6;CW%BVliOJLT z1lxzJAPRT1$Hs5COl;{ip=f z$A{7L4H>y1{({AO%maY)`CD~4O^q_5A zf=wEfD?8cX6ixlfvvy@ z+`Rj)+_oNML3zo~8dXq~45AvbWK(O!>NOf7{&c(Zd4?Vc=b- zS<|n$ClYKh9y$+rF+?TVJw_RjI%b{QFitLSQg}Z*B!GjwtDRuV-4K4tX}K+`I%hc+dX&VmG zERAh(?)-GYvkc=UW4*DAu+kd#JO>w;`j#AQ4aT3R%e;`%Z$XMXKYQlA&(>JGRK5He zy!cDbD%be*Iglz>F_?1s_->E?d%I%np1&@$A}OO~pi8H|2q{kBF`E)sN#^Tf@w5-@ zd_}jaJbjqI9eg^RN)QQXY}pUSgFpEbn=>G^nOZj@g!9O~0N?2WcQ&m|r=K_;JY$YB zmtW`-^$O=L`ZvD2;@SOV8C35T*xQ|;f36RKlz7t&A%caZqQ!Ev3OLN|mptVFhTI<< zH)8$-29#F&wNNwve4)nm#33~cON%An5=Ev0_0k~el61}Z&z9-Sd0GL)Ffn5~Yn$Uv z-1ToaXuxLSBNc2cd5wtPWWEY+U(H$wdKV+FI&a{w{>#NKelHFxeapFQ4d}53AZA*0 zA~k8V(9Z7hK+IliZ8^@|KL)^qNydEc5q}`Nq@QvpCDG*kInw#qGT?k~tru#5R~Vlt80Y0*Q7^K_ z`f^HH|7k!GclEj-2_o3~2;RA)iKPH#sY)8)^-xhK0n#V&;#&yx11{xYGFEA%n+&%; z)C_=HSpP1?qgT$Nkr;!3(nBp6Uw9MSKd;&0lR?$^HZsls&>o6BN& z@RCc94E?Y~x*m6`s0c@mKwem|Gtox2McbX0`h;p1{8vf1ju(mphgvr%7YnG5y%bS{ z{-TCv3Dv9=xS*Wg^wi*%MzP5*-RuJq-73zts8M$3sET?ZhG82Xxm3C_g!97b@|QcZ z=;=wkWQxBP+8K^4yRbBtnH_;(5bQSm{cSjp7Z`x_y8|duM+V$+%msYL)Jmj6Q~|y{ zi5ee#6)FT!F)!?E&Zx$o)d2|`ABKhl|GeU4Q_$31;!Y^CBDSWrD_sZ3rJX{JYJQ5A zAe1Sf*qQ#He*`^92P%;a4hc9bO~+J7`L%6>n!wyiD4|M*D^l_J3>77Ew}uz0!Z6&>|7e-n#0k#8gE&=#8|!A{ zuD50k;Ht(b_zxt6OHz_C?Kp+Hc55_ZAb@bA`QS46?Ew!B%sFJMz2LdiVT>JkHu|z= zxs;ig_*B(@2oHBA5Kl%Stakmww>v!dB`KPd#3Nym5Wrc&ihWH+lKn>L+#-=u=X=YJ zZ7SgU0IR~{T#X}j__m6y51ft01vH5FpHakd0`XvxUOq|u>4}7gF5wgBP>2C(;|2xC zC5EM)G@gk}pcfS=hz_A;>WrL^B&akn^(MfvR@A>=ceO&|UIt_1lR?-SScdMR{xzRP0|#VLSUw7etGk%Toy2P|m@aOk*V7z7d}SUa$-x z<>u(vo0luZj6Zk*RWhJ;W2P_c^DjSSZmUhp>y{(hA^#1edXn$f0Ft@aAg$Xzp()cC z?w2qh2T{Kwh6X)_g#wz3lOyppFoUQVxxxB;kfAt&*(ooKoq0~AmZKL+>2#}18nwZB zR#T_!%vwEdDk~OS5d^QoRGtOz1Btsl}A1&lT*$c;kM+l3SPnerh1gOKzR18MTdR zb-O{`PTnru^(dvj0ws+FO!a-m8pM_DUOfF!itlyL8^)F!`L-yfKb3?`Hp{!N(&=l= zz1qsPIMyDEdUV&2mC?osxn|MzXa`B2zWQ!H8)=7`A=6h+(cKy6o(K^&a2@g zGe9h(;#Wyk>tE~?93bR1!M_DW2>@%*uqR}va4)ADidHEq_CG0YpFb6$$j^+nnqsd< zpJ?l!Js_geBZN<(Ys1Ezd@iRL}=U&Gr0|!+_M0SR`5*W_Rr;2y-?x?FwVVv+1Jg zC534Fd5IY!X_=T*CTTg`b^U)_y=6dLOS3hMGq}6EySux)2M-$D-2#KVySux)Tae%m zL4!kZ_{cfWd+&X|zcYLG-d)|*RXtT}t*1m6FYt7$OQr#p3={?ow4k$cT7DHWxNR8* zW^YT3RiRS<*I}E?0WV@lI7|XIVgWO(Eg3-nBULB2gy-`m!lSi4Z=5H}<&@087>a?) zga@3G;v2%(4)-#4gcj5U7t0^bcq1(|h%`^wp|jjkHNy>8F9>Y54~^U`znFMLiASq} z_2T^OrDgG&MpLad^P6<(E>x0jV5b|2X^I!YJOUcmxMCNkcLRZ#0pzk_!c>*Kxcm9% zJhGXqW|B)%fBNa-Is3f*xp~5)*0(i=$qBCRU+5lru!)1s0@)`O@{Pu-~EDb zE}2`(JicSRVaAts6oU80g6-KMVX(K9NKF6+HP}$4F9@#L8}9Eu!)GhV8g>ha-`to6 zQXWgNFO6Xd6!h*A2lO0ZQedlrqaHr@AXR*?8imvodVpM}SKKb3Qk}3b};mhN85|{73p8ALm z44>(zwIr+0woU<;{4Eo|qH@7yi9jBvmtQov()lYD#0+Ios~ zf?(fOIbNrjtvf~M8rU-x5}gV)e++Kkm39m>BWp>E$=!x&H#;l5N)M3zWQ}EH1&*1SmNr?(zHb`5w?Gxy1wQ5fEh!oUFFsB@pn?p*!c4j=c8kt%?rc~QIl=j<1H7Kbq;%5leVaHj))&En!}{B zMP|#93kM2WS(QdTMAxh<(e&~!AyP?~638)H^P zJm{%ZB{Bcgy1iyt`&H(*2QKCy>oEOqWdlU33jSRxe4Uwt1!ZA6^Q-Z_kO+Y#2Dp2Z zu@Aywhkwe;?EO0f>M|I9ac8(#gM`1b6&(UfC39os`hekb*S{b0EjQXxielYJzP}cv z6Q!%2`3>8jN@6e66cp4FyDCT4Ad6U~{2Gb)jD^*K#~e)Y#E>!zBxpB z-61d-Vlm9vPsTNrZqSc6d|+7Z2WYN9cawlJF|35MY}%0CK4VP1UJIp{h_1)DtqIf9 zzf4H|#8od&`Mut(b?xWw*#=6p59XpS;iXA={fiz6Tn4K2KKO4Y+j&IRefXlH)!M9lCeQWqxz#6QhjnRVYY!*ju{E@Bvyhw7BlO?^Gy##68+soI^*W@+?6 z6>1S!ak&9uu#WqoKBlSFt#d(ol$L_UX;OBRDmD! zgjDW{Zc520E?oxIK@UE$@e|73YFtkbjSP=q5bgj=Y{94lop)rUs{eyBC2{am6b+fo zg0cUS{f3D!zu+WYK4if%Av&hw0E_guKg0z~(^Sze9};GyxSM-Q zL3F~$8;EbuxkhOMfi-V=?K7fZ59U%_TN91H#0Sb~Yd&OwxCh3}=!*no^k}jQ=afGX&Hx5C48nB>iaWK9>0(3+UD1oEz(FrmT`H*+lD*JT4B?yQQiBpub`P7K1VxUS^J!Woh} z6J7%&2kO}G`Y_*?{Q|+|)HKrV6Rv(7>Bo{@Njnn`)OoQe==~DWl>uPK5(V+l=s50) z>qfS(br6*m{Rf{RB0{_j2C;@*wA=}u-?>C1i#*-a_joUFd0Ch&su}`2l(vdHk^b9& zFffDxpv%^|LxU9M)Af?T1IjRlH=!wI=^{^96#7r!@r62s6O%BYJ;`nmrF|c14WY=E zW!-}#kWuCnAH2{4x@)KZg2wmfB<9mUXm%q^@WpZKHwsu$!O&MC$aX?Rq;>>&1t~f% zg`;Y6Uud6E2y!fYdXc`^X}XN&wgh7)Z(^X{?d`yK6UV*OCSqIsw1lVWKo<_9q-C*1 zFUVwus=!!3$rJe%@jaBmpJvOkh@8M3HX0o#x@k$E$~vsozvkD1g~bKs_i^2{2h|19 zG-H8Kg~vJ54v263hq3nib4E&QED-w4 z&xNnUfYST|la&9+jIo9liCO~=$)j}$%@O%lTAKU3zd2$Y;!BIM6Zn*wW)sMKw?c!? zhtO+bE8ab!DZ-}R79g41MDuFGTZH?oZZN2L>mUXE9X}}aKA++CRVe4(F_lxjxI|cR%*Bmm&G{po{cB3h6PowLyOq>OzjGwh0?M_o z73Cfv)ZYiNDVte$kpf#FA|*8pmv*y+tX|i`-E$#u?czT=(ArqrnS6f4aUW6h|;7aCGzL@9XocKlF)ej=KJ4Am#-qX)_Q3{}z>K4#v z@MX}x-R2jp@Yu=U0r#Urg8^s}0x)U8iyO%Ws0W@L<26i>>As!Jz$Xwf2kY8Rh zLc*}aArlD`RzJzmczQNk5yFTnf;yIPdoEK6Rv-M2XBG@_^bTHxd^kYLUCK-fB0*rH z0aDoQ$<|va(Vxs~>zSz6nw`{920pl^UkGTG5@l^G+lmTjc8RCxY>eyf5+%~uZIBKH zlomE+dM*V5b6@zh<8Afb0&dvEXD$s58Acw6J_2TCsKOM0nT!|xiO#KH-Ov;7?-O!| zN5A}%KGj%FR*JT-N5*OzOn%LAY+agNe=p98Tfb6LzRh~>BcuK^NUzE*tqPxfJp^Os z%L1N1+jYPe#X}QbQj0E2nVWTiucU$&p;t(`$%BJPTxvR^gG;0rcTCF;%Sko%hURv~ z#W!^|2+7C%bbZJ!ny+SXc^2ixT8}b%*Qb{)yAm`nVP%d&BK;^JB3jEDv;pMZLNTFo zsrTrxATR3gek|~pppxCTDz0P#@+Kk*ylz*#9WS(SW;|Sq2~lAJFL&x{3X1P#Cull9 z$Wm@jrhNoWzh`R2mWJjj@yO+|)cz?6CQjWyBeO=gUqVr!2WPgy1{J9O#Kzv8s^ZwB zW|8Wdz8)QIp{0WsPcOd_XsmN2kFVsOCKrmuLINt-p(L@tX;JjK0*Ldxgy$ad2Xt@4w1pxzTs_aR&Leg3(3 zxpS`_$!|3O1941fgsw`SW6G zX7-heRH93MVwg_W`-MP#(Za(U!LIS@6UquuD)a54h`zF}D``s&^|+gEHEsJ7V-1T8 zrD8oArfj=w;X-pwF$`pB&+~R8SEut2L&iH|$vQ#3W}6G!yR3Y`ex7kgCnIyTS*dig zf1H`@?#0KuqJfFU8bOc{Tk@qc?P)+F>*2QbjQ@x@{#nZQwh|fgDJDF!02g8Urw*63 zL}?z(*&y|VAgnK<%F$89<)&Il^}`BdKz2th!_HJvsqllx@GWK&rj+?on=MM&D?Yr} z%;PGRwRUG;K*!_o0uc4&fpsXjy%;Fl5CcN5IFjU&Q9>!AP%C>O;AoV@SBZ_%bkjgF zwBAkkTQP-i8p0p|*tAY%Ja9zK*TF^5nC~;J=TC>?VFDoa(`O(OKCUNDYP`jMRdh7l zzMd}KDtJEK%IsESHhM}T*Tr{l#}C(KQSM}T5R2e*(Y5e#28wFyg3z4vk4&w^>e%1c z4N&B1C1U^HPL#Z)T`Y0^<4HHOU+9-y^)AW(GMDRNVXHL+(TS4JUniK@+BA>_+)ecR z-l9t8!xD6H2#gpu&89PYNN8Nz&`wMX4*R5o7@>^8Tu%4A5AOYOQV(jWs}Ve$h`Cp3 zif|>)P$^D%UC*K-I?4@4Z1c{mq$Y=O;P5%a@!>&#s9Jyh($DL;FmvphqO}}nI*9sg z7}xeg_<2g&=*ksh`^=H|8!skTe5A}6kb8^qQ|+iGy$WJ9ly>7$qEES*a-R2L#ua>z zt3XtdxHzle%vg!1R25KKIPG2dEeqI6$t3wp6@}ZM0bFhSG}=)uW&hXglF3?{oh5dA zvQC3vMjx#`q0$yD9~|_q`04M;{Vur)6S6wAS6m<*;Jev&=pM zCwi!XU>TPP)yM%Lr@T}$DM-B5RJ#a#Z7^ZhR+4MIC)$}_MtScKQ`{Yry=I>k?hX|7 za_LkvfxD7`hv~mZ>MgV~I?7Q4rKH&|Ou6h$QysPFQi>voqSB{6#10X&4d7$PYS< zTm7?PDY%gUjDd;D2cp$mvTJiZfnYb=bdn8}R8o9z?GcCi{)XA}oDW&rl}=N1VxG+8 zQ2z8v8&_0AB7-sSOWyfDe8_gT=wzHM{yVFD{qRK+{jJYmEF|P7Oa%rxrL^kn199OS zINlKigs$DNieE$QKGVWpk~?s(W7NJvZ7!5rv!NNp&QNZsWkNYxi1Om;ts zgh1 zl#tn6aB(RyiHu%JUrAw~3v?3}di>?>r$s@NPPLnJnp}6)eazooP{Fy#TA3wr*50(Z z7jBIU_6SaV z{fu6CV0h;n+obb9IBHyo8~uQ~tc-|M;en`jYJq3RG^aX%oxHrL_2eJn;Ts>6pAYpc2G5jY8mscF1MTp!pS=QQYYo<>cZdAd~ITVK*KQ-c$LrEJ5rJIOqg1 zhP7+`;*V!yO*`-ou=TdTXz=n5`MRLlvGlMvBGqUg3xBQuPgw*r&tDn)G0G=6eSnodR-Cd2N$k5UF!R>JW0jQgk zyiqfaXZ>x}bGVC$$dCV7lZiV5pwoWB^1q#4&5iwDL@3T70=>Q&=Cfuel^p(&^b`LWkJGV3Z7mex zKltH+AmM!U&xe@$aOmsy5VCB~icP)F8qaifEA497uuGo^Ah+gtXV(-U(9mx~EB#yd zxrz|Z#~`L32Fqs6yjh_xLiB*3*z!O$UVra7Z+WS(3`@>e^wobf6P$9%U|hbqdaiat z`ke5*I1=SDNFV}T_@=8!>C@L7!N7@yZOLQ*^~f$_;|%1K}mahaL@B?K$DQFew|+C2?}IB`ir7NX9tte4Or=0_<<|) zb+qP2NmfbhaoeBN`kV^N_?wMv2dYoxr!YN6?>VMjN za-av~o8#FR`EBmMsycxEQ>t|ECUcO2iCgca_PqE9(=Yr^xeM)Zq2mfy*)wOl3#m&<@?YGC)oSHXywm~0_eb+X-Ce>wP{buom-WRS)eluZ9v}twa(w$XhAHZ zT3P)Ezx_Y={TH)-0}lEEpJHl;Ra2}f@WUzEdSX0 z->d6XJO-<*x@9dTh3p~-!G6bqj@yQ=%40Feezk-Y5I2Q-n!| zo73MXyA0%xy@{2TVDZ9uA8DHLzZush(jXbgT_F(kEtLzRL%*n@K9^bl7+wk^X&_}C zP7v011Y|H%ZN2`hAMtl7{bSZVOR_vm$RbON`s30Z1k5NMt1vFp820wpF4Xz}#oV77dQR$ao1iFEhIr&v{0yLpvQ1M{b0mW7 zV;}k0@X+diEwM3<&*vxD0GuZRT7=W#k>}Bnoo|V7Ep4T=_XI6nmO*?gTqOnvGOMxb zwzKVQgVF!6FT9KX$dfM=f>M}g8OkzW+y2MRz*#pYqn}G;z@{X4q)lvgq+-iRQbRr-(+fxES`iLjtZAn)f=EM*ULBI8JOT9HP?4g(hvdx< zAgh3$P^=Nx`!JaeF9nY&ZPV@~?9KfjLpgMkeh#3WUPnxmw?OTI?9Ybknr1Uf!9|Ks z7XzNdieGzF5cI4V#6h*5q=5Q(DT-L{%EC72sWNUjJoNbXh(gLk4#{_ zq+1Eio;yI)hypISSz8R?*jLo42t`hcxReif(9I;4C7gbNO4Dvas#5FW?0S*8Z-n); zP5nb~@z0%wYmj%qk%_L-Eo~GnpRLe^nn6@x&hq9dg0kOfXOmt4T4~(}7Y$;$>E9So zB|4^JbHFyM`*TnY5G*lIN}SxPs-<&*lpA7FnJgrmnBbdAqN6a4!9fy@fui$qQ5S6S z_g_{J&Kow3(1Q`N97F}W($}tQzvFHt>+$WW%l!IQ&$2%uc-*q&3Gs1W?R!Wp6*stRT zi{UE`*}GDMdi(jo48RzMU}&TB*qk(2P5b#2O=;Yj}kW2>)9eADuwMroE5W) z1tZ3Z3d(zVdgDg%>dHt5Cwbwh&ReC`5<#dkiB5EZno-VEL|UPTLb2IOj0a|a*V%oc ztBv}>aIu^EA#OHc$Q(9AQZCB0QT9wuK9Gha4r`cxjsAc30%ecCMz+zBX7?sa@B*Vy zoXJ=G1tpwHPM3(aBm#}6UpLu9Ap%a4)dD7w0={Q0La%`Yj0rL{gw~3C>W%G;xw0SfD1`G5#u; zqA_A&D?-%D1RO%%r@U95Pk6Kbqxn_{k1^7emK#WFE2wQ;foUb>p(q=5e5+-AkF@jv zW>RklWbvTv-q-umIMo^=d6araoW0ur3e^|-5Em<;KtVQw;@T?VYqVUkWXE*H`UKc% z0Rmfdv8B;G#VX};f+1M~%O8%d=6J~wEpnSylvUhAwAsIMvs`tn6YiRd&kj+fvED@;yhGC3s2^_pS1t@yqEPXh3Hc8=*I z-~x%iqoH;)hK$J0KyrQd^xRSTA$8R769HqrM$8LGbTtLJLI5P+g8@*aM@*3$_;iB^ zkT(qC-P=G0BmKq)N70>fQw{oDuG^z1b@a6_%LQtlLYJgn*b8SFNo2lwOs{n%uZTD* zT|%Ch(Q&j_xc*k|IAJiSP2(H4ftPl;W|O5J{=bK@zg$g!6M+k1h>J8(pfK#*+>ut^ zL49og1o^sQ>s{l_I8i!{t-FWGM_!eo5 zAtUiG$@ziOzsv1af}lvbXc79WGiLj+H9L^O#uJDBcZc~r!P=v=okJ6Usu7WtrWVeZ z<0zYN<^@8ZARI815VkjpK=WN_0xO~FCGB;8wdJ}HS|ax+HNRoUSBJX1!_{DGI5Q&W=w&!j)dUqR4;5eHn`%W~Q89sybiD zf4)SMF1`${xIor16bFRUi05jN$iUK^!0i?TK5~Tb2^-#l4u6=+6zRiU%&Ar=5}!N= zF!|=-aJW$1_vFM4b)z#mawVOT16>NzKl&|6M3Sh2|0iGnd|BZblr{!v;*7h`gT|G? z(3dv?D6w}$e#`ve7OgpJDG!)58>H_Q?}dv`4DtJ5PO#D)NEL@ngJrP6Uq5U;<>Ke^ zpc&uAI*>ko)Ys<6(Ygn9oZPa%7-mE9O5C~7m&!xGNCf0X2UQ%3fxOJoSRU@v5ufKm z(_BNyOLp+Htf+YHj*gI+JX0iXauE*C_{P%RQxbQJ%S{f)eHn1D_%C0PAP8Ky1|f|n zb*gXNo*UP6CjPn^5_o@gGd@P8H3uSG=E0<16il3C4X+|mNz~6-be3oLOfT<3UTZyj zO;cQ);4JMp#qlnuq*4W^*xJ>2GggsgtbS6%n-8?vu9__lnQ-!2oPc+0HfNRT@8Z)RM+ zOySTVBYr^qRGD`NmVXkfVwYLRF@}KsdhnT2*o~0IgQ=ep*2VZ-p%ZpSNAN(c_wRFN z?H1%rXp@`)FjmXTe|l4qztm&ZaCxAWAot)Vq^3D<{U0M9Fo8+9fedI03?v{H90Wd~ zIQmwOB6`&2H|3gXRDce3N{Ll%#PHrgdaw|?CjD$eHJOqPm5w<1>YFlZ`gOH!XiV~J zcqz%-6Dm2cf?U~Fwx$GOVYRM?a}hd`tt}kIXVXcgk8_9XyLVdcd9d|1|IUVF$9I9t zb+4Td6Y3L+kHr{>Rgn}<2#ORBrXc0qYbBu8r`JjEPagoMg}M)D;YNIpeOo_XQT+*^_2x^1s|^>(u{@Tcj&% zxzyO0zz)@G&J9XOB;NVo!}nijR*}pbLlB1O81GsP{K$VyzdS*w=Iy7Box@sfkaF?t zP}QD25sc^nk<*(;Mp##S_m`)mE(q) zb0*`z_LwbFaD(ZhsmK~#oP(6<=b3MY{%^)^L8?p+MVw$J zxl$cW-F(L2by0}(-bF{xS)^hmQ%m-V9)9Q&;YGdn#L{8rS&)tXD+h=sEbU)b2Hddpcyg`4QNlk>M8@c7YV=_31>_n%+cOSz z8y=PW0nw(^4co5=zru+`;q{JuJmq>Ih4|V;!cOobn z9WJA7Ept9=zgiePa_adI0tbXZ@^#EhfO<|x;+ASN5bTXamc|p6gu}FAmbbXyT03GX z)8K^oYdqD5TLTx(xQh&O2eWbxPOFczv-ctw&yZW>!#tPq0rw|@@rO!cfJim;cSSN( zQPX@o1*wOnqJ>j@8g&{W<@nxxuTX4MSXv#>br)l%9xhG z^BUWX;$tJy7ppZw&ApMiGk<*$k;8HDN;qQfU?gMXWm z7rPG5jUNudS_XwOpzrJX}Drf@igD6a#8cuqr^fZY4WHa$a76D znBEQk!loZaSW|v@xrI#Kq_JkpN_=`FnY4a`4`KRs3sJB@R>by}b+dA=RJz8i4YLMW z1+^knEWg(WgtlWda}y;2kE|61M~2z{19jg2mZZri1&TEG3IhcxuR()UVRDXH%M$cw zXe)_+3Aq1I`OZ&gfPKiqltlOuUTb1{QxP^PRc7q?@|@xwPLkLKf*Ul z)I8h$C6DWVS?U>!-is!tz#}d!fEnRdCa3zEE4dsGRwVGd z%iS;F=3ys(b$=MW_MB7yeEib&r2OQgJ)G#^Itta*cnCY+eGqJ(iuVCPp8*9-pN{Bj zN6vqALq9(Ubz~2qpkkc2qn0_UZE_`Fcv(sd@Y?Gk;EQ1(bZWPtEV>JU-fk1kLq?Yi zrg9`70)KLlO@l@1eFb~*2eIT3{|U4UKnho#*h9=O{22#3P0TOdx@i9lGEmRat{Z*> zm9g>1ytr^EQ0FsLmWYf|16;V^xgetg%m$J-Gp|p#s9k*S{NT%Fr97}45Yz>L`SPB zC!%(rVi9RvHf--QuG0vvouYCDFFW3Ioe`HJUmt2xb_1C*m zm~yHjSPJ#>Rla27n8}WG*!ztlSrF`%_!VlJ2j#d}w!^>&ezdw{1UTYry{vaHgr_se zVXh?5Po_-ar>C1juA$A4+nk@zAo{TJy=?JY`@;jy$$7u5L+1{n`(~IBH#yw)h_I_S>CsCneYYN< zYf8MFGwMO~4t_4Z=mhDw)g#tlK}^?Srb{Aku#dYL`*pd*MMCtJQzWG%iosF5=?$w^ z_JWB;*5)4OwYHs4^^^X-2g#V4O!CKGDZ}L0V!benQCDJ5(8134pt5z6$ZGn?ma6h| zDy_zEsO0DtUyY7xOtrJqB28|i*LdF&i%D&tPu-K4vcg{q9@7>p*A!m zjjfv2_0VDagd!x#1!fsq4vAJ{w#P$`K6K^tHljWsLikq;J%eyq1-nu4ne7(G0I4K5W#Z92MqSYj5F$4gM}i*!f_5v ze>}FapVa`cdWq%JClO(AygB#po0paC6xq^K<*lJRweb6|N{@qAW;FF~L*32|6I$TY zS0%=#m?T7GAZ6+94od*^>#DvMff9!OqZHB~ zc7owkJL#V7H=8f8zr$hXXj+X8gCTF7DB|kdICBDrE#KEocdZzMIJVN6||Vg_A{5=JHY z>%hm(HZhN9!Jj%Ft1 z=$2y|j%}v9WUd!Blb;{%vh_D7*gySkALV$=`Y5A8tc>%2)@whGmh4io6y_b=iarjxM*{4oSpceS4F?IT=IaxkuvSw znyG1UG>r1)0PhPsj7tpR=zf^*rz&xo{M1;u8Q+yYQ0{WkgNTZGn=1WgI{oXxn&gKa zHnOh-%&X!n(#&UAwzxxB9SB#eEUaRm8^)t0jnQ}uf66m*G9Y2OXRTj`kUnY^S^P!Q z^YSH{ge>45Ll;Qp2hMmA!h8T)wvY8g_Dm(a*ye*Gbw*x6!Rao@YuE%EHq9%)fey<# zw~A3VZ28?ikLa3~wyS^yIesXy8W|boQx@y~UG6KNsjGEi8&o@_Zh|mp&A$E-kDmw+ zgv-;)>%vzbe@&4v%DeXz{mv-E-qPSS-_Z--t49JyU_sHZ>inpeL#+?G%nTJo--zG=3&y=|s24~6VMW!%_Ao!E)ee~o z`_*YV#7QGG-t10Ur@~MKBp*Yg1AgNn=&#BB0fPxAe>??)9tou%yPt{bxRx|VcF+YiY#O8ht^+vc zYejCykJ>}@5`FeS0E6=%UF5#$rpvT;D)Bdxc5i^g!F#WSQg^IF_rr+P^e9lGC%(q zY&%v?bq`^)Y|I+0rGhDc;t0|s)B^*tHH*aX4e5NR6Lb32ly5z^no55Cf&lQrC+tP= z=yRqMwyX1prpQ&|Tav!QZR##3Q{Z>LEie6z794x~8BvDl{E42prnAS0=En_B%!^pf z71*Uemd#!WsJ@9Ql$vS2i4Y20Qv_#7nP^l&Xd@f^@TJ?E;lbvV22{&6RGOC~EnoJT zOLXFK?xkOSv0Bn_DeLyE2x2l=F`YYI^X>C?qQ(drk=1Nu+KN!|dDEkxNc}v-83B0xyYkwiO6RsRw)(Lux1jE53(cQ{lxPk4t_< z)_Vs{CtafBA+MsxV7bi+&lNc$47~{myl)ONwBZEvDmvf(?ygx%Qd`au=?3zmND%OD zU_RQonznW*A~vEyp84cjtr2?+3u2xmj=XDT zck=d4i-3kM#03{$`o~#=|*X^CsM@LIIA2D zAxD34+dTu~v{5xYMR|>%2s}Otzk&Lj3&`lCjncx6_v{^Xdz^>e9*QtaP(`(w(_Ta# zo;Lm@D?KoT3zHnDtxcs+R+lT4)7zQ&EeKC(|8Y;xz-#I#h|G$<_k%B-)MlZEa|@{% zssgCOjIRSbc+^PJ877MFhNQkrcJfPQBsO^G5GfzpI6hteiyqzvqHS09bk`(5$mHr> z^Ot9A_;@s9t~U$-W6v~wbs~RBLm@Xqt8l7&I_b1OB)FP@Ifr(DaagRHr&tS8$HHeNIF^|XN2}^jn4W@y( zaP!#y697>BWAsI?mgBBSJ!0CBNouJLrOhTph@W8(55k3r73|<7u@v(Y-JJ_GoK44l zNQmp>fxngXV%M|p<(Zzl!;g}Z`nqS&H|>7niiNbFC?Mit7ZN$_;f8SBQQyU@?2^Kh+=UPjmwSa28 z81EIOr6C^#H6IPUCX5=>pyEfIV^d!p_@;upjuD%S1TMjnnj#2FW6tHtMk*tH;87N) zyy*B*mcpXqrilFJJt)lAkSA;vVb`t~0By>2Prj>?tq(3X0|`_cGq+z+#wDK%D&@1n zbPCBM^q{+2o)PA5jlNQefw7>#kzV|j(}6K;jHYtP9-|-8a&ori2Ngf`A=OGSq=MHX z?Qjvgeks?doy*AI`wduIED^aCpN5ed;*ONc(RMH%s*1l}hoWTP_N7Ajl(l9n2Y_%R z>ETW!9`Z=Kv5gbs!(AJ^n7~FuWW!aIhE^2fdFkkQy#9)C{j~)M-%CYqwDny>TXHWg zLn95a;*95JY_Lc!Uq7TN!Z_Sg3@wanekgO-H?=+v_oeb-vJ2I(+XR+vwT5vpa0epAkasQ{nj(=vjaJI4CR~}8F zk&TOvoEHK_tyodgfy%?;){deMq#v&&?w#bP_+CWi><9%!1ZJ4=Ns<^Gh;8T2*qX06 zp;fnWNUsp-wd~zl&*Sf?rlO z$^h8#!G#viMu)T7fdL~_=y3Lg5qPibwpPsr%?2cuxV}eIBR21P*N1DBu5KlGqQWH# z83Vs<@lB;noHlKn-g(kwNV)bTE~J?^T#+cvg2AT-1Z!z3YA0FMqORm5f_FK(C@yqxf9<^-i@Od+JVHsttmdd^{*Fj6nppW`Pl%Q>ea>BWi zKg>-87KuupP<9GArd9&6*(=UXXs=pTw4-vgCq#jJn#Lb=uqQ07cIZA~l_`u?pBlSf zNdHv_x#90|tzFQA$%%EDpZ~HDokk|hz@OUS8@|md#Z7~U&(bV~dI5$>9g$AC_Z3lz z>z5HgWJK#810?$(vIA7H}{RV@|R?1*Z$XNwPLJ*NKNJIcg%Cg|ty|7@?mP zJK2ozUJlR*!0K2oRi(i&U4O8-zQ=Jsg3jpEe!cGD4sq}Doq__f`AGeCH*DLr?-jv& z=IF?m6~UOeCbREZ-933&bW8~NX+(OQ(cWnHEj!}KLSA}>c`R|v()$Lz-|a z#S@?d7UCP!$?+v}$9*98t71ZgeCt?eYMvvdyfiC0ocKXT&3nW{Ujf+tkal<}r|1|3 zA8^&5Ozg>Thwrr3y9JgiQ9C(>;3bT%Aahgdpr|OOP2xXPsZsPqFeNO)Zqak4=A1 zwdCR<6f0J^987B&}j^ze5H6Jq}OJtDxFX)ghEqJeX!FGA%bNyqE5R zFO(X1aA%Ul2_rjbBwehdeLKyheYS|;&WIZfF*_4tF!@zK0P3+5kj$O0`hV(T+aX&#HK+E}E$q>~J}vyDE_ z#B1fOMpis%hBL)fuMP$5xSI{SBEM$DP^UfNaBNN1@}GCflnFY^pKY zu;4Plq2yXI#+mX}9E&RO_5Pt>`^u$nDDx3oBr%k(lnIZ38bO5AQ{fR8$$k3)ed7(h znx<<$`8UPf9QfTxJ`yEg#M6E*y>Dlv9uNa|Gyh**?-*TK)2<80wr$(Sifx;nq+`32 ztk|~Aj%}-xj?=MiJDr?7`;5KM^X_ka>-QXURMouisx_{#O7pAn7*~$1zI_1csfmzgQbDqv zys=Z|F{iPZP(Vo#ro0PfRv{#&>F?-Bc>zTN-QnR~eOp-?=A@Tc$gErYHiIV7_hqWW zSC!deV%8iTw_q-QfuIgE1pX1tr`7w~;D&cF{jS6qm$aIl{S}1lY09{e7(}eWRt`d= zPY?VBrI>!{)w`V&J}~DMN`{SGv#IyUDI9iEw4Mu79o3@gdwiCMUA~`FT)0*ZIyx_R ztV6v+-Sa75r{D3=o78A zm$2wbwJ(*$HzDn<12zKPNBm2pQU;XJs?qB?`q5J?*|{E6_@kW8n4y zaS*&A4)cObw`EICQ(k=5$oA8h=(}A&IA(_emOWxqj42^R>r{M}TW^7A}v14u<-fe?&2rnpbvs87#oG#WVcE&?C+QNQX zU_m5$S4043Q_}`6*O6{3sR|0^;dl0_j+GojR=K0Ud?gST1KRh$4_D>fuNUJEVT68H5^z@%_VwgvieMHI!7fYc<2}2v*VW0^ykbzCsR9LQezr*CT~j$HN80!F(Ki~ydSpqd~k6& z3uG!_p}(h^seBN6`$Od9c?lFwdPSPt+))Y(HTrDL>FW81Rq%GATxa!;i>CFJaU|+h6wU+-Ul7d{l7}O+Q z;H)LBaYhEE9j9N4zuT8^nXxe&v|>n%3if<%^)8zNa-RC$%40KBJa5y}bwop;muzrI zQ$liNDrkAepNz_9w*0hqr_5-YZ1j8>a~q2r4Y@gmwfhCbmb&+TY+_9E*CLp?`Fw(g zl|YS5zvumIzg5klL4Q5r^Tg|_J9mKDzOn$vPoWVbK;JR#vuo2pT|EAl-PU_#?No*W zM1#mlSQ_zp)MfJ{5VNLR414JmA>2#{i^Geycv*g^;Kj2UM!IOt;P#e$WIAw0#ZDLx z5Ky5}A-NF~*`%iYt&HQeCglY^Np^zdQV)tiS5akboW*C(4CROSOrVUO5+RuI*BO@mA;T|mYVuGAmmVSvt`n}Qu?j;|BhJ1|;4m=c&& z<3?V*&MxqdyqxHxN=75~(QG&yJ9>dkGP}M|(ft#R9kfyyi4aGVJM$=Z$G>G9Z%3{@ zg&P9`=TeG1Z|1*F@ne25L&pzoX)e;%2gy3lLuiZ(f$x7w&*@C)HNj;LB7m$locsrf z`_}Ky^xGDHKq7TFER|sb?M#EK2K!@1oi@b_`?T4gzCcm?v_vpaB5=u_p1X;gortq6 zkp@!u-s|}`*2YpSu}pa%)wsr z`yg$`@dtAE@`k7z%u`M@5UpS{n z))=Y0%imuFmGihji6cag%6XjJoNA4iSC^=)fZz_(hTYtrg~bR<_)tTpfWvL zTP@tr%_zpS=tmXqqUldiSb|WR*Um5S%UrrhZ3?s;C?e^CayJ5%p|_`cT1>uji%09v zF}%7sDwONs`?7D$do7)-4RYpuc&#q2D2nYzG|`#mFvq2?=b3K1)a4#zmEVj%E^fk= zpC7)v$Rl`Z-#vK${LM-z_=gU(->huspq@y9ZMDXUdwW^}|C-JVZax#kEdOd&&tERo z(vx^ys1fgbG`M`*LU*3rKAl17|7P9D(EkLZuu5r`) zmPw?j#1k40y$>u3XHzhC*T0Dr^gH`Pxv@p^rU%=;+1c2PH#q_yd^v{qlCm>Hwnv%qlKU8m;jp?N?M&d3Gw;b2zI~BiCe6rfC0PLHg@~LHF5nH0Qb@Fq`E(k|yRvlr@=UrevK>d8-Oy$?@3}Cx56o4Z zh;|2E&af@E(vqnw+(v-yaL?!#dxwe?e8ol7B-b}aR%x!3;skT+rB}5>JJL9Jr$#iE zc1kW?3WJxMZba?8rG_ts+zmcqf6KPQ|r zS%p_=;@z%0>g$hAYCNlNW@+P#PM!i-%+Vq=M*?`@l?I!QFHKb&Hoq6uFwO$PEgOf+ z-I>n(s}_*Vhwv>a7OMR_%mhf6zvts|V9yPU!+4!2Tn%NoMs;7Z$%yl*&S@#{-;>*n z)dwIi%;yz;g;l3==dDa3WJ40F>5-?<{~-U!#pUv@CVGV*&Hp%MmHyaKW?lTb$=%e$ z5rd<^njHwmR-xpSV2XwUMG!?*z$6`3$$^cAENk_mZ@rierY~l8Guc=_f=b`zjC$sg zJrcVwfU#HvX5uy?-TwAKs*~V~F~OWMYJi$8>a=%!HnRR8xMWxpA>!oe zJHXl1M%oFgF(T_$EpdaizF8I_qV^`SmvK~RLNnQysR3QjS5ZgHy#J6|esdlwlLT$* z9gV*wTZLT^>s0@>$#r&3#N)7*lYkGL3nSoZm9M|^L7o1#r@TuRS0&;*za4+jSNkBz zGEjQ+oEgKB=1_TLCO3OUDR*d_rwau*=JT?iTfkIMlNb~RMF?a{GjK%+T`(-<34v&| zT7@SOQ!X+jN~4I)>qe}wjMr?C?CX<%PYGm(I|D(F;6xO&ansoGhPcNO2G5JpYFp-_ zk1a+QF-iA_C$z952}2(qmY!ykG*rQblWn3|!i~<|f^hCB(5%|twnFB{ruOu1Bbix) zMdD%&KDY8jcUckyYu%Iyqs>hu3VAV4SMY7qcr1EkaeA^LTQX z2q**RU_`@m<5-vPZ@AU})qjOQ3I2xLjS&+e=6l9ip~=5ry48CjxgdZLFu7rxxUUM#vZ)gOn4m9n4&r zQgwY$Q4+fEkM)_2EyPQAM(>+S8@0#U?SNo@2Z_Lj68DHX-5gNV@j;amxOivuh#z{8 zk=KlEFYd1*=-JX+4NHzVinc`?sii+&JfHx@4QR(p{nCOLY(ZQsCP($}F z@jA&)av%lr3{#OGY%)qco(PrKK;>!w2rvT4Iq}BsKxQ>^jIo#o&x~}c+t3ni(+|%z znRo2r*9A&=w0tKgi7$b^`6uQj$1t?>-7Ne%9=r|f3;|XwfDz)j^K|zh z#L9i4W7yx_5aMgp_0w|fvceb}%NnTg1G|2%iOa+>irmeF>Zn1WrY*%k7x2!Nl{7 z%fqICU57tz>m(q$Q#@I7CA17d6IfZxTEsXnoXVltJ*K&`9ZI7UQ!eZrVKTVVRKIa* z!}k8C8pP?JMPXg_qfvliEv$q`)s&Zz!kk0CT>;0ai(RPeklt$M!*wrn2M`OvxWl%h zE`2Y+2)^IdP$5@wa^zIk8bh>W2W5xyne2gEe8Mo>pTS$+c=k=vO%1uQdN=6uOIxK+ zOvo7u{7&~BY;Jfn>Fmpw|7*7mA0##(?E$_7I9L0t(s0oLmVGxC0b zm1oi+l5q_AsbhBa6R&EHJ0s@Rcd~!2Isq^g)#?XSu{XBnxPcG3O35{o>tK3w690nH zABPgy1^2nm(9C}I`rri$;|cB=c^z}s9S4r{$1B*NV*rAljM%WA?*@)h2@CSTug`ok zPF(l9T;MUW_03(3Me(+!Gi&>=Z}tETeoL#_6G=HYBsr`t1swDDwBurS(?$U-Xxn1;b~xs?1d}T5~LV3#03^@K<{Kubpt&- z!uK<^sA_d(XCL@-Hp^ovlgx+hcdCj{Ifv+&4ITnVMp7U%n}(n17H_=qr%n$+3<8j)4{Ed6b^Cc(1b=(w_CeAvdYFp-_@RkG5iwwNfO+2*XK_Z%yC4!98*98 zZeBLOPH{$OJk|pik&e&RBil{Q)=e$@*xCMfZ~9+?mYHT$D>o?dGh8r*$BH;QW6;&-U>7k4CHn^8)I`dHHRo_^-5jFs$!g-nE1m~>FEfM-Z7^1fmgbv| z@Zi9)^)6zv59C6IseRre6?fUx<-M8TWuV|0@$P|{3)XW*gR0Q2-3V_Ho8M?$1R^dc8092QmcN2GJ*pxMXfI zX|FmCX^#Q^M*J##Lo)rO#51eH+U4snFAjRI7$ldJ|vtFHp%(}*D>=p%MH zw$D+G&l!Jjd9^V9iA-kFN*-`DCfpZ*o3s=Y)lC>#Z9sYD!HtY0HIfaaOFU;

}zI zrXrnD{6}41n^r^F@iN0b33Eby=={$QSjLzXix0J-V9#`m@9)_aMwGauhpaB;OH zkt}VHfTMkjh(-Cmi+vAXFa?r6Q=t1rn4DI}dtXV^TN(KZ zcUMtBD+mG}MhxuiWnHbul0n>0m!tZdZOUwUdVnVD%67s=MrRoRfX`#!my_D;Xk$+- zdQStAmSSFiX_1yARW}H4$v!|#vwwC47r60`LQEXN+AX-P-nnTZuykDW!w+rq{fRU? z#enso2e*y39Blk8&g>zScPHAAXaivYS zILP)Y*q(HQ)mxo7`lO)0{@cX0Um;RuuGmc+zxP13d!9A6)!>#h{F@%O9H>lH>K^f% ztWA?CJd*7IICMFBASD_#EOqKIUuM34KXPNoBL(~pHyPESjJREiXQC?&jT;f-Bn z;$5!d1~7t+c4DKCHnfRw#<+AA-08T-a#PcLo?a)x&CLX8!bN<3cbwd=8}Dw+_7m;5 zH^Z*GOA%F@`{6)nD&t5aPDt3~?DdGFG<{Lnv(KRf?XEs#Lk+uBx=2UsPZQsVJ359G z4jj)td4cum{^i3%p`~Nb#9-~gy5wI8@;|&8q=F*Yp-pBBNgWHRD=APYQ0@ri)J(E3D2K=lr z#b{)7O$XvEZQKY@jaf45pgXo?uIAkHy36&v-$^v+{xTEem<5-=#ab;$)XGOE`&#GC z76eX>QPZ-I{KPl}t;#Nrlh`b%BiCTay=4E7uw~ zw5Al6wW1x4bcw@75BicpUr(VBH8ey|6N4DWei2-3yBd)a{Ze-p+L?zFH7lu4f2Q2| zYBHd}&icA8)OBs=r!c&PTGPJJLH+yE;f!m(i!1YtUzcSm%QtP(X0MkA%r54iGp|rL zsNtH_aqS5|II=3T19A~vg046_^eZnYyvQ<1zj3kCGU+Bc>Q?#YVlBRfDa25uB0&Lf zZ8ZcBw|+#}L%uLy?+)?89Su0$7N{w2JF1nF2R`_EMdA)0c8tGZd@+}DH$!N4Egn9#`A2v-D;>{FOZfmY5bLl;Ht<2cE<3VY~bx7 zpcnT}WO#g7T7HVU)2TgfO6jPt{G@WCPs_ny>XBZdxWSd6or1In`z$zobG~f%`pXMg2JFXv#bk z+%BHAP})$AXg*jh+y*%&O?hSUt0jULhTh{=Qsfw<{2X6PL#@TaP*+O?;2PC8=pQZ? z9Ki=`2aVfp%)3%W=dqOqGN@>G>!@I;87UgEIY^ZX-xyg*cvjIdywePAimE+rWAg~O zATAcgVs~QO%+q!gnr3NXvQQL7!4QCTPc`!7R(Onl5 ztXQ*3L`uRi=N@8pIir5m)t95k0G#r?4&}`Jxbrx!E$*|rDK9^&xvBML zdtSc=ch&PFuK*h-TSXBTb3)x$kSmDAmpBD}xYf-XDTbOnSr1rP7knoz#>TibBLDMQf*74Vok+h{Tb&{U3DBOT^|XAOa13KllN6|g~^T|>UhrI z1yMNmZ@CLk=SR+&3%l#~(R@DG9xWgGZ9hAv@#m6m4r1k#)joje(8lRy=&z&CGcFl3 zYTa*aZ^nv%jiYD-@`@()>JQbbTDEq&WDV1j%#3oNk)y|6LOe^jGh0fQo9A~iB(4uv zm>*;0s=bvKUp1_|*!oVrgNnBg!`ciHO26b-N`@wOwlX+lz!!BPuTX_6Lx>e?LUdFw zRO<}n(iaT%@OK)7zZ2co$vR1za58<6^mWbVr^02~;a|C~=C#^4l>~8U%cZqog&NNrUcW?agZ_7gd$eKmp@Dw8TJ!pyQ>wUvfd; znidfUM3`uUb7lB7{SZ%XaaG%`Hw!m44XhTuW&X;#I+YzCx;=oIH$cyQd_Wc(3A1`n zSTDLSs%6NxP^((QxsE=HCb2|0K`yx@)OUQ0J^>f*_H}joJ^HnSKyv(LCHsV#@{(Tp8!ODZ`<6(@;DKlp)dV|&kul?9@!5&ZXRmhg$h-=3=`E3(MsD*#|c& zZFM8-ZzeRJf$II@Pb8C>L~-@q0^6#;^Kt3oGhNZP&L=4!*g!Yy3&KtRl5%ES2wK`E zFn*w-a{AFoQrq>nyiYve|79H0VL?a|rPmlO-nF$gYQ+}L(J12INzIv$evtdZ$cVC_ zrX9G9$01D_j``DFY@u_7{;2yyv;{wmvbR1-O4~5n1q$k?KL}pc7YD$G6CRVK(V1Q6A8dSrYQo9zxX>tqky5@bXSbk zyv?A$LA6O*NE7<`bjuJ`k%_c`++_I+gB#(ZeMvesO*(T8Gd@)dXMdXZxA;-^yAY$P z-{Qj_pMgzwkgmn63q)vUJ3(gSW@{9C@AMMmLm+leBE=1S0m(M*CREe`zd&1b=m7X-ro6^(>>Y(t)L!l z=4)S%+4^!-gJH)}o9!j+c|L7yj8&1O82Cs*KtLL=E3mWlwDO04UH@9&XY&veD=m=U zo%7oAxk3^9JA$lCA~IJCD%R9s7_-JB%!Zkmr?XF58Q+d@?&M~%=61;u^15RAcw7zG zOvouKE7k;w*n8$=ZxbI!EmtI6OB>~#B^G_1@^jY1N*CZxOS^ik+z$kr7d&y*!VII# z!8s!V?x@da7l;(LmUt`y zFf7bloh^^CqO-)Mo6_yhk{7Hx8j!_G@-y#Sw^RXxDNA;&ef~1SFS?$yv5iW>ip}Ff zC{cFz*kLCeU3mWt$ZR=Y$c+w47J_xK_5gK=* zUc=e86eOD@z-9?lG=X@Sn-bSi`^d-;S@w4i|9iq#Q`c?<%I`r&EEZE&A$n(P(^X@D zUz{z!$@-R-erMxWwRVLvspkLcLy@*A!V&aABgEn;R>T4|LATs( zwC>F3d#YmMFssp9I0`-8`|1U|06-QSNn#asq$E?o*$X+w%3EfkDvIQUmKEa6`z!A~ zD5>%j{vW98Kb487W6>wtAC&iR+=)S{rcg3z3$xihRX1PKOS+xL1oBIRek}fFkZC$Vzb++kwL@iqm(0fF!2$nv~TAMBni76 zH1zN6TzU$jwG#Da%6!(K;&8u)(WL;i5IhNG7>R_%1Z?8OUTa{{GgxFd05*4m&6u>M zlz3|luc!It!HH4)ZmF07K`~L3ePe^XXZ;2w=G-junG6&wmtW!(gzE#a9{o%YUj(A_ zFh!Fhn_I%UuIh7Z$#;Q)SR_rHp}_gSCiK5n8{$Ts>T|ARuD!ccqX@ ziyH64pZ>9@u#$sV%?35eS$Yd0@&9O?@omBxXqJmaKj8}^ss!g({>ak`hxJM+RE2Ja zJDfyU8?H4Hd3xYC-xIyW;e9H=xrE}9Q>3EPG_1l9w^j~JqHj9$12*}?x97La29r!1 zznEbaaug#k=s#3ygr;f@{6BAzfCHE&c&Ol$vb6hfk&k_!?dE%fd>$hws$kTg1N{c& z&)c<*p0Bki>jI2Y6}-GMwUVtqY-Q_zxMT$3pgwXyX#a5}M?y#tM- zz;nMn0S90t$IA@s(BlDPiAaag^<9W4!j3pkxqY8t^W{h=dnPJ8gBMx}*)NOAB%jnbNwY5P7~OLcfjBY=kCSpC{PEAhHFEgLq|#Kqw@I5Ug!VHSx!D4m58{YjxbK0FWiI; zb!#$(F4gx3CY&RTp6r$6=`xMP=Vi5n&rnDzUyPijE!1qb`!9+;=nop@WRE5Hhm!M| z8IRKu?$e&u`A2=pKLtV0&Mrv$P;aIJmH@sBY5I+N_tu{cV}E!(dO(;X_jY`Z(IvpN zasw<;vBjMUX?pJ|D$KgU9`aEG#^gOxWY{3l7Km4&D}vYi*^11*MIMwmLYztfin|qm zv?}j!+;X^Xke&V>{6YdIQD(4qcp(3*Ww>Huv(W+!aUsXhBW4~QrNqUNDG;tAdJGYL ziOdOWvW0>aq-wY2y+NI2-Dbuy^r!y=DH|f_S(NcM&*6`Yfo9$@*9C3RRu;yDL@)Lp z{4dPy0UG`1icaePAMUphNvV&blQVT)9{^f0A2Pz!1ffNzMfZEKKghEF(-w=#GA%zI zlBw9qsWd-qqZ8T~@M(yHxf3`N3|lDOC-+Ty)q}t$wv?Q*6XAeEh>3_O{Cop`UMh@q zNY+84xpQ6rBr7>5+TYI^ri|9A`)a%1)63gdiX>GJk=&Udh0W`@l|ZnvNs?4_m_k6d=kIhO`-6ZsFCOb;I+N+YvN+X??Yp0 zzm(*LR0#1Y>hYCc8E2|a(_cT>9h3Y?AMJTVAE;|dyTiG3IWbV!d2sedpN7i;h}Fkz zx2lQPYmaw4ekCvx6izOW*W&WMIFp2!5Jn+ynq@ykFr_#B398~axcn(7s&!-i&*{JQ zXhc^8ShYYS&Co)gawLH?Tm3Gl%l5KAscNG9YRY;L`mVreuCt zCs4WRu%0%yAyT$Mwpwx@8HY5O_}2i5zS3^!5mh84KGer2Gtk7uO?QE8y4n5SCUnaXmEfYfI<-acA6SYu=n>REooQ zMKqe~3e2mHNQv~WeoKUCQe;5v`K{WX{CE@;D8RoV&@5A8=%5|FwP1=B+UDncvx30d7mMt!mKshA5u zA#fT^@=3CEkb6TT{m6OlWQ4zqaZ(CU0s5!z9JohhUAZrF$T}3X>!g~5N$@~~mQ=MJz#&tlw zGf{@#ouVEkE%;*?*R91p*<-^5WosCt)oQVG#QaERd;_zfuF&}^DXLo27oqRp^`TD; zp`8|ER+Ww%UW4Pm#=(HV{wW~0pM4;j0OEly#m8YGZm19@v!A;+ zhwrdK42PXmfJrFGVyXxR*QjMGQ;8K3p{0z6k7Su7!1+zvkqnb!uUcz7LsCt~bu=2f z)Xb1ODN-DJ-SI8v*vFUJP|q98-|Gwyiy33K#jiCK|E!MVSDt!(>p3<`(h)U3dg zxXa0sIel*1`ip1O`JKue_efc2%3(Fgt()4UlDn$Y?a;WHA|Hui0fHQiO~z>bZTI4( zzZ_LGfrE+059jjt*wEHfGRc#^kR(H9kQZ=?Rju>cbd@nE{`Sd$=jTL!!8~O;&p=5e zP(9y)^J1QKLkP`5n9yFxpJ1KURuk-&{rg9;4!eJ5r^SLuywPzmOnR;3MMaRf{t0+s z!ruX@>-YOwN4~m@^;9|s^nngrmSd7yJ;al3KdW3n)XZR~fFHkajS1=YZM00ZN_mlG7p*&k29`I|z1f^(g#xADpceA5WUcC$?P z0N0-jUu%_@hy~>?wt93r?O2Zj(`4_{Z(!tut|C^lsjV2CE0-Y zD|er^)wcd$ogxR?Va_lYH^S-~U2(-nXsV@Lb@-G%@fn&P-#FF<1DvKKtqRAhW9^#40nms^B!O zh16pq_KpIPpZ=q5U4#NmRU~9X5?dol?=u2IQfUImz7mSBZ#9t%*41FVcZRFiM1^t{ z@e#5kM5cJatYIjTO55v#-pteaU`8Aj>K<+-DveM*`sdRfB}RfkKX<6Q>m`2Bwd~)j zQwFNsTx%u+;ewox^oXRJkD*GrM63)gHYdIVI>j7bU>w&4r4P8N_QGYl;~T{~+SL8Z zi3g@&$thOY5>dE>F+2_ozuN>Ncc#stBO%OPBi3H!Ey-Fy7E4HE_1N^l&Aw|k+4uSO zCjAD~843729mK*@0%+BO7$pn#x8EpTAD$nZE72s9T0?myxE{rsr2E`ENb; zOuE#qt7n)dAE;mvhBqj(r-)vWD|Cpb10(!yvlZF8d}ea3ZhFnWIy?MxEJ3*6U64)W zJLWIa^JH{Ma9B>VuG*c^+J=bJE1amx73wtjJaD{my--0$+{3+?2Iz}Sb2b$y{O)Ce z-(Sngp>{&Dycds`jHCdPul4ZK-_7sDx#GV(5Hu^rBk-OfJ-CTrVS~zXaB=zn?35t# zEa71vrVY zvbjVRxZREB7xcKMdrZdXhNlVh0$m@!lbT&(Ms#C(wAvUJA^IoKZPa7h+-&Zc!fehy z&lduZzqJK^{%Auy`*>oHMZgQfwsfuXr_ObMoT4fz;t*=eedG9K(ibDj>ZNlK4^wBY zGWS^ceq;=Nob_N6ig-lC+_6D2LyChEn!3Lm8t9W33QR|xz7mFjbG9qpFi#HMKM?bK zQ;@FG9JmZg%a1)VYcFGHdB#1i!%Q0j*{PTy%`Y|R9jI?iaQkFecGBuJuz$DlST^M( zBU2vP5X5f{ONfzV_IP)uM1F;*zA-Zexs33rzArbY89kVn(QbG|klph(%NHYhOdD_W zWyL=L?-(riBE>e_ORM@y_7VIyG{+dvqE5njLeaC5Avci+aq1}Y! za5Xs^iR?d8Pb%j4w2TtW0{>lBRR=Wo%Z9+gLtg@D8+J^qc0K8+2EM!6?I6`VZc!xB zHWxqS#;ilFh2EVRt_C$bGnv*#r) zJmxm1tH5UFB{G-Bexhu1)ka7FF)m}T`W`Gj%o9^?RL?%>SrNG2^-uK?5QUex(q6i#*roIZ{-APQdSYq6w9n;=GOj#!qLY z)Q^HF6(`!^2I@K}C3<)6qAYPwAz2LE;I}RU@GlhBB2)0s-I~k8C=md$$N;}!&F{}* z&pV04W}C9|t!sX$!msrcUme51z!32x5+ip-BI+rcJdHGao^vR$!-4w_9MgY`)}CK%gvdLyRp9?XdW(vKt7l~+XklSgCCZ< zcPbYA><^6}?{PxbLZ~o!vLKMnn-N5=``S~n;OOiw3J`}t^A6h!^)r9pD(G2X+@mDV zLzp%)@WzDrvb>_MY_l-|Rhq{9v_SHJ+q^hP`wfvdZy7YBAY@z?Sc^q09brJ{p;Hbh9bU z+?OB_DftiJr`KemK8^p;+DJCDlgbL*b+eLE>|5M)y7oQN;JI1>>&{irwN1wHQ8UT^ z+jX&4!0(AtS--dQJd}je>EWPEM&|v7N^Yt&61@L=EfgU!reiY7P70jX5Z9+u*SMm9Zo9XgNFAOyrz!t=BeX8mo> zgYoheyLRKxE}JLlFY=kW`sRnWmZl%2;VT{0rEWJaUo67>ALKKvxbbxI5?b6CjPKyT z!s0?kg^fb@X2psVHA*OK$k%mF(1wv%-@6b(zx9f8nzL}k3Bo;Wi!{9`!7G!y8v#H#i!7j^UtAHH}_QIrg&#G?<3LeH3Js=8(N;~?XE7) zj0b;bO?;t8>ZCITxF}}C(!2TaS0VS@LIF>=l-KX?y%@I16qsB@rX%XdV+S>UPeRAY z1fkNo#^?6!xS20^B%a=Vm!S|in##5bw*>gD!Pr|mD8^_=7F#MD7GMoT-dJR;qyjxs zmbeC+|CQuH3BytP@h8#wrSTuX$2F3fo3ClFpGzN9T*pEyXR@FYefq)2`nUt;zT3rC zs&Gbu;qIP89vk_iV_v;uyU$aJaoVA&|J@~VSb-8;iNF5BEfGL5S?5--bI1Jlag$@@ z17tZ=&TP&_{Nnz@!x366f1ZV8vF%K3!13bu({_g&x$nn|77VY>Olu;VdWp9tDuP_9 z=(88?;;)#Qs?m_oYGTk*`x0ko$*CX6MPF9Y&fE&8J`a`$TL1Fm{zCuYAovKu2P7pV zF1_Wb6$cMg%yu4smpT4DfDY$f;nJF@%te)_Q$2Z))bm$28~FTkmP|)bu?yQBpI7mm z7Mpp3;hygE{}+?65B0bh##I)o*y1Mvp~`dL9!(}2Jdrv6Zi5{;qzIiorI)k<{WGjo zJ{0JPCk&oXvP$|MJu+ix=z2oKGONIUzKIUT)gG$Y8si{ddB6LW3$C_!;9??fr{xqI z#}y->sw$f8$})?*4+oi(6%TXvNBrO2t$g@1SRebN*~7g7{Qpjc_~%=lk{rEHAUPmWEym#y^#5sW@JdJHDOJxBY!&S5^m!kYcPsfp>yJ*%c*ne?q8O8D^*hot2 zWb7|#dR`PUHa+RHku2kdb*yQ6Oea=~DCtaV+~=&e*?3lz{uE z7LyaWao(hR*#D}K;{g~)s3G%fTvQ#Qcc^4Ue7k7k-IjZ)w)Y;0%5w&3jE8LM*Z^8E z`N+~z93qLLQSztQHKkR?baR!sUs$yVP!V+_W&oDU@6`R*#D_f+RUK~Vn_ zQUSn)-~pe5h;8TwZ*l@xo~;#b$n`}+l0xne0>kH{APQ907YBvrWuJoxCj4lTMyLSX z<&&}D4bLAH+=t%8cXhRMS0M>Ebb-=XV~hrb=L7-v5m8A7k%2 zIMAs8ZOAkk3o6_Q;_#pT{!rko#Icr&gI<4SdF-|z+%&0HqQ?Y!!eR9<{wA|x&?UM` zGue;O98j{`i{r}lm^&*QtB>1U5pG}X{lDo4kk5#qY$Jq&ftQepncKaS>?OQSMky!I z88QP$wQ63Rb5s_5QjT`EPsk|MEi)xcFz(7j@MPIwgU* zyJ8rdPvJKxza|PgydfXWxpy6{gb88^g6X)|a9o1D{_Jix>k5%X&lEtz0Ba zJxH$1s7S(M;=>0;!B=QFX%-zw-=Y17*|K&Vh$Z0X=uZiyF(jm!FiD)a#1(E0?^!y! zfZSq=%{O;cF=vp|WEW-ZeQ*qzglL&)d00#N)3BU+RJID$2s}iDDW)07ko-U|Mtm+f zO2Hx$Z%+{tsu>QaW;!*FXWoBL3HV1MfSsf|$|Gkr9oV>i2&xbb9clWivYrqYErLvG zDs&DHU(n%;8r#{@mrK@I#a;tYaFP}`l|@ij)l%Ivl@E%P;S>k;mZCle>qvN2NBAWb z9onMQHOp87Bmf6s*hG)~UWw(Kxr6^#q5e~0b^%|LTm`u=t;C23#~MGV0uuUt%pl=e z`vwma02SdovkA)Qm_%~CQBPfD>V&fpI=l1(imAw0X(}t1DWXLZ_yTNXd@_x0N4Hr2`Rl0pA#Xy*_OT_p5hjRlRBY%U@n%E1lif3 z3UCOpox%Sc$S*pgfH~JOtNhTb@{ICFqX&G-$>S9i(vGO-j}Zl}nB$cH<#Qm$p_%K- zjeekH=Ck}ux=5SjPhzgbT!axcg!Ax6^K;R=@iyvzEzkwZSLZbiihTD=3KyWpq*OkK zV(Bf`DpZO?W^V*TcSBrmQO}h6q%2Xcp-LAcXCoL#!x@$wQB` z!=&HhS|E%CiwLF`f5VjWy=?1rTKYLY|KJ6dtiu_t=aE vHNn2hT|Gi{SGU~u|6kkp|Lt5=n0H|_$Iz@qFmP3nub+&BqIiv{ap3;}{ry?{