From d10b02242050e9eba97ef6ecd9c165e2f3a5477b Mon Sep 17 00:00:00 2001 From: Vesna Tanko Date: Fri, 15 Mar 2024 08:53:44 +0100 Subject: [PATCH 1/9] Permutation Plot: Add widget --- .../evaluate/icons/PermutationPlot.svg | 40 +++ Orange/widgets/evaluate/owpermutationplot.py | 330 ++++++++++++++++++ .../evaluate/tests/test_owpermutationplot.py | 105 ++++++ 3 files changed, 475 insertions(+) create mode 100644 Orange/widgets/evaluate/icons/PermutationPlot.svg create mode 100644 Orange/widgets/evaluate/owpermutationplot.py create mode 100644 Orange/widgets/evaluate/tests/test_owpermutationplot.py diff --git a/Orange/widgets/evaluate/icons/PermutationPlot.svg b/Orange/widgets/evaluate/icons/PermutationPlot.svg new file mode 100644 index 00000000000..4a7720cdb75 --- /dev/null +++ b/Orange/widgets/evaluate/icons/PermutationPlot.svg @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/Orange/widgets/evaluate/owpermutationplot.py b/Orange/widgets/evaluate/owpermutationplot.py new file mode 100644 index 00000000000..5886ec61879 --- /dev/null +++ b/Orange/widgets/evaluate/owpermutationplot.py @@ -0,0 +1,330 @@ +from typing import Optional, Tuple, Callable, List, Dict + +import numpy as np +from scipy.stats import spearmanr, linregress +from AnyQt.QtCore import Qt +import pyqtgraph as pg + +from orangewidget.utils.visual_settings_dlg import VisualSettingsDialog, \ + KeyType, ValueType +from Orange.base import Learner +from Orange.data import Table +from Orange.data.table import DomainTransformationError +from Orange.evaluation import CrossValidation, R2, TestOnTrainingData, Results +from Orange.util import dummy_callback +from Orange.widgets import gui +from Orange.widgets.settings import Setting +from Orange.widgets.utils.concurrent import ConcurrentWidgetMixin, TaskState +from Orange.widgets.utils.widgetpreview import WidgetPreview +from Orange.widgets.visualize.owscatterplotgraph import LegendItem +from Orange.widgets.visualize.utils.customizableplot import \ + CommonParameterSetter, Updater +from Orange.widgets.visualize.utils.plotutils import PlotWidget +from Orange.widgets.widget import OWWidget, Input, Msg + +N_FOLD = 7 +PermutationResults = Tuple[np.ndarray, List, float, float, List, float, float] + + +def _correlation(y: np.ndarray, y_pred: np.ndarray) -> float: + return spearmanr(y, y_pred)[0] * 100 + + +def _validate(data: Table, learner: Learner) -> Tuple[float, float]: + # dummy call - Validation would silence the exceptions + learner(data) + + res: Results = TestOnTrainingData()(data, [learner]) + res_cv: Results = CrossValidation(k=N_FOLD)(data, [learner]) + # pylint: disable=unsubscriptable-object + return R2(res)[0], R2(res_cv)[0] + + +def permutation( + data: Table, + learner: Learner, + n_perm: int = 100, + progress_callback: Callable = dummy_callback +) -> PermutationResults: + r2, q2 = _validate(data, learner) + r2_scores = [r2] + q2_scores = [q2] + correlations = [100.0] + progress_callback(0, "Calculating...") + np.random.seed(0) + + data_perm = data.copy() + for i in range(n_perm): + progress_callback(i / n_perm) + np.random.shuffle(data_perm.Y) + r2, q2 = _validate(data_perm, learner) + correlations.append(_correlation(data.Y, data_perm.Y)) + r2_scores.append(r2) + q2_scores.append(q2) + + correlations = np.abs(correlations) + r2_res = linregress([correlations[0], np.mean(correlations[1:])], + [r2_scores[0], np.mean(r2_scores[1:])]) + q2_res = linregress([correlations[0], np.mean(correlations[1:])], + [q2_scores[0], np.mean(q2_scores[1:])]) + + return (correlations, + r2_scores, r2_res.intercept, r2_res.slope, + q2_scores, q2_res.intercept, q2_res.slope, + data.domain.class_var.name) + + +def run( + data: Table, + learner: Learner, + n_perm: int, + state: TaskState +) -> PermutationResults: + def callback(i: float, status: str = ""): + state.set_progress_value(i * 100) + if status: + state.set_status(status) + if state.is_interruption_requested(): + # pylint: disable=broad-exception-raised + raise Exception + + return permutation(data, learner, n_perm, callback) + + +class ParameterSetter(CommonParameterSetter): + GRID_LABEL, SHOW_GRID_LABEL = "Gridlines", "Show" + DEFAULT_ALPHA_GRID, DEFAULT_SHOW_GRID = 80, True + + def __init__(self, master): + self.grid_settings: Dict = None + self.master: PermutationPlot = master + super().__init__() + + def update_setters(self): + self.grid_settings = { + Updater.ALPHA_LABEL: self.DEFAULT_ALPHA_GRID, + self.SHOW_GRID_LABEL: self.DEFAULT_SHOW_GRID, + } + + self.initial_settings = { + self.LABELS_BOX: { + self.FONT_FAMILY_LABEL: self.FONT_FAMILY_SETTING, + self.TITLE_LABEL: self.FONT_SETTING, + self.AXIS_TITLE_LABEL: self.FONT_SETTING, + self.AXIS_TICKS_LABEL: self.FONT_SETTING, + self.LEGEND_LABEL: self.FONT_SETTING, + }, + self.PLOT_BOX: { + self.GRID_LABEL: { + self.SHOW_GRID_LABEL: (None, True), + Updater.ALPHA_LABEL: (range(0, 255, 5), + self.DEFAULT_ALPHA_GRID), + }, + }, + } + + def update_grid(**settings): + self.grid_settings.update(**settings) + self.master.showGrid( + x=self.grid_settings[self.SHOW_GRID_LABEL], + y=self.grid_settings[self.SHOW_GRID_LABEL], + alpha=self.grid_settings[Updater.ALPHA_LABEL] / 255) + + self._setters[self.PLOT_BOX] = {self.GRID_LABEL: update_grid} + + @property + def title_item(self): + return self.master.getPlotItem().titleLabel + + @property + def axis_items(self): + return [value["item"] for value in + self.master.getPlotItem().axes.values()] + + @property + def legend_items(self): + return self.master.legend.items + + +class PermutationPlot(PlotWidget): + def __init__(self): + super().__init__(enableMenu=False) + self.legend = self._create_legend() + self.parameter_setter = ParameterSetter(self) + self.setMouseEnabled(False, False) + self.hideButtons() + + self.showGrid(True, True) + text = "Correlation between original Y and permuted Y (%)" + self.setLabel(axis="bottom", text=text) + self.setLabel(axis="left", text="R2, Q2") + + def _create_legend(self) -> LegendItem: + legend = LegendItem() + legend.setParentItem(self.getViewBox()) + legend.anchor((1, 1), (1, 1), offset=(-5, -5)) + legend.hide() + return legend + + def set_data( + self, + corr: np.ndarray, + r2_scores: List, + r2_intercept: float, + r2_slope: float, + q2_scores: List, + q2_intercept: float, + q2_slope: float, + name: str + ): + self.clear() + title = f"{name} Intercepts: " \ + f"R2=(0.0, {round(r2_intercept, 4)}), " \ + f"Q2=(0.0, {round(q2_intercept, 4)})" + self.setTitle(title) + + x = np.array([0, 100]) + pen = pg.mkPen("#000", width=2, style=Qt.DashLine) + r2_line = pg.PlotCurveItem(x, r2_intercept + r2_slope * x, pen=pen) + q2_line = pg.PlotCurveItem(x, q2_intercept + q2_slope * x, pen=pen) + + point_pen = pg.mkPen("#333") + r2_kwargs = {"pen": point_pen, "symbol": "o", "brush": "#6fa255"} + q2_kwargs = {"pen": point_pen, "symbol": "s", "brush": "#3a78b6"} + + kwargs = {"size": 12} + kwargs.update(r2_kwargs) + r2_points = pg.ScatterPlotItem(corr, r2_scores, **kwargs) + kwargs.update(q2_kwargs) + q2_points = pg.ScatterPlotItem(corr, q2_scores, **kwargs) + + self.addItem(r2_line) + self.addItem(q2_line) + self.addItem(r2_points) + self.addItem(q2_points) + + self.legend.clear() + self.legend.addItem(pg.ScatterPlotItem(**r2_kwargs), "R2") + self.legend.addItem(pg.ScatterPlotItem(**q2_kwargs), "Q2") + self.legend.show() + + +class OWPermutationPlot(OWWidget, ConcurrentWidgetMixin): + name = "Permutation Plot" + description = "Permutation analysis plotting R2 and Q2" + icon = "icons/PermutationPlot.svg" + priority = 1100 + + n_permutations = Setting(100) + visual_settings = Setting({}, schema_only=True) + graph_name = "graph.plotItem" + + class Inputs: + data = Input("Data", Table) + learner = Input("Lerner", Learner) + + class Error(OWWidget.Error): + domain_transform_err = Msg("{}") + unknown_err = Msg("{}") + not_enough_data = Msg(f"At least {N_FOLD} instances are needed.") + incompatible_learner = Msg("{}") + + def __init__(self): + OWWidget.__init__(self) + ConcurrentWidgetMixin.__init__(self) + self._data: Optional[Table] = None + self._learner: Optional[Learner] = None + self.graph: PermutationPlot = None + self.setup_gui() + VisualSettingsDialog( + self, self.graph.parameter_setter.initial_settings + ) + + def setup_gui(self): + self._add_plot() + self._add_controls() + + def _add_plot(self): + box = gui.vBox(self.mainArea) + self.graph = PermutationPlot() + box.layout().addWidget(self.graph) + + def _add_controls(self): + box = gui.vBox(self.controlArea, "Settings") + gui.spin(box, self, "n_permutations", label="Permutations:", + minv=1, maxv=1000, callback=self._run) + gui.rubber(self.controlArea) + + @Inputs.data + def set_data(self, data: Table): + self.Error.not_enough_data.clear() + self._data = data + if self._data and len(self._data) < N_FOLD: + self.Error.not_enough_data() + self._data = None + + @Inputs.learner + def set_learner(self, learner: Learner): + self._learner = learner + + def handleNewSignals(self): + self.Error.incompatible_learner.clear() + self.Error.unknown_err.clear() + self.Error.domain_transform_err.clear() + self.clear() + if self._data is None or self._learner is None: + return + + reason = self._learner.incompatibility_reason(self._data.domain) + if reason: + self.Error.incompatible_learner(reason) + return + + self._run() + + def clear(self): + self.cancel() + self.graph.clear() + self.graph.setTitle() + + def _run(self): + if self._data is None or self._learner is None: + return + self.start(run, self._data, self._learner, self.n_permutations) + + def on_done(self, result: PermutationResults): + self.graph.set_data(*result) + + def on_exception(self, ex: Exception): + if isinstance(ex, DomainTransformationError): + self.Error.domain_transform_err(ex) + else: + self.Error.unknown_err(ex) + + def on_partial_result(self, _): + pass + + def onDeleteWidget(self): + self.shutdown() + super().onDeleteWidget() + + def send_report(self): + if self._data is None or self._learner is None: + return + self.report_plot() + + def set_visual_settings(self, key: KeyType, value: ValueType): + self.graph.parameter_setter.set_parameter(key, value) + # pylint: disable=unsupported-assignment-operation + self.visual_settings[key] = value + + +if __name__ == "__main__": + from Orange.regression import LinearRegressionLearner + + housing = Table("housing") + pls = LinearRegressionLearner() + # permutation(housing, pls) + + WidgetPreview(OWPermutationPlot).run( + set_data=housing, set_learner=pls) diff --git a/Orange/widgets/evaluate/tests/test_owpermutationplot.py b/Orange/widgets/evaluate/tests/test_owpermutationplot.py new file mode 100644 index 00000000000..38f02750867 --- /dev/null +++ b/Orange/widgets/evaluate/tests/test_owpermutationplot.py @@ -0,0 +1,105 @@ +# pylint: disable=missing-docstring +import unittest + +from Orange.classification import RandomForestLearner, \ + LogisticRegressionLearner +from Orange.data import Table, Domain +from Orange.ensembles import StackedFitter +from Orange.regression import RandomForestRegressionLearner, \ + LinearRegressionLearner +from Orange.widgets.evaluate.owpermutationplot import OWPermutationPlot +from Orange.widgets.tests.base import WidgetTest + + +class TestOWPermutationPlot(WidgetTest): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.heart = Table("heart_disease") + cls.housing = Table("housing") + cls.rf_cls = RandomForestLearner(random_state=0) + cls.rf_reg = RandomForestRegressionLearner(random_state=0) + + def setUp(self): + self.widget = self.create_widget(OWPermutationPlot, + stored_settings={"n_permutations": 3}) + + def test_input_disc_target(self): + self.send_signal(self.widget.Inputs.data, self.heart) + self.send_signal(self.widget.Inputs.learner, self.rf_cls) + self.wait_until_finished() + + lin_reg = LinearRegressionLearner() + self.send_signal(self.widget.Inputs.learner, lin_reg) + self.wait_until_finished() + self.assertTrue(self.widget.Error.incompatible_learner.is_shown()) + + self.send_signal(self.widget.Inputs.learner, None) + self.assertFalse(self.widget.Error.incompatible_learner.is_shown()) + + def test_input_cont_target(self): + self.send_signal(self.widget.Inputs.data, self.housing) + self.send_signal(self.widget.Inputs.learner, self.rf_reg) + self.wait_until_finished() + + log_reg = LogisticRegressionLearner() + self.send_signal(self.widget.Inputs.learner, log_reg) + self.wait_until_finished() + self.assertTrue(self.widget.Error.incompatible_learner.is_shown()) + + self.send_signal(self.widget.Inputs.learner, None) + self.assertFalse(self.widget.Error.unknown_err.is_shown()) + + def test_input_cont_target_ensemble(self): + self.send_signal(self.widget.Inputs.data, self.housing) + + learner = StackedFitter([LinearRegressionLearner()]) + self.send_signal(self.widget.Inputs.learner, learner) + self.wait_until_finished() + + learner = StackedFitter([LogisticRegressionLearner()]) + self.send_signal(self.widget.Inputs.learner, learner) + self.wait_until_finished() + self.assertTrue(self.widget.Error.unknown_err.is_shown()) + + def test_input_no_target(self): + domain = Domain(self.housing.domain.attributes) + data = self.housing.transform(domain) + self.send_signal(self.widget.Inputs.data, data) + self.send_signal(self.widget.Inputs.learner, self.rf_reg) + self.wait_until_finished() + self.assertTrue(self.widget.Error.incompatible_learner.is_shown()) + + def test_input_multi_target(self): + domain = Domain(self.housing.domain.attributes[:-2], + self.housing.domain.attributes[-2:]) + data = self.housing.transform(domain) + self.send_signal(self.widget.Inputs.data, data) + self.send_signal(self.widget.Inputs.learner, self.rf_reg) + self.wait_until_finished() + self.assertTrue(self.widget.Error.incompatible_learner.is_shown()) + + def test_sample_data(self): + self.send_signal(self.widget.Inputs.learner, self.rf_cls) + self.send_signal(self.widget.Inputs.data, self.heart[:6]) + self.assertTrue(self.widget.Error.not_enough_data.is_shown()) + self.send_signal(self.widget.Inputs.data, self.heart[:7]) + self.assertFalse(self.widget.Error.not_enough_data.is_shown()) + self.wait_until_finished() + + def test_send_report(self): + self.widget.send_report() + self.send_signal(self.widget.Inputs.data, self.heart[:10]) + self.send_signal(self.widget.Inputs.learner, self.rf_cls) + self.wait_until_finished() + self.widget.send_report() + self.send_signal(self.widget.Inputs.data, self.housing[:10]) + self.send_signal(self.widget.Inputs.learner, self.rf_reg) + self.wait_until_finished() + self.send_signal(self.widget.Inputs.data, None) + self.send_signal(self.widget.Inputs.learner, self.rf_reg) + self.widget.send_report() + + +if __name__ == "__main__": + unittest.main() From 7821364313bb56106810bd380b89b43678f316e3 Mon Sep 17 00:00:00 2001 From: Vesna Tanko Date: Thu, 28 Mar 2024 08:37:00 +0100 Subject: [PATCH 2/9] Permutation Plot: Handle multiple targets --- Orange/widgets/evaluate/owpermutationplot.py | 2 ++ Orange/widgets/evaluate/tests/test_owpermutationplot.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Orange/widgets/evaluate/owpermutationplot.py b/Orange/widgets/evaluate/owpermutationplot.py index 5886ec61879..65acd8228ae 100644 --- a/Orange/widgets/evaluate/owpermutationplot.py +++ b/Orange/widgets/evaluate/owpermutationplot.py @@ -15,6 +15,7 @@ from Orange.widgets import gui from Orange.widgets.settings import Setting from Orange.widgets.utils.concurrent import ConcurrentWidgetMixin, TaskState +from Orange.widgets.utils.multi_target import check_multiple_targets_input from Orange.widgets.utils.widgetpreview import WidgetPreview from Orange.widgets.visualize.owscatterplotgraph import LegendItem from Orange.widgets.visualize.utils.customizableplot import \ @@ -256,6 +257,7 @@ def _add_controls(self): gui.rubber(self.controlArea) @Inputs.data + @check_multiple_targets_input def set_data(self, data: Table): self.Error.not_enough_data.clear() self._data = data diff --git a/Orange/widgets/evaluate/tests/test_owpermutationplot.py b/Orange/widgets/evaluate/tests/test_owpermutationplot.py index 38f02750867..350435990da 100644 --- a/Orange/widgets/evaluate/tests/test_owpermutationplot.py +++ b/Orange/widgets/evaluate/tests/test_owpermutationplot.py @@ -77,7 +77,7 @@ def test_input_multi_target(self): self.send_signal(self.widget.Inputs.data, data) self.send_signal(self.widget.Inputs.learner, self.rf_reg) self.wait_until_finished() - self.assertTrue(self.widget.Error.incompatible_learner.is_shown()) + self.assertTrue(self.widget.Error.multiple_targets_data.is_shown()) def test_sample_data(self): self.send_signal(self.widget.Inputs.learner, self.rf_cls) From e7f019f97a5897ee337114078ed2de7e0b8d660a Mon Sep 17 00:00:00 2001 From: Vesna Tanko Date: Fri, 29 Mar 2024 11:14:03 +0100 Subject: [PATCH 3/9] Permutation Plot: Use AUC for classification --- Orange/widgets/evaluate/owpermutationplot.py | 101 +++++++++---------- 1 file changed, 50 insertions(+), 51 deletions(-) diff --git a/Orange/widgets/evaluate/owpermutationplot.py b/Orange/widgets/evaluate/owpermutationplot.py index 65acd8228ae..b0e45268690 100644 --- a/Orange/widgets/evaluate/owpermutationplot.py +++ b/Orange/widgets/evaluate/owpermutationplot.py @@ -10,7 +10,9 @@ from Orange.base import Learner from Orange.data import Table from Orange.data.table import DomainTransformationError -from Orange.evaluation import CrossValidation, R2, TestOnTrainingData, Results +from Orange.evaluation import CrossValidation, R2, AUC, TestOnTrainingData, \ + Results +from Orange.evaluation.scoring import Score from Orange.util import dummy_callback from Orange.widgets import gui from Orange.widgets.settings import Setting @@ -24,21 +26,26 @@ from Orange.widgets.widget import OWWidget, Input, Msg N_FOLD = 7 -PermutationResults = Tuple[np.ndarray, List, float, float, List, float, float] +PermutationResults = \ + Tuple[np.ndarray, List, float, float, List, float, float, str] def _correlation(y: np.ndarray, y_pred: np.ndarray) -> float: return spearmanr(y, y_pred)[0] * 100 -def _validate(data: Table, learner: Learner) -> Tuple[float, float]: +def _validate( + data: Table, + learner: Learner, + scorer: Score +) -> Tuple[float, float]: # dummy call - Validation would silence the exceptions learner(data) res: Results = TestOnTrainingData()(data, [learner]) res_cv: Results = CrossValidation(k=N_FOLD)(data, [learner]) # pylint: disable=unsubscriptable-object - return R2(res)[0], R2(res_cv)[0] + return scorer(res)[0], scorer(res_cv)[0] def permutation( @@ -47,9 +54,11 @@ def permutation( n_perm: int = 100, progress_callback: Callable = dummy_callback ) -> PermutationResults: - r2, q2 = _validate(data, learner) - r2_scores = [r2] - q2_scores = [q2] + scorer = AUC if data.domain.has_discrete_class else R2 + + score_tr, score_cv = _validate(data, learner, scorer) + scores_tr = [score_tr] + scores_cv = [score_cv] correlations = [100.0] progress_callback(0, "Calculating...") np.random.seed(0) @@ -58,21 +67,19 @@ def permutation( for i in range(n_perm): progress_callback(i / n_perm) np.random.shuffle(data_perm.Y) - r2, q2 = _validate(data_perm, learner) + score_tr, score_cv = _validate(data_perm, learner, scorer) correlations.append(_correlation(data.Y, data_perm.Y)) - r2_scores.append(r2) - q2_scores.append(q2) + scores_tr.append(score_tr) + scores_cv.append(score_cv) correlations = np.abs(correlations) - r2_res = linregress([correlations[0], np.mean(correlations[1:])], - [r2_scores[0], np.mean(r2_scores[1:])]) - q2_res = linregress([correlations[0], np.mean(correlations[1:])], - [q2_scores[0], np.mean(q2_scores[1:])]) + res_tr = linregress([correlations[0], np.mean(correlations[1:])], + [scores_tr[0], np.mean(scores_tr[1:])]) + res_cv = linregress([correlations[0], np.mean(correlations[1:])], + [scores_cv[0], np.mean(scores_cv[1:])]) - return (correlations, - r2_scores, r2_res.intercept, r2_res.slope, - q2_scores, q2_res.intercept, q2_res.slope, - data.domain.class_var.name) + return (correlations, scores_tr, res_tr.intercept, res_tr.slope, + scores_cv, res_cv.intercept, res_cv.slope, scorer.name) def run( @@ -158,7 +165,6 @@ def __init__(self): self.showGrid(True, True) text = "Correlation between original Y and permuted Y (%)" self.setLabel(axis="bottom", text=text) - self.setLabel(axis="left", text="R2, Q2") def _create_legend(self) -> LegendItem: legend = LegendItem() @@ -170,49 +176,46 @@ def _create_legend(self) -> LegendItem: def set_data( self, corr: np.ndarray, - r2_scores: List, - r2_intercept: float, - r2_slope: float, - q2_scores: List, - q2_intercept: float, - q2_slope: float, - name: str + scores_tr: List, + intercept_tr: float, + slope_tr: float, + scores_cv: List, + intercept_cv: float, + slope_cv: float, + score_name: str ): self.clear() - title = f"{name} Intercepts: " \ - f"R2=(0.0, {round(r2_intercept, 4)}), " \ - f"Q2=(0.0, {round(q2_intercept, 4)})" - self.setTitle(title) + self.setLabel(axis="left", text=score_name) x = np.array([0, 100]) pen = pg.mkPen("#000", width=2, style=Qt.DashLine) - r2_line = pg.PlotCurveItem(x, r2_intercept + r2_slope * x, pen=pen) - q2_line = pg.PlotCurveItem(x, q2_intercept + q2_slope * x, pen=pen) + line_tr = pg.PlotCurveItem(x, intercept_tr + slope_tr * x, pen=pen) + line_cv = pg.PlotCurveItem(x, intercept_cv + slope_cv * x, pen=pen) point_pen = pg.mkPen("#333") - r2_kwargs = {"pen": point_pen, "symbol": "o", "brush": "#6fa255"} - q2_kwargs = {"pen": point_pen, "symbol": "s", "brush": "#3a78b6"} + kwargs_tr = {"pen": point_pen, "symbol": "o", "brush": "#6fa255"} + kwargs_cv = {"pen": point_pen, "symbol": "s", "brush": "#3a78b6"} kwargs = {"size": 12} - kwargs.update(r2_kwargs) - r2_points = pg.ScatterPlotItem(corr, r2_scores, **kwargs) - kwargs.update(q2_kwargs) - q2_points = pg.ScatterPlotItem(corr, q2_scores, **kwargs) + kwargs.update(kwargs_tr) + points_tr = pg.ScatterPlotItem(corr, scores_tr, **kwargs) + kwargs.update(kwargs_cv) + points_cv = pg.ScatterPlotItem(corr, scores_cv, **kwargs) - self.addItem(r2_line) - self.addItem(q2_line) - self.addItem(r2_points) - self.addItem(q2_points) + self.addItem(line_tr) + self.addItem(line_cv) + self.addItem(points_tr) + self.addItem(points_cv) self.legend.clear() - self.legend.addItem(pg.ScatterPlotItem(**r2_kwargs), "R2") - self.legend.addItem(pg.ScatterPlotItem(**q2_kwargs), "Q2") + self.legend.addItem(pg.ScatterPlotItem(**kwargs_tr), "Train") + self.legend.addItem(pg.ScatterPlotItem(**kwargs_cv), "CV") self.legend.show() class OWPermutationPlot(OWWidget, ConcurrentWidgetMixin): name = "Permutation Plot" - description = "Permutation analysis plotting R2 and Q2" + description = "Permutation analysis plotting" icon = "icons/PermutationPlot.svg" priority = 1100 @@ -322,11 +325,7 @@ def set_visual_settings(self, key: KeyType, value: ValueType): if __name__ == "__main__": - from Orange.regression import LinearRegressionLearner - - housing = Table("housing") - pls = LinearRegressionLearner() - # permutation(housing, pls) + from Orange.classification import LogisticRegressionLearner WidgetPreview(OWPermutationPlot).run( - set_data=housing, set_learner=pls) + set_data=Table("iris"), set_learner=LogisticRegressionLearner()) From 67899c57a2c0ab4d5fe72f787c3ce5f7f6e6874c Mon Sep 17 00:00:00 2001 From: Vesna Tanko Date: Fri, 29 Mar 2024 11:47:00 +0100 Subject: [PATCH 4/9] Permutation Plot: Tooltip --- Orange/widgets/evaluate/owpermutationplot.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Orange/widgets/evaluate/owpermutationplot.py b/Orange/widgets/evaluate/owpermutationplot.py index b0e45268690..7e8b9b179f6 100644 --- a/Orange/widgets/evaluate/owpermutationplot.py +++ b/Orange/widgets/evaluate/owpermutationplot.py @@ -196,7 +196,8 @@ def set_data( kwargs_tr = {"pen": point_pen, "symbol": "o", "brush": "#6fa255"} kwargs_cv = {"pen": point_pen, "symbol": "s", "brush": "#3a78b6"} - kwargs = {"size": 12} + kwargs = {"size": 12, "hoverable": True, + "tip": 'x: {x:.3g}\ny: {y:.3g}'.format} kwargs.update(kwargs_tr) points_tr = pg.ScatterPlotItem(corr, scores_tr, **kwargs) kwargs.update(kwargs_cv) From 647eb31835ffdd0ed67ab8b9730205d49ecaa7bb Mon Sep 17 00:00:00 2001 From: Vesna Tanko Date: Fri, 29 Mar 2024 11:56:35 +0100 Subject: [PATCH 5/9] Permutation Plot: Emphasized lines --- Orange/widgets/evaluate/owpermutationplot.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Orange/widgets/evaluate/owpermutationplot.py b/Orange/widgets/evaluate/owpermutationplot.py index 7e8b9b179f6..9f35101cf79 100644 --- a/Orange/widgets/evaluate/owpermutationplot.py +++ b/Orange/widgets/evaluate/owpermutationplot.py @@ -187,6 +187,9 @@ def set_data( self.clear() self.setLabel(axis="left", text=score_name) + y = 0.5 if score_name == "AUC" else 0 + line = pg.InfiniteLine(pos=(0, y), angle=0, pen=pg.mkPen("#000")) + x = np.array([0, 100]) pen = pg.mkPen("#000", width=2, style=Qt.DashLine) line_tr = pg.PlotCurveItem(x, intercept_tr + slope_tr * x, pen=pen) @@ -203,10 +206,11 @@ def set_data( kwargs.update(kwargs_cv) points_cv = pg.ScatterPlotItem(corr, scores_cv, **kwargs) - self.addItem(line_tr) - self.addItem(line_cv) self.addItem(points_tr) self.addItem(points_cv) + self.addItem(line) + self.addItem(line_tr) + self.addItem(line_cv) self.legend.clear() self.legend.addItem(pg.ScatterPlotItem(**kwargs_tr), "Train") From 3312ffab1a0c44b818678b603ba0e4dd39bb143c Mon Sep 17 00:00:00 2001 From: Vesna Tanko Date: Tue, 2 Apr 2024 09:09:23 +0200 Subject: [PATCH 6/9] Permutation Plot: Add score info --- Orange/widgets/evaluate/owpermutationplot.py | 59 +++++++++++++++++-- .../evaluate/tests/test_owpermutationplot.py | 25 +++++++- 2 files changed, 79 insertions(+), 5 deletions(-) diff --git a/Orange/widgets/evaluate/owpermutationplot.py b/Orange/widgets/evaluate/owpermutationplot.py index 9f35101cf79..4aede1591d8 100644 --- a/Orange/widgets/evaluate/owpermutationplot.py +++ b/Orange/widgets/evaluate/owpermutationplot.py @@ -1,8 +1,9 @@ -from typing import Optional, Tuple, Callable, List, Dict +from typing import Optional, Tuple, Callable, List, Dict, Union import numpy as np from scipy.stats import spearmanr, linregress from AnyQt.QtCore import Qt +from AnyQt.QtWidgets import QLabel import pyqtgraph as pg from orangewidget.utils.visual_settings_dlg import VisualSettingsDialog, \ @@ -26,10 +27,20 @@ from Orange.widgets.widget import OWWidget, Input, Msg N_FOLD = 7 +# corr, scores_tr, intercept_tr, slope_tr, +# scores_cv, intercept_cv, slope_cv, score_name PermutationResults = \ Tuple[np.ndarray, List, float, float, List, float, float, str] +def _f_lin( + intercept: float, + slope: float, + x: Union[float, np.ndarray] +) -> Union[float, np.ndarray]: + return intercept + slope * x + + def _correlation(y: np.ndarray, y_pred: np.ndarray) -> float: return spearmanr(y, y_pred)[0] * 100 @@ -192,8 +203,10 @@ def set_data( x = np.array([0, 100]) pen = pg.mkPen("#000", width=2, style=Qt.DashLine) - line_tr = pg.PlotCurveItem(x, intercept_tr + slope_tr * x, pen=pen) - line_cv = pg.PlotCurveItem(x, intercept_cv + slope_cv * x, pen=pen) + y_tr = _f_lin(intercept_tr, slope_tr, x) + y_cv = _f_lin(intercept_cv, slope_cv, x) + line_tr = pg.PlotCurveItem(x, y_tr, pen=pen) + line_cv = pg.PlotCurveItem(x, y_cv, pen=pen) point_pen = pg.mkPen("#333") kwargs_tr = {"pen": point_pen, "symbol": "o", "brush": "#6fa255"} @@ -230,7 +243,7 @@ class OWPermutationPlot(OWWidget, ConcurrentWidgetMixin): class Inputs: data = Input("Data", Table) - learner = Input("Lerner", Learner) + learner = Input("Learner", Learner) class Error(OWWidget.Error): domain_transform_err = Msg("{}") @@ -243,6 +256,7 @@ def __init__(self): ConcurrentWidgetMixin.__init__(self) self._data: Optional[Table] = None self._learner: Optional[Learner] = None + self._info: QLabel = None self.graph: PermutationPlot = None self.setup_gui() VisualSettingsDialog( @@ -264,6 +278,38 @@ def _add_controls(self): minv=1, maxv=1000, callback=self._run) gui.rubber(self.controlArea) + box = gui.vBox(self.controlArea, "Info") + self._info = gui.label(box, self, "", textFormat=Qt.RichText, + minimumWidth=180) + self.__set_info(None) + + def __set_info(self, result: PermutationResults): + html = "No data available." + if result is not None: + intercept_tr, slope_tr, _, intercept_cv, slope_cv = result[2: -1] + y_tr = _f_lin(intercept_tr, slope_tr, 100) + y_cv = _f_lin(intercept_cv, slope_cv, 100) + html = f""" + + + + + + + + + + + + + + + + +
Corr = 0Corr = 100
Train{intercept_tr:.4f}{y_tr:.4f}
CV{intercept_cv:.4f}{y_cv:.4f}
+ """ + self._info.setText(html) + @Inputs.data @check_multiple_targets_input def set_data(self, data: Table): @@ -296,6 +342,7 @@ def clear(self): self.cancel() self.graph.clear() self.graph.setTitle() + self.__set_info(None) def _run(self): if self._data is None or self._learner is None: @@ -304,6 +351,7 @@ def _run(self): def on_done(self, result: PermutationResults): self.graph.set_data(*result) + self.__set_info(result) def on_exception(self, ex: Exception): if isinstance(ex, DomainTransformationError): @@ -321,6 +369,9 @@ def onDeleteWidget(self): def send_report(self): if self._data is None or self._learner is None: return + self.report_items("Settings", [("Permutations", self.n_permutations)]) + self.report_raw("Info", self._info.text()) + self.report_name("Plot") self.report_plot() def set_visual_settings(self, key: KeyType, value: ValueType): diff --git a/Orange/widgets/evaluate/tests/test_owpermutationplot.py b/Orange/widgets/evaluate/tests/test_owpermutationplot.py index 350435990da..66600591cd8 100644 --- a/Orange/widgets/evaluate/tests/test_owpermutationplot.py +++ b/Orange/widgets/evaluate/tests/test_owpermutationplot.py @@ -1,4 +1,4 @@ -# pylint: disable=missing-docstring +# pylint: disable=missing-docstring,protected-access import unittest from Orange.classification import RandomForestLearner, \ @@ -87,6 +87,29 @@ def test_sample_data(self): self.assertFalse(self.widget.Error.not_enough_data.is_shown()) self.wait_until_finished() + def test_info(self): + self.send_signal(self.widget.Inputs.learner, self.rf_cls) + self.send_signal(self.widget.Inputs.data, self.heart) + self.wait_until_finished() + self.assertIn("0.5021", self.widget._info.text()) + self.assertIn('CV', + self.widget._info.text()) + self.assertIn('Train', + self.widget._info.text()) + + text = """Train + 0.9980 + 0.9996""" + self.assertIn(text, self.widget._info.text()) + + text = """CV + 0.5021 + 0.8948""" + self.assertIn(text, self.widget._info.text()) + + self.send_signal(self.widget.Inputs.learner, None) + self.assertEqual(self.widget._info.text(), "No data available.") + def test_send_report(self): self.widget.send_report() self.send_signal(self.widget.Inputs.data, self.heart[:10]) From 544ee8c69eb9b0a9ff9d63c1db9d5adb99c26344 Mon Sep 17 00:00:00 2001 From: Vesna Tanko Date: Fri, 5 Apr 2024 13:54:25 +0200 Subject: [PATCH 7/9] Permutation Plot: Default #permutation = 20 --- Orange/widgets/evaluate/owpermutationplot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Orange/widgets/evaluate/owpermutationplot.py b/Orange/widgets/evaluate/owpermutationplot.py index 4aede1591d8..882d7fe52b1 100644 --- a/Orange/widgets/evaluate/owpermutationplot.py +++ b/Orange/widgets/evaluate/owpermutationplot.py @@ -237,7 +237,7 @@ class OWPermutationPlot(OWWidget, ConcurrentWidgetMixin): icon = "icons/PermutationPlot.svg" priority = 1100 - n_permutations = Setting(100) + n_permutations = Setting(20) visual_settings = Setting({}, schema_only=True) graph_name = "graph.plotItem" From 261a7b75228cf097225cddcdb5cd1f8a12c63764 Mon Sep 17 00:00:00 2001 From: Vesna Tanko Date: Fri, 12 Apr 2024 13:12:09 +0200 Subject: [PATCH 8/9] Permutation Plot: Docs --- .../evaluate/icons/PermutationPlot.svg | 12 ++++----- doc/visual-programming/source/index.rst | 1 + .../images/Permutation-Plot-example.png | Bin 0 -> 46768 bytes .../images/Permutation-Plot-stamped.png | Bin 0 -> 21321 bytes .../widgets/evaluate/permutationplot.md | 25 ++++++++++++++++++ doc/widgets.json | 7 +++++ 6 files changed, 39 insertions(+), 6 deletions(-) create mode 100644 doc/visual-programming/source/widgets/evaluate/images/Permutation-Plot-example.png create mode 100644 doc/visual-programming/source/widgets/evaluate/images/Permutation-Plot-stamped.png create mode 100644 doc/visual-programming/source/widgets/evaluate/permutationplot.md diff --git a/Orange/widgets/evaluate/icons/PermutationPlot.svg b/Orange/widgets/evaluate/icons/PermutationPlot.svg index 4a7720cdb75..29b2df50d5c 100644 --- a/Orange/widgets/evaluate/icons/PermutationPlot.svg +++ b/Orange/widgets/evaluate/icons/PermutationPlot.svg @@ -29,12 +29,12 @@ fill="#333333" width="0.999" height="2.864"/> - - - - + + + + - - + + diff --git a/doc/visual-programming/source/index.rst b/doc/visual-programming/source/index.rst index e90d19c1bc8..40b08a5d2bc 100644 --- a/doc/visual-programming/source/index.rst +++ b/doc/visual-programming/source/index.rst @@ -132,6 +132,7 @@ Evaluate widgets/evaluate/predictions widgets/evaluate/rocanalysis widgets/evaluate/testandscore + widgets/evaluate/permutationplot .. toctree:: diff --git a/doc/visual-programming/source/widgets/evaluate/images/Permutation-Plot-example.png b/doc/visual-programming/source/widgets/evaluate/images/Permutation-Plot-example.png new file mode 100644 index 0000000000000000000000000000000000000000..b00b20623c9767120b9e0ce834ae488ce235fa29 GIT binary patch literal 46768 zcma&NWn3H0_dXn4i%Th9+-b4m1xj&uO>uWC5-3otv{<1OcW7~Uf$~~J_7(q)X=8`0KiWW066*p07(1<0H{5)+jXQ+69cu?^%b9&8~sT+3d%aq!j&?cux%2b$ zU!tQzLqngo`@etxuA`-)@cw;PR@UzReq&?f*w~n!o}RnAdr?sl4-b#Dv~+k#prMJL zudi=b`j6GsRSpi0)vK%eyts_7A?dMSx5n$6`UZE8PDXcjz_YWqmL@_%LUQkAn>$)! zK;e~@mEqyxznWU>s>+1LB>Nlkl9Eye1_tb`%yslN%L;Rz4kwqE=7Arqnrmv)Qofm) znPn6ggCaji1bS3=^o2!$zJC2`YisN6<-RyEHoCSpcYNGBFx=VG(LOkKb9vC!)y4N( z;N%=C^-kQ>!b)9TO+i5+_@m#SzV^FoxU{^|T<4&egov7o(qL0tTHH5K=qG4N_O+Gq zrkpr*DsHz@7?MMj_%~UoMS-M({pxi0SXcGQ(r~w2;H>;PvW5Mzi~nGEm85}_Wl*vx zJ9B4kk#pp2bOdN(bYMeTL|sosUPJ13|KfUK7dg6)99%;#?<023$;rua@Ntln8-#>} z$hjR_TH31YBILir{qe=VnigA&52~7aQE`zbK`J(1HO2LL)lJ`d6dKs3Xz2LMxMb?; z8f#g^D7|2#RCkd&pWoo-<&gN;OfO|7=JAVKQ2E&l4jz*j93l!K>tsROpW^-vv~LXF z8%P`Yz1Osr=appr5Unn&A|a^2ZW*Db<@ip~TwLOvsAH;*$UD6koIH$gty$H)ge4V> z?6igLGhAf;O`)O_&00LuI7Q53a+p|qKZUtOH{ee-;#&rU1ZvaR*MKdI?_kH5~|rOwS@_fCCs zOfJu;ul2)8dQB@bxvjW%DKAdMN|H${p!!nfq_3MzLBXqcr1Mk5-N#?zUw%EqFPkKv z^7_Rwsg6sse!PI0cOtYR-%MRDM{=kzW{@1~;%K=J}GMFShLc z{x%!ELT`J7p}^3K%oQs!aKCKDpOc@@-CnTYbilgN7%=1QIR#3ak9Z3h;Z&@zRO*MG z%~38jGZ3E3g}0@YidF6!V5}ua+`P|R4}YXL(5cX@^Rl|Zrgm@tmw$GO@2<|j#&JNC zkq#Sw|IkSbpMP_Ht<&M*&#HvD$)i>8-T}@pe=T)sLIDIo<(-`V$CcBEZ#J~UX&hf} zCSu%JZwAPonaWi}QQ>-W)}yI@Qb7+CTT~^KE38slB6{Z{HHqU>!XrqfIKpZ-aWd&n z)d0+V#c@@?{q6&8xX8%TBK*rjzk?j97gjl&_W}#>XTe zvn|mgnO+Zol*#xpGBsY|`9JSj{9OL$AX(u)eyf3^iMT*kJ&)es43A7$*Hmut*JNqvR8V`A-w7rnoobi`K!)b8lp~O85k2Y_C^e5C>Eae zL6qmi8b>eV%UU@!*A4hb;v*k>Zl2xO8HaB(3XVePJVK+x;d1YZX#iB0MoCO~E|br@ zaND~d+924Np-ivttslgl^2g%sUvt>+_tgBDRExU(Uhb=!@+JHOF@(5P&*jfL4&x4B zoY{E8C@G%>E(d#AMH7+E5&@Y~xL=PsH5x{bgQRemlrnz*8+E^x;(Px}h1)^XupaFw z#kyRm(KJKxoO(Lu?av;gr5a3nPo$LG`Mxat2??PAU4%W!z5yuQhcfJk!lny`K_>ja z0C6R$rP-R)Y<3K6yrmd_ci%L8WA>DRl?dn^gocNJp;*-fg!P0S*Cb#`Uk;dVgv>5IdAajB~9|@R-l{ks?0g0mzAOGN8-BjS6Pgx^m5W5B((98{v{8JKI7)RpC%|dr#?YE z^qos-ZSV}#*qyi=3<)jlr{Zax4v89HpGlF zk9|dn4D}>C8bM%U8N?rP9BAlf@?cHqsS8jNmj7Ie%(wQ?2LSdQ$1PmMgqWvoQCCc9 z--i;>i-#Ue6G;E~At5d9g^1!HUOv!K{z&ehGLk0B&&TkCeAx91P+2?rI~wQ{;wn$krkgmq7g!)oAn^-E zNd!ir3K66%8TR1D4XQh($v^QW*;BE*+}y@&6`+&(kp#MQfptNd4sX#BuTe6x9x{62 zy;d!SOmYeZT7}gDbRy`251&~=&Xrb#D7xVwa<+R9U!D&n34iK3GIT^T7-|2#YKA^ zBz+xnPMa$e5qTBpx&_@HZez|@Vw7D|{+ux8^)s{auy!UA_|bO0P&{lwC78jV2cJxs z;`MtFnrinJGw9je)edt&5Dc-MD3y<;mv#z!lk-(MMAUUQ;9`s@2WS;U<*R)-r;N1z zcgu4&OU*L{&)6i0aRz?eL~MH!9)y2phtp%lM+z9^hQl7%^Pk8iOe~N&kh)gQ|Mol$ zb1IYyhJj=+D2t_q5TnmKN!1(ubaSsD=9y-hcYAdCYSpFt|DSnvYM%X)lqqtw_Loy);)88GpnVmmk&%(0+T|SV{vn~F&BR*P|qMJrPt1;>aW@90*!O4^b&$D0*T}e%T#Ow zL!ntfBkZ! zFmz*=BX8#p%xaxeYmO&T3lp!tI>rV@TdrN7+Vtg1vXK+hHETW(A<=w(ue)~)d_sCy zOxos#K?#5|@Le3fJ|l!WzC@3&EO~dHPo#&y+_i$v>kC0KECgws`r;Gc^A!FkI{;)n zUCaJ;1_$F)5WCMvp%lSClW942Swy|DpMC%l1uq9?&X6wrA|}xa;Vek%GS!WR1QSaW zW1YYJe6GHCoKXW>5Z)u$Pt{5jYM#R=eR~ccwIBhE zN#MNtXs2PA@VoQ9L@Y{vlX&RpTaFwM-qMvRBsv3 zTLbuHnEUr+_ulcvf-mVIcLy)IdKWkegL}OQk^_ zQU;@6H3fW@N&I=_aIzCZUyJ}N-wFZ363mz=df@n@_)V+5gh*!;N%2<}6(AYyPp{@z z=HQm%q(V@#Di{6(euAJF1>T4EAN{lemhDn-f=Dv(cYf^J)Qix3g8O5df(;d6P96wRiaOn#BtIeLjfg!ES>tZ_f1-aBG+8Y5&_ zD-9zsb=sd~us^8ccEU*qO;n}De{s?Z&;_k+a}t(ba}Mq~;D$18DRWLnV458M;b1Z* zjaEsgJ`0PWmzDaJaLyt#VsiuHn7 z?HA-u7t=}XkgML*9!e+Qk4JXfy_`9uWc`zzV19e56^btp*UP&Tjp4meQBAVbMsZmRNePCLEoIcumt0L$rYLKhA9|s}1@8;j$dEoJiti4_ zGccIPnxI;z0?+&dvHxo@Y6NLsp}akfJ9nWFnNg3T#`zEICI~frG79mM%M{b+QmZx? zugps>mDY$wi=4^iBHrhH8C0#)6`&qFjOrB%gH1b`nU?5Q+EbigT@F>)HZi3wj@|HT zXrC_Aa2e>(Ym%`Ze9$hb4j~9XT-(^Jc%#@_{&%-8Waw^S{A*p?PBt4*Hx(8nVW4yG zjXDFG^~d`>N>Wi+>Q#V*0o*jmHQR-FYYj0Ks~pJk*V3G?{#uu|8s}B*6<=1evQ0V0 z4_*=I1~s?mE5|EBEmT!@qmJa|W-ZORCv5!MunfK3 zLU?}b8I}A!@x9p@+W06Nc+jX24gl+w{1wqpH65Wy{k`>DzDfU&5!tsw%y}z6D-(nA z!?-9X6^lP?Z+CS|4qeYW(d)IOMB|tKR)^QzqVVnS3A;pdl3vWepZ}ScQV!UNyGc;& zQShsnqDW|2U?gD-ojUz$Y~LrUFnboDZRO;1_4aB)MfWg>rbi4f<+YEX12+!wc<(4> z`G+u3;>jdsESIzp2|Zo_BE?2NkjfYO%iCm*0uxf9+m8E7myzPwCPBxZC+(JFuUIe5 z&p6c(tuTxT_E`)4B#mG0Fc_mRKt)y4s5eV7g`YQkbi@f@JI%7dtx|rr$bEGc@267D zc}RZn^An1#DHWf`7kfOEgx81m&NyID8+S+hlQ5Jqg*dPtmQ=ldE0kIdXgEBL7H;}P zsGIM7eZ_PR6Ohc;DdMgKw>5`@PfiKF$On@OW;2#afUb0ob(ln&F`3Med^7u6}v zKlt$n`WVh|i`BxS3a`d@ZSD9oFrXIW#j|XR|5`c2HE4Q_e)E@TR$*93j%>tV5dSAb zrDSnab=itO|d|Jwl*SD$-{0l5@Y7Rxc^PU>2~Cb2}KLaa%kpS5MrzfXbAV{IN7` zdsK>W#6eNXKllS%$U+X4XA1e-F^yJ6|24akuWFM~PmAyKG$kiQQBRE8bO&fN*OaWB z)v(rBZ?^x^Oy_@n&A-ACwy{ip|0Px#h#>~~EH#m$6yr*y zS#tk+1GpoI!KU<9`VS%GGe)srEW0S&0l}6+dvvC^($=_{Z&&rqX`8 zqgG1z`zx;zo>d!4gkiC|gZ+`QLoU?BR2#L%BE_jP4?EG?{b*wb#s8F&&#Je1;=6s> z)j=T_brpqo6o<~QXC~@msLA%Jyi>B_yhbmz6aE7~{fLBX6zvMPGJ21a#QaMfIPsl} zK!8p{`efyQ%avy4t8k*T)BWFMOJ9RgWhdK@RN>wz$uztBJo|gem%Qk^sO5_3YH4gP z)&5Uz9UCd9Qc^xbhhBh~o7V{Cw8S6ND-kON+tuO;bmHFqmkuE&j|ufpMWxbL$wg~k zku3Kp5#%pkyXy(??Oft5VgWFMyG) zgsib?Pu6CspwPT@bBmfRm+fHfnsQbriAmr^Gyb=cK_{D~Z!F5wv2(S_@dsH6nzUlk z|3p!N@aErhQD0{9;aGbAIb1T~OJUaod5Ikg7DE^c+b@eq4<1Hia($IWHiCEIp-;ev z3dmCzPe@hG{jn@U>}ikqu}S8&hWbcQcV0lkhVaUX%O&;t~kFdMM$LG_Gpu1+0HE?5H zLqh}9oagPN1F+3{efeMDO8jnenSZX0*avvpN^OmArxWJJu@o?To+N^Lmhq7}G$FxU z+GH`w73uMGpoRM|(@hDz zE+Us~Nvu9~ZA{>|%Uw2?^u9Zs*ypd7xrXs%O@-K;);YX(QJT}LVd%Q_-5Z^wmHcpw z6w7TxQ$bXvGrEyIXy>4*yvJkB#SQ zdyku?@KNibg+4qK8Q(D6Mn1jUyvuw1gpgslySPIGcrr%tm& z&N3YQ0|NX3Yq|fHm&}&}_iXJse0-g@my4>teJg6q$vIzh&H2b4;Jq*D>2QlYnh?k5699K42rdq)%MvU0#R={fkhaMLvW*N*9G; z!4p4qhh62-JkWl2U`aEhYH|cA=zg(t?o_cn2e`xBy|9fb1@+_HU z3jOmsrnDKLuSiXDilMFE2Yt_#zHPP@L`(MuJ)mcXBLUob#fy;k+Fj0pba@3kMAoXa z{LnhxNswx2APDXD!Q`A$8u%8Q68+8c&}$#{pdI=&iV4J>FJeb9D8|Zo4`M=!(7V=s zt#PL3I$8oqKn~O&uJAvd^C&I&gS{v|A#p!@5cD7p;+F3c8$WUW3#Z~)g8%#1<_V*f zTufvDSv#CA=dk@b#vdl@xoKOKoT9_Z&ZPoD{x~fi_%U4XraEANyThVz4J-qr@t_wd z9|)b^t||?UCdn>k-C2NX@y1WJY*l~#ssQ4mui4L%YTj$`0cODpG!=dyqNYWsnYfVtkIFbaMLW76Fk(l4rBGcQattVXy9w z2IMXW?xpdx#KAx}2iFd@u&&DvNy#ZW#Cm7z)k)e!P7Ylv*mM7ece*ey$a+6x*c2Bk zC^kiwaEIRfv>WGuHb!@XMerpd`WZyY;s;gnwWK%gHXH>Rq4TpHNHhZNqH{%uxd1I zrN&a^ujJHXVvH$AI-4}ZBS}n~Vb6QN&iJxygjVUo|5_*;!Q9^N`-O#L~~ zDgSqu#r_UWp}It6f-ED}*?)mjfrQ{oY;^p3u}iFR^%wi(9x_k?*ZN~GZWC|X&sO*m za$V9|_BPfo@;$okOEYa>VRJ-#&GDGQ1%Ay^)Jx_tjAS;dG8M*V+a}+XL~l{-ZQMy# zXa_OsHU@u-#qx6^E{E`!>c!tA{)i@kD8!^ZLQnUf z@$Yy5yb0q^XT3v!>G$RZIhOq7icO9Wj!wT0Ks^QlqC=p&V8G}5Yc}e;6tyw>SK4D` z89#B4pTINPo5#)pd&M4k2+FO+M0wHaG!{{_0ZN;l5s{XBWyhZfe%H$Y{ry9U+m=!< z;Lx6*kFJ*f^X;c3ibk#)Nq5r~-veq!co@$!a`3Q};jSUYuGuIhR!B7`1i8>s-4oV| zC&LHD!2Ra9;)m%|=K&BGIq zd@Y2myJqI~Yy$i`(|}m%QpatxFR#@RGC_a3KI&DO?c`}W(cmC702@{<7mnY@J^rkL z^PkU>%p8jvO%nl8#7TUHWb-bfV^g&URV@YxOToAqqIu9zSJ1WJ{xaF!$?HNM!NE@t z=V9l;HS4!RV+A;5y&v0y&A*9-z~O9Mj_OA>AA1Hl^{<#+9M{A?&c9?1}V}_R6D@kd`_4S#5um zLX6Zawp`ENCLX%EXRIX(ea>szp ztDU_`jUkxUqXGc#FV*bL5=&>oq})sH*K@%IDfA*&d{AQYCaWT^@y(7j!ZZtINO&D1 zQwSq_ulaY#-6Py94<1}o5QflTx|jAY(j)2SlFdzIl%UG@r!y!`d%r7QKQ}D`4SVs; zeE3`6zjw;pJRq!)e@-8uX$LRgg}rALc*oBYYfbpUdO#Nt!g#&;aYZ2J^!ItTUTPnU zDyTzfRr?0kC3Rn8l$Lg0eJ&^7SuH?8P$m={Jk~$7|JWBGPFGW#cyOV-On^EeLfl;u zF~2o>W-GWpj|0j^^>TyhCWUlxs)=Wr%>{sAbP$ z4mLW(W|)E9OpIX-7#QeeamhTaD0kWZ zLi6!_?cwV5Xt^U65X}_|6l(9*zHh$>=>hur`>t}T7v|N!9DDmBl22-n;HKwszVSvC zhqatVmW1w-COKr|F651+d?3SfcD$QKFMX+iUPzyLaWb^bLRW4`!ldn z88JQn2IVPqZOoOuhNjTbqkQ#ns2__5@yxXxfimJHd;GXk< z7PBs?ABMeL`?O=#QU|=9QGgI?Gl7=0I7U-n40&KY%m71}Ty_LSqgBgSh(qRYw+Po? zu!j`U!zrPDWpaJ+;MVPUH$y;O*kk|NN<}uJE0}r=_>(w?iP76I87+vBNS7&aVgAxB zEoDeluz9@Sj)(FxY~Wl zeGQ;_2p7UM8HUHmVs70`r87CFKSTREC~)!s{-P-!`llD1jr%4mX`$M+mk&(<$A-`@ zM415p!?!y|0|dGR={w}miZ9#7vF1ZoHA8{Ww~w25M>dIF*p2&a`GUZYTj-c)6>SYQ zDeD3+q~yz_Ha-N0B9fjy)%^Ci<9Q*>8^o0o_|~P9p(>;9cXHy8Ci7F-%#>3agRMsd zT0zSibx1nYqh;N-J(TKbqH1i|!xA46a8z%0HnvB6YuZMr>6k))R&zMMyKF!GCBHje zAZ9edB}^JRA8pH6f0^&r*BD}aqb1zs9xT+;?|bt6J*{{8=L4*Vb;%l|MPQhpOIOh5 zS5|~zGWK9`N znl_uUn1Of7s+16=X||gF)!j_}DEuE>S_33{CKyxE5pd@2=Hc=0)~X3v)DciI&;40I zz4sq*{f2NOIMe2b1Yx7o(}ktaDM^TyP@BtOwLNyOJ(HG5(E<%{C7;w~*aHUr)->hw zXL*3MfswD`o@pbVgQ)AvhFWyUdF3PYEQb#lA}QA5Ug$1x@+|UEa6Z>-e^K}{_{FD1 zq2@H7(@`qea<9uDqX~Qi=kznIz=v!pLB!)AZjaas*rT(=qtR`yd-ry!-`G4f(X{## z6y$fjo8DsCF#&8i{i{arI z<6rjUkRinGx^V+ev7D~rVSruY9S*;c)_v;VPW zU-fvrx*n_MHJ8?zu12>2Lih6Xxl#Yc z3qGt@tbmvHvhuS=x06DH4(re$LwPCnrCt2xoVwh#`%QXmVD@D{<4cGGJBWjr2G+Ov zsm{i&iKk-7lZ2LWt2w(w;FXork4M3>d1r2cul?(y`&r+Y43-mCracFXV$Q3Q<_S&w z8}Y4`&rlAcpJ093%JO(XREepHz1m%CzMXh`F6%(j(%oUT(1m1gFEV05s$n;pe7>gkic)e<1*4L%7m^ zX-0Vt_ONyP{xlX0KYMt7|5PFws-Om3fIBa_%cLKsvJ@n7($*#QT@ozTm>9Ayzhn4!MjQl^Uqg9ZKmU4M4o->5(trL+vH4 zS;w+dX(PDCcThx+WR6niQQA2W92q{=58w480%tG!^a)yA%eVSJJ1LoY(*{!wN^ddy zHwYY>uF;TjSuypSXZd7r$9<_CSctb z3Yru7U=iyDiMST7S6W;C~dCb(((d33IIVJk4$5v1zP2%BijvF!@}gfiCMm z3-IKs5?L_f%dxdg+at5;DBsU7)Slc;j{Ei)-6lRQ=T?{UV-~l28T07FuqE<+h1Bq> zFJ+qN)0vs>Uyx8Mk z+PJPvAqunL{%GLrZ#@g=Uu$o*`6v|dL*Jmmtm|Wd1ABv9#~$2w+E3&7ILCSx@l1hS z8>>4L8@R9esYb+H`Gjw(&$p@jWZs^?q3jqbX^dPy#sH5y=|-1UoG%xj$KA?y_p3n` zV_}0Axx|42);kKFu=&{_!#U?5)8MWZuHE^6XCr5my4OlMqk056XFSelFa8(=9$71$ zyPxKs9IcvlSb#hvXR3a?Nam=%Hvs;&zxg7r5nNJ8ou@lr(x`&0RuKw%Kc(s&pfh{p zeIiCt<#kn&qWe1Qtw`7gqT@nw(7om&PpbUuyxROimq z>T2Lj)S1|I7yPRIq5i5p*}T6d`k=&PfE;_UwMO!pDpEeK6j#&U%D^(4ixHl98)9UwjOkQ&FJ2x>bJjm&q0XI@e=fE!0@_( zhlWX0%W&a^38=cDyU>{@=Q9%jlb6^-=!AO0i21dpk_)*>vTJonUD z;26)(2$(soWnUoQweV@F@P~kHJ2%E*$1E1@A<^A4MRKgUkU?~R5vRtT*iG7FyvPf~ z+&k*C78g6G_DC%LX`YgLY`wG;tH8$}v@L?f79%J~JXYva)XG`3k8@H+rJGGkbtk2zYEJ2vCBJ z1Ag4mz|weUsZo3plf)sz1&=RTn-*D)pM;xs^x0oefHy#d$*k;eJT0D^O;^$%v}u3) zTG&KHcJPaVcKnTs^6Pk{sr;ONT9()65)TE_2;BhZ#E0lYJj*~$c7oQ$^(2vs`)3d; z>$QNerE{&-UlZ-ZO32onqwL~eH}CaN^?=_9#iOenMuQ5ofab~E ze=qeC0YT@}+TR0a@M0d)xvp4^#oFv(pk({Z1)p!f&$v^-Ldr9F`QgQpH+Am7vFrrP3t5?M~j2oUL(~^t~2E zSue8E08!=&Q4h-l{L}H_?u5kXY_!>g!fw9kImE45iuAxVc-Fhp3T!&~1bVH2>X65C zc?_kjG)24)3#+jPiQj|fNc=g2u#?{gZMeCDOkTj7q*kgI1D|{Gj@rVhR06+MC8H}s z7nTLwlcN$Wi#DD$Dyx!2__>TcFq%_QYZTKsCWpLoL3bGBc)0qa$4_e9-JKf0 zILKmz0LhC_NoSQBrqbEZUb*yD7ieZpkJuv{KBe9u=scgdc9&a^{-`^}535Ogb!E{; z?@@y~Cp*4dv{p>)v&NaE3Dp;0PlgK4h<-ir{)1Ep+-FjDLiQIp6vlku93gCq?jtYK zz=nlBXY>xtP+E4y44NzrE1S(qa0qg(4b~=nFP?501+k614-WIJuFMoU)1talKZ}{O zt?X}1OuSUD8Vxe!map)(JWYRKqE?anD+-Obg}UgU=@tpeo!5mmXJL^oU+9ndH06iWzv&5^>HXf<<#`c+6T@Km%}M%!$9Y7A=w5a1C5PA z-CKTQ?-vS6LR)&?LryY_XcvlF#CbuO^)0Kq%#&WLZ&4L9P2E8^f?d(K#Z)AEu)A(R5s(gwO_Hu~ zYCX>qD(&1lSP7~7IBBIiph+3XT~U=XgXz`5JVsHeyQ@>>?CMV@tEXaTDn|S7wdNr0 zckQ@Xp^&JgoQ04kahdLnaNhEqp($zb7Q#;Iu((&}e zqLIqas8tIgPmR~pyi_6$TP_mc66r?y`F53i{;){QsuX%Y*y9STyRM-A8-GTV+-cv}H2^(q}^$#rUCV znl5_}ws(`nu{21{RWgIpnPAj=;?oVAn%^DSKW)Rao^7m&Y#vwtURASg@=rgQ({J+c z5ss`aId>%+yMOU{?;5x2%{rUir*tg>^Jo*47J>q)bK+phfTr8ElvIc9Rw^-sBhc-k z=KNSig$wT&{HiOCu(h%2olW}Pc0f;=xw~iZw|Xv*Wtf@&rnQDBk(sRf(*kQ`6u!Pg zi5qG6=BjmwhEY+4RD;)Ap}s%O@HfQ#!V?m#z*?f18?+QC{NSZLodn7(l^eJcH!Ed~ zl3W@he_0vkCPVDKl3GXh3GB!n#iBN68_DJSH~J^r2&>Ab3q6=`$OxIR^`9~7^-Do( z1VU%G+x0^;B+3g*u}-uKfcr*qiVwmgd_QX@m==djW|jqoWM=!uS5k?G_*ZO0R$xu*QMy{=Om_hl0#FL?-wl7;p1l9_v; zOxUlht3K3W$NXd>3nX1N#0@d^T^uM=)O9VYe|_t0^^QDll2I5__D>`cjm0We+SL~> z@uAX+$X90s26kK{m7_epYgF$;0?({c+QybdZy6*QtX14zwwidaLPM-WYzJi$8AiZiPOXQDwO1@#4`6Hc^O((a*;vV(Os;ad$-Mi7Tk18m)vUd+q5gGB1*6`0dcw3 z&pJ43*BJZy`!uU{W=WIdVw&|IB3Aa78h`ufm#D}V`BvOSS;4OSdP3A+kv(oK)?xv&qLh4%yhQlw{e>h;iBPx~3 zoKWRX{nXl^hQ;5^OdIirDS5KKuY2XmOir9fy7f@r!LTA>e#L)hepsm+JYSO#t$Txvs^;Cup4L>#; ztNYAC^fy5|xi)U?E3&&LR))JyPJ?!I0gVz=bpCu(sz+$zw>J*6_{OBYbGkqJTxc@;F_;pa-BboQ|#W4g7Zs-!OY{O3LMhNdV{kT+m{<=*?gI8exy82RbK|ES%+k% zF6iJUr*DP32rOGYzYVQCtn){V{xeapxH1D-9RGlHs7f&mkngl*BH+!+pa zqui!``FRp4-usi9-9&G!wWZFvSu)qYG0Wne-CULb zdWWoa$kl6SP5bJ#_Df-YaKhIh-ZeB|?=*I;2@N0q+D!dzrg_ds=U|#{x{HcH+HyCL z`RofMLES4cS^K5367*^i}ojL1a}jHA8Fxm??R>F?@{!rA3! ze_zFa6&a)evq+wql^NPe^H})RaVvF>%Ilxh7&80Kzztf|&!AOh^%>+uI6JCi83rW) z1|#^9?GO3p_;(sc*`0%oqL17zsw8>bW|ag2&bNC_z9kb3GgK2<3@#>w%m0UjU0?4C z<6tHym4wn|&h;)gKUE<|!AYSB;0pJQwM~LiaUzBcn5&;$vL>JXq=UYSX?wFYRX{iuJ|5PS~g-a^s8%9Bv^;ZoeW;E@e|2xJVyaT(J zJ7FI=)w)X=pNT`Agld#;VPf zt5{d%x22Sr*DDqc1ABJ&aZQKrI_3$t)GPx7i$77sVI?Ec!a^ZrX2wc(v-K19OF^T5 z0Z_KNsE~ukk;K{00sM_h@k0-S_1@VEVuR0lrsUdOJl}AB%SkIRSXS6N1Mad2SzO~m zqQ>8QQM#9SU(Hfz7tLMGR%t*lD;%SobyeN}Hh$r*vix4$;3zIVZC0(nP_nDh`r6&w znPG>wS(j;Q9J}1xp5L}(W96bS0Z+#!NZ=w3CL(}CD}leJvA3sfyvO|ASdw!*#w1YC ziwHu6v*MdpvfYYo^mUf`8?uH`F1253k4E-=&H$k1#LuGAWOB&;DPLCykc38O)TpXk z?xw6uRO4Z^JS(ix4ya%ImAZ*{IEoCa(=~cMNT%GmVgp&^eaB^O5|iF)&;-g6LO-06 zzI6~!{acFB=YEZkit1zG!$EqJG@m#1uTxrAI0bY>m2W^iQxk>UB`powCz@!&q5;K+ zcG@yY;8mvnp>o5v@Qsd$#e9Ln){{90w%_I|5#%Xmi?}uV?Yid%NK?;Vc03ZR1$!YJ zsX>?g8OYmzj+yBuJ*VcWKf2{fz}mbJ9xPJ!%+nl$Q9a z=!j7z!Q3WQ51r-Ov}bm%*~GhY^Pv-)0Z+h!%-ux-Z|pp1)peV#kKu?$S*o*z>G)^h z<(F)=>EAGFeMfBgU&I+bZ}e6ogFDgv2PP>| zRXBs8J#OSVhey<54`QH<>I5VGzye+AwznlUzW!Y)6`ZpR?{`f#X1TctA&UOk`DxC( zh0&TxM-6Gtrp~Su@pGOIqPG2SIHx#2u?|+&ZE_etkjCL8uJ%fHFU^^3Ztr>hD%G)t z6Z{A37v$a<169i3-5Vm`+r_>1~{gW2-ANYeW+blWI0Lm*9&{%M6r|yl~>d|0Z)dG;&-%FTPh=u`@MuCylDE z;R0+8_^ljvHT`<$-iVmaigDirw?77S-y1-n^z6$~neU@@tNKN%l5&%YKK%7mK47ZP zY_*b%2kCeHI9Yl{gboSuDryfGJDy*@MZGLYtU5gUqw*ggNgdf83+150oHJ&p-D+WX?_Zr4OI4 zuH0s{J#&3dN4`sAk_Kmxn;g~Oiq1}wycuoap1(01x#2snyoeAuez=*NX$m*$9i#Jd zbieLd$CXxjX*Uz@r1Wg%Av1ZS%H~^}pji%I3KkcS3Gt|{f{u*&jcX9e32oagcXF$M zLprrXCev>;V)dv>jXx{I#g}KW(ND{Z4)CL8^nX`vb^qxoe2EzoknGJmFf?ACo-3SP z*%Onx-&HS?1@Q0QgQ-C)Fvaxd>E6Bmp_w}yAa33fohJBzRoR0!Gp9RLl1S^7y&qfT zMre%OgTsi7wj0*lY_Nbu0?R{@QV%xi2$PG^R``jPk@H;Ch^cQl4wb-qCwHM%JoTe0 zmgl>MhI&fJsGLFjN-OTEE|&ZeWb2&n#9%qy1vdd{e?Y#43r**iY6uHxKFh3f!zIEb zui0Kz*g3}P9nout>1(?Wf5$hJi}0cNk;94}wy8mN>=S^JwoIMfHv9x15`mUySSD%I z$#|5i<~2?rQZ-0y4m-|rQ?-%rze?qAI*|19s~c7(`Z#`qwL*N~r43`ck1EC^R8`Eh zbPKIXbfJ8fGCC$Ws#kGeSw;5Z)5uQmf<*q}wz(A-$6;}MQ5RP}g{=KT6%T*x_jb{z zWVKt?(Ip1|l8DdWiR2;vbsm2@m`Mt%UZlpbu#Pj^2>-^KdU6Y-Oa~0CZ+WDt+OTq&;XKY09g-v}la25YGq#}r zQO8lOKJ#R<%ES(Sr})hwFBZ#fPU}O-Uo8rHx{EEYzilvpF6tE@E`<6>=nGEku3TP+ z%`_z`FTN;K#DNrWO|>SPp<<4{eSFWc%8TNn_58y+(Qe9_qctrKLRa!EzrBOs79kB^ zZV+8yDE7g3BaiEEi6Rb3+jR&*>f!^a)z%VNFL-DOL1|c!QQ}%R>`VWj z#|Ob8Wf(Tr?MMG!&_$RwrW$q^>FSF(CNC>B*V1mT_K|}#&fU9j7rI*3c|qG9Bm<$w zXIg!4<0L!EY#erv6+8-R9i{2y~o8B;o zE&Bq^cbi=oY5Q8Da6)#Y^18x^=kB_(nL;lVzuGlO3|X}s;T5UQzWm;5$94Kz^5Crs z-p@9-8bTSd?o94^nYCDUB6}fY{F0$b)+R~8DG z`>#rYpMMewSWSnlNp}Hq(jJDqlw1SPNYlZ;zgBx_wc8~ILKfeiyp`-eTVjO8u2s@Sei`p(F__n(fo)z{13c3>W@Srt*RAP^i<5v&MAcn^}a~U zL;ZcGuXgG%Xi_?Oe6vDOe5pe2jqokbx0~J?KKvj30~=7cO>E%qU@(009^v+7sm1bs zB8%?FD|0V?d4W>(sia_8n#AoRr$hNxuQgJ=T&3k>?zWdI8F+~&b!T@kNckK;-yF=u zzwE_Bw}Y`IS|CApVvLX(fM^)b zB5aapMvBtZBn#NRgM#+>0FbbDrRm#VoK*pET-Ed{W5oKobo-Ud$NT+Pq05sQggQUPbAlv!L^2ElbuxFVMrZJAF`V1E z(|!Mmee@pRRHGhuD@sZMB@VyWc++DrMiyLAvc#?w`fiRF>B}T^T5qVAk*qN0Fz8sPqs&$hi_}pC}oUJoL2;%IW>5 zDF~ka#~SPwzdM#RJktYz7W?+iS^V_v=FMI9uJav>`##3@AIa%bEQ-A{dq~i}gG51f zajRk=9rA_JLoK694bQ>vS2ec|iA{&91^5jGQgBKVQS1BJrVvV>=i{Ee-fMrxF7N%} z<2P9(IUeA!^T?Lx){vkJMfd$kx8|!!Ge)bp3dkKE0=X=EkNDTJ{`fn1xu~x1L*j{& zZeVpsu=7vOy;7b~N(>4+Z(!wAS9toSK||^4Ha_ZO#6>R%?2gg>Y0k_e6gm^reG9)eMIhX0SIs|<*$>$-%1bVzq7(k0y?AOa#H?SOQ5$Iu8!NOz}5 zcMsj&%^=+j-SAy}-tRBmxpVG4`|Mb2?LB(?8uNfLqe1yryr1~QgwDp3e(89gAo2&F zzxuT5^_@5idb^Y3;(c%Ma_PzL*M}vS6~Ru%S)2L$Rm{Q9a7o8|l=5xrLVs0q4O_R;-6m8Qkapg+rdGwv ziP&CF0U2{COazhRR*ATv?|g_PrSAA)W0)#O)cV9~ClLP4(PH0eSWETFLScoSu2&Yv zPH@doyTHuAnQ8c4ei0TY&l2NNKJ&+}{6VA`(l?Si!+n~AdXck*wScRbcC1lEi8#`$ zA|P3Q==Xm3en*wq^>EA%xtl4g+pRxFp~5@4UyCDEE)zKaBFkccum>sOAV#{2;sR|t zwlopYdLJmN<5QC&AY8QkT)AVy$0aiPTEd%VXZXk65Edv^=Qx0%mg@bW{&bH0dF`fb}WxPlO+GdmZ_?0dA^*QflT06c69LV-L=EXZcNd7h| z?G<<1!l9nzIn>5o%#wT+BCPoKw$iCi_pj;lnW(<2iUC6>hOu>6x3d@@T$B> z(kd0^7IE`alg$U4sBT~Fr3D$Q-Xj2KxAiSUuyfn$?Q;0|G~e-+&nK+OyGSFlRffi`}u(w|c+u@w2UhZP+=L2TqfFC64$({J>U^Dp{tljaO_zT}0kzmnp6DUS!T;Vy^qc zE@Fab@RXPk^+tW;>#)DOCnLggp*6ea?_;;m({)fsy;u~~x&107*BI(hIKg@_t_Epx zZjDUlFVd46Q25TPCT~=ZTZ4(-KnmzPD$n!rJE~@+WxkhNKPuIXH0lPuYT$Quu|X(t!cC?u1j< z{<)nLhy7@~frm!ZtQfi&e*j(epbYqMjStv?_N!r!<9B66e06%iE{EUQ=Ht$!W{sYq zEN-ldwJKHbx`?fXX`LDWfHIN9n+W#cmOKg^twTOa&+-)(^ho%#?@Fxo^{19k$F{R@ z@>54ssDm?)6Lu8T#g-vgm$I$9kf(N8s??iRP_Bec>%(P}s*VFMt&&#&&$`RX1lsxN%Ctc09=L*2xqg$P>+)mD($<5 zr)$yaRPUDI8=e?Ov}2eIYYLe}NkB_!*tV$F!@HG%s?o))Z(6bV4JMZ-d>~FSX7rZ_ z!6fN#WzoCmY^7>Dahw2~CgXR1H8S?ZX>Z6Y<8YistZsv7*W~acqxFfRn55Of=YgGq zvX$9pD21XTVE51LLT4gr{?)pKN_q&sL;1Ta%FqUz%D5%xs8)t?J>Ou!xgzL}seh`d-N!y;=nr3~0m+1=wsbK{_kttO`%TS2PM)Ew zTk(#B`6SuF>7;`Ajt)IR@@jKzz&#C{;US!MW+->xu##$?X4F05?hG?iwcJGfeSums zWcstC%p26`xfyOUL_6c(Rd1tIqvUeP$%94jDUi^ww?365y!-*y7}$IZl;q#uVsw=5 zi2?U2;S+Oz@_)C;Zryr4jzR@qCy%XRPe!p1h>K>AB<()}>i~1pDgi<%|)?^;)P2r0L;NOp^GC z?r4@aXWHgKZ7Yk#Z};N~FQ{Q!RMYO;!$wrP;i{&8+%>UgjMJMt^V!Y?D7`91Al(+RNZ;04>Hc4?Zzr7SE9U*@R& z%Wm(PMxc5V@%1tbB}1K>5-Z_7wo7%NixF;w;4Xp0oG#cN?!ql&jI5Y=YQJV~1jwx4 zdfqhMtIW*il}1r%l?+K~_$~Oal?V&9P{cd6tl@FpWp*TVMR!!W`(1i>%KhCDSL*y) zbsJz4x(Wl{M#}NsrIVz2-~mXPOTinM|Yj%{d!;YjPp#Q$qP$y+yw5W_S8rxy!G2RoOYc6kERLODOBA- z5ahJSMdTq#R^E*=#tw=fRya<~acB1qgc*4$FvgM;6W{?bGeFHTin8fvR;{28M48>i ztj`j6Y#G3l{XRWN`w=X8I-@H1H0927)%CXskp4u-r8)Pq)f8C`oPEJNu5Dhqt;%~xL z&8ikTDAs~KL#(l1LG+hq<4TA2Zi`7tJm8_}{c^Whf36Pf#%~5f_K;T;;{KrTlQdH- z?GI$g$h$?H)!S6<6RQ|}k0XMGlTY{u+Wd))iDlA3c6NXJ#wZl2nxbQWAVFr(k-%V6 zAZI_$b{aRo2-n9D$^ex6?woJr8jz|%!}5Huxg5B|X5*;Bodvu6}uc+wT4+XU*>PXn`7(VD$--ay7A4;jM^wPWGtP1@+5fEl`7tNUpNvS*{7DSK{VLj0;PSgjrW!^NZz5YLXnAb%E7a?IPGx#bZg|-1yibJ8Z}b<)b<2u^ zSw|7oDZZdLdWCmS1;6zUkblu<5S$@m^Zo&RmLI zVyWb$*M}{b6(?lC0l*9-im)_W26}-Tu-S_LUBpJXk}3nT%XJIjgJ$-baqM5KzAWRb z&@HH^?A-mJzxx{4jtE#xSlM}a)M<0^{#ksk=^=sv^ol~b+7J0~r#BN|3j=n|z2`h^ z+0`lZ&0m%=XDncb-ru%8H1l`Vbzi%Kx}|SN_)EQL71Tv*(53uiy8U&Lg&{D4d}g^X zCQc98o#0pb&mRiXYGKRg#<-RdUjM)_`o;HbiX(~xbTh?fUwN+oSMZ~+8ZITp^M7~R ze%?L@eIcfZ2USe9Ojql?(y$0s1=ASUve<#h08QZ{Ip{eY@MeqCm z=C18Cg;vd-wjTzA%7EgZ19XV(!F*LE7*_cAQ-|ZGiV(IvMe-pk7;*O$3kjkhQt4a4 z=iqYwzegDH|E9bUdNlaHOn@hPz5^g@o(0$|T`k4?pbQO1Nw>?wG?a$yYNFjm`=irK zA-@e*=Vxt4L4Q( zf?IGITAw$k)CLW!iB}~(3i^)X2}%2~z0n?s5hpyL@RfR-tn7UgM23;bfZc%t=d3=Z z_sAkEYdt;{pRF$#eH04-I{YtpgV+K5)mPave7`NCXEV6Kiq?s+*wPIHOz1_)hQ*%uyuV2zH4^ot=`BcOV-KFUy=sk4LvhK0M=e& z2Q_PnMvKvXer-e_J1&^PsE%3FNon81P_T!$dfMmm49kfa!vF1_qm(6nuy6_NxxyG#BiO?mcqp_~W(#dzo13 zZb(J0JJ4J@`U7Mqr;!demt|-kdKbP{TTH|A?Gr5+lmxX&am^wJe+2Lp8U=NcO6+fL zK#~1UzpVCz6BO7RIv+HEV0AUQs$mn#)s`KfP^UrYuq`C-W;0I((DAd5JWT5lX>b## zd0A}*HwfwVYw5zoYKG?YTlO!BjG?t@7K;HU3F_^{-HktMPRYZ-G^|DmF@GD-C*yPv zZ>dXjNR3OQ4@A3PQgL}mT#XU@cjie0ftLibIqw4TE8b3*vYU^WmzCh0Ik)&*!-;P3 zSBg0L1DcC#K}9U@B|dZm^Vc40YzpdLHD{UBJw^w;OY#1S3voPUVN)y=v5Q{qeEO5q ziNEefV4OTM`_sIl`OJLPNO0&!Pv-A&-sv6ai(JM|PVFt(rv~89Q38JBnIhS@)2+Yl zZ~psQK&z~llMi$v8L2l(OTWi+-qx9=sX!vdma<1kw*01@NaX$9AE_d6YlW+Zq*5ZoWiTCTlcqS-r zzKZ6I{sG$prq;S3kTru-Znb7C(N&rQ+vtr5p0k|SG(TruZSM2qL}qzK^--86@t8#@ zODEvZSr$#i-3-?k^FQoIlDD_hBfbg~6-bX^Mj@F#)jyTi=-xOKB;K>Z_)co=V_H}? zROLC{bZTNL^`(OTNlWxggqx~@IC(57M{+LA$%H23YFyB1-blSTp;Yt4n$qU!H+YL0 zu9TI%&uiQT`@2v%{Cf7iOruhyS7|d82IgTiMOH(xCKK(OwCo;AaBJtF zGT~rdvgFv^$jAnlu&ZDnV`l0L&MO8_?O;IdWdvo|yUm{LS^HY2*SQsSt2L(o);6{G7~=?%F#>N z{lC{N`s=PEk(6@(?ep@NVod@sB;^unHVJPK7Jn|jS33o5#CM-At#z?bK}o3eCOrhU znUao)CMlhWaRGM{FuC!4?ykg(>)DV4*kfH;XN;t&oe|gx;u&(x4quTCHI!Ba23a}` zw1HRSH6_=H`saXPGf)*Gaz4d>+;H1(+Op7t3qhO3wxY>ky6a<0z3&VPvP^<2)~u|p z$h2B?64sj&lM{<=m`JgUF^53f` zMs_nZC%dh2)+@2r=qY?*ONg05J*oF$9&LxvO43C|?s2lxeWwT_hm zs&4N{X`G>o^D8#ioz9gZEZjGmzepDH7mR03M051(wUDK3hE~ZeCJ9QLwSac!m-YYr z8hNA~(Sw7Kd0bxpP=1zH>bP>E!%eUXp%b`goLFY_) zb#m#Y!4lu{_?6#(I|VpXT-+V!`pwsJtQhqM{8jJxLPL^Gl%yOOxZVk+NIToWRBPp{ z2%KY7st8V}Fee=eEH-cGH6PxG2Q&czaC*d#ZQ|I;p|0{$k7Das(CItqS~J#~$wDAW zp4V)($VW%xXtudNDwgZa(Z($Y{{;jH_WIDJ$&0+;dZ1~q%vr4iq{-*Pto^puXn$4< z;VaUcm+ld-9L8zc5lQhg2@DCd?s(MUzf}>$Egk6VIlovX+qZ+R7d7gz%65YiOYhB1 zqJn-DYE+=qR66Qc2+XaNSzU1{CuHQ0vO2zhVOQVKdX(@jfUjPo*W+w(f}xFXWF2Cm zSlCET`}AyZSdiS#T`nez!D3T3Bv)yt*&);TUL^zw{zvbu0!E z@ZAwKI?%*DUNoR~TL!m&#x)BGWa9^6^_o6HXkaIWZ4YND2hQk7!+ zV1D36QuQA)kt3asXnDuRDpbaU%Y%Nrv6oFes=nh%*m_;4^Pa?<;5|t!5Rv}Qk2;=P zJ1|cS$YMat+^HgnJPUm~rodP|A4!ORqbrj$L44TE7ov2c*~PLhowo z+jvE(E$qOmK{st0a6v)r@7!|g$Kna73|Ys}={>bwJFYB0vn9&xP*!2X|^e`9|oRxc#fz7H1orZO8DUqLQU z9r5hiCNI`=gqb~RraZoS8oJjzkY@Ld<^e5~?OGO340`mKN;y8g+bc zWJ}Yop$EE=c&y{MzvIv4l51QJpiy-urB7$)GxIKx*{&uueNe@mBGm85r1CJ*ITmEO zA+ea8ZVxEK>w_1l4*w3fp>9->yW1RwoUo_xF0?rZ#VVeNtdQa%xIB_bn{kcNeYH ztMZUog-(A{Kx=s8`z4rj^}ea*^Lvye*Z12b+F2_-gv<`cTx1$#uz`Qxrsaz~7DwLiHz+4r^vq~=hp&PUqpI;oI1Z7Z(uRhbI*BB3p4jf0 zb7r8j`ZypvgZiWBt_>;3*df#_UlWJA&6-+-VQqiVV)d@TW-GO&yqOxsM}dR-Q!-pO zLlW%=DgXP3{(TRzzG&z= zllH!I9-JED%*QM2+eU$Q{l5-K`wBYyB!nTW-NS6D-)#OQ>5G2;_jOeH%trn_Q?>;f zzwRg*u4$*nE)WXw$PDdm#Tkc5V#8l*4H2`(bo(L7EPi1B_@zoK6>ILc!_mkDuw+kl zv5<@UV|YJye6Q3pf|zF8ALY;ntNBsYdBco+EUMfbi&Iyu4Hy#Jkz3{&2FMwU!fm(uQ+82a8)q6#4p4V_CAmMS6U#^3fHN>W4#Ny&3-W_q4sqL+zy3X z;PW}2`a_EAi5-0U?Rf9jyz9pyfcvfMH=c%SNjFC-ri~I(rsmPLw-lS=|L{yLs)O^I z{ObE(t%k-zNo1!GZrU`Hla~aDl;Wg!l7EL^7?XPExSZ@MH@7dH9Y%^E!KH@NdCKZ9 z5xJYQ(05h=58IjTbDbqwxA|RFF#OA+FT&@G57!Sq1F*|d>)b2^3M5ZDUQl>PJ-gP! zYZ8(>9cK1_6<(YdsYFyL%)#~(B{>Zdi}2`d6UJ|{-`j9rDY$LVh2G1sJA`flx~neH znRN&X9OMVFqZ~bFG8pmmg=AN5_(tO>8hUc7&zj5r`RZSxqx>SjTtZESZ~)iMnO5lO zb|m|5OuOs-Uf1UisN7$ zRcGdzqlrEA(b<6aE(%pJS@EuAEE`GlPgAwVE+!1A4h%$&JU*}PubjL#s7NNa=7eVE zC6~+}!Hr0ElbpxW`Pu*=WBMAdySawGd06}@K(PTR7A>N->(1pHD; z(B>?&oLRdbkn?kaP@lnzM{%z5@ydWE$+K{Ly2mN;pa@HyZ(|aZ-Bp8JvH;3V&OfQz zkSiqC47{k&8vrrRsSLN)z{z2d$w(Mh$kZ8Or3-#d7>hWOe)ynM+vg`z#we)v8b%>C zIsHu*>Y(GjM$I@1Pziiab<&dguU?MtwpigwFZTxly=UNYj)v+ee2wYkMPVS81&j?=1pl-QjeND8_WRaIFK`LDoTarM^fVj!s&{)va zJH`KD9Rh%n*?x+27U-OG9P;KVs+)v5kES@+R=D^m6d$Y^30wXFlDh-+lqxGgsrkP> zIg9q|N)DS{uSFL^_llk*1sJ0T`jV-86`Xcji?jy{(Q~0cl}XU$}3L3F{# z_c*F*yYE)=_>Re|o0UBY1dng$TQXk;hH{1{!d|l)+MWV_JJB^2y%wlA8G$Zd0K61S zHTxMupy810wdnmc%bmDHkDe2l&_+oF7R9u-mqya=Po2 zb@*L`m?yy^-W9RQ&(8Z5_#puV$@Gl;Hkl&Nbzzi%G6X3Sd4dIah2dz<*b4RZIBOFo zv`fJEQy2imk74MGYzIMOMYAh2pW5)t6az(fkh=d_RNZ^ z-LFu#Vc*^cKL?hCugStaDuiD`8+9Z_W(FB--Dm@Rvu% zm*{M>yL~%Ois~Jw2P#4?3IYnb zJTX$bb*xwWzSCGt_9FIfAg6o{&B(k{cTr7=0F_>~+m30adtsj2!WQBy3PL3DU^7l# z6#EXdBHcTOCtPL`cA-s2R|NwQ;~P-x>VDz4(jnbtBVN2o#zF%Pkq}iAY0NTmNFToy z)v5ZC_kt||Zve8?kEm4@M3tv>^x(kBT&2S$y(wcKN9whgn_+A5>lnG2v;@+lu=v|M zbZFwOa1mMNuQJQP;$c%c-7Mj|sS)fhO8FdyPZK2U;h7gq5^@3k>2JxpaUi%N*}EcO zCDanHh{SUM)6ZDAi6o0nRGov*=N4B}LTuwQS`LB(VwizRqhwLW>)f%u?O;rD)ZKl+ z>?(zXqNIr!nkgd8L`LvCFnDM?Jcs~@Kz}KAdmYr`{~#fS@V)DQE`;Vn7|sabAH3L? zYsrnyoU}#m6tWUs?8S=cpDD5te$wWEzal)6Yy0AiqwAK9T)(aC%Oo8lUAZ0TY7D); z6tSaoi=8fNV?XY_-UdnEE{goqw>0`hdh~^jB z7ot_07>Mal#=#m%2?gMLk+6kdh8Rwp;$WmWzP2Z!G`k}9#ak@1ZIgS`d;{n}!oqja zdZtWasK9ajWMJ5M8uP<3vyaDW;n>lsJcrsC(_ z(Pgp~)c8!#%v00LMatedfbL~n`ZT&GAEg1W&yI3C(- zFxs<4-04D%z@i$tr&=H->E>MEuh+N+e?UHnR(2;(BtsoiqEr5%SVxO`(<5V*3~^F5 zYtBxSn?rw6wYV3I{d0GK8s)G-x+jFgRXY`(P&1zfNJ)P&ZiI|?xST7h4>s!9G4kr~ z0!_d{(jqjSgr}OulMds!;pk|G`28cFSc}AnyhzTuzT@M1%7Wb3)SX1=nhb#)%|-A7 z?9G-@5M8QLb-yvW!UIoM^)M!>N5} z5Zsi~+p0&y^G^%RO`p@3cj1ys4R!U*9erpnc-7ToqA(Y)rs6HrtBDz(v#|>ry)O>Y zm6>kFK>BvXO*r5{{mKSTjKRRGOlIxt0^Ep|U=>{!@4dy;Wi&uE)28Pa8LNwJCxD(u zBN4(5EmU6-1t3RGQ}$L<%ip?_1BcI!8`Pdix{uj<05H30J%|v0NjCCR%ma(RNfjm6 zf}$Q2*ib@FZV&-e<6|OFExyNukhpm^90Q$7_ z{3!P!FI*{K;I_mM;=gd`Ri^0*ks=$^6zj(L_l0CO$e3cPAF z00|aBS0WiDNBi!9r5YeV7C{(jXCant6&AhwXWr?Vzk7bzfKvrI2i?=z^eXRDECk@x zujw}q?6`N&s&u}8{{IV40pToS)kg^~A;D&V!A*e0{!JAvP69jYakVh9q$ zjG~f^kfi&G=a}0|gxUP69h3lP)79x17nn{uzGl&tYg285qSq+tN^7TwV&m%kdJQXl zquC+qKNQvnFJCw%F1i2r$r}ZAWD1;XbHiF?-M^cD(c|k@gVs{R|6TTdCW8b7-3bJO z{<0jy=nLg8VquO9W*4p|XMtZ7fG<>5Pj5&h9Z&uttKdfXOk8ba^nDICKlUH%o;I%_ zLRuCf&S$Q_MGY^ciQNVUnyFu3BCw98mtFlaY)4miwUJZIXa1wX4-k1#@sL8K6%_+O z!~n!?z`av8XWbXN!+s%8%y6NjBIQ@3KLIXpAiu~`{kZsiNC!AAF&rReb3L3pLoP); zG^>2%2||azL@diFgAQH!%wni9f4;gYVd^!K5_@C`)4i4TE@_MMcRQ=8X zC|ZuVWnhQPfBlAdq6wgOy7DB49EjZvHe~Ef#aTuaOBR4#$~Zu5IvMkSJkRBN1Pphn z-S0L#*~}>?6Q7=#TPHXW6(DzkZ~sur@lsu^@M7@!aV8U!O5K0P(`3+daSr`i!1wbN zYSZ{UXvrxxdIntkGP!h06V$q6%lJH%>yGnFI_C#>ANJQ%ftbf!Aq5ynd+_f}n5WLH zJQhm;GHoZRbV?-QUm{8XRIGOXWn$TB5>iC`dq}qu9&M}v%W62jfceP`JLcu)hOz-v zn*41KP+Y^c@x8dHEslcvNhdPwi$4Hj8BYCAF~diG8-&P!^&$UzBH@?p_hbvl1Jq*h zLkRN7EY#l-b5G5ZopKe_0o{1QH-G+L@u%d#7jptFUka65xOW{NKl#nrh|pE;Ns1$h ztX|k87&CADz@Bt8isP`oh%vhY)+!GSQYq3K7atEzqd)9Wb{?>jKaF2{QG3|dAu@Nt zZv8rMNO*-Z>zPa}b1m_reKsm0*qfbBcpCq}#kR}0%#R|C)kAwMcdpuj{x&?AM=sLR zi*yEVBe#Gqpa+c7qtix|^|N%3?DtuypI*}GO~$PLHr6LMwVyC!qJ2}7ufXy}S{BI} z0F!9~>iZ&~8pt-ov19f*pv+y-TN z97@08*2c|qWQ;9r4wCT@US5kQWcY~;;qMwdf6SZ5%hf#FCcQt3|HwWb5bcIokn&XI zMbh^qzhR2I-K6$shvMs6Xr2XoM*vYfd_BBM+l)MnsN5UXxZ~|QRcr=$WXz!coVE$i z9j@X%d1=cBhp04*y!o^19g_Pq+TQ-Yo`o$fvzCAn$)J%${(1hK+IGu+J;$U2?n!pD zPA5;s^OR+xZQ~ZT3rkyf-O{Mhq*7%9l=M1%4CpFi{d>w^xDzhX5D~ZgyDQnH9 z?neaCxr=}Bd{u6Hx`rAZ5RFEKX20zjOU{hc;NHbaM|f zgP3~2#TSykIf+S`I;;AeLZ57Ly1;d4mL4ctjJR(e*P_B zPdY`53HaYW^Z>OM5uRH;s)|$cleVa5bh}Bk|DZIiu^e7s$1Fb4sYE=h@&^($_zvQr zBsbsvb@c${qZ{GGY=JX4#_e@Ht2q7|dCs6P^$8ikf$!9E=Im#U@P8ht`lqK*;zIH2 zYO_MY$-U-J`xD@tDKzt4O%oMLDCJ+KHE|L6nSheDKrTxdI~3UuKQyVK8}9I}T*Xyo znA5g6L`%xwCIAd|ls!(xj1qh8!3Zc-pT#90XcwVCTXw=HMq8q`%AUva4NSouA(m^I znJd*TC$N+S@(5M52<@}4H9*s?VmJ>XOhTPn^RMv;u#@rnnj1vkpBw-@Ji~xPRj4pe zHom=jD_CLKI56a{o(!#j%r=Bx!zXfql28IVH|k(Z~t zMef#jACLKdSHhtBc0EIshp*$xq6JS6Om8u>T-^^IgRPfy@lvVqiqh}&#UlaH_0GC6 zdB%wpnO6(-mCdH+2jJ`6ayZH7TMC5>wgm&0cZ#lmuL`+N68tQbNPC?FEJ`h1I#&*m z5=D?_{_dxwdA*1@o#ti?EZjS8n;`91A#RDA%yWba=I&P+oJr|s#&++AidPhMe7UZj zQ@YjyP}`bQmgiIwQ8s+oM-kDkJ_EN)!eVF@9bhMlGP-~vWfq&7Z3%CONl~MntwfsY z{V(T#@;r46GnXH^cKjHUp8v?|8#gA{y+fK zM6?*5Wonp>dy&f*Bz~Mc@rV6Dz|lAiJYAw{;Mc-*65670u+lR8n5AzvvjqdQo= z@@RWMkK7q~0j7f6Q8zPv@usXY2mJnds1^nB^+3GtzFv4KS+Mf{#y}XtB<9e4vo0*~ z@=vK=5gMlMPGQAi%dOn=Isq`+HlI1>gVW3Dp)KE8VmV6z zY!pZ9p{wA)^v(q^GJviqaQ@q;e7oio#TsKa4_|mk=SJsO*1bWF1K|;A`!9bE0LuL) ziO8_qu1Xn$Pk3;P1*ZdEf6IE%AV!Ru80EKe)xUl}Z#2AafLvhc;?Rq-mM3)Sb4b_& zFcg4cRcNwbPC^YctY()95DECO=Mo-WW$#LpQrSy6he2@9OK`}p)!bZ)i#ABUl(}IO zk-P?SAp8kikouz=&I>Jb%~zYRcUu^_Q&GmIh@wfNZBOxYur8#{TgVAu6O>t&&(PU(Kwo@9*> z5SC6YfM;kNB6)hy^o~=<8tMw$idAm|S0O`-ruS~$UD8lj;9w=Qc`<`}1B5{F@Za;O z>UNc|e^^(HHj`EEvRe_mrv3W3grK5y?R;TDGYDJr3ADk56KvQnQk{_MTO#!)Yz zQ@s7}{_2@!+m-ij;|h6+EcZ%2Gba@A-Jdj^xUkPYeI?BxNJ+DMo1l1Y6{Rw!^d9Eq z%?f17I56;NF7p}e0STwY8$@352m)&2e~8apxh-T2Cb{r+hW5nu#SSsji#c2LCQh6U z^Ui39s>$vhyZHIzpvOMlW$;g+tRW?vj3Ysfcu<|{b)u^6E&@l_5NeUa&%tq<%(}=8 z@+bikXgYAwu>ilKT9Gv9DMgo~tO(~dkaJA~=&>-4)aq_NMh@Nkm5Jk3wvqUbhrrt2 zCV;1S;dk8o0zxJ90owYoc=ed)@pvzI=7t4SKQ7;E#C{JraGZX_{+tijTN=fslJ7xl zb3QRgd55IyRr%P9(?kXI0C=6u-a;SNTF$Oh$D=u(T|o~8gqLK;SC(rgbki#I)8ipY zYq~ifuQRi?2#}xtMS}in+TSPtn)dz}x+@5bj$REY+MmvzMPtHx4P;#^?Z+K?hPKhMq2e0R8CzMngR6K=#cmi zG1FJBC)(2Kq~4g~VSg78pNAEtWfL43m^a$LCydIsGWme(W~L?2v2;&s5G_`#UAL@D zV3k3D4hSU4JhOs&8sB<85dk|K3bg%{oWd~kMMCYMdKFYIoAW=}e?eF-E6g=#fZt=f zx~Q~$70+sdx>6bHT$%|y3lkXlWjhP4ZPN#v^VUbCw^oWUOnNVa5`eatF7Lw?(5Yoq zhjG=JXeZRw*+F?xhyF4drA;(MchpG}U#n$hhlP(Wv!(v3hCJGd!3YM<+RVMj{_Y#4 znm1g@*tygNdO$2kl$AOm#vIXsB&jT`l*Isy!G9e+tb1Qm`V>v?-R$KBZb4I=A`q_O z;PC9OKL+lws;F)a`Y7lzyGc!=EQ#2eJjo3$&y8B3e#=(1)SJDsZ&+6JX>;=2)s3kn zS`qu&%ksx@+0M&}TPW>YIW(wK{C2|B+d+7UIz>G1tN)!>u}Ol8>jj}SR(9#RosciM z%l2qz1 z&5`Fnl)I3;@9Gr!lXD<@Y&90_LEv+$_f3&*QnW{48GxR}Bpw5pCCaMVMxAu7POw+8 zp0}ECP!hta{$e?L80&;1Zw!&!HwWC6zxrth^d`_YGD>SQ>LjI6}*3%r-q2S!i1Tb zK2I%=qgpxg75Hf~=KbZ5Q4_c9Pmc+$OLr-{kEgm`nm2h8)Y@@}_lN9qk59cLHP=@h zD346h)#{a53{-3$g5NI750s<_M4rVpquj#Sy*Ln0TVq93k|H!zkZxzR7%q*4!~B@1 zF{xX-I`|~dqAC#o8;o6A{Xv_haO1anZvU^7TgX1g&PFeldME`b$Xadx_~rvtw}lT4 zkiDN460;6E^es@i4mu1Lvt3ckAQrFh7d8iLfb79jO=^+sEB*&;lQC zQ{}*e0tPO;vpS_q_t`i$Zw&4j#vfBkfthMu@&6GP#(iaq`$=T7#C#_NMEoi6#Y zs@w0PD-+|?m}=~k0#sZA*#izoPhRN0_VV31{4Oyl4|PKKq@dmV?tEv{__J=a;i;}O z6~&7G?WD>&if%!iVuwm=MVgTs!Ce$JFuF}prI@Afi;bw4=CprcrC=ON zkv$?v#0CNo{0{d718pu^TBp6OgS7{HdZ(IIOI#ftub_?uHU<5nP%MY^9YxkgincXA zV;D#&hic5FWQaE?eA)|;Y}U6Cq0H{k9z60r5VhV0V%EfWqtGyrEzPSHAGMA;0DX4A z4MbsSl+4#^Y9TWzV=h7AZR5Vj9v^NApO8sXy^W*~dohNm)H_{&q`ogfM4g-l4qfD5 zW}?ZDd{yeh79O`cjXH;^W!Pjvoj=%X8J;{TO1(~9W}GA&j;wWI;TN%ifE!L(f1#Ua z|LMpO@2!b8Dk!FDaL?Xlj-Q;@6CSRvNZjqm-(oMj6*OIyIZQO@#TYOEdY5U<4*01@ zQ2*r=(I{h@UuDf(KF7Q2-m!hit{NdTbGxZwqPa9 z2xN#U2 zNtyNlj`IpT(07O(I+wDHzR=Xa0I? zJYC_#E@k(1|Fj1aVp@uzojs&-tM!-6H3|vfQZq$)UQt5kk0IdW6>YJ+rmBzTZeU)X zI?j|&E#nHqCcN=BT{sYXdHGmQo-y5qaZ`nCrFqHP7X=s_t$GJY)L$w?RB{ylNX;ig z`g{Wj89k#Skzmqo_j52inXCmdN*qrERxlu%62lzxI9^GyA%>-`z-d)Srzt7UeyIv% zW@qFKD9fhDOU{=>5F@?+(Yj+mqwUA)|04+p`41v?!rA96<4>fes7$Ik-4@rOCrStA zfNBg8@}gtuW0wCvKD~nEO0^Yz+~e5$TPAG!OA31b)WE$bQoFhwYplUbU8`9P3f3$V zQ&c6=t5>Q1TW;^)ZLVcv^vgX@%c7o`^>GnjYw_giIny(qBTEl6q`wTIK)w*ngVr?U^JLpi#)Ag2^k{znt&KZVavptiO zR7BxsgWgU~GExbXKUI_q>0-qPqhVwxbV?rg#-^iUPJtpV9r$L~8B0M41JjAwDcmE9 z=T59NWWl4R*qff1dq{Hnxlwg^E2^g$)>f$m01_YJXH9Nk3N$c6gM~Z?WoDzL=nABo@)lq_)L?<-`Rc>h zV8t!A6x#2fHs`qyqE3LU!v?G?Z@o5qA|K;W?LGgD)FYB#Il;mBKkA|rvKFe<%+m@r zyh!Qd*&eSKd-YL~rI?@h)Axt7N)dV^pt%$?-d}RMp>4JifkZVO@m5gfXU%zd%&BFL zT_hxR!s1oRCp;Ag{M_dJL3z3GS7{L)lRvCenm1MLgDjPqMk8ks<)K9kQ>rz1c+bN9 zZsVod5PdNt32PopajsBBws~W9`vbU?_Z1Y2$ap-{7|z4E%`;S?q;R=FOWcEUjW#Ad z7#{SygApAllW|;dtvb^_Z*KfBuBCx*w5TD|49qu92u>ErF>9V>vmdUUew|&S!Rx<# zK;So#9S@0iB}{q-)X_9}2I9@8O&64IkZN`GD2WH?IsdQ(7V9ikQ;Q;`5+r3Me%(&n^$c##r4 zEaaTJ*b~X}@EsjFEw6ssw0qBx;8+GJhy1GBu+1fn>eX$O{AKxbbKenu#c8Tz1*jVl zV%ueu(BIvpMq)DajrAw8SXyMxyr-j~{QT+Bl zKN9WW!wLMCDXHYHL+pq0*uQZG^D9n-$R?9iePGbHUBln9EjA0m1cN1JZBE-J7rt_F z8UNayE!|B)77jTOE^=Xaz93J6{{Z0@q=}@r+;I;q*9c&Md>+rJy!LxvCJaUE-3KPB zTr-R|{lcH2WW@haAAb6!G;+xkDHtj#QVI(kyyBhfLi8hZu$e8t!80>=blG=*ll2K6 z)x9cLT}N$l`E6rTK_hw`m2W5CWLa*(gi~V0#Ju8x6Juu5+T!6<4G$P(l{&Zo$dGl2 zzb$03oTiVvF_-2pf+<=aIWV~TwWbM0)8}9okMF{b$u((HK(LHi;aA|gQzk%=0AB5B zja}#Vn%!6o@^jq7img?7afn-`aA?@IL){wwFZ1yi$K=D_AG>nvxrO5>$c419>RrI#1 z5n@ZoF_InQG*zM61PZTA^pk8>;j}o@>ZHu$^3=xodW(#qEDM}_y!A{`I5-X(x%U!Z zqbjynwY^YVK6!J7UfRnSe!W~OneL9g{Q^jqq{m4KodDTWAS&Z*ldzA2JJw&}GL!1$ zbb&l}`YG;vgb<6GX-`pIIFc%HL-V)i2)zW4&?&w#51MgJ_32NlMSD$f`KssfA2MSs zG;R^EzYsdyR{uFtWua40Zn6qUfY1y^0DkegwuL0g|2}5cRmr#K3}ipJF}Ga}t|OU8 z_5#L_?j+;wHvJj3F#p^2Q41OXOY`d>2K#z$TW`iEjLx(fBdjVGIV!AZMk*GhjNvI6 z;<=+*5>4-==g0ZJmsy4xiJLgo8^Y;!NH!JfzOeDf45bhrU_?*s_y~eX_K7_KiP zfrJ*BF7Y)a>Gt*DL;80AfM$lW)eY$WFukKs33A!(v6db!#=j`+|KwsOr7M`nZn3Zr zLnm+bnSv@B>dh!w@1d*JkiXWwTNB>lPdH!7exxgDETG4OOMV%t;nwp8{#ugom#=7< z*0}oCMe2#Y1i`YZWzoMmk01Yy)mCO4H5)@pAF#?tJ45d$?o?MMP4?A(6|Ctpx92Z? zFqX`#Jbf{usVLuhLQd@pU!Kp=SS>^N<^S6H%CNSAU{Ra`1&Wv87Ti5}krLe9A-HRy zf#RiDakpYc0|bXck(T1n;10!#I|Yio^xk*xyYGAN$7c6CGqbZhC*RDR-JMyqD|OWl z3umK-I+h)Yju8H2Bh$I71am(caBK)Q!F^-7?$=7yF9h7!n0s4sC!vjxKmG ziJZ>(*LDNQI|UW;X1*>kCfA;A3C(a#n!7H2$&DEs%bxqC?pI8itqQqxr$T1nLY}L# z5j@US+1OM30{gNzeUg9&-Od2E+PO60_v6;`UsA3D=JDeCGl-V;o4;bKc}{0x2+Pm- z=77m2+mKlYY>s59@b);dO_2AI)THmuXTf4_wxjA?)4N4~VOk1pDnkke$&YnX}5^&ct8&fJXmK_rOGCa+IbY!DW;(dk0z45t_7qH_jC& zs%mYd^prxDMSTo!j6IUnT?Ky8yT{BnyMIQ6n#P@8((FxC<-6w`DyZiuIP#6GWy3QRmb&f7>Z}7mbVs{M*#M&nx;{ zSzM-_h)48dQp#@Kx+LDE9KS-msb>*-d_ht$)2L&Qe!Q$aRGHx9nE)!5y`2(+&JYe0457l#uLpPa7q!rZaS2reqI-#z*z*N z#o^xh=i5pyBJ&MwO>qL;ZeDG-t2#n7_|GxX4{)(lsbV7CNPQl_tdKU8; zW>a-`+l#{)dX($g)A9SJGz4n@ZRS~GQ#-w%*VMVg1-e?~1ipVxQv*ATgt^m&Nh<<% z_+m#92iLPpXRjvZ%t5Vl@=9?0KmDMirGNV?(H)H#_=wSrz^E3`OS%pL!h?fDC*TAf8iLZ{Yx+OkkPiXw>M3YHskru z77H_M^RsB78B77QC=ZlCTf^aiq-!;)wqdv2Bjo6ttGB?8J+M!~1RG1p!`#O*fyUXa7t>~PxU?KJEht%D3 z**$9iqP?@}hw~~8t?UjYia^PjTl_2EHs~Y2X?gZlRfEXx)oS(y5ds%BJuwJt2OiiCMkspNr=eehBe?0}}M_JIWzz6S5YdKJOuB|SqqHjoW6E(z=&oQ`pisu=}+?g zc$HDve9t#bIhyM_^mqQA@1LBelWOF>3A5pJ$a@1L%6^7q)sMHUuF$>Vg^BVd$jp8P1QrPaY<2^WI|Se zqxWEKAw_=jXU*e80{yc^O8|*|kw=YgUHB_S%&B5I=hzoS zpKLCBy5?&?yphic=TgOKuK))aCFTXto=SVWaqkN;+28VEet%witW$k}?_;N%f7nzW z16TlBP+PnJ3(h?p&dQgcSP|x+h_n775w6`0uh-^M zFjD9;LUl{#b=MQc8kf+o;;C`4+OF2tlQzsNtW}y4*@t=!SRHE1K40gg3bWZLeK3Dn zOBS7|=IGc!avAsNRq)NN-bVs8pQU07V^MBFTZ42XeS3}NbL#H~YL?$#!`!G$zgRra z>K!}49ATVMGC0KaTRgO4&Yu|SV@gkU4v8EIK8CBTvzrVm7=1g8Qo&v4Vr8Gner(Tn z3Swe`^o}_A7{nHxtFRExTO<^_4VR4`#`n&bgud)CT-wf*m$Mc(;5RzrmOnKI#zlqEL00UuT^3i(D}hfZk_Q_vJ($dC%!&FZ(12dz~)UR9Dc32 zEvx7tQq1{ldjT!fqbm!o&((Tzv^tO-lRJ0Dmo!~dZ@3K+;gNfo*TnC@cg=bzq&-A??OrA00Kx5kLYvu1a>z5+&};ce#Upce~mbvj|O z`en2~rIg2al!?BFQE2m!!({_C(9hUr(75yfaPu9N?+Lld`XND^(<8esL=A<7*704q zF{sd?#bowC&xwteO6rbb zy>TYPgJH&lpr2-)3gA{B!jgTHzD92^@ME(Ervfd+#um<94O@;(Nk>JWp~GkS%&aE? zF5XGCdN~nr&YavfGnIPA*a#jQB8lSyPFeAb`yS-QKSHp;y>vE?!ie&7*9L0*xAT9> z5{`e~FcDamdLx>j#0uF{YdBlyUviTtbPlxfvYL1}>VdnpyBR3EjYQk5f$kmBtYhjt z=1F?Xtx>7uc1<&VvBA!Bm*tc5JwH*xx7()v66;qbTJfOKfhpu z4Du_omx9>p^#b2Jiq}o|ef>`>c>_8IyNr+hJ=T4v4?w+`BO4#Ouum6Wm(Kb-5dKjx zp<1`os$~VeWjQ4B`N-lbg2v`>sY^*D5G ziqc&-8h+{KiRW2LHKct91b;AEJ9cN$PRcKBdVc3ZF|LAH?_;gok)#U%UuNXV#EqBW zp@_PguO+*A$u$)JGXfWzzOo0dh5nD~)?|kumY2h^R z)G9Rn+9qVK^L(tm1OmJapZ=zA!}wshYE)VX;EQk)?6Z09N=5Nu!g!?K;&1;ABk9=u zD1y{%oo%^q9mN0JzTHDD^*0+7@HJOpk^C)X9`=`LBGSo}uiq@fngVS?NY-hvf5g&flVWM-}T4nB-_{Fm0+GA2cWug~YWn33hrEKyonss2Ol=c#H0^>09l zqhTn|Xt;)IfJkGJgH}+8xd~fN_%@$lQ@hyxovfVycyH}Kb*QJd%J4z1)d?W<*ctQ3 z_$!460am2>BgIL$?vMNt>{JT&xmQMfRzFB@ccK9;BU84J(19JlZKw#Ot$Ba;wDs2= z4tL)lVk9m1@$qQ|`S3~MAY8-{_@jG=et!K{uZy?08WHzVb^?tIWwR6;$=QD5foB{z zYhedrZWEocp>;)Uj!ksHLl>i+Ws$6ROj9L>*S<8j&7Q|!$5Mdd$QfI2N;}EaDo3pf@+G$ z)ctGnFms&WOp@uVJKWe%rN7pF)m&*qqsC-qEKbYzg2ivlOlQSX-n?NA?8&|V>{Nik z#R{nxt>tq}sJFc;w5KxygWeil24gD9BRwYT%9*0Z-|ACN>@4^<+G3P~iK7pOsxdTw znHdfurBxfBz|F zHBHSVC`}|d=_y9VWJ3_wTH=K{kCR`UEGxxzny3^zz}}g3;ASQcfXZjVF#d!6#*G%+ zVzdh5@=G~ay%1enRKK=)F~IjOBJ$TIKVALX#X!tv1Rr`gRniW4?i}R&&o}x#Ib!g;At?zu>F%JH|nRqUcFR03c91_w}C~A|+vNgY6n)!;P z_4b{^Q9AKevxbP=y$S@<6BdT^{(3QmjF=HDSI24mb~1UjZr6vMzVUJn2AZ>OPeU_L zFE;%a7sNjenakG@p=e0T*b$ro#iO-jf;29)qd;eFxyd0}leTNlT1no$^x~vdq{~7! z)alV`FMYt#Z9C{wPG%{T-ICYYzeN;|@rp(O6VjOBlQ|ZuY_AV#U@x<`3}Hv7%oO}i;?t<(Qvm5>nKD%F zHgZR34Yp^-;&F^%PvprxQyc4sYmYhB^VExtb_xla=BrK^d=8`5?``)`p5s`eaP;dP zx)8+p3nk)x(&NZ1tyE-}iraaf!I|3M{u=p`KRM7+X2(CW+$`^;S&?9x(ai-=+ zH;HWi1iq>S);IxuQ%F446!|b^b{LO5NXQoJZRY>z8~K(csC_aX2Jii{k&Ct*Rs!bo zh@krFlXROAk!+`7oHypR!rK{x>T}cMqD+KY^Fu#RmwJ2gc;P(T;t(?lN+~bs zxgXb`cUt(Ra=mRMqMzMmffb)x#tL+vDNf@BJLR$K2T^8>3WbjL9SW=tXXY&6;nfgO z;YC5o`dhubjYCuQJ+E}r6UuX{MjduzFQTAju)Y}2sW=_mhjg$B|E8TTk&eBwiow){ z)3&5ji5w?cm(aj7HmZ%R5tfu5m>TESG zb%4po1F4-H)6cSM`HYH^qZ-NR8xXwU-6GoOU*YL+Mh9|!UmcE6ZqjlT2-|PQ(RLp2 z5Ti*Uvn4nbcIH~5ld-|B(+pf;v&U=1Q*TBvR^s>ZnnON+Dw@D+x7#MRF{>Dq{rLJ+ z>W9Fm6n5y$m5Y)jF2p{67FEAMc?~!5k6Kk>m5?<+jBb3TK)u=%w)+dHQ_$FEvplrJ zxU(z!H!m+4wv!U4rJ_nDzki>2-H&kqXgi6Y8A3A!aC<4dFg#5lkr({yXKzV9Wxm7_aWZhs!)PjxNx zel!D`0B^vdOsCq>sd;{DIdZ#mk3;r)(J`BG=Q&apOme}8^MbwcW2Hj@q-r!F5U|3I zjg%!9Jxeaw^829iD_xG<^bF?wAg2T@FN-=<>%44UyE^W1WfHOU`=lv@4SRx|EWr`* zGyYQxHhIoPp=jRWw7TRJN~qvOYQlF+{Af*vTcFjg&&%#fS2I`zW=$c;jH8^~|0br@4KL*wN%!^^DX~|b zdif%dnSo;FJc>HHP5|G|%Dv_JMsBe<{+?;I-Sq2m%?RD5m1=R6RCUE1vpQfI;tpW% zD;%&1*m+O%hgq^#OR}t#(=U=|zH&TICYRmo)Vz#^>J;*Th%>R>l&tLEPe6d1wHOh9 z%4&kFGYkrQD4?}Wdz|{B&7iznoT@BlGC|}V8S)j-yYME!h>@wvVkP~@hkuhKz5AVN zq&@j!<&~0Iv>^s=5{Dq=`&Q3c5e>P`A7(aq1>tRd7tf}ZEr3kmMJEnX{sVGj{!fY* z@){HC=*he!)GI(nwZ%3TBESN z$6^a?9|W)Ak(wsBq#upBAS82 z)LlbU@wc;kDfkP+OOzK?zaw`?DNk{UnTSMWB81>oF?CF5N zvJ)>%*b<}&bFq__a6hG?G~sd2<6S-ch}{x_AMFhk1(#j+Xqm7OoC@E4HuweVQ=o4# zGE8N_{$rKB660^EUA&1W#{}2el&HSpvX>FT5ZqJTJcfPv$XzyeLeL4>R3hA}`?F$y zh3eb}{Gq~9d{uV;Z)lBKzG+=*d?OX2F8$*By#*ORzdeyZCL4>+B?~6QV)Ad@tbeJj zJ>!O*xK`%dP#$jkN3X!c2f~pKo%ah^_ErWjQQH+3*DBgVzk`RAu zwQu#-!~sFAZC_}vN$E2WY{`?+u@p* zoP1`PK#`&BtgD|5bb%+&e4ySs3nA7Q_VhD7M-3_`oc_+iL}=Ma6!U@-=bQBetq<4?<4HkXnb9|8$`E#T&zrj zU(myGnmR@b0cUu5nM-VC+2p5nk-AStG|L8+zFXUv$ze1*p~F{ZIaxHGBg}o4u_a-bLQ-TZV)URw?Ue-U?yLR zboBFrVj4Il&2yc`Im7kLA(K@jWEtqp=N=|z&HAA8yB82q9QO(c~5sYd0@jW%PTmX z@_O@Ri?RJ3_KwFqpC6N=4?|S&!k=aMQb1a$gJ-D;Ap%ZzuP9AT=(4R$JJpl;RQRxY zvuhHpi-~o)Yf9zAGpkUO4ISst^aLp{90JUZgYKP{->s|aakYBWKf$gwa zrkRtk{Sa|4(O-ohfeq^)l~6c*d=1FS--jft`!^1zo96W9R5_E4n7tjda$zdqIcRNr`xCM)a}~8 zWngV`ezM+rU+Kd)_g9#7mmfE)syjJUUJ=cDU0z`*hqHGIId{sP#yC@dcO9(!?U6}c z@#2_GA`{FqboicjS#g;X{qnWHr_<${oEG*@M3` z!D;*l_w-ElJo~(D{byDp4+?7){q?)GKDO|$85~^L3Jt@K#6#az>5{yWq7QyFjyrcw z{Wla~0x09>k3>#)lI;TK?1aPX0~TZP*-rBUsVBo7POY7G4U;z0+yaB)$KO4ADbh~j zbG8u^69i{;&^IYz)+sDW;p&EBP8$O!i;E-TE1#r~#cONbg`$NwnQO)Gd8u;8qcjDc zX#{2Nee2a}^C4d|U~5ce{rh;~F0X2EY0mS9u*tCk89Ny2$DP*&dNTTC-4Sz%AT65v zTpk~fPai}jE(A{-I@SPc0Ur45%>+XqVk?O_S8#7aWs(}yve1|_&t#TM%)|`_c9^-+ zfL@e9Fl3n7uw9Lil4@IFA>~y_+gB;8cm%4uzUV1_BY!ULrRgXd)wOGKx>)u^#v>0-|N{0JfEn6Eeb;*zr(lMICo3(!o>P#AN41Dl}f4(KnmJsks7^(OM$qoPJe>e zo{Hk*uDe&IVD{mVN<-V3aDA}#fO4Jr2n+@j>6yFDNe;T6ty~^mt~FI{KHMKBo%jMD zI0?%WOQ$vGCWSB5e$C~m;k9o($ad|}?cY0n>qP_^cC|;>E$Ou&!@qYPvOjzONJV$Q z^GTDX)1e&NMFeWKu6NABYq4w6i|LHUGW0tp?JG2B-O!W%QBV(|zr?^XZ5B zqboU~fjRRai$RzxY^-3D*rB^%j2!oc*}~zR_zld037NUYU<8)@&D6nTCFqg7sBAN8 z6hS`m#ijj`E$SrTAodg1p!*XQ zFsf2DYf&RByVERi-apIJb~E5HGT!xDabS35RlnwvfhoUgXBH||1`xn9(dwMTMd+jn*Dsi$AkW z$(`-JbXbY3{b&Ko$Ro+Pi04SDain_6HR{NF9d|Lf9s=#Fd?l8w7q0Kz=Rb@w=3ZS} zIgcagP;iR@Qf}STwWu#zS#~Fw6R^(f7cTT7q&}etV3Zc=?T+6EH1~|oPR`VY5#GEq zwWE5PWO_qI$A{QhsEOX{0JcTJ$L-s?{3`U2c=b<{W8|kpB%j^6R~OA{ zd_5R$80}5V?!T#g_VlxMv(cZ}xlI4y1Je793f=Ic51#nUiW|q;2%X`Y2={BOkA5>% zMFt#v0^DwYW%bm^YYufWSwFYKyFRb^9%wfRFuNsY^*qn9T@}`$gXuJh#FMo_3|2)9 z5)EHv*Y$q=fcE&Kv&KllrHzcWjo5V#)N7KXKYPCY=Ifx;Fu-^gh`WO|444VU);8b6 zhG1F#hZO9n&TRIwFsm85xC}TdxvaJMhO=Lq-7lf)_P)xEtFs$rx9qGps3N1cP{pJV zHU`aeOFn-D;x1Klo$W8An@&89SNc@9s_=5}28t~31aQ)$Zs2`A(|7=%mes8;@{k&E zM6uJ*4t?6Ji-_j3BgdPoT0-fLl6D+p%e{v!Ao3>wLbYV~DpxRd4?k~lm*S5;=+uYp zRpc-dT4U@L=4;#jXeX^3m>vh`P8zQTTiSSUg3lbTp<#>BUAvC8Jp=;TiHo|RhHAp@ z$7|EqjEBZ1ER580Z+y8~xQji|)^ZAXwm?xn`MRb{hIpp3e42YPF59H{(l}n7QSUJD zGxqyoveZ@Lu2~&F6=-_U9(y&rwAM_FdzVawq~33I>Wlf+*4OJ;WuxZsa+$TYC9#`H zyw+rs-M*cz&1T)*(Z~m7^PG-$rX6jd8TLIfvv~MlUuU3{OXOdezCTdScXuEv^uv8r z^Xq(j>J#g|^sCzO!B#Ox`u*(*XoD&}~Tzi#>=cYhXA9jVG_V01EhohEEFi&d5!rK(ZY;&~5mHpFBv%DCtG zOrwZ<_E;u~GkM%*G%WHZn!K-PYun9s^lB|w)o|CRRv6o5e{GsRk!hh=!|dLOPSx0ZNb}z#ij;L3PwzKICz&$ zMzgBB1v=|B97Fu0%aL(T9)-anfq}QTyB_(P=v$T@iMb(r`}cF21(JbRTc0)~ul%w) zyJxx-e0t;4Ywf$*{chisX?Sm#zIH)BFTp<0In$#l48bYDGY;kcwne-XN4V6of7sE| zw{ow#%zmE@Y`OVczAb4cc%4vY8*_As8K6BA`gnS_N4?IAOsb*R~ z0`1=I-9b*Dho<%dR~4g@xJ>z+@I0d=GJA)T@WasM?WZ+erMoo)^T@>(L3r(Xn%I;Wy+ z-ME4uhMs_SkK&%`>9$ekmRwR#a7ye^yfYw-f?b!IV_rx18Ygp7u~^sBv^ZSL{O1D` zotc1p=p7`DQe@vxuVEyK>5I(zUr;4(yXnp9wDB!{-On%TX@7%Aq&P%*KjF0{inKbW z(*D?+v&NjHVfnKSg2=^oN1ot08=VWeZuqxpWSMnG9uJcT5%X;D-i)R!hNT^3yvw-| z7=yNJ(BbsG6hU?GIA{}_P7@P^p)S)bSc&U=!g2qxy*W10yeY~n`cv1pJ+N{C!w|K5 zUFqUxjQi#tH8qIfgp7F-E%W2rw|8Qw1#_-_yeJ!+qArw(xZb6$*+A(Z!w1DRg_lk5Zc<;9fO>^-bsQc2)SxfQuFGkeR?*);x zkaxg$3`l0lTV6U#&1)3_*Qw3)+u~r)$rdA*t<)3yL91T~79T)ChUNp!5t-3d=oe-% zQKF9rF`L`9i(Z2`!UU47AF})6qVGMuuI%y6slQ~ddmg<>wTocs2V$J0a2xgb4eofo zDe|ZG!K51`v2TCwv7+uTH{{FVz#cS^|(GXa$UGUz5~-5~PS{V9*Rh%@s5>Ufug z<0$59$&)M*Y4i!VFHW)6ad{o!Ztb}x5E82;ZDj7d@|$oC(})imdHb~Ebq?*-g_Yob zk_e(93x!cOM3(g!v`v2-(#1Qfbnn7%r1;`Gn~UT;E&Fs z{Biw6-hzA&7_E4heO@o3m(@eBdv*&ZJi))M(qJK$nX^kda}%+XGxr^r0ar)aG7ePHzm%|Z#)^}=11K}}FSvqt+} zr>+7?iHI|Qwh0CeK z!^8Fv6Fg^P?zedLvQJ?^IbZ@k@6{C7azgP1SJ+?F%5_5bB=xkI{a2dwU(IF$m1g0t zh6W|-{bX*{-Iee(&j#TOvfYI1@Yh}~GbTRGA95BeNXNq8%r7n)Oyuk_8qWeAnZWnL zR22OtOdjojgmt^L{Sp8^mdjJN-Ox;=)XRWWA|0! z)(4mM8+)26EhrX3<<+I8)dT4!fZ6Ga0d!OO&qeovqVP4=k(Fj4_|b9h3BtLcj)r|M zJ98U4m3f5IXqHLB8nl?|+kYSQiz-a&fEw+k;PNC-`Zk@&`&qA`-1()&Cl8eO z0}Xma4x28vRY}bsC}B6MUTu~cBK49s0}KaJaLd|b}3Dwkk&ExTAhXVx##Rg&owpUk2`PY7ig8JSG1q0baMHT?EprBx+M5ACM zYhq;4Dndm;N7ktS;6-Tvg^Au>g#KS`v`Qo#MNU^93`W+vw%!nkyN{!X?;~D_4)Otu zucDE!owY9ngoGd~6ahW~A#Oe)Zhiqhetr-iABazk^WQJuFpE{`|3%>DVdrEY_&*8w z#6iORAVGouBp9UiLm&y*{+olZlN-dx*V@hVf6^eC@biHL{x=hk0frzX4beXu$Qvhn zki3Vhhqtb$wJijNpD-8g-yDel8>ooH(6qPrf%u{bi0D!kLy*vy{|(i0dINDaQb00w z_i^%YM-dkmlSptY{(o_d{|}Cc7{7H5J`x3m{J;Cr_knoJd$=LH@%>LZHf@@$A~7ib zVZ3qk1pR*)F@)3e{y!-HH=nm9vI$5Oz<>4fKiT}B*EF>bha$01z=|3Quvb>${|kEw Bb8`Ry literal 0 HcmV?d00001 diff --git a/doc/visual-programming/source/widgets/evaluate/images/Permutation-Plot-stamped.png b/doc/visual-programming/source/widgets/evaluate/images/Permutation-Plot-stamped.png new file mode 100644 index 0000000000000000000000000000000000000000..05f17cd7d45ce5fa99835ed0eedf1fd52bf904dc GIT binary patch literal 21321 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 Date: Fri, 12 Apr 2024 14:06:11 +0200 Subject: [PATCH 9/9] Documentation improvements. --- .../source/widgets/evaluate/permutationplot.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/visual-programming/source/widgets/evaluate/permutationplot.md b/doc/visual-programming/source/widgets/evaluate/permutationplot.md index 9593dc91266..755b1574df6 100644 --- a/doc/visual-programming/source/widgets/evaluate/permutationplot.md +++ b/doc/visual-programming/source/widgets/evaluate/permutationplot.md @@ -10,16 +10,16 @@ Check the validity and the degree of overfit for the input learner. ![](images/Permutation-Plot-stamped.png) -1. Select the number of permutation. The target variable is randomly permuted and separate learners are fitted to all the permuted y-variables. +1. Select the number of permutations. The target variable is randomly permuted and the learner is fitted to each permuted dataset. 2. Information on the model performance. 3. Get help, save the plot, make the report, set plot properties. 4. Observe the size and type of inputs. -The Permutation plot displays the correlation coefficient between the original y-variable and the permuted y-variable on the x-axis versus the cumulative R2/AUC on the y-axis, and draws a regression line. The intercept is a measure of the overfit. +The Permutation plot displays the Spearman's rank correlation coefficient between the permuted and original target variable on the x-axis versus the model score (R2 for regression, AUC for classification) on the y-axis. Two sets of points are shown, one for evaluations on the training data and one for cross-validation. A regression line is fitted to each set of points. The intercept is a measure of the overfit. Examples -------- -Here is a example on the housing data, where we analyze the performance of a [Random Forest](../model/randomforest.md) model. +Here is an example on the housing data, where we analyze the performance of a [Random Forest](../model/randomforest.md) model. ![](images/Permutation-Plot-example.png)