From 4ef1c8e5596472ceeed9bf2544faedba98ca7f10 Mon Sep 17 00:00:00 2001 From: Peter Shevcnenko <57573631+MorrisNein@users.noreply.github.com> Date: Wed, 6 Dec 2023 14:55:55 +0300 Subject: [PATCH] Metrics fixes, tests, refactoring, typing improvement (#1214) * fix f1 & precision evaluation * rem flattening from QualityMetric._simple_prediction * fix metrics hierarchy * improve typing of QualityMetricCallable * add tests for all supported types of metrics * return **kwargs to complexity metrics * use MetricCallable where it is appropriate * tests: simplify regression pipeline, type hints & additional assertion * remove __self__ from MetricCallable definition; remove default value for metric_by_id * rename & redo repository methods * rename MetricType -> MetricIDType * reorder module * rename metrics repo module * rename metrics test module * update test data pipeline pickle * correct typos * adapt documentation * fix inheritance * add kwargs and metric() to complexity metrics * better types for metrics_repository.py * combine all metrics to a single test; add default values check * unwrap pipeline predictions for time series * fix confusing naming * add fixture for expected values for faster tests * remove test for binary classification * add a less strict test for binary classification * fix random seed for binary classification * delete simple test for binary classification * delete binary test from `test_metrics` * fix structural_analysis_example.py * make `test_metrics` fall if `update_expected_values=True` * fix unused imports * add `str` to `MetricIDType` --------- Co-authored-by: kasyanovse --- .../credit_scoring_problem_multiobj.py | 2 +- .../evo_operators_comparison.py | 2 +- .../river_level_case_composer.py | 2 +- .../river_level_case_manual.py | 2 +- .../advanced/hyperparameters_tuning.rst | 12 +- docs/source/api/repository.rst | 2 +- .../classification_refinement_example.py | 2 +- .../regression_refinement_example.py | 2 +- .../multitask_classification_regression.py | 2 +- examples/advanced/pipeline_sensitivity.py | 8 +- .../structural_analysis_example.py | 25 +- .../composing_pipelines.py | 2 +- .../custom_model_tuning.py | 2 +- .../multi_ts_arctic_forecasting.py | 2 +- .../sparse_lagged_tuning.py | 2 +- .../classification_with_tuning.py | 2 +- .../classification/multiclass_prediction.py | 2 +- .../simple/classification/resample_example.py | 2 +- examples/simple/pipeline_tune.py | 2 +- examples/simple/pipeline_tuning_with_iopt.py | 2 +- .../regression/regression_with_tuning.py | 2 +- .../tuning_pipelines.py | 2 +- fedot/api/api_utils/api_composer.py | 4 +- fedot/api/builder.py | 16 +- fedot/api/main.py | 5 +- fedot/core/composer/composer_builder.py | 12 +- fedot/core/composer/metrics.py | 163 ++++++------- fedot/core/operations/atomized_model.py | 20 +- .../optimisers/objective/metrics_objective.py | 6 +- fedot/core/pipelines/pipeline.py | 3 + fedot/core/pipelines/tuning/tuner_builder.py | 6 +- ...cs_repository.py => metrics_repository.py} | 54 +++-- fedot/utilities/define_metric_by_task.py | 4 +- test/data/expected_metric_values.json | 65 +++++ test/integration/composer/test_composer.py | 2 +- test/integration/composer/test_history.py | 9 +- test/integration/optimizer/test_evaluation.py | 2 +- .../pipelines/tuning/test_pipeline_tuning.py | 2 +- .../pipelines/tuning/test_tuner_builder.py | 4 +- test/integration/validation/test_table_cv.py | 2 +- test/unit/api/test_api_params.py | 2 +- test/unit/composer/test_metrics.py | 227 ++++++++++++++++++ test/unit/composer/test_quality_metrics.py | 119 --------- .../optimizer/test_pipeline_objective_eval.py | 4 +- .../data/pred_ints_model_test.pickle | Bin 171132 -> 173414 bytes .../test_solver_mutations.py | 8 +- test/unit/tasks/test_regression.py | 10 +- test/unit/validation/test_table_cv.py | 2 +- test/unit/validation/test_time_series_cv.py | 4 +- 49 files changed, 510 insertions(+), 328 deletions(-) rename fedot/core/repository/{quality_metrics_repository.py => metrics_repository.py} (60%) create mode 100644 test/data/expected_metric_values.json create mode 100644 test/unit/composer/test_metrics.py delete mode 100644 test/unit/composer/test_quality_metrics.py diff --git a/cases/credit_scoring/credit_scoring_problem_multiobj.py b/cases/credit_scoring/credit_scoring_problem_multiobj.py index 01fccbb658..4a104531cb 100644 --- a/cases/credit_scoring/credit_scoring_problem_multiobj.py +++ b/cases/credit_scoring/credit_scoring_problem_multiobj.py @@ -15,7 +15,7 @@ from fedot.core.pipelines.pipeline_composer_requirements import PipelineComposerRequirements from fedot.core.pipelines.tuning.tuner_builder import TunerBuilder from fedot.core.repository.operation_types_repository import get_operations_for_task -from fedot.core.repository.quality_metrics_repository import ClassificationMetricsEnum, ComplexityMetricsEnum +from fedot.core.repository.metrics_repository import ClassificationMetricsEnum, ComplexityMetricsEnum from fedot.core.repository.tasks import Task, TaskTypesEnum from fedot.core.utils import set_random_seed diff --git a/cases/evo_operators_comparison/evo_operators_comparison.py b/cases/evo_operators_comparison/evo_operators_comparison.py index c9554b7cea..1a89fe81be 100644 --- a/cases/evo_operators_comparison/evo_operators_comparison.py +++ b/cases/evo_operators_comparison/evo_operators_comparison.py @@ -16,7 +16,7 @@ from fedot.core.pipelines.pipeline_composer_requirements import PipelineComposerRequirements from fedot.core.repository.operation_types_repository import get_operations_for_task -from fedot.core.repository.quality_metrics_repository import ClassificationMetricsEnum +from fedot.core.repository.metrics_repository import ClassificationMetricsEnum from fedot.core.repository.tasks import Task, TaskTypesEnum from fedot.core.utils import fedot_project_root diff --git a/cases/river_levels_prediction/river_level_case_composer.py b/cases/river_levels_prediction/river_level_case_composer.py index 138dd1efc9..b60f51e3cc 100644 --- a/cases/river_levels_prediction/river_level_case_composer.py +++ b/cases/river_levels_prediction/river_level_case_composer.py @@ -16,7 +16,7 @@ from fedot.core.pipelines.pipeline import Pipeline from fedot.core.pipelines.pipeline_composer_requirements import PipelineComposerRequirements from fedot.core.pipelines.tuning.tuner_builder import TunerBuilder -from fedot.core.repository.quality_metrics_repository import \ +from fedot.core.repository.metrics_repository import \ RegressionMetricsEnum from fedot.core.repository.tasks import Task, TaskTypesEnum diff --git a/cases/river_levels_prediction/river_level_case_manual.py b/cases/river_levels_prediction/river_level_case_manual.py index d63431e0ac..a85308ce73 100644 --- a/cases/river_levels_prediction/river_level_case_manual.py +++ b/cases/river_levels_prediction/river_level_case_manual.py @@ -11,7 +11,7 @@ from fedot.core.pipelines.node import PipelineNode from fedot.core.pipelines.pipeline import Pipeline from fedot.core.pipelines.tuning.tuner_builder import TunerBuilder -from fedot.core.repository.quality_metrics_repository import RegressionMetricsEnum +from fedot.core.repository.metrics_repository import RegressionMetricsEnum from fedot.core.repository.tasks import Task, TaskTypesEnum warnings.filterwarnings('ignore') diff --git a/docs/source/advanced/hyperparameters_tuning.rst b/docs/source/advanced/hyperparameters_tuning.rst index 65484a2a50..0a6c377130 100644 --- a/docs/source/advanced/hyperparameters_tuning.rst +++ b/docs/source/advanced/hyperparameters_tuning.rst @@ -378,7 +378,7 @@ Example for ``SimultaneousTuner``: from fedot.core.pipelines.pipeline_builder import PipelineBuilder from fedot.core.pipelines.tuning.search_space import PipelineSearchSpace from fedot.core.pipelines.tuning.tuner_builder import TunerBuilder - from fedot.core.repository.quality_metrics_repository import ClassificationMetricsEnum + from fedot.core.repository.metrics_repository import ClassificationMetricsEnum from fedot.core.repository.tasks import TaskTypesEnum, Task task = Task(TaskTypesEnum.classification) @@ -466,7 +466,7 @@ Example for ``IOptTuner``: from fedot.core.pipelines.pipeline_builder import PipelineBuilder from fedot.core.pipelines.pipeline_composer_requirements import PipelineComposerRequirements from fedot.core.pipelines.tuning.tuner_builder import TunerBuilder - from fedot.core.repository.quality_metrics_repository import RegressionMetricsEnum + from fedot.core.repository.metrics_repository import RegressionMetricsEnum from fedot.core.repository.tasks import TaskTypesEnum, Task task = Task(TaskTypesEnum.regression) @@ -517,7 +517,7 @@ Example for ``OptunaTuner``: from fedot.core.data.data import InputData from fedot.core.pipelines.pipeline_builder import PipelineBuilder from fedot.core.pipelines.tuning.tuner_builder import TunerBuilder - from fedot.core.repository.quality_metrics_repository import RegressionMetricsEnum + from fedot.core.repository.metrics_repository import RegressionMetricsEnum from fedot.core.repository.tasks import TaskTypesEnum, Task task = Task(TaskTypesEnum.regression) @@ -568,7 +568,7 @@ and obtain a list of tuned pipelines representing a pareto front after tuning. from fedot.core.pipelines.pipeline import Pipeline from fedot.core.pipelines.pipeline_builder import PipelineBuilder from fedot.core.pipelines.tuning.tuner_builder import TunerBuilder - from fedot.core.repository.quality_metrics_repository import RegressionMetricsEnum + from fedot.core.repository.metrics_repository import RegressionMetricsEnum from fedot.core.repository.tasks import TaskTypesEnum, Task task = Task(TaskTypesEnum.regression) @@ -603,7 +603,7 @@ Sequential tuning from fedot.core.data.data import InputData from fedot.core.pipelines.pipeline_builder import PipelineBuilder from fedot.core.pipelines.tuning.tuner_builder import TunerBuilder - from fedot.core.repository.quality_metrics_repository import RegressionMetricsEnum + from fedot.core.repository.metrics_repository import RegressionMetricsEnum from fedot.core.repository.tasks import TaskTypesEnum, Task, TsForecastingParams task = Task(TaskTypesEnum.ts_forecasting, TsForecastingParams(forecast_length=10)) @@ -663,7 +663,7 @@ Tuning of a node from fedot.core.pipelines.pipeline_composer_requirements import PipelineComposerRequirements from fedot.core.pipelines.pipeline_builder import PipelineBuilder from fedot.core.pipelines.tuning.tuner_builder import TunerBuilder - from fedot.core.repository.quality_metrics_repository import RegressionMetricsEnum + from fedot.core.repository.metrics_repository import RegressionMetricsEnum from fedot.core.repository.tasks import TaskTypesEnum, Task from test.integration.quality.test_synthetic_tasks import get_regression_data diff --git a/docs/source/api/repository.rst b/docs/source/api/repository.rst index 962d106259..1f2d3b58b4 100644 --- a/docs/source/api/repository.rst +++ b/docs/source/api/repository.rst @@ -16,7 +16,7 @@ Operation Types Quality metrics --------------- -.. automodule:: fedot.core.repository.quality_metrics_repository +.. automodule:: fedot.core.repository.metrics_repository :members: :no-undoc-members: diff --git a/examples/advanced/decompose/classification_refinement_example.py b/examples/advanced/decompose/classification_refinement_example.py index 5dc05ec479..1806571eb3 100644 --- a/examples/advanced/decompose/classification_refinement_example.py +++ b/examples/advanced/decompose/classification_refinement_example.py @@ -5,7 +5,7 @@ from fedot.core.pipelines.node import PipelineNode from fedot.core.pipelines.pipeline import Pipeline from fedot.core.pipelines.tuning.tuner_builder import TunerBuilder -from fedot.core.repository.quality_metrics_repository import ClassificationMetricsEnum +from fedot.core.repository.metrics_repository import ClassificationMetricsEnum from fedot.core.repository.tasks import Task, TaskTypesEnum from fedot.core.utils import set_random_seed diff --git a/examples/advanced/decompose/regression_refinement_example.py b/examples/advanced/decompose/regression_refinement_example.py index c7a3c93d81..e371375937 100644 --- a/examples/advanced/decompose/regression_refinement_example.py +++ b/examples/advanced/decompose/regression_refinement_example.py @@ -11,7 +11,7 @@ from fedot.core.pipelines.pipeline import Pipeline from fedot.core.pipelines.tuning.tuner_builder import TunerBuilder from fedot.core.repository.dataset_types import DataTypesEnum -from fedot.core.repository.quality_metrics_repository import RegressionMetricsEnum +from fedot.core.repository.metrics_repository import RegressionMetricsEnum from fedot.core.repository.tasks import Task, TaskTypesEnum from fedot.core.utils import fedot_project_root diff --git a/examples/advanced/multitask_classification_regression.py b/examples/advanced/multitask_classification_regression.py index 9d3dda5719..e4f31ee7c5 100644 --- a/examples/advanced/multitask_classification_regression.py +++ b/examples/advanced/multitask_classification_regression.py @@ -12,7 +12,7 @@ from fedot.core.pipelines.pipeline import Pipeline from fedot.core.pipelines.tuning.tuner_builder import TunerBuilder from fedot.core.repository.dataset_types import DataTypesEnum -from fedot.core.repository.quality_metrics_repository import RegressionMetricsEnum +from fedot.core.repository.metrics_repository import RegressionMetricsEnum from fedot.core.repository.tasks import TaskTypesEnum, Task from fedot.core.utils import fedot_project_root diff --git a/examples/advanced/pipeline_sensitivity.py b/examples/advanced/pipeline_sensitivity.py index fe6ada2a7c..4ea82fb952 100644 --- a/examples/advanced/pipeline_sensitivity.py +++ b/examples/advanced/pipeline_sensitivity.py @@ -11,7 +11,7 @@ from fedot.core.data.data import InputData from fedot.core.data.data_split import train_test_data_setup from fedot.core.repository.operation_types_repository import get_operations_for_task -from fedot.core.repository.quality_metrics_repository import ClassificationMetricsEnum, MetricsRepository, \ +from fedot.core.repository.metrics_repository import ClassificationMetricsEnum, MetricsRepository, \ RegressionMetricsEnum from fedot.core.repository.tasks import Task, TaskTypesEnum from fedot.core.utils import default_fedot_data_dir, fedot_project_root @@ -123,7 +123,7 @@ def run_class_scoring_case(is_composed: bool, path_to_save=None): train_data, test_data = get_scoring_data() task = Task(TaskTypesEnum.classification) # the choice of the metric for the pipeline quality assessment during composition - metric_function = MetricsRepository().metric_by_id(ClassificationMetricsEnum.ROCAUC_penalty) + metric_function = MetricsRepository.get_metric(ClassificationMetricsEnum.ROCAUC_penalty) if is_composed: case = 'scoring_composed' @@ -141,7 +141,7 @@ def run_class_kc2_case(is_composed: bool = False, path_to_save=None): train_data, test_data = get_kc2_data() task = Task(TaskTypesEnum.classification) # the choice of the metric for the pipeline quality assessment during composition - metric_function = MetricsRepository().metric_by_id(ClassificationMetricsEnum.ROCAUC_penalty) + metric_function = MetricsRepository.get_metric(ClassificationMetricsEnum.ROCAUC_penalty) if is_composed: case = 'kc2_composed' @@ -159,7 +159,7 @@ def run_regr_case(is_composed: bool = False, path_to_save=None): train_data, test_data = get_cholesterol_data() task = Task(TaskTypesEnum.regression) # the choice of the metric for the pipeline quality assessment during composition - metric_function = MetricsRepository().metric_by_id(RegressionMetricsEnum.RMSE) + metric_function = MetricsRepository.get_metric(RegressionMetricsEnum.RMSE) if is_composed: case = 'cholesterol_composed' diff --git a/examples/advanced/structural_analysis/structural_analysis_example.py b/examples/advanced/structural_analysis/structural_analysis_example.py index 1a58e5c0f7..482f92b538 100644 --- a/examples/advanced/structural_analysis/structural_analysis_example.py +++ b/examples/advanced/structural_analysis/structural_analysis_example.py @@ -1,15 +1,15 @@ import os from copy import deepcopy from functools import partial +from typing import Callable, Dict, List, Optional, Tuple from golem.core.dag.graph_verifier import GraphVerifier -from golem.core.dag.verification_rules import has_one_root, has_no_cycle, has_no_isolated_components, \ - has_no_isolated_nodes, has_no_self_cycled_nodes +from golem.core.dag.verification_rules import (has_no_cycle, has_no_isolated_components, has_no_isolated_nodes, + has_no_self_cycled_nodes, has_one_root) from golem.core.optimisers.graph import OptGraph from golem.core.optimisers.objective import Objective from golem.structural_analysis.graph_sa.graph_structural_analysis import GraphStructuralAnalysis from golem.structural_analysis.graph_sa.sa_requirements import StructuralAnalysisRequirements -from typing import Callable, Dict, Any, Optional, Tuple, List from examples.advanced.structural_analysis.dataset_access import get_scoring_data from examples.advanced.structural_analysis.pipelines_access import get_three_depth_manual_class_pipeline @@ -20,8 +20,9 @@ from fedot.core.pipelines.pipeline_advisor import PipelineChangeAdvisor from fedot.core.pipelines.pipeline_composer_requirements import PipelineComposerRequirements from fedot.core.pipelines.pipeline_node_factory import PipelineOptNodeFactory -from fedot.core.repository.quality_metrics_repository import ClassificationMetricsEnum, QualityMetricsEnum, \ - MetricsRepository +from fedot.core.repository.metrics_repository import (ClassificationMetricsEnum, ComplexityMetricCallable, + ComplexityMetricsEnum, MetricsRepository, + QualityMetricCallable, QualityMetricsEnum) from fedot.core.repository.tasks import Task, TaskTypesEnum from fedot.utilities.project_import_export import DEFAULT_PATH @@ -31,19 +32,20 @@ class SAObjective(Objective): This objective has to evaluate pipeline in __call__ method and have 'metrics' field to identify which metrics are optimized. """ + def __init__(self, objective: Callable, - quality_metrics: Dict[Any, Callable], - complexity_metrics: Optional[Dict[Any, Callable]] = None, + quality_metrics: Dict[QualityMetricsEnum, QualityMetricCallable], + complexity_metrics: Optional[Dict[ComplexityMetricsEnum, ComplexityMetricCallable]] = None, is_multi_objective: bool = False, ): self.objective = objective super().__init__(quality_metrics=quality_metrics, complexity_metrics=complexity_metrics, is_multi_objective=is_multi_objective) - def __call__(self, graph: OptGraph) -> float: + def __call__(self, graph: OptGraph, **kwargs) -> float: pip = PipelineAdapter().restore(graph) - return self.objective(pip) + return self.objective(pip, **kwargs) def structural_analysis_set_up(train_data: InputData, test_data: InputData, @@ -53,11 +55,12 @@ def structural_analysis_set_up(train_data: InputData, test_data: InputData, -> Tuple[PipelineOptNodeFactory, SAObjective, SAObjective]: """ Build initial infrastructure for performing SA: node factory, objectives. Can be reused for other SA applications, appropriate parameters must be specified then. """ + def _construct_objective(data: InputData, metric: QualityMetricsEnum) -> SAObjective: """ Build objective function with fit and predict functions inside. """ - metric_func = MetricsRepository.metric_by_id(metric) + metric_func = MetricsRepository.get_metric(metric) get_value = partial(metric_func, reference_data=data) - metrics_ = {metric: data} + metrics_ = {metric: metric_func} data_producer = DataSourceSplitter().build(data=data) objective_function = PipelineObjectiveEvaluate(objective=Objective(quality_metrics=get_value), diff --git a/examples/advanced/time_series_forecasting/composing_pipelines.py b/examples/advanced/time_series_forecasting/composing_pipelines.py index f5d25cda85..a2cacd3e20 100644 --- a/examples/advanced/time_series_forecasting/composing_pipelines.py +++ b/examples/advanced/time_series_forecasting/composing_pipelines.py @@ -16,7 +16,7 @@ from fedot.core.data.data_split import train_test_data_setup from fedot.core.pipelines.pipeline import Pipeline from fedot.core.repository.dataset_types import DataTypesEnum -from fedot.core.repository.quality_metrics_repository import \ +from fedot.core.repository.metrics_repository import \ RegressionMetricsEnum from fedot.core.repository.tasks import Task, TaskTypesEnum, TsForecastingParams from fedot.core.utils import fedot_project_root diff --git a/examples/advanced/time_series_forecasting/custom_model_tuning.py b/examples/advanced/time_series_forecasting/custom_model_tuning.py index e8b97f19ad..32f0c5d078 100644 --- a/examples/advanced/time_series_forecasting/custom_model_tuning.py +++ b/examples/advanced/time_series_forecasting/custom_model_tuning.py @@ -12,7 +12,7 @@ from fedot.core.pipelines.tuning.search_space import PipelineSearchSpace from fedot.core.pipelines.tuning.tuner_builder import TunerBuilder from fedot.core.repository.dataset_types import DataTypesEnum -from fedot.core.repository.quality_metrics_repository import RegressionMetricsEnum +from fedot.core.repository.metrics_repository import RegressionMetricsEnum from fedot.core.repository.tasks import TaskTypesEnum, Task, TsForecastingParams diff --git a/examples/advanced/time_series_forecasting/multi_ts_arctic_forecasting.py b/examples/advanced/time_series_forecasting/multi_ts_arctic_forecasting.py index 36aa166bec..9c4f309cab 100644 --- a/examples/advanced/time_series_forecasting/multi_ts_arctic_forecasting.py +++ b/examples/advanced/time_series_forecasting/multi_ts_arctic_forecasting.py @@ -15,7 +15,7 @@ from fedot.core.composer.composer_builder import ComposerBuilder from fedot.core.composer.gp_composer.specific_operators import parameter_change_mutation from fedot.core.pipelines.tuning.tuner_builder import TunerBuilder -from fedot.core.repository.quality_metrics_repository import \ +from fedot.core.repository.metrics_repository import \ RegressionMetricsEnum diff --git a/examples/advanced/time_series_forecasting/sparse_lagged_tuning.py b/examples/advanced/time_series_forecasting/sparse_lagged_tuning.py index d3d0d77690..ddbf36e46e 100644 --- a/examples/advanced/time_series_forecasting/sparse_lagged_tuning.py +++ b/examples/advanced/time_series_forecasting/sparse_lagged_tuning.py @@ -12,7 +12,7 @@ from fedot.core.data.data import InputData from fedot.core.pipelines.tuning.tuner_builder import TunerBuilder from fedot.core.repository.dataset_types import DataTypesEnum -from fedot.core.repository.quality_metrics_repository import RegressionMetricsEnum +from fedot.core.repository.metrics_repository import RegressionMetricsEnum from fedot.core.repository.tasks import Task, TaskTypesEnum, TsForecastingParams from fedot.core.utils import fedot_project_root diff --git a/examples/simple/classification/classification_with_tuning.py b/examples/simple/classification/classification_with_tuning.py index 1eef76d04c..c1b024c91f 100644 --- a/examples/simple/classification/classification_with_tuning.py +++ b/examples/simple/classification/classification_with_tuning.py @@ -7,7 +7,7 @@ from fedot.core.data.data import InputData from fedot.core.pipelines.tuning.tuner_builder import TunerBuilder from fedot.core.repository.dataset_types import DataTypesEnum -from fedot.core.repository.quality_metrics_repository import ClassificationMetricsEnum +from fedot.core.repository.metrics_repository import ClassificationMetricsEnum from fedot.core.repository.tasks import Task, TaskTypesEnum from fedot.core.utils import set_random_seed from fedot.utilities.synth_dataset_generator import classification_dataset diff --git a/examples/simple/classification/multiclass_prediction.py b/examples/simple/classification/multiclass_prediction.py index cb0a69457c..eca3ca3897 100644 --- a/examples/simple/classification/multiclass_prediction.py +++ b/examples/simple/classification/multiclass_prediction.py @@ -16,7 +16,7 @@ from fedot.core.data.data import InputData from fedot.core.pipelines.pipeline import Pipeline from fedot.core.repository.operation_types_repository import OperationTypesRepository -from fedot.core.repository.quality_metrics_repository import ClassificationMetricsEnum +from fedot.core.repository.metrics_repository import ClassificationMetricsEnum from fedot.core.repository.tasks import Task, TaskTypesEnum from fedot.core.utils import ( ensure_directory_exists, diff --git a/examples/simple/classification/resample_example.py b/examples/simple/classification/resample_example.py index 27259974c3..fdf505526b 100644 --- a/examples/simple/classification/resample_example.py +++ b/examples/simple/classification/resample_example.py @@ -12,7 +12,7 @@ from fedot.core.data.data import InputData from fedot.core.pipelines.tuning.tuner_builder import TunerBuilder from fedot.core.repository.dataset_types import DataTypesEnum -from fedot.core.repository.quality_metrics_repository import RegressionMetricsEnum +from fedot.core.repository.metrics_repository import RegressionMetricsEnum from fedot.core.repository.tasks import TaskTypesEnum, Task from fedot.core.utils import fedot_project_root diff --git a/examples/simple/pipeline_tune.py b/examples/simple/pipeline_tune.py index f6902ee9e8..66ce2608d8 100644 --- a/examples/simple/pipeline_tune.py +++ b/examples/simple/pipeline_tune.py @@ -9,7 +9,7 @@ from fedot.core.data.data import InputData from fedot.core.pipelines.pipeline import Pipeline from fedot.core.pipelines.tuning.tuner_builder import TunerBuilder -from fedot.core.repository.quality_metrics_repository import ClassificationMetricsEnum +from fedot.core.repository.metrics_repository import ClassificationMetricsEnum def get_case_train_test_data(): diff --git a/examples/simple/pipeline_tuning_with_iopt.py b/examples/simple/pipeline_tuning_with_iopt.py index 1665514c81..dee9153183 100644 --- a/examples/simple/pipeline_tuning_with_iopt.py +++ b/examples/simple/pipeline_tuning_with_iopt.py @@ -7,7 +7,7 @@ from fedot.core.pipelines.pipeline_builder import PipelineBuilder from fedot.core.pipelines.pipeline_composer_requirements import PipelineComposerRequirements from fedot.core.pipelines.tuning.tuner_builder import TunerBuilder -from fedot.core.repository.quality_metrics_repository import RegressionMetricsEnum +from fedot.core.repository.metrics_repository import RegressionMetricsEnum from fedot.core.repository.tasks import TaskTypesEnum, Task from fedot.core.utils import fedot_project_root diff --git a/examples/simple/regression/regression_with_tuning.py b/examples/simple/regression/regression_with_tuning.py index 665a6725bd..88acfbb903 100644 --- a/examples/simple/regression/regression_with_tuning.py +++ b/examples/simple/regression/regression_with_tuning.py @@ -9,7 +9,7 @@ from fedot.core.data.data import InputData from fedot.core.pipelines.tuning.tuner_builder import TunerBuilder from fedot.core.repository.dataset_types import DataTypesEnum -from fedot.core.repository.quality_metrics_repository import RegressionMetricsEnum +from fedot.core.repository.metrics_repository import RegressionMetricsEnum from fedot.core.repository.tasks import Task, TaskTypesEnum from fedot.core.utils import set_random_seed from fedot.utilities.synth_dataset_generator import regression_dataset diff --git a/examples/simple/time_series_forecasting/tuning_pipelines.py b/examples/simple/time_series_forecasting/tuning_pipelines.py index 18d12ec7a9..4127dc6b4f 100644 --- a/examples/simple/time_series_forecasting/tuning_pipelines.py +++ b/examples/simple/time_series_forecasting/tuning_pipelines.py @@ -10,7 +10,7 @@ from fedot.core.pipelines.pipeline import Pipeline from fedot.core.pipelines.tuning.tuner_builder import TunerBuilder from fedot.core.repository.dataset_types import DataTypesEnum -from fedot.core.repository.quality_metrics_repository import RegressionMetricsEnum +from fedot.core.repository.metrics_repository import RegressionMetricsEnum from fedot.core.repository.tasks import Task, TaskTypesEnum, TsForecastingParams from fedot.core.utils import fedot_project_root diff --git a/fedot/api/api_utils/api_composer.py b/fedot/api/api_utils/api_composer.py index 7bb1f2dabf..5a0545a85b 100644 --- a/fedot/api/api_utils/api_composer.py +++ b/fedot/api/api_utils/api_composer.py @@ -17,12 +17,12 @@ from fedot.core.data.data import InputData from fedot.core.pipelines.pipeline import Pipeline from fedot.core.pipelines.tuning.tuner_builder import TunerBuilder -from fedot.core.repository.quality_metrics_repository import MetricType +from fedot.core.repository.metrics_repository import MetricIDType class ApiComposer: - def __init__(self, api_params: ApiParams, metrics: Union[str, MetricType, Sequence]): + def __init__(self, api_params: ApiParams, metrics: Union[MetricIDType, Sequence[MetricIDType]]): self.log = default_log(self) self.params = api_params self.metrics = metrics diff --git a/fedot/api/builder.py b/fedot/api/builder.py index 449b076833..52b8f85ca8 100644 --- a/fedot/api/builder.py +++ b/fedot/api/builder.py @@ -1,12 +1,12 @@ from __future__ import annotations -from typing import Any, Callable, Dict, List, Optional, Type, Union +from typing import Any, Dict, List, Optional, Sequence, Type, Union from golem.core.optimisers.optimizer import GraphOptimizer from fedot.api.main import Fedot from fedot.core.pipelines.pipeline import Pipeline -from fedot.core.repository.quality_metrics_repository import MetricsEnum +from fedot.core.repository.metrics_repository import MetricIDType from fedot.core.repository.tasks import TaskParams @@ -274,7 +274,7 @@ def setup_pipeline_structure( def setup_pipeline_evaluation( self, - metric: Union[str, Callable, MetricsEnum, List[Union[str, Callable, MetricsEnum]]] = DEFAULT_VALUE, + metric: Union[MetricIDType, Sequence[MetricIDType]] = DEFAULT_VALUE, cv_folds: int = DEFAULT_VALUE, max_pipeline_fit_time: Optional[int] = DEFAULT_VALUE, collect_intermediate_metric: bool = DEFAULT_VALUE, @@ -287,19 +287,19 @@ def setup_pipeline_evaluation( .. details:: Default value depends on a given task: - - ``roc_auc`` -> for classification + - ``roc_auc_pen`` -> for classification - ``rmse`` -> for regression & time series forecasting .. details:: Available metrics are listed in the following enumerations: - classification -> \ - :class:`~fedot.core.repository.quality_metrics_repository.ClassificationMetricsEnum` + :class:`~fedot.core.repository.metrics_repository.ClassificationMetricsEnum` - regression -> \ - :class:`~fedot.core.repository.quality_metrics_repository.RegressionMetricsEnum` + :class:`~fedot.core.repository.metrics_repository.RegressionMetricsEnum` - time series forcasting -> \ - :class:`~fedot.core.repository.quality_metrics_repository.TimeSeriesForecastingMetricsEnum` + :class:`~fedot.core.repository.metrics_repository.TimeSeriesForecastingMetricsEnum` - pipeline complexity (task-independent) -> \ - :class:`~fedot.core.repository.quality_metrics_repository.ComplexityMetricsEnum` + :class:`~fedot.core.repository.metrics_repository.ComplexityMetricsEnum` cv_folds: number of folds for cross-validation. diff --git a/fedot/api/main.py b/fedot/api/main.py index f1e6718adc..2c19b86f5f 100644 --- a/fedot/api/main.py +++ b/fedot/api/main.py @@ -1,6 +1,6 @@ import logging from copy import deepcopy -from typing import Any, Callable, List, Optional, Sequence, Tuple, Union +from typing import Any, List, Optional, Sequence, Tuple, Union import numpy as np import pandas as pd @@ -26,6 +26,7 @@ from fedot.core.pipelines.pipeline import Pipeline from fedot.core.pipelines.ts_wrappers import convert_forecast_to_output, out_of_sample_ts_forecast from fedot.core.pipelines.tuning.tuner_builder import TunerBuilder +from fedot.core.repository.metrics_repository import MetricCallable from fedot.core.repository.tasks import TaskParams, TaskTypesEnum from fedot.core.utils import set_random_seed from fedot.explainability.explainer_template import Explainer @@ -187,7 +188,7 @@ def fit(self, def tune(self, input_data: Optional[InputData] = None, - metric_name: Optional[Union[str, Callable]] = None, + metric_name: Optional[Union[str, MetricCallable]] = None, iterations: int = DEFAULT_TUNING_ITERATIONS_NUMBER, timeout: Optional[float] = None, cv_folds: Optional[int] = None, diff --git a/fedot/core/composer/composer_builder.py b/fedot/core/composer/composer_builder.py index a3eae7e1cf..73217e2575 100644 --- a/fedot/core/composer/composer_builder.py +++ b/fedot/core/composer/composer_builder.py @@ -6,8 +6,8 @@ from golem.core.log import LoggerAdapter, default_log from golem.core.optimisers.genetic.gp_optimizer import EvoGraphOptimizer from golem.core.optimisers.genetic.gp_params import GPAlgorithmParameters -from golem.core.optimisers.initial_graphs_generator import InitialPopulationGenerator, GenerationFunction -from golem.core.optimisers.optimizer import GraphOptimizer, AlgorithmParameters, GraphGenerationParams +from golem.core.optimisers.initial_graphs_generator import GenerationFunction, InitialPopulationGenerator +from golem.core.optimisers.optimizer import AlgorithmParameters, GraphGenerationParams, GraphOptimizer from golem.utilities.data_structures import ensure_wrapped_in_sequence from fedot.core.caching.pipelines_cache import OperationsCache @@ -20,11 +20,7 @@ from fedot.core.pipelines.pipeline_graph_generation_params import get_pipeline_generation_params from fedot.core.pipelines.verification import rules_by_task from fedot.core.repository.operation_types_repository import get_operations_for_task -from fedot.core.repository.quality_metrics_repository import ( - ComplexityMetricsEnum, - MetricsEnum, - MetricType -) +from fedot.core.repository.metrics_repository import ComplexityMetricsEnum, MetricIDType, MetricsEnum from fedot.core.repository.tasks import Task from fedot.remote.remote_evaluator import RemoteEvaluator from fedot.utilities.define_metric_by_task import MetricByTask @@ -88,7 +84,7 @@ def with_graph_generation_param(self, graph_generation_params: GraphGenerationPa self.graph_generation_params = graph_generation_params return self - def with_metrics(self, metrics: Union[MetricType, List[MetricType]]): + def with_metrics(self, metrics: Union[MetricIDType, Sequence[MetricIDType]]): self.metrics = ensure_wrapped_in_sequence(metrics) return self diff --git a/fedot/core/composer/metrics.py b/fedot/core/composer/metrics.py index f0eb97f886..20c6fdd395 100644 --- a/fedot/core/composer/metrics.py +++ b/fedot/core/composer/metrics.py @@ -1,7 +1,9 @@ import os.path import sys from abc import abstractmethod +from functools import wraps from pathlib import Path +from typing import Optional, Tuple from uuid import uuid4 import numpy as np @@ -14,13 +16,13 @@ from fedot.core.pipelines.pipeline import Pipeline from fedot.core.pipelines.ts_wrappers import in_sample_ts_forecast from fedot.core.repository.dataset_types import DataTypesEnum -from fedot.core.repository.tasks import TaskTypesEnum from fedot.core.utils import default_fedot_data_dir from fedot.utilities.custom_errors import AbstractMethodNotImplementError from fedot.utilities.debug import is_analytic_mode def from_maximised_metric(metric_func): + @wraps(metric_func) def wrapper(*args, **kwargs): return -metric_func(*args, **kwargs) @@ -38,23 +40,14 @@ def smape(y_true: np.ndarray, y_pred: np.ndarray) -> float: class Metric: - output_mode = 'default' - default_value = 0 - @classmethod @abstractmethod - def get_value(cls, pipeline: 'Pipeline', reference_data: InputData, - validation_blocks: int) -> float: - """ Get metrics values based on pipeline and InputData for validation """ + def get_value(cls, **kwargs) -> float: + """ Get metrics value based on pipeline and other optional arguments. """ raise AbstractMethodNotImplementError - @staticmethod - @abstractmethod - def metric(reference: InputData, predicted: OutputData) -> float: - raise AbstractMethodNotImplementError - -class QualityMetric: +class QualityMetric(Metric): max_penalty_part = 0.01 output_mode = 'default' default_value = 0 @@ -65,13 +58,20 @@ def metric(reference: InputData, predicted: OutputData) -> float: raise AbstractMethodNotImplementError @classmethod - def get_value(cls, pipeline: 'Pipeline', reference_data: InputData, - validation_blocks: int = None) -> float: + def get_value(cls, pipeline: Pipeline, reference_data: InputData, + validation_blocks: Optional[int] = None) -> float: + """ Get metric value based on pipeline, reference data, and number of validation blocks. + Args: + pipeline: a :class:`Pipeline` instance for evaluation. + reference_data: :class:`InputData` for evaluation. + validation_blocks: number of validation blocks. Used only for time series forecasting. + If ``None``, data separation is not performed. + """ metric = cls.default_value try: if validation_blocks is None: # Time series or regression classical hold-out validation - results, reference_data = cls._simple_prediction(pipeline, reference_data) + reference_data, results = cls._simple_prediction(pipeline, reference_data) else: # Perform time series in-sample validation reference_data, results = cls._in_sample_prediction(pipeline, reference_data, validation_blocks) @@ -95,43 +95,14 @@ def get_value(cls, pipeline: 'Pipeline', reference_data: InputData, return metric @classmethod - def _simple_prediction(cls, pipeline: 'Pipeline', reference_data: InputData): - """ Method prepares data for metric evaluation and perform simple validation """ - results = pipeline.predict(reference_data, output_mode=cls.output_mode) - - # Define conditions for target and predictions transforming - is_regression = reference_data.task.task_type == TaskTypesEnum.regression - is_multi_target = len(np.array(results.predict).shape) > 1 - is_multi_target_regression = is_regression and is_multi_target - - # Time series forecasting - is_ts_forecasting = reference_data.task.task_type == TaskTypesEnum.ts_forecasting - if is_ts_forecasting or is_multi_target_regression: - results, reference_data = cls.flatten_convert(results, reference_data) - - return results, reference_data - - @staticmethod - def flatten_convert(results, reference_data): - """ Transform target and predictions by converting them into - one-dimensional array - - :param results: output from pipeline - :param reference_data: actual data for validation - """ - # Predictions convert into uni-variate array - forecast_values = np.ravel(np.array(results.predict)) - results.predict = forecast_values - # Target convert into uni-variate array - target_values = np.ravel(np.array(reference_data.target)) - reference_data.target = target_values - - return results, reference_data + def _simple_prediction(cls, pipeline: Pipeline, reference_data: InputData) -> Tuple[InputData, OutputData]: + """ Method calls pipeline.predict() and returns the result. """ + return reference_data, pipeline.predict(reference_data, output_mode=cls.output_mode) @classmethod - def get_value_with_penalty(cls, pipeline: 'Pipeline', reference_data: InputData, - validation_blocks: int = None) -> float: - quality_metric = cls.get_value(pipeline, reference_data) + def get_value_with_penalty(cls, pipeline: Pipeline, reference_data: InputData, + validation_blocks: Optional[int] = None) -> float: + quality_metric = cls.get_value(pipeline, reference_data, validation_blocks) structural_metric = StructuralComplexity.get_value(pipeline) penalty = abs(structural_metric * quality_metric * cls.max_penalty_part) @@ -140,7 +111,8 @@ def get_value_with_penalty(cls, pipeline: 'Pipeline', reference_data: InputData, return metric_with_penalty @staticmethod - def _in_sample_prediction(pipeline: 'Pipeline', data: InputData, validation_blocks: int): + def _in_sample_prediction(pipeline: Pipeline, data: InputData, validation_blocks: int + ) -> Tuple[InputData, OutputData]: """ Performs in-sample pipeline validation for time series prediction """ horizon = int(validation_blocks * data.task.task_params.forecast_length) @@ -160,6 +132,14 @@ def _in_sample_prediction(pipeline: 'Pipeline', data: InputData, validation_bloc return reference_data, results + @staticmethod + def _get_least_frequent_val(array: np.ndarray): + """ Returns the least frequent value in a flattened numpy array. """ + unique_vals, count = np.unique(np.ravel(array), return_counts=True) + least_frequent_idx = np.argmin(count) + least_frequent_val = unique_vals[least_frequent_idx] + return least_frequent_val + class RMSE(QualityMetric): default_value = sys.maxsize @@ -214,16 +194,12 @@ class F1(QualityMetric): @staticmethod @from_maximised_metric def metric(reference: InputData, predicted: OutputData) -> float: - n_classes = reference.num_classes - if n_classes > 2: - additional_params = {'average': F1.multiclass_averaging_mode} + if reference.num_classes == 2: + pos_label = QualityMetric._get_least_frequent_val(reference.target) + additional_params = dict(average=F1.binary_averaging_mode, pos_label=pos_label) else: - u, count = np.unique(np.ravel(reference.target), return_counts=True) - count_sort_ind = np.argsort(count) - pos_label = u[count_sort_ind[0]].item() - additional_params = {'average': F1.binary_averaging_mode, 'pos_label': pos_label} - return f1_score(y_true=reference.target, y_pred=predicted.predict, - **additional_params) + additional_params = dict(average=F1.multiclass_averaging_mode) + return f1_score(y_true=reference.target, y_pred=predicted.predict, **additional_params) class MAE(QualityMetric): @@ -259,15 +235,13 @@ class ROCAUC(QualityMetric): def metric(reference: InputData, predicted: OutputData) -> float: n_classes = reference.num_classes if n_classes > 2: - additional_params = {'multi_class': 'ovr', 'average': 'macro'} + additional_params = dict(multi_class='ovr', average='macro') else: - additional_params = {} + additional_params = dict() - score = round(roc_auc_score(y_score=predicted.predict, - y_true=reference.target, - **additional_params), 3) - - return score + return roc_auc_score(y_score=predicted.predict, + y_true=reference.target, + **additional_params) @staticmethod def roc_curve(target: np.ndarray, predict: np.ndarray, pos_label=None): @@ -281,20 +255,19 @@ def auc(cls, fpr, tpr): class Precision(QualityMetric): output_mode = 'labels' + binary_averaging_mode = 'binary' + multiclass_averaging_mode = 'macro' @staticmethod @from_maximised_metric def metric(reference: InputData, predicted: OutputData) -> float: n_classes = reference.num_classes if n_classes > 2: - return precision_score(y_true=reference.target, y_pred=predicted.predict) + additional_params = dict(average=Precision.multiclass_averaging_mode) else: - u, count = np.unique(np.ravel(reference.target), return_counts=True) - count_sort_ind = np.argsort(count) - pos_label = u[count_sort_ind[0]].item() - additional_params = {'pos_label': pos_label} - return precision_score(y_true=reference.target, y_pred=predicted.predict, - **additional_params) + pos_label = QualityMetric._get_least_frequent_val(reference.target) + additional_params = dict(pos_label=pos_label, average=Precision.binary_averaging_mode) + return precision_score(y_true=reference.target, y_pred=predicted.predict, **additional_params) class Logloss(QualityMetric): @@ -324,21 +297,41 @@ def metric(reference: InputData, predicted: OutputData) -> float: return silhouette_score(reference.features, labels=predicted.predict) -class StructuralComplexity(Metric): +class ComplexityMetric(Metric): + default_value = 0 + norm_constant = 1 + + @classmethod + def get_value(cls, pipeline: Pipeline, **kwargs) -> float: + """ Get metric value and apply norm_constant to it. """ + return cls.metric(pipeline, **kwargs) / cls.norm_constant + + @classmethod + @abstractmethod + def metric(cls, pipeline: Pipeline, **kwargs) -> float: + """ Get metrics value based on pipeline. """ + raise AbstractMethodNotImplementError + + +class StructuralComplexity(ComplexityMetric): + norm_constant = 30 + @classmethod - def get_value(cls, pipeline: 'Pipeline', **args) -> float: - norm_constant = 30 - return (pipeline.depth ** 2 + pipeline.length) / norm_constant + def metric(cls, pipeline: Pipeline, **kwargs) -> float: + return pipeline.depth ** 2 + pipeline.length + +class NodeNum(ComplexityMetric): + norm_constant = 10 -class NodeNum(Metric): @classmethod - def get_value(cls, pipeline: 'Pipeline', **args) -> float: - norm_constant = 10 - return pipeline.length / norm_constant + def metric(cls, pipeline: Pipeline, **kwargs) -> float: + return pipeline.length -class ComputationTime(Metric): +class ComputationTime(ComplexityMetric): + default_value = sys.maxsize + @classmethod - def get_value(cls, pipeline: 'Pipeline', **args) -> float: + def metric(cls, pipeline: Pipeline, **kwargs) -> float: return pipeline.computation_time diff --git a/fedot/core/operations/atomized_model.py b/fedot/core/operations/atomized_model.py index 5fba8f6d44..3fce78bc22 100644 --- a/fedot/core/operations/atomized_model.py +++ b/fedot/core/operations/atomized_model.py @@ -2,18 +2,18 @@ from datetime import timedelta from functools import reduce from operator import and_, or_ -from typing import Callable, Union, Optional, Set, List, Any, Dict +from typing import Any, Callable, Dict, List, Optional, Set, Union -from fedot.core.pipelines.node import PipelineNode from golem.core.tuning.simultaneous import SimultaneousTuner from fedot.core.data.data import InputData, OutputData from fedot.core.operations.operation import Operation from fedot.core.operations.operation_parameters import OperationParameters +from fedot.core.pipelines.node import PipelineNode from fedot.core.pipelines.pipeline import Pipeline from fedot.core.pipelines.tuning.tuner_builder import TunerBuilder -from fedot.core.repository.operation_types_repository import OperationMetaInfo, \ - atomized_model_type +from fedot.core.repository.metrics_repository import MetricCallable +from fedot.core.repository.operation_types_repository import OperationMetaInfo, atomized_model_type class AtomizedModel(Operation): @@ -50,16 +50,16 @@ def predict_for_fit(self, return self.predict(fitted_operation, data, params, output_mode) def fine_tune(self, - metric_function: Callable, + metric_function: MetricCallable, input_data: Optional[InputData] = None, iterations: int = 50, timeout: int = 5) -> 'AtomizedModel': """ Method for tuning hyperparameters """ - tuner = TunerBuilder(input_data.task)\ - .with_tuner(SimultaneousTuner)\ - .with_metric(metric_function)\ - .with_iterations(iterations)\ - .with_timeout(timedelta(minutes=timeout))\ + tuner = TunerBuilder(input_data.task) \ + .with_tuner(SimultaneousTuner) \ + .with_metric(metric_function) \ + .with_iterations(iterations) \ + .with_timeout(timedelta(minutes=timeout)) \ .build(input_data) tuned_pipeline = tuner.tune(self.pipeline) tuned_atomized_model = AtomizedModel(tuned_pipeline) diff --git a/fedot/core/optimisers/objective/metrics_objective.py b/fedot/core/optimisers/objective/metrics_objective.py index c6b54448b0..d7ae719192 100644 --- a/fedot/core/optimisers/objective/metrics_objective.py +++ b/fedot/core/optimisers/objective/metrics_objective.py @@ -3,12 +3,12 @@ from golem.core.optimisers.objective import Objective from golem.utilities.data_structures import ensure_wrapped_in_sequence -from fedot.core.repository.quality_metrics_repository import MetricType, MetricsRepository, ComplexityMetricsEnum +from fedot.core.repository.metrics_repository import MetricIDType, MetricsRepository, ComplexityMetricsEnum class MetricsObjective(Objective): def __init__(self, - metrics: Union[MetricType, Iterable[MetricType]], + metrics: Union[MetricIDType, Iterable[MetricIDType]], is_multi_objective: bool = False): quality_metrics = {} complexity_metrics = {} @@ -18,7 +18,7 @@ def __init__(self, metric_id = str(metric) quality_metrics[metric_id] = metric else: - metric_func = MetricsRepository.metric_by_id(metric) + metric_func = MetricsRepository.get_metric(metric) if metric_func: if ComplexityMetricsEnum.has_value(metric): complexity_metrics[metric] = metric_func diff --git a/fedot/core/pipelines/pipeline.py b/fedot/core/pipelines/pipeline.py index c5a727108c..526fb263ba 100644 --- a/fedot/core/pipelines/pipeline.py +++ b/fedot/core/pipelines/pipeline.py @@ -274,6 +274,9 @@ def predict(self, input_data: Union[InputData, MultiModalData], output_mode: str copied_input_data = self._assign_data_to_nodes(copied_input_data) result = self.root_node.predict(input_data=copied_input_data, output_mode=output_mode) + if input_data.task.task_type == TaskTypesEnum.ts_forecasting: + result.predict = result.predict.ravel() + result = self._postprocess(copied_input_data, result, output_mode) return result diff --git a/fedot/core/pipelines/tuning/tuner_builder.py b/fedot/core/pipelines/tuning/tuner_builder.py index c061768436..379028a959 100644 --- a/fedot/core/pipelines/tuning/tuner_builder.py +++ b/fedot/core/pipelines/tuning/tuner_builder.py @@ -1,5 +1,5 @@ from datetime import timedelta -from typing import Type, Union, Iterable, Sequence +from typing import Iterable, Sequence, Type, Union from golem.core.tuning.optuna_tuner import OptunaTuner from golem.core.tuning.simultaneous import SimultaneousTuner @@ -14,7 +14,7 @@ from fedot.core.pipelines.adapters import PipelineAdapter from fedot.core.pipelines.pipeline_composer_requirements import PipelineComposerRequirements from fedot.core.pipelines.tuning.search_space import PipelineSearchSpace -from fedot.core.repository.quality_metrics_repository import MetricType, MetricsEnum +from fedot.core.repository.metrics_repository import MetricIDType, MetricsEnum from fedot.core.repository.tasks import Task from fedot.utilities.define_metric_by_task import MetricByTask @@ -55,7 +55,7 @@ def with_n_jobs(self, n_jobs: int): self.n_jobs = n_jobs return self - def with_metric(self, metrics: Union[MetricType, Iterable[MetricType]]): + def with_metric(self, metrics: Union[MetricIDType, Iterable[MetricIDType]]): self.metric = ensure_wrapped_in_sequence(metrics) return self diff --git a/fedot/core/repository/quality_metrics_repository.py b/fedot/core/repository/metrics_repository.py similarity index 60% rename from fedot/core/repository/quality_metrics_repository.py rename to fedot/core/repository/metrics_repository.py index 537d87bd20..7c02d4b934 100644 --- a/fedot/core/repository/quality_metrics_repository.py +++ b/fedot/core/repository/metrics_repository.py @@ -1,15 +1,15 @@ -from numbers import Real -from typing import Callable, Union, TypeVar +from typing import Dict, Optional, Protocol, TypeVar, Union -from golem.core.dag.graph import Graph -from golem.utilities.data_structures import ComparableEnum as Enum +from golem.utilities.data_structures import ComparableEnum -from fedot.core.composer.metrics import (ComputationTime, Accuracy, F1, Logloss, MAE, - MAPE, SMAPE, MSE, MSLE, Metric, NodeNum, Precision, R2, - RMSE, ROCAUC, Silhouette, StructuralComplexity, MASE) +from fedot.core.composer.metrics import (Accuracy, ComplexityMetric, ComputationTime, F1, Logloss, MAE, MAPE, MASE, MSE, + MSLE, NodeNum, Precision, QualityMetric, R2, RMSE, ROCAUC, SMAPE, Silhouette, + StructuralComplexity) +from fedot.core.data.data import InputData +from fedot.core.pipelines.pipeline import Pipeline -class MetricsEnum(Enum): +class MetricsEnum(ComparableEnum): def __str__(self): return self.value @@ -18,13 +18,7 @@ def has_value(cls, value): return value in cls._value2member_map_ -G = TypeVar('G', bound=Graph, covariant=True) -MetricCallable = Callable[[G], Real] -MetricType = Union[MetricCallable, MetricsEnum] - - -class QualityMetricsEnum(MetricsEnum): - pass +class QualityMetricsEnum(MetricsEnum): pass class ComplexityMetricsEnum(MetricsEnum): @@ -69,8 +63,25 @@ class TimeSeriesForecastingMetricsEnum(QualityMetricsEnum): RMSE_penalty = 'rmse_pen' +NumberType = Union[int, float, complex] +PipelineType = TypeVar('PipelineType', bound=Pipeline, covariant=True) + + +class QualityMetricCallable(Protocol): + def __call__(self, pipeline: PipelineType, reference_data: InputData, + validation_blocks: Optional[int] = None) -> NumberType: pass + + +class ComplexityMetricCallable(Protocol): + def __call__(self, pipeline: PipelineType, **kwargs) -> NumberType: pass + + +MetricCallable = Union[QualityMetricCallable, ComplexityMetricCallable] +MetricIDType = Union[str, MetricsEnum, MetricCallable] + + class MetricsRepository: - _metrics_implementations = { + _metrics_implementations: Dict[MetricsEnum, MetricCallable] = { # classification ClassificationMetricsEnum.ROCAUC: ROCAUC.get_value, ClassificationMetricsEnum.ROCAUC_penalty: ROCAUC.get_value_with_penalty, @@ -101,10 +112,13 @@ class MetricsRepository: ComplexityMetricsEnum.computation_time: ComputationTime.get_value } + _metrics_classes = {metric_id: getattr(metric_func, '__self__') + for metric_id, metric_func in _metrics_implementations.items()} + @staticmethod - def metric_by_id(metric_id: MetricsEnum, default_callable: MetricCallable = None) -> MetricCallable: - return MetricsRepository._metrics_implementations.get(metric_id, default_callable) + def get_metric(metric_name: MetricsEnum) -> MetricCallable: + return MetricsRepository._metrics_implementations[metric_name] @staticmethod - def metric_class_by_id(metric_id: MetricsEnum) -> Metric: - return MetricsRepository._metrics_implementations[metric_id].__self__() + def get_metric_class(metric_name: MetricsEnum) -> Union[QualityMetric, ComplexityMetric]: + return MetricsRepository._metrics_classes[metric_name] diff --git a/fedot/utilities/define_metric_by_task.py b/fedot/utilities/define_metric_by_task.py index c168ad1605..b11ca03612 100644 --- a/fedot/utilities/define_metric_by_task.py +++ b/fedot/utilities/define_metric_by_task.py @@ -1,7 +1,7 @@ from typing import List from fedot.core.data.data import InputData, OutputData -from fedot.core.repository.quality_metrics_repository import ( +from fedot.core.repository.metrics_repository import ( MetricsEnum, RegressionMetricsEnum, ClassificationMetricsEnum, @@ -27,7 +27,7 @@ def compute_default_metric(task_type: TaskTypesEnum, true: InputData, predicted: round_up_to: int = 6) -> float: """Returns the value of metric defined by task""" metric_id = MetricByTask.get_default_quality_metrics(task_type)[0] - metric = MetricsRepository.metric_class_by_id(metric_id) + metric = MetricsRepository.get_metric_class(metric_id) try: return round(metric.metric(reference=true, predicted=predicted), round_up_to) except ValueError: diff --git a/test/data/expected_metric_values.json b/test/data/expected_metric_values.json new file mode 100644 index 0000000000..8a293325c6 --- /dev/null +++ b/test/data/expected_metric_values.json @@ -0,0 +1,65 @@ +{ + "complexity": { + "node_number": 0.4, + "structural": 0.43333333333333335, + "computation_time_in_seconds": 0.0 + }, + "binary": { + "roc_auc": -0.9799498746867168, + "precision": -0.9473684210526315, + "f1": -0.9473684210526315, + "neg_log_loss": 0.19864695688257933, + "roc_auc_pen": -0.9757034252297411, + "accuracy": -0.95 + }, + "multiclass": { + "roc_auc": -0.9832500832500832, + "precision": -0.9777777777777779, + "f1": -0.9719701552732407, + "neg_log_loss": 0.17094588819131074, + "roc_auc_pen": -0.9789893328893329, + "accuracy": -0.9722222222222222 + }, + "regression": { + "rmse": 52.5400204534369, + "mse": 2760.4537492475683, + "neg_mean_squared_log_error": 0.11968905129112402, + "mape": 0.32791432529967834, + "smape": 49.48675123994897, + "mae": 41.55089094096007, + "r2": 0.389822739375963, + "rmse_pen": 52.64510049434378 + }, + "multitarget": { + "rmse": 15.753366859480218, + "mse": 377.5025166058113, + "neg_mean_squared_log_error": 0.030627538521796293, + "mape": 0.15337090733886807, + "smape": 14.144394353302935, + "mae": 13.50645038033778, + "r2": -2.9713973901034954, + "rmse_pen": 15.784873593199178 + }, + "ts": { + "mase": 0.6080909603204148, + "rmse": 6.977694399080989, + "mse": 48.6882191269662, + "neg_mean_squared_log_error": 0.005433112046490622, + "mape": 0.05897445640267307, + "smape": 5.602921894471363, + "mae": 4.960742044719172, + "r2": 0.8502177471021775, + "rmse_pen": 6.991649787879151 + }, + "multits": { + "mase": 0.6080543658437002, + "rmse": 6.977578875807477, + "mse": 48.68660696811473, + "neg_mean_squared_log_error": 0.005434854312738277, + "mape": 0.05898285612710732, + "smape": 5.603672314933528, + "mae": 4.960443510830185, + "r2": 0.8502227066753377, + "rmse_pen": 6.991534033559091 + } +} \ No newline at end of file diff --git a/test/integration/composer/test_composer.py b/test/integration/composer/test_composer.py index a30d2a933b..2cb174c4bf 100644 --- a/test/integration/composer/test_composer.py +++ b/test/integration/composer/test_composer.py @@ -24,7 +24,7 @@ from fedot.core.pipelines.pipeline_graph_generation_params import get_pipeline_generation_params from fedot.core.repository.dataset_types import DataTypesEnum from fedot.core.repository.operation_types_repository import OperationTypesRepository, get_operations_for_task -from fedot.core.repository.quality_metrics_repository import ClassificationMetricsEnum, ComplexityMetricsEnum +from fedot.core.repository.metrics_repository import ClassificationMetricsEnum, ComplexityMetricsEnum from fedot.core.repository.tasks import Task, TaskTypesEnum from fedot.core.utils import fedot_project_root, set_random_seed from test.unit.pipelines.test_pipeline_comparison import pipeline_first, pipeline_second diff --git a/test/integration/composer/test_history.py b/test/integration/composer/test_history.py index 70807c14ad..c266ba7539 100644 --- a/test/integration/composer/test_history.py +++ b/test/integration/composer/test_history.py @@ -3,8 +3,6 @@ import numpy as np import pytest - -from fedot.core.repository.tasks import TaskTypesEnum from golem.core.dag.graph import Graph from golem.core.optimisers.fitness import SingleObjFitness from golem.core.optimisers.genetic.evaluation import MultiprocessingDispatcher @@ -20,8 +18,9 @@ from fedot.core.pipelines.node import PipelineNode from fedot.core.pipelines.pipeline import Pipeline from fedot.core.pipelines.pipeline_graph_generation_params import get_pipeline_generation_params -from fedot.core.repository.quality_metrics_repository import ClassificationMetricsEnum, \ - RegressionMetricsEnum, MetricType +from fedot.core.repository.metrics_repository import (ClassificationMetricsEnum, MetricIDType, + RegressionMetricsEnum) +from fedot.core.repository.tasks import TaskTypesEnum from fedot.core.utils import fedot_project_root from test.unit.tasks.test_forecasting import get_ts_data from test.unit.validation.test_table_cv import get_classification_data @@ -116,7 +115,7 @@ def assert_intermediate_metrics(pipeline: Graph): get_ts_data()[0], RegressionMetricsEnum.RMSE), ]) -def test_collect_intermediate_metric(pipeline: Pipeline, input_data: InputData, metric: MetricType): +def test_collect_intermediate_metric(pipeline: Pipeline, input_data: InputData, metric: MetricIDType): graph_gen_params = get_pipeline_generation_params() metrics = [metric] diff --git a/test/integration/optimizer/test_evaluation.py b/test/integration/optimizer/test_evaluation.py index 52a5a616c8..1629736e9d 100644 --- a/test/integration/optimizer/test_evaluation.py +++ b/test/integration/optimizer/test_evaluation.py @@ -10,7 +10,7 @@ from fedot.core.optimisers.objective.metrics_objective import MetricsObjective from fedot.core.pipelines.adapters import PipelineAdapter from fedot.core.pipelines.pipeline import Pipeline -from fedot.core.repository.quality_metrics_repository import ClassificationMetricsEnum +from fedot.core.repository.metrics_repository import ClassificationMetricsEnum from test.unit.pipelines.test_node_cache import pipeline_first, pipeline_second, pipeline_third, pipeline_fourth from test.unit.validation.test_table_cv import get_classification_data diff --git a/test/integration/pipelines/tuning/test_pipeline_tuning.py b/test/integration/pipelines/tuning/test_pipeline_tuning.py index be281a92c0..48510f0339 100644 --- a/test/integration/pipelines/tuning/test_pipeline_tuning.py +++ b/test/integration/pipelines/tuning/test_pipeline_tuning.py @@ -23,7 +23,7 @@ from fedot.core.pipelines.pipeline import Pipeline from fedot.core.pipelines.tuning.search_space import PipelineSearchSpace from fedot.core.pipelines.tuning.tuner_builder import TunerBuilder -from fedot.core.repository.quality_metrics_repository import RegressionMetricsEnum, ClassificationMetricsEnum +from fedot.core.repository.metrics_repository import RegressionMetricsEnum, ClassificationMetricsEnum from fedot.core.repository.tasks import Task, TaskTypesEnum from fedot.core.utils import fedot_project_root, NESTED_PARAMS_LABEL from test.unit.multimodal.data_generators import get_single_task_multimodal_tabular_data, get_multimodal_pipeline diff --git a/test/integration/pipelines/tuning/test_tuner_builder.py b/test/integration/pipelines/tuning/test_tuner_builder.py index 18cb50a3dd..c2bb4ebc7c 100644 --- a/test/integration/pipelines/tuning/test_tuner_builder.py +++ b/test/integration/pipelines/tuning/test_tuner_builder.py @@ -16,13 +16,13 @@ from fedot.core.optimisers.objective.metrics_objective import MetricsObjective from fedot.core.pipelines.tuning.search_space import PipelineSearchSpace from fedot.core.pipelines.tuning.tuner_builder import TunerBuilder -from fedot.core.repository.quality_metrics_repository import ClassificationMetricsEnum, MetricType +from fedot.core.repository.metrics_repository import ClassificationMetricsEnum, MetricIDType from test.integration.pipelines.tuning.test_pipeline_tuning import get_not_default_search_space from test.unit.optimizer.test_pipeline_objective_eval import pipeline_first_test from test.unit.validation.test_table_cv import get_classification_data -def get_objective_evaluate(metric: MetricType, data: InputData, +def get_objective_evaluate(metric: MetricIDType, data: InputData, cv_folds: Optional[int] = None) \ -> PipelineObjectiveEvaluate: objective = MetricsObjective(metric) diff --git a/test/integration/validation/test_table_cv.py b/test/integration/validation/test_table_cv.py index b0a0cd09bb..a7ae120ebb 100644 --- a/test/integration/validation/test_table_cv.py +++ b/test/integration/validation/test_table_cv.py @@ -7,7 +7,7 @@ from fedot.core.pipelines.pipeline import Pipeline from fedot.core.pipelines.pipeline_composer_requirements import PipelineComposerRequirements from fedot.core.repository.operation_types_repository import OperationTypesRepository -from fedot.core.repository.quality_metrics_repository import ClassificationMetricsEnum +from fedot.core.repository.metrics_repository import ClassificationMetricsEnum from fedot.core.repository.tasks import Task, TaskTypesEnum from test.unit.validation.test_table_cv import get_classification_data diff --git a/test/unit/api/test_api_params.py b/test/unit/api/test_api_params.py index 7295ababa9..43479aeb9d 100644 --- a/test/unit/api/test_api_params.py +++ b/test/unit/api/test_api_params.py @@ -11,7 +11,7 @@ from fedot.core.constants import AUTO_PRESET_NAME from fedot.core.pipelines.pipeline_builder import PipelineBuilder from fedot.core.pipelines.pipeline_composer_requirements import PipelineComposerRequirements -from fedot.core.repository.quality_metrics_repository import RegressionMetricsEnum +from fedot.core.repository.metrics_repository import RegressionMetricsEnum from fedot.core.repository.tasks import TaskTypesEnum fedot_params_full = dict(parallelization_mode='populational', diff --git a/test/unit/composer/test_metrics.py b/test/unit/composer/test_metrics.py new file mode 100644 index 0000000000..b8b868a9e7 --- /dev/null +++ b/test/unit/composer/test_metrics.py @@ -0,0 +1,227 @@ +import json +import sys +from itertools import product +from typing import Callable, Dict, Tuple, Union + +import numpy as np +import pandas as pd +import pytest +from sklearn.datasets import load_breast_cancer, load_diabetes, load_linnerud, load_wine + +from fedot.core.composer.metrics import QualityMetric, ROCAUC +from fedot.core.data.data import InputData, OutputData +from fedot.core.data.data_split import train_test_data_setup +from fedot.core.pipelines.node import PipelineNode +from fedot.core.pipelines.pipeline import Pipeline +from fedot.core.repository.dataset_types import DataTypesEnum +from fedot.core.repository.metrics_repository import (ClassificationMetricsEnum, ComplexityMetricsEnum, + MetricsRepository, RegressionMetricsEnum, + TimeSeriesForecastingMetricsEnum) +from fedot.core.repository.tasks import Task, TaskTypesEnum, TsForecastingParams +from fedot.core.utils import fedot_project_root, set_random_seed + + +@pytest.fixture(scope='session') +def data_setup(request): + task_type = request.param + validation_blocks = None + if task_type in ('binary', 'complexity'): + x, y = load_breast_cancer(return_X_y=True) + task = Task(TaskTypesEnum.classification) + data_type = DataTypesEnum.table + elif task_type == 'multiclass': + x, y = load_wine(return_X_y=True) + task = Task(TaskTypesEnum.classification) + data_type = DataTypesEnum.table + elif task_type == 'regression': + x, y = load_diabetes(return_X_y=True) + task = Task(TaskTypesEnum.regression) + data_type = DataTypesEnum.table + elif task_type == 'multitarget': + x, y = load_linnerud(return_X_y=True) + task = Task(TaskTypesEnum.regression) + data_type = DataTypesEnum.table + elif task_type == 'ts': + file_path = fedot_project_root() / 'test/data/short_time_series.csv' + df = pd.read_csv(file_path) + x = y = df['sea_height'].to_numpy() + task = Task(TaskTypesEnum.ts_forecasting, TsForecastingParams(forecast_length=10)) + data_type = DataTypesEnum.ts + validation_blocks = 2 + elif task_type == 'multits': + file_path = fedot_project_root() / 'test/data/short_time_series.csv' + df = pd.read_csv(file_path) + x = df[['sea_height', 'sea_height']].to_numpy() + y = df['sea_height'].to_numpy() + task = Task(TaskTypesEnum.ts_forecasting, TsForecastingParams(forecast_length=10)) + data_type = DataTypesEnum.multi_ts + validation_blocks = 2 + else: + raise ValueError(f'Unsupported task type: {task_type}') + + x, y = x[:200], y[:200] + + # Wrap data into InputData + input_data = InputData(features=x, + target=y, + idx=np.arange(len(x)), + task=task, + data_type=data_type) + # Train test split + train_data, test_data = train_test_data_setup(input_data, validation_blocks=validation_blocks) + return train_data, test_data, task_type, validation_blocks + + +def get_classification_pipeline(): + first = PipelineNode(operation_type='logit') + second = PipelineNode(operation_type='logit', nodes_from=[first]) + third = PipelineNode(operation_type='logit', nodes_from=[first]) + final = PipelineNode(operation_type='logit', nodes_from=[second, third]) + + pipeline = Pipeline(final) + + return pipeline + + +def get_regression_pipeline(): + first = PipelineNode(operation_type='scaling') + final = PipelineNode(operation_type='linear', nodes_from=[first]) + + pipeline = Pipeline(final) + + return pipeline + + +def get_ts_pipeline(window_size=30): + """ Function return pipeline with lagged transformation in it """ + node_lagged = PipelineNode('lagged') + node_lagged.parameters = {'window_size': window_size} + + node_final = PipelineNode('ridge', nodes_from=[node_lagged]) + pipeline = Pipeline(node_final) + return pipeline + + +@pytest.fixture(scope='session') +def expected_values() -> Dict[str, Dict[str, float]]: + with open(fedot_project_root() / 'test/data/expected_metric_values.json', 'r') as f: + return json.load(f) + + +@pytest.mark.parametrize( + 'metric, pipeline_func, data_setup', + [ # TODO: Add binary classification to the test after completion of https://github.com/aimclub/FEDOT/issues/1221. + *product(ComplexityMetricsEnum, [get_classification_pipeline], ['complexity']), + *product(ClassificationMetricsEnum, [get_classification_pipeline], ['multiclass']), + *product(RegressionMetricsEnum, [get_regression_pipeline], ['regression', 'multitarget']), + *product(TimeSeriesForecastingMetricsEnum, [get_ts_pipeline], ['ts', 'multits']) + ], + indirect=['data_setup'] +) +def test_metrics(metric: ClassificationMetricsEnum, pipeline_func: Callable[[], Pipeline], + data_setup: Tuple[InputData, InputData, str, Union[int, None]], + expected_values: Dict[str, Dict[str, float]]): + set_random_seed(0) + update_expected_values: bool = False + + train, test, task_type, validation_blocks = data_setup + + pipeline = pipeline_func() + pipeline.fit(input_data=train) + metric_function = MetricsRepository.get_metric(metric) + metric_class = MetricsRepository.get_metric_class(metric) + metric_value = metric_function(pipeline=pipeline, reference_data=test, validation_blocks=validation_blocks) + + if not update_expected_values: + expected_value = expected_values[task_type][str(metric)] + assert np.isclose(metric_value, expected_value, rtol=0.001, atol=0.001) + assert not np.isclose(metric_value, metric_class.default_value, rtol=0.01, atol=0.01) + else: + with open(fedot_project_root() / 'test/data/expected_metric_values.json', 'w') as f: + expected_values[task_type] = expected_values.get(task_type) or {} + expected_values[task_type][str(metric)] = metric_value + json.dump(expected_values, f, indent=2) + raise ValueError('The value of `update_expected_values` should equal to `False` ' + 'in order for this test to pass.') + + +@pytest.mark.parametrize( + 'metric, pipeline_func, data_setup', + [ + *product(ClassificationMetricsEnum, [get_classification_pipeline], ['binary']), + ], + indirect=['data_setup'] +) +def test_binary_classification(metric: ClassificationMetricsEnum, pipeline_func: Callable[[], Pipeline], + data_setup: Tuple[InputData, InputData, str, Union[int, None]], + expected_values: Dict[str, Dict[str, float]]): + train, test, task_type, validation_blocks = data_setup + + pipeline = pipeline_func() + pipeline.fit(input_data=train) + metric_function = MetricsRepository.get_metric(metric) + metric_class = MetricsRepository.get_metric_class(metric) + metric_value = metric_function(pipeline=pipeline, reference_data=test, validation_blocks=validation_blocks) + + assert not np.isclose(metric_value, metric_class.default_value, rtol=0.01, atol=0.01) + assert 0 < abs(metric_value) < sys.maxsize + + +@pytest.mark.parametrize( + 'metric, pipeline_func, data_setup, validation_blocks', + [ + *product(ClassificationMetricsEnum, [get_classification_pipeline], ['binary', 'multiclass'], [None]), + *product(RegressionMetricsEnum, [get_regression_pipeline], ['regression', 'multitarget'], [None]), + *product(TimeSeriesForecastingMetricsEnum, [get_ts_pipeline], ['ts', 'multits'], [2]), + ], + indirect=['data_setup'] +) +def test_ideal_case_metrics(metric: ClassificationMetricsEnum, pipeline_func: Callable[[], Pipeline], + validation_blocks: Union[int, None], data_setup: Tuple[InputData, InputData, str], + expected_values): + reference, _, task_type, _ = data_setup + metric_class = MetricsRepository.get_metric_class(metric) + predicted = OutputData(idx=reference.idx, task=reference.task, data_type=reference.data_type) + if task_type == 'multiclass' and metric_class.output_mode != 'labels': + label_vals = np.unique(reference.target) + predicted.predict = np.identity(len(label_vals))[reference.target] + else: + predicted.predict = reference.target + if task_type == 'multits': + reference.features = reference.features[:, 0] + + ideal_value = metric_class.metric(reference, predicted) + + assert ideal_value != metric_class.default_value + + +@pytest.mark.parametrize('data_setup', ['multitarget'], indirect=True) +def test_predict_shape_multi_target(data_setup: Tuple[InputData, InputData, str]): + train, test, _, _ = data_setup + simple_pipeline = Pipeline(PipelineNode('linear')) + simple_pipeline.fit(input_data=train) + + target_shape = test.target.shape + # Get converted data + _, results = QualityMetric()._simple_prediction(simple_pipeline, test) + predict_shape = results.predict.shape + assert target_shape == predict_shape + + +def test_roc_auc_multiclass_correct(): + data = InputData(features=np.array([[1, 2], [2, 3], [3, 4], [4, 1]]), + target=np.array([['x'], ['y'], ['z'], ['x']]), + idx=np.arange(4), + task=Task(TaskTypesEnum.classification), + data_type=DataTypesEnum.table) + prediction = OutputData(features=np.array([[1, 2], [2, 3], [3, 4], [4, 1]]), + predict=np.array([[0.4, 0.3, 0.3], + [0.2, 0.5, 0.3], + [0.1, 0.2, 0.7], + [0.8, 0.1, 0.1]]), + idx=np.arange(4), task=Task(TaskTypesEnum.classification), data_type=DataTypesEnum.table) + for i in range(data.num_classes): + fpr, tpr, threshold = ROCAUC.roc_curve(data.target, prediction.predict[:, i], + pos_label=data.class_labels[i]) + roc_auc = ROCAUC.auc(fpr, tpr) + assert roc_auc diff --git a/test/unit/composer/test_quality_metrics.py b/test/unit/composer/test_quality_metrics.py deleted file mode 100644 index 426faef66d..0000000000 --- a/test/unit/composer/test_quality_metrics.py +++ /dev/null @@ -1,119 +0,0 @@ -import os -import sys - -import numpy as np -import pytest -from sklearn.datasets import load_breast_cancer - -from fedot.core.composer.metrics import QualityMetric, ROCAUC -from fedot.core.data.data import InputData, OutputData -from fedot.core.data.data_split import train_test_data_setup -from fedot.core.pipelines.node import PipelineNode -from fedot.core.pipelines.pipeline import Pipeline -from fedot.core.repository.dataset_types import DataTypesEnum -from fedot.core.repository.quality_metrics_repository import \ - (ClassificationMetricsEnum, - ComplexityMetricsEnum, - MetricsRepository, - RegressionMetricsEnum) -from fedot.core.repository.tasks import Task, TaskTypesEnum - - -@pytest.fixture() -def data_setup(): - predictors, response = load_breast_cancer(return_X_y=True) - response = response[:100] - predictors = predictors[:100] - - # Wrap data into InputData - input_data = InputData(features=predictors, - target=response, - idx=np.arange(0, len(predictors)), - task=Task(TaskTypesEnum.classification), - data_type=DataTypesEnum.table) - # Train test split - train_data, test_data = train_test_data_setup(input_data) - return train_data, test_data - - -@pytest.fixture() -def multi_target_data_setup(): - test_file_path = str(os.path.dirname(__file__)) - file = '../../data/multi_target_sample.csv' - path = os.path.join(test_file_path, file) - - target_columns = ['1_day', '2_day', '3_day', '4_day', '5_day', '6_day', '7_day'] - task = Task(TaskTypesEnum.regression) - data = InputData.from_csv(path, target_columns=target_columns, - index_col=None, columns_to_drop=['date'], task=task) - train, test = train_test_data_setup(data) - return train, test - - -def default_valid_pipeline(): - first = PipelineNode(operation_type='logit') - second = PipelineNode(operation_type='logit', nodes_from=[first]) - third = PipelineNode(operation_type='logit', nodes_from=[first]) - final = PipelineNode(operation_type='logit', nodes_from=[second, third]) - - pipeline = Pipeline(final) - - return pipeline - - -def test_structural_quality_correct(): - pipeline = default_valid_pipeline() - metric_function = MetricsRepository().metric_by_id(ComplexityMetricsEnum.structural) - expected_metric_value = 13 - actual_metric_value = metric_function(pipeline) - assert actual_metric_value <= expected_metric_value - - -def test_classification_quality_metric(data_setup): - train, _ = data_setup - pipeline = default_valid_pipeline() - pipeline.fit(input_data=train) - - for metric in ClassificationMetricsEnum: - metric_function = MetricsRepository().metric_by_id(metric) - metric_value = metric_function(pipeline=pipeline, reference_data=train) - assert 0 < abs(metric_value) < sys.maxsize - - -def test_regression_quality_metric(data_setup): - train, _ = data_setup - pipeline = default_valid_pipeline() - pipeline.fit(input_data=train) - - for metric in RegressionMetricsEnum: - metric_function = MetricsRepository().metric_by_id(metric) - metric_value = metric_function(pipeline=pipeline, reference_data=train) - assert metric_value > 0 - - -def test_data_preparation_for_multi_target_correct(multi_target_data_setup): - train, test = multi_target_data_setup - simple_pipeline = Pipeline(PipelineNode('linear')) - simple_pipeline.fit(input_data=train) - - source_shape = test.target.shape - # Get converted data - results, new_test = QualityMetric()._simple_prediction(simple_pipeline, test) - number_elements = len(new_test.target) - assert source_shape[0] * source_shape[1] == number_elements - - -def test_roc_auc_multiclass_correct(): - data = InputData(features=[[1, 2], [2, 3], [3, 4], [4, 1]], target=np.array([['x'], ['y'], ['z'], ['x']]), - idx=np.arange(4), - task=Task(TaskTypesEnum.classification), data_type=DataTypesEnum.table) - prediction = OutputData(features=[[1, 2], [2, 3], [3, 4], [4, 1]], predict=np.array([[0.4, 0.3, 0.3], - [0.2, 0.5, 0.3], - [0.1, 0.2, 0.7], - [0.8, 0.1, 0.1]]), - idx=np.arange(4), task=Task(TaskTypesEnum.classification), data_type=DataTypesEnum.table) - for i in range(data.num_classes): - fpr, tpr, threshold = ROCAUC.roc_curve(data.target, prediction.predict[:, i], - pos_label=data.class_labels[i]) - roc_auc = ROCAUC.auc(fpr, tpr) - assert roc_auc diff --git a/test/unit/optimizer/test_pipeline_objective_eval.py b/test/unit/optimizer/test_pipeline_objective_eval.py index 1c1ce688e7..145a28d3db 100644 --- a/test/unit/optimizer/test_pipeline_objective_eval.py +++ b/test/unit/optimizer/test_pipeline_objective_eval.py @@ -13,7 +13,7 @@ from fedot.core.pipelines.pipeline import Pipeline from fedot.core.pipelines.pipeline_builder import PipelineBuilder from fedot.core.repository.dataset_types import DataTypesEnum -from fedot.core.repository.quality_metrics_repository import ClassificationMetricsEnum, MetricsRepository, \ +from fedot.core.repository.metrics_repository import ClassificationMetricsEnum, MetricsRepository, \ RegressionMetricsEnum from fedot.core.repository.tasks import Task, TaskTypesEnum from test.integration.models.test_model import classification_dataset, classification_dataset_with_str_labels @@ -51,7 +51,7 @@ def actual_fitness(data_split, pipeline, metric): metric_values = [] for (train_data, test_data) in data_split(): pipeline.fit(train_data) - metric_function = MetricsRepository().metric_by_id(metric, default_callable=metric) + metric_function = MetricsRepository.get_metric(metric) metric_values.append(metric_function(pipeline=pipeline, reference_data=test_data)) mean_metric = np.mean(metric_values, axis=0) return SingleObjFitness(mean_metric) diff --git a/test/unit/pipelines/prediction_intervals/data/pred_ints_model_test.pickle b/test/unit/pipelines/prediction_intervals/data/pred_ints_model_test.pickle index 88fe04d97a98914f1643a5387910376d0fc55780..4c89b978be62a63f87f0e8a9afcc07e1d7edcef5 100644 GIT binary patch literal 173414 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≪{AxetIn@vjJ}6KFExKTRU063ctqx~b#n#BR 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 literal 171132 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 mRjgs4#m++X}Xt$P~|Yt!>Fc=fPxv zn~&8r3l(asY&5>dmbJ~W_T-FEt3_QBe8Gx@A6Z&qmIA+nZM z0tC8M7_lY6rvlgQvf;h0S*A$)mlmpI2U{*q9s{3LkYh;%*7e!aMuXavi)`4_@HOSx zzl8E)Y$)qgo;Oxwl8s=i+KLNs8Z=foP(laRYBMk}Iop4ml1VjaWNJ<@8iAInGN5`u zLx8Y>aIpNb;~~(w!513tzWVAGRSdqU+#q_#hC2y*oS*eXfzH_ zZHY;2k72CVZ8nJ6LRCg1>yB}(53BYVn!$Ul`Go4av6>G_`Ehv9AyvJ?jHV}#5|fy=_Fk%bp*?`15}oD^slvMeL;2)UD1?8leKaL z<%fM@Hk7tdm8?a=L+lpi+>6dhe4~@~F`Pkd7hbO1jE25|S)5V_*j2_rz`a3*(XoV0 zZ?k!Gs45O^#r_!RFS{mTPgJ6<-NhBdI)Uu6(?h9IIqQh|WWuHk%9Br%#`cC#z91v9 z#)`$3h4QiTF;)queBfiH$*g6)wgDXH#;@2+J>g|C0d75Pf28UX^iUz+F6;oC+TrI@ zutypEOW30df)4y9oRg`H)d3~o%LLLqxu#<0sgIHb{|;9pfB0?H50bD$k@Cj=EbM2% zuB>33GWV@jal#g!e{P5Cu*=3_08)*q*ip%GeDZ34?uG#vn>b%D>%O+t#6_9xD205Uo5ScE zhT{DEb3evR-7p+K42pDmiZe5egcpX1SW!VKq=V@pych z0GCX|M7ZLy3UW@)j)+4aLY0&bBV(8fpFuAd!#*pplN=^B@DspBpjXTla#6ele!<$m z*uR%f9N`=@TDna%oX{=scT{?Kkv@KQM?yQrH z3bOh?KTFcO<-OmESd)~=b@#H)v%I;vwd@jPO$rV3?+-S&O_A4t^5~5pKp`aD!21B! zD&z15TZ-lL<6$_Vg$jiJXac|5SuqX1uyGM=c2R-APobTlj6iah?uTHyRl{5Z<)t!% zlNIb`=vA250fiNROjWiPm^*AN^K!&mDAih|4Zj^qrAmMg4suh1x(!AdARJ5%1=$Qq z=cky~!P!qH*d7DT=O)!-Pa>B=s9s}&mcSGe0nR^LZaC{O{6-IDSv?|-uPUg(+mQk~ zKiC@BPyu%?*7dP>9TUP3i=i|i8&%O&dmX+34^?9}qnO-$+S$9qsp(vSj!h5cp5bby zvaS^{Ia0AaEW^FARoSYfCxM*}Z_O$b6^OM7yR*l!%Hgi6jPb#y&IWhH44;+eG1i`O ztY=w=*0DAexcI?7f(pVcjz3sgYBjh}SxZL?(=p)K!seP%WlRP70t&h?1OLsW%I1?0 zCSM@cEzXt*wKou^Rm_PXU-U^@BObe84ps6#hgm_Z-vPT_aCbosxHR~)()Q0>ybQ2q z2AvI_5!}&u>IrlRJp7;}gEtTG93F=`_5%9?d|U)OG}wy7Dvmub?EL^YMrC_9KWV~fz*{4HSiSF7%YGN9Z61OF&u&PhU1tL!W`bInPDo>5$V}4 zFh66_jsUk}IyfdQHq8f=EC-d#I(L}%?o=?ohcN?4(O^F&JIyF`<$a?Z3cyo(_Rge=Tf5(4sNxO{_JJm8z|jee5v)fm zQL%CtH!2ceKwW8TPKC-g!8i7bdgwJU)g20mm7H#!N@u+tL`n()ZY)2R=_UaZfhz*D z)M9KxV6(u5*(7)#X0#=yh{}RzVj|*-#D$Eu78lfFScZ6SQB_qMH*V# zv}x1w6a2B4I5+U?X2!HT2uA-QfWrVs0FDA21Nar-IKT;jlK`gxP6PY~a0cKkz&U{P z02cr*0$c*HV0SSV9p3E7vX!B#I`j=-fo)11rvSXNaUFl$NgXlMvKjfltZW9v01ckC z;9v&N=2JA(h2|RS!3Fn)K>$B&opBKErMNTPwes99Fw}=fF8ZKxRB<=Ao<#7ofGW?C z$EqIVFUvpD?Y9L#^r`ZQUiIf`pY&xROjM0(r4P*UWJYH8>Y6djgBkmxn5UP@hp8AS z>QK7ed-~YJ@5YkvyqI0X8hQtfcuOyRJ4@ho@(O+JM9=Wa+92lL#tW&>?*uaKc^5kL z@C#%bRDJYkkAClIVVMq#ulDd@O0`_*>8juo z!OW0R1ETJ{F2+1Nw)SnGmj~!z^6;bZsA7zvdza@on?0x3jQL%^ILU{3)*`mkscI#d z#-*Q5?!EI9y(w}}$6|)J^qZ{rn?@ZDV(y*YD1O-X6W#vRXw7P$U}pY{-O~*B{g|7- zRbG6g+Mjf_W7S_*JW-4ZE1MzOU)-JftLyr-=Z)aIx<9SiqAJaFSTW77Rir1Q2`T@y zOl>#j!TDpw-Yp4cS{>27S!(oRympsQtU9?gb3bIroHM~;O#JSIIVZZ7WMa-n`c3gJ z$z&B9@O-X&2$K?ZcFeVdAxvM<;XR7eK}>eD_rDbBUy(5loGCvtKY;nJ--}Nh@0VuQ zw~INoHN67U=*MxxE~8*ZYAMovYSB_m(Q3yV)vFZF^xn68Z-OeEY2+SVV+#|`sMcJ2 zG%Kwl6VxW09{aKq)2v~gm#N(=Gqs;-`>Y-u&g?sRoczV7Dig5mTqko*MMg1$iK%0* z&Wv8Wef3`xt1>-(mtP)x5I(-cR)tJUe>b_uG4Mtk5OO06vDl@&u z?I`xaw+fSZC7VCFdktn`gJ&(S{!x=b!aF&Zy|tM)@^<>1Nj&DdrDtZNXEm9l)f#N8 z->wd`WI7!?;CwA6MN-22q(U90Lf56wHs#c0_U%1aMN)&uyz-lRU}N7pOz5!>7qTSv zm{Du0$7GDE&CIy8UU%wcUFOM4deE~fJZ5W^X%iZi;W6KPB*zZ+jAWKqY_%jJs5Y~# z`v(5wVLV1MuKkKVb@8soFbuO!FhL zV{eq;F(}Xxi95n08Tn7|qxbZv z%Z&7`5IpZyUFPV<%wxaatixQ{e=$RTgU5_~@H}k!hC0lELo;H8PiixrQr+8hKL+V0 zl`c`#w>H!L?5FcX9z`-$uMSM^yQdaYZdydq1;=YMyfOW=D{QI7c+P0Cqe^mZ#w$F+ zZT_tMg=2|-N77O`XR z|6H2syCLFe22+MH>3gQVtQW!<>Ua6|&B>C?tH2ZCM3u74<27%a6n|2Z`7ZkVD!t1F zGlJkb3o~k#WjwwAI4wOB#MC?2*{hf`h?y(yROwe`Fw@SBpLH=TkO|K`(R<~pqD=Qi zm$ELu@Ma>qSJ~L?ejt-^!SCdbq5e!}1LMQ;?aDBvLzeaG&hutMv%GgcyIqb6ieEf% z<*6{H^}rHkkM0U)9!!*_krPWYC612Vkli7WQ7x>iUpFpM>3CrdL8 zpLCk~OZ~FU-LCaJzCKx&$ub>0QfW>Yb7A0oohd7fS@QOW>B)ntFvm65T2BZLXL39) zc#YPSVdNjZYt5Qko|zCkv-Yt(#sKR)ZnL5&c zRwbrH%aX6#cPh`Ey1}cwcTp84b;b7g@sF!By%yxmS=F@~Gv!t^zsGuWM2YS;NRp<_)3i#;$2b5;CYE29ngZylav z(ccE5NOF}*_JOZe>(?l)?{hUjtg1n*6RUARjF8Ek46~QmJB)1;EJ5u`Q?BaaDp61e z$1ky{&DBQ&U>ejxO9Znr5a-O!+S@;5rQB+1%f*$7Y z!Dxb6z+Al#l-^9L6t-TCpnrY-3Op@ISjXo2z|X2bbLs(j1aa&It8iJqApEf=)Cweh zX3RWcxvHg)%4;x@*Aq^dH`HVnlQTBSj@Do*&iI8l4F+vY@_F)}A%a=8hEBRNG=jOX zeP&eiqY=#N-W}&Q(AHw+>^iY)?(PUCcv@`Z+5KxVi!Shn?t55^86P;eTZMDLyehx^ zSZQc&hCijk7M{E|)3$!h%&NTF%-vIC+Xa_fImP0KnZ|AfFOX9 z0Kotu0HFY30Hpv*1C#+M3s4TAJU|72aDa*cl>jOOQ~{_8Pz|6uKn;MJ01*JS0BQr& z0jLYW1Be8u2fzmq00;p@0Ac_MfD}LmAO}zY)CXt)&=8;zz;^%$APOKFpfNxUKofwb z0L=hm0h$A}0B8vi2ha+jH9#AHwgBw_z6WRz&;g($Kqr9C09^pO0(1lD4$uRjCqOTN z-T+E~J^%!O3P25@0U!ai0Pz4i06l;Kpf5lIKq5dAfDs@WpdUaAfC(TKzzmQE&>vs` zKsvxcfI$F*0fqn!1sDb}9AE^%NPr&zMgfcl7y~dCAOj#1U>v}Y02IJ@fC&H-0e%9Q z1TYz33V;P*D!|VG(*ULe%mA1PFbiNdz%KxE0OkVB1DFr60AL}&B7nsJSpZ7_mI7o0 zECW~$umWHuz$$>%0BZo&0^|U!16U8R0bnD*CV;l*g zum@l-z&?Qe00#hU7L4ILcNl~w_|g<#zHD!>EazWS;$KtZUsK{=Q{rD!;$KtZADI${ zb|7)u7`|t(?b&Mw_S%uXV(()cLnpWzy28a8GG*uiKQjzH;Tq>==mr1U7<#i;CHtZe zTr&*>`$UDW>=e6qDh|H0>X(V|l*{X1jS@5gDS!+>4xj+256}RhAwVO5?*I@$6hJgU zV}KZdCIC$VngPTD{Hsy^t5IT~cwUXNIndjGkJ1CxW|-sz>)ICmVA0Lyf-Svl0cx=N z%^?g32joM_~6zKnyb-&T+*5{FR0#6~+V)EdXz z=6>$Oes1MKFe&B(YmXpm1ES_r!Cm0VM(IIt8lHGA%FbIkt;$|iQgr%Z>wtzcnL z5(Hgw&pOz0a#c3WM?+vqIaA*R{1NU~_-_rLk5qCsD%PG|I|P$A*5E02?I1gc<`moP zP&fNeYhbN4h?WX&YYPo>6#LrU2>6k8e7Z^cq#mH zi_*ii-(tXB%GbzFZds??*y(VWQ;JI=ZSEWzECR7Ds$-+I@(t~>r?Yy1*u>Vwm@tWJ zZy`Hj)ZRX+WFL2zHQAk86RHdZ5yit~>i~6vSpz5zq1CpSQjEyh8O5_bpuEWe5aeOP zfyks`5pH`i4&6%RPH0)DRbhc2MnH$^MjA|bhOn5mxsbVG7tSF!@CriGh!%KO+m1Ny z(8s!-l&^K_6VIaIX?;U9{?6ux?}k ztPMBWVz{0#++ptkSDdmaZo;$<{LUYInPdvI&=x<(Hl15vXkS+dAqUpr6EsGjHQVXhl?r<3*q4I3)4E69Yj)@tB_(p^qX79_Q zEceZak6D*R+Yl>)a>OiKm%CtCI^~xMEy95NSyzi>af=m4*2><(9dE1bJ$!Q?Umm~( zL=qMgf2X1ah#ATwUX@Zn-~O!{JD>mr8@rT_`LHogCc47r;(ZfI902&`9>)3r=sYHn zO%SH#$i@m*fE~W0YzlCUJ+H54Up7U6lX*@^4_bsT32M)Oi^OTL=j{LNP6A!$r0uIQQkGPeCK>acSl zoBYa4+WD}ZeCh~$*JyH8B-aAo5p5P=R)|$S!$SPa#&ojq%@T}Kej2?=4SE;E5rS!& zyGO)l*x_0sa%7dRu)HWlA zr~MC9>supQG1x=_LSDaIQ%6)QaOeuGAzrLvlOOhOEC|Cq5)+cYX)TjTyl6I$dB?HC z&ryu>WUavhweGL#DJNQkr+@6kP6#s{a3wWj&MsZ`2d-5u^SJsZ^l8?Frio!P+}mMC{;*cZryv z>l(VNwodym@}OXkDOa88 z0`+VT0FDqMtWk}a9IP`+Me*J#tRsm0%<*mOS+=rsVKsCfH^H2D{uCzYU{g4&G+@9`#+GW9w%P1;qW}jiz=b(VwvBBqi z%|?fsoVn3Bb2lpM`&OAo9P{Yjl=o3fF9R+1K`Ec{-9+ahT@NRzNqqMigvhd-)-SqK;9szgF^U(G#yAP~d zaSf&F%#*UHbtt2Bo6^}&W~2K#=dSiHwF{N+;Z-xZ;(AoO?RV8PHta#$`#fI3J2exL zlD^M2h%I#gyVw3&NUcD7%io*y2rWiYZ#UO7^XJnW+9sN3SazWFZrdNvI5Gvb+B7zF zW%fq;S>;lV7F=3NSM`xrFpgbAZ|p8v;9nw#zMVL}{IH^jP@MYGw2$4ovHI)e`1AGqCb?>wY474a;4W+6Ry z(cUA}^&GVB=Jl=HRzbU`{%~yhx|#GAvMTea+YB_Nz57j%$35u9sr8!4K5V3)wBenJ z_1lH2-bPn9l-oqVI$zJcPq2hmZ`dhld|@Vib-;|Kz1q(}HQ#rOdVX{{ojmgT#XIp! zQASzy$)<;QBiTdQ%_};1-%@_KVaz(TPet~wJoYAP(4SbhBQ^_-UU4dFcldm?eCqR% zh4P=!FY|7PUmm}huGV?^G@bt>dO^U#=zGoAqS5u={y0u$L}kj{KOpB#r5l7)Ya#Mn ziXxVblCNpIgMJaZWy(&?DKuKr-Luz#rF86N&u$&IEvGlHxG^+FvVo559yrBlnSrYK zo!ND?RcF-Y>D^ZzJ$9lo*AEDq)!T(q64#f!GxBFTcERudi(6zN_wJn+l-+UznY%Wv z`03?5^kC=532{(f3&PuP*P#XIkCh+04ZgmZ{vbIpLAoM~PU~HM{FwSH&`^)!VQZ(& zq5HhQH$LR)0=h#JaQ-%3jViVCz3|;18|X_5pUo_hcp42E(Q|X)r5w8cjBrx*^BVd| z)`6?;TbI&5?^*Gwb$Avka+){es%1C2S8dYF5gT&QzW5%MU$&c%`tHl_*S7XNB=HlT z&>owM>R%`MN7H9654pB(4I;7^51P`n+h* zQhLO}Ssi9a3`LaNvx^;ho9Nm%PEC7C45bg5D_38;Vl7={ooK&c;3m4q>d3ejE1{m= z6x7X!_jY<^*3Zc+eT|T zg)dW_+e+_qo1UgI&-`H{6adkN0V9QS|30`7n@bRPJ=j@-h4c(mIjo3WiDMddex>i?e?O^vc;Xtr%a+#H+pqS@3n;Xx$)^-7vCK8aqNnV{)ZN#;FBpH zIq^TC$?hS+yWMV~i9x)vWs7d21D^M6Q7SDFE$H~ixoKzT(@)I$w=q-Jp^JAbo;p^I zqL;S`6_{@CM5#YMET>(OgYNBpy`o(eK(BBj5&QBwYhs`*>B%3qLWNzb9|Z%qz5-PtUuTy z8&R7!B~|)48=X5;C8O8Uy-40+&`&$}??$DbtQj!*#CkM#QwSsZDUsyOui#G8tSsNA%ZmqvG7L>tr|BQx6HL8-SFHhUPe z6p609BPU<|1!ccp_g$;jV~}`P)8K$Rsr12v^hs%A$!cbN$A%&bG3Bft08|}(w_}njWUn%nS|0m)BKMQZlA3%klsGov~qsn za@1mJ(3*KcchIA&e!`(!<{{z8-ft56rPJL{WLCc3DVeU?eD1*6GqdTs#IFw%&aOq3 zJ=@hPH*qQIys*^#UzuM}<4Wr-MTh2~vVqi`fp3cZ|?H zoV|A$N)wJ`j6>(q6_1wgJ7mg6dY`+t!IPc?>5_YoRFmhlN72>azYLkV4z*~+JmdK; zK~1K7zx8O{IcVO8N7YnsXChVC>`#Kjw^4Rl@uX=NvQgh{JENa*xD_eQdJgV&%^XGX|(KihP?yNvnTC7mgZR z`V`rXyt?RX6uG^F?&~(23K6V9vG=GSqe|DLH{aZH$8uyly|Ka7E@pZL>N~T{wZ9_9 z(;bJ8j?Qk7jl@IO4n4JeJW3lE8Wq-PEKNS#zII;RawJ}>uJgwC4w4=dH;-Sr18wTJ zwDi*E(`d52_pLWKC()BiWlz{*o{1I?kGM2;`$F{cR;TW%V{%ZV#g(Eor6l=|uk<{HBEBD0r(v~?^knta$CmDL+M=`jM@6( zC)DQCEXmrE42qh+Xv1IEccbKM$ND_zRvgY(jk(C%q2t_!B*N(1+4RUoJ-H+!oyURADo!6)`~H(3Fk(nS`sq zU$mC)b$$G?!-tpA!jJ_o&x*3>8Ut=pYy)$7Q`p`9c=(8ES(V!$zgE{dl(5AJL6hSscX_KQlg4gGYwVeHa{+fly(md&lo%|$iZL>ziG zJq<0)Zn%zaIlE7#XO~W+ zu?K!%+4|U4`azYTBPW$R=)I43j~!ccDmwrC{P3ZGheGq^r=O_(Fg|nil^(7@5B+YQ zJhN#Q{V4k3dh^v8bfv~q6IYL|@b&F?9r<>x5xgvLBidMY(IY?4 zE$H^bS&JStpO4l=)~`xm*@Fh?GMQl;*U>9C->s$WGzt0EKXSI*j1Battk~J-eu44X zxBI6u-L}#rer%g@an)gTs>#5Nw|jn}uRU9kuwv<2x<<3M@0fM-=;l{K(tIMa=%4z( zS4)b_Mw5&A^-bHb0@Yn{_wlyMKcmLUx-It}EI?0xiywGl;8yfgQq5EIJ8eMcE?yW? z!Rrp{R%*oyp=c|5b>48x`~6^)68(CNr~WA2xQ2Mt+xUa1e4`~-uJ|scAJy5-`>W@4 zROI;X>-3;(dhIMpP}EqUub{DA9*kc=*F45FJhW~vnpe5k$%kcE(LS^8mwokSC!IYw zp-Hso5<08Q^>IJkosIg-mh3LIWg|K_e@xGZmig%U?eN;h)|*h1b^1!<+Rs57?#-x~ z*mx`IcSU%u+3>AM`Rn=1(KT+M5?fmyD788VZ9EuUf5+Hew1225djIW1^pl^=SMNn- zBg5;J%x>8a$aG}*6gp-hQtb>e+gA~T|K;rZu3{SL+`y-qN!1TwJ74b67BpswkZ$f=#=bcSLKVd(Ylh0l3rHs zN*_DD>HO&v^N~rLymloq1;xKUDbF9_H-vUr*C+o+W>57MVr6$<5D;^qhm-syto!VC7EaHezk-v{%2P zN^5?6FlgLapO-x5-14RVVLZgzT=fo8P7FPWZo1Tp>{ z4^S-*AUD;9zs635d;$jSD1Cnxa@)Um+n-shXt(}jM{SABq22zv6!S{C2t~Pd{wq7~ zAni77OH_@6muR+yfl4kmQVE8gHEOt+AW|49gbkWp#;48$tI{4Rs|$rarZ&Tq+{j!r z7?2Q6XkD&hfmf6tj-|%&qwF&-epDCJ#spiV)ue- zrH2DK*vc4wwj0WZ8SoRDGze^*d|8+KW+VJ`h-F5zl-H_t-N%>=Ti^xW5X-O)f6=rUT(;w1c=Ik@F~bh{4KEq| zXb)Vh1;LB`_{kx-;CS65_~s~FtiivAWBC48d^wIUC-CJYzIfyOec%Flmc+kv{RD>H`$xp=L-cdEE5_0vPNEiJ1G^OqJ3vqFv$)yD z^6%)50|LgRZ!Z>ggg#N;JN6G6y;yW-&aqD@ZLo50$q4WjjBsx`J-k01NsXP6+#F=( ztLUCXc4pJE!{47AUUCn*Qflq)c2n2V)t>h~O~=2W=eac%Z5VtOZR}IKr84^j*rFAM zrJCKO-HYB(%-nq)Jq)>Y>&5Sn=cJ9sciW(G`Q#m-}0$MzBOuHNU zd+2W99J0!t^Yry@S8`U(pNDR_bw}n#!{{ZiPbGDZJV_5&#V@|4;vssB#P`mz1t(F5 zxU|8yQ$NsCe17WiLrJiyh~CxTo|;Q{X=@(Ui+Mpm{^^gfPsjd5JEzVmdic+4w9aph z;BeG)y7k#Kk2%Brqo%c9cY2V5K}O^2YwiEIj{YohWq6gdC+Osc9r{U^ZJ?`27d>6L&ul&#IZYO9Sr@Arl#*my-M#y zVXasDm-+C4Ua3rs+1A24N_{lBbF%?A>D7;Z`|V<#B2kLBJxmA6-nDRefoHp4)+aNcJ%ZSbWYLm-PIJ&sDAFNc37tT zf!>Bydl1jthdxc}-bPBDK`Qa;XRXeyqR*bF+RE?lRW$UTTci4G&(fYzaSy}B?Vv9# z+Go7;>s@-C@Fm@S+C`dQuFI$Htxtk3u@4cZ8+1MM(h1p@UeN)H&P(TrPtpE0Mn3fX z{RG|Q@W>f&&1X=@l1+qRM2V<-t-4T?EX(Ml!`oHtP=6P?`e#F;$gBs=-YJ^HLCaAhaQja{%6O)r*!p4;m5M79i~&KHVD5x`4N4p ztXJcV*m(%-_aS?J@@CraXi{`pJNGEw`-$E!-S(h}o(K9=D()M#Ct`k|&}CQA!_D)v z`c=3xmfBNb&-66qa?voeK>Dl8|IeX=9 zQFCW*+*$OgSJbiL1Co!vyo+`YXlQDivIeQoSDEqacPHo)KXp#{`N;;ir)_B)xb*PuSc&`PTYts91aVkD^U~(v_|@UOV!`Df&}X+q)ly&*%%|>MqdI59y-g z$3EY?{wDpAXg$mF^K(>6aU*Bg4}a33W6s2s-WnWL=S}9e74?2c-&f1gr_4Bux*a?c z6&-j2z1}=wu5j!ndP&n#KcC*1MK>S1X~v;ax9Dk?v6>bso?s4o4l#kv^vvU%VrID||s;{1EuQMay?+luyUF*HI7AZ|ka)7e{+W zbqP8hSGmCn^he!8=O(^Ci01WczMMGuJ9;rOGUU#md(ge=gU`+zbBn%xr{aTsJ5SS> zx{SRw$^8;teSwcAyzf(ba@wlqiNYg@XqW8ab^H(dhdaeIe#f8CrF?(5cAx)<_SyO8 z)v(y3be-uv?!`ZUiAMZ<-s3_M|EQ!x2gmcs{iycdofUQU)*>?T(6;-f&!Y7|T=AID z{VbyE_pVZ8%w`l+bL6WO<#Sq7uFl7{eRt6*PmY*;+CQerzCqM|l9=4>2vl_r z?xOt;#&|CpyoC-Jem-0oz8W>%(=Tal(dYDPsz`*a)L~SvGP7dd%_nrTI*osw3|psN ze3?Ezdgl|`cl@jibj8Q?h#>vORH6uTJE+|EPlLUp>ZXc=6}L{)BNAF3n|vxT>g3FQ zYtmmoLnErLKRGpWKl;Atj*XN1-a$<_b%}bv=QMrWcXjgg#{1~E6~F&*a@%v@e~kN& z?#Jof(*zIAbsy1|MeePqJ$_2RTihjV-ieQNOS(*^Dd;ub&8vk zR6)}(9vt8iRnjdm`OIzKsB=rM={6?bK;s_IY&bXj7V6UY-71CVEHd|h|GL0D(U+B%wke$VX`nizY$@08F|Q7b!~uC(IzJyf?o|Jfg_ z=c9gnLxbff?xCWyU#_Yp-h;lYbooR3UAIx0MX^1THh!YN_nG6~&-)~b-|$B*f#M^* zuE?zJo98^D{pwBjTDs#tZArO4VU+YG{rK+q&LuARG9!i#8u(pnuc+(s&!)eqahj&L zr+E%`4~;6hyIIVI-M7)7E48zSb@&Z!-h1!H{$)qew9Fmyxb`>D!80$)?615IEy+pj znN;gEof4*g_~iH3^ttC3>b_`phfdb-&wjG#HT}c0YR`MGc};7hyPtPAJ*G1cTpFLV z>OGygR-(E->Mm_ecdwQ{zIfD<+XC@VU4o)sY>lg$ZMluUpWZMw^2P}iziCm_+FF;9 zpIcGW50_VQ2O^YiNL{RzYkI+&Lj zK4S0(`tp-2PZN**N$(jkb6|h>cXXGj7cX{JK0~^igIA8J^n}h12)r@6N!ci@=^LuE z3R;ZkIA6>8vaEY)89U zt%^6Gu5+Q`9Zel=JG4STyu%D7XLaObYdCFKA6Fu;snHiV(!tdD{FEO<0wm$gJ$!^9 zw=B{^^>W=>hlQy2#7cIp8M_}CyFs(nB(uLZMZ$b0tf+I?e-^B?Dy`UXFng=n^-L}B zqBVO)(05;L7IC{z+Y7g_CK46yJW8J}jAYFxY-QzH;>R5^#BEutPs|OTcv{q9$sc>e zE_Ad`QRC$!>^!w?4M`!E0J1f-76cpqHUX^az@8@MfD3FqI4pwnZD&2|iCx2NUq0tN zGYy9jV#Ks%Xgw;)p&gJKMuex2hqDA>*-A-Rgk;-d+Ws&UNA(i0*&8f3!v|WjCv@em z4urwU+g;W@+qh!b8GT#e%SXrlVsF0GUxslQbJ_6sAMr0+`1=HWGZ8M_?r@GJ{byPf zzvpXL{2DBaFbjV^`B`|fK!`t*XlTh_OUnrY>e`j!^J^xaiGp& zBn6{mZmdOKw9_Tnqe|F-5?pm#6bbC@h>4MX^rhBNk^&hX-i00r0v6M9d$8lBOWbPR zc8(8SkgRpxvJH~6>ua9Yz&QaE`a=8#GTrLm|U%q(f#v2GVHQbb$hx)}OC9bQDO;GI<5+V+M2?TXH2j5YnvX-7u34rNQrj>#&q#eCYF+;~ zd5e8u>`UZbp_(lmshkE5mVliE@D`Z(;2OJdX3_4m;M;ug(0W)}Ee5^9w+;Je|@IZdnI45TNcN*wy$9#2jb+^81sK3;6H-O%+mB^r&X_+(7mx@qgUd}YV0{>SUr`y1B}Hm(4D2yX+a&t0vbA4cP~ zFc_nAy^e)LXX^oh-?--Xk@tin$>7*Q828Ue(1}2EYws+sz7j)j+uOm?A*Rn<_txJa zO?x|>D(jMYxDcE^>?zl3*h4aR8yrXPZiE5f+fohJyfb_c$BzsoKX(V_ zS#QQw+9L@z#K=?iLW(aqZB(n*yaCNIgbvXg$TpL3Kep3ryYU3sqwg$odjVT%- zbQ*S$f=3CgY1pHa+t9Ay`mo(s4vumL@7{mC_q^dqXzK~e_8RBs+%L3YlNJf6&6!)Dbnmq~3jPHS@QzDKFSZlVG=)Z8#<0ZL>lL|>+=alS2J6#W`^=ku3(=(8}2y>R8KC1G}J|SYQ9s( zRItP8=StXDN}p&>w8XhnfhO1%6b`zzyNU6h$m}WCY4|jyK&}_@Ij`*Ls9#zIS|m3z zG-kkG=Qb*4rj};*)w^9cizvf1aWmID@SzM-aYu`*$<4|!_lpc$5n8`IV}AR#*R|2* znaMYFm(P`}z$~i0XVK+3;f(ptlVRczyXK_!N{_1*QIiz_iNKdc+YcdyKZ zAibvipvugo^tAeQ>r`PzjC;PeVsaHWvcP5R^FZjj+}P)0E~d-lol-y`g7ca2Fa=-e zPy}0m^A|%A429niAD=qK9 zo8h-r32>ok8aBY&IETiVa0KTB_H%Yw8TbC5=}VM{8ICESs;L#El&{c`Je5Q$;fdu! zK2I)JNqIuPT2877xj?OuTMT%G*5@kmXEk-H8%~99ZGnb$zpb)mryg&GjP zQQ^zaPz{nQiBE_~dA3iiq{}TVic$ zxho{l&EhaX*Dk{lf|2PsEa8KEd{y`eD|XW!e2#?eR2u_@(@ca5#{ee7PaJ0YcUB~_ z=bf-DrOkhAtza9cAnW`|6HfQS;neW&tK+EiF0{0+hs3Ka|8F-dj~pt;YlMA&Xo=n7-%OnC)r6B%hry1S@4>x#0s|{grAr05? z{SCN)l5rFMVH`K&b<#GD`zzdV-1H>;gry*-t^dxzzf8kf>&3?qk(GbukCu$5ukIly)roI8;MQC-~ONkgDx zq2=*PfhEq<`b%hu^K;ra!Fi#Q*;$I}fe<(hx9w1n2#*~7%LXGn0Ms|ZzdWm+@?>V^ zdW2dm7D*K{o>&WvK`c_qcmzobf$K^5pczY~0-+4_A)C%BRFM)=D%bEda;*TEBq-8^ zj39YhiAJlHX;dOYBF1ZT`a|&?$&LqNqQV_IEK*6rP^UX8XY7I=*cGA#^b|e?(7H;; z2pbWy$eZPaFMHF9#jU-Kr<$!L+@D(3*Z*K+Ql{aNI??Z|0csCcvKi9 z6G+4|nOM%}355cX5;DG!rvL=vNdSL9P>4xFBE>!NdDGZR80O=0tmPM?%zSczcqMBr zfvqLZ^Bq2gZ;nJG7YVdLGcvhU$P){IT)?~mnvf6zp;jZ5NoAM_Y&4@#Y57`-Py`Lf zQdlmO^9Vkl&r?aopb$ufa-ocbaT1#(}i(n`U)lL&Znf&|S*4g)UYOG$y4z%~6JKyX*6bphI+ixijO?z2f7 zK#D84m%ckb+MNB{IH@_(U0Z#+TDOEuasl^0%m-s@7P%7hVBnmu&F4z&S zoS_K5SnF1ZGV{qL7k{leIh%TMfWL)pj$EM;ss$P~Poxk4QrGbZA?wBA>}GUqu>eTtnwfQbeBlL(W8+G;fX*jP)Q)Ujj3tD!z5IR`8=&a1l}j; zAf7@lC4odpiBKleCoKG?TBv0965UhoByYAHxInOFO`DJ}5N;0go@}$XC=C zi0cdakph97F9OM6)j5h3q;q_V+;aSt$%W1#V*gHmrgKPzQjt*3CwL+P52J>!1p`C^ zUTra{B83_a@E4Zr{txIJE-7m`ph_;&9tn4@l#Wpts|rJ1QaK?}sX?(;lb|~Y6oAx( z4Ad~SP((_k1ObXgKGell#Geb`v1!KE`al4cL%@$E&WG|#f)Jj>WLV;s1Od0P=$h3R z?jisv!yy0&kf4z5P^$%?^ory>QjTS*OsnCkghIGTL5d3G0zS-%Ks#*GgcOqsffke} zkqV@)SSyq9EwpcXtiA4o6{3pr4ezZWT6LAB4N5`1{ zk+r!6ddorNY=hf8B4;P=&q5Fd=%yM#0FuWiq|i5fU=b=InBi)^RzQO5m9NNGF~gNG zY{eIA`G$habfK8BO|c6ut|fU$%2bG~Ilr_-%?BM!%aci^AVC!>V0KcWipQ7n1!{px ztQAQeJv1`8Rw9?GrQl|eVsnB3jg*kUlpCQH^VNh9oXn1Lj+B7YhA#x`L#P3a76C_B z5d^4xVvSm^(uhFQ?)1mGmTapeH90BqFscI2;2kT(tm77l2QRP=Oyx zB9+QXU|$l61RG7(J$$}`o|g9Xp1ZTI3$bGL)qHNsf|@6gNkH6_08`D)loa84+LwF-uUi*-RjY({nh;u`ZA0TrCJm4L(|xm-)g z_&ga2vpZt2P(Y(s!_2pk04)#Lxq|h>*(@6gU#3<`NC{6QgP9YUrPhEr70Gxa(CA5+ zxRJw*IiP|~7Ks!hm?4sYWy2Q(w*mIclanx&ua&BV8i_;6qq;H9)(aY(@MdV*3NyWYF#$9r7eE{iC=fh_P^>1k8V#vX+3g_1 zR|p{!eeq&B<5vhF5ZEFip%U>FA`J*3h}YpMK=ec85?>|<#aOCPu*CeoN<(HC6v~4H z3&GGIezMbDWJ(Iyu9OF%fncm+sW7{vfi2a&OeA5#o6Nv(KMgDm0cVNz9jqF#y;Fka zt2b&)Fj;0cq~lR91oa7pBA8ks!C#}50`;jd^;!o)(Lz9_ zfZ$`h!iI^U`w$u_O!Ucsl{XrAYf`0 zJXiue3#9@1BZa9~Kx2_YA^;sm1J3p2o4R@wSytmqrZ3A#E<`Y{gdqf9tmPXDE;FB8 zsJsqRYlh#>L0WLlta^Zu4^yx*Pz^P5;JIR$f&fWPfY%0dHaTCW6=)pw06wHuh-Bc) z2Y)>1-=Iq?z}wH0DPUS&tbr*R)`++10dloe%~wHOf&$`qfVTsgg2@b#=rZuZfEg?z zMXdf^4)9%mgupMwpx%%Yzn4aNXpBKm*qy?4ANb$$O2EJd&) z1h8PGtmVWrGiREjtPxptv5>?b#i_6(EV!377)2$Dpsod7Y*DO;2#O6N#)2A6(67Cc z!~|onQDay1_k4Zk%(-*!nR91m@7ajIhx{>n=W_06=FGf5@A`Vb-gc7szH8oe-_7^X zL!Y?s!~g5Zd7E!G2^3~Q=A)kmwi_vO!!a`1C>yLw;$XEx<306L+YPq3!s{#+999a7 zllnnMIg4~f%A2=y^r%ak+6!{5w_{oKJQ!u$D|RYtN@YTP%H-muWqz0;m${?hFeEt4 zgKU85_wc6N1m9JbuJ611#xtI_{uQsi*}Qjm zJ<3ax&^Q5=$R<&agGeRRioh8V07O_4bRiW)JWeoJKr}5YmmQx=QTm-0W%b>bth{$dGQr~rR37ICS14uhQg}CgEGdaZr=;u z89u*h_ie>j^~P7#k@GHH@7gRc++nd&*a6U`p}~-lgh48Rk5E&>G-s|8Q@Zm5TQ8%M z-Y5hVLqUHq%!rl=6{+0Ol!DlgR3@`PRn_-~&3d_@+FnS7bL1j@)H48~jckxuE)W&Z z#7W=KSN{=*H3<8Zk{$tm2|0%JHhVYupYV>e zN_t)q+@`C=_d`mZP9SSRwu{uVz?Zr0ctf7DZNTUYQ_{O!*bltX3Y&irznHQ4H&`le z$?sQ}=TZbS9}Aw~`Fo*5>^;Jg$2dcz6UlH>JVp&mF;yZMP=U>{vxDIfpK;{i28UF3 zD4kQ?iFvu;Go;6E!{vL5(_EX6*pNHIyYRCBKSSxr$l`qttr1mk(k5ACUDJ>XSRh>Bl0CQjk$mn#BVL7<24fWVl@8d@M|y8e;DWrs8&6UwPzz zzW>2%!&Ll*#A%%Y!5(7-sWS+6V@LUltzqo@R7ZWAO#4Vu6Z)j9GjM+djU#%%jGxq? zU3GKCeRL@At!TgIoK==nRpm-63c$r*Z-f^&d_v`JI) z8Yv-OZ5LW$6ck$Ekkrs;@f<>%%3nUB$vO1=EDRMlPr_`=$wvvWr5tZZHBvIpK2cie z(FoyKa)eBtd6OQ>BXl@{!y5M4&!@7ZLy7_bqy|cN6B&~6k6gYgD%6Ssy1>JHr}o)B z{>r=6Ex*mEsph_z*)SWBG~hx2m9ZNt-Od&x)1TtBc>vf@-1sk2GP`o0bu1M(`PR?m zoh(b-vpAbk&`8|5dVPHz4zu)4D|GtA-zl%rVaZa6qoH6tw3>K$g7|=B&H{Cwkb-KT=8bg*jJx)hEeDS=9-hqkIcEF2B(*B{iDS-C<`Anm!$pqs$3OeXmpNY z1(Xs9{%kZo>4OnX(L=H?R751Jm0>`l;Uxio4MrWY8c`exZNl6P;2Gg8%|V%ws1NG- ze^Q4pa_G6S>KLLa!!WeDmP5G67S_%3wYYcE?dS>0FCgQ04%GUB<3IY&{a!IQ=`w2= zK>Feg`pWsx9LN(42V|kiU1oG6k%R`TWeUQIMhukQfO89dnWlD(J<4{B-KH9fi?66^ z^-9G=`n$y$nOz~>?eZ9N*@gI`d<-J5^9Y)YE)z%Gx1fk(`Q;ue%3Bf&=TfDYaf z{_d2XSHoKD5#YrzDz)vQ zWV{H1j$}yEd6duq79fzgLLiZC-wyFX_;}&!Io^{)PrtTcb@l z2Reb~$5CW_K~90?V#FdyWAaJCqVtREUHl@{wlc~%7*3MTBbRorV1ymDg3-`TQ@~7# z$@)G~81U<<#(Fl}X*sNVE@w?))$_FTR`oy|G?-a>Hmn+7W3Q#nC!P|~)O6jJcW%1Z zFP^?sv|pr@H3t~ekOc1_kEzU4YX!OKllq5djh0zj=+dap1KXB)g!Qf5kT+(5xPeukBq!$Ovu0JL41-!Pob^5j%$AD zMSsL^V?b?|dAj0?G3>ecd!0N(hi7tlP;s_7K8xeCi*q=3M>H4D<>QEXgIeo<_j6Zy z2^XnQ&o9&ck-_SQxGr94?yr3R#jDIo8P*mTn3LjrdeuMC;a45s_x630t*6o*^+Xl> z)R4LT*7vD?@z?Y|FWna!sXy1e#nVpRyyf&AXfD&LIpBiD<2gKQ(&C&DZ%b*$#yk%Z z_Rs}g2NoQRcu3>$jCtl|DK4C*NqNxO%d|d}PDt%KtR$GtC$l&S*)UtTz`%3@bf_6N_tUpHDB9~ukAa7S7Y=+C zIu1UuV@;G;x4%hUU*3_kCQo8Lzh7Mym--U)OX%RsQSDUN>~O&8BRj(5b7IQr;2CNP z5YY1|#nGgJl4`&RnisOE;Oe2$7==zAyY|RH>F&zqd+NTr!Ct%W6UMjrwhkq}w`hm+ zHs~O;%1>&m{FI-iJ=#~(<1K#4$1Cem{K}jZ_v`v-_RuTrp&#C=`&9mCq94drQ*{w1 z1$2nKT$M+u!V^xWA+0~4PKJ{fgt4pBsvqF<0b2#IC8ffT#U$;ATMm%~yg67~z=Vbs zX`&xII^WSmgeWc*e>kF$EplH_=B8E}(X-)=`iBr#UA@l<=kI@$*`)To;%AP4;>wcc z7?NTK$VdMZ9mI4(QQh-t$)ZFhcWcdpR|S8o6+aJo*O~iY_M)@aR{T5_=~#A~p1|2k zHL!8RUGDgz7hQeLdzOmRB}%J0W~DYrGJvu%v`*ALl~AB-EOfy>SSkNy;KZo>=`l5I z6>~o;^C;zQt;nNn+;GokP-~e;UELDPcJ%AOk$MV8G4$3&*2ZM z;lyxJoTWoaYtha|(dgZoWu9AG=6U=q71xd2qBxI_H%o+B`TXUT*B37}OBXNWp9#rE z@p8`2EndO#*aDz~>s#roT;+EaN-#{6YczBz9U?6>0u#Xz z-9jx1K*>u25p^W7$iI0qwQO#yas`5dH6^J-UmO~f46TxLpObWU(7<5~%*?i6 z8iP@a>;$dFv{Wlk;VR`a#09TIG;*I-bbw-X(8#)~n<$`PpGObzn8V`_4IaMuQrCU4 z%o4NMSoBZYY8*C>wbjoh57%{lD}A+JQ31NJ(ZB+9Ok2A8 zje5I+7O^1AMaclx6=DaMoEl(e>TVFz2o$X9a9vx4P+Pe^4UA>km~=JuFU&DrFhdgC z)S9jS+UkH0f9R(-ynRC%V4f@JXC!fAv=u!^Sb18FNud+9`gpJjt$2B8n)iz>Dxy9> zhsEiw)sPP7@4g!?{QS47nrO7)hustnw$1?Ij^M{0kcahreAVlXSrPzwTLB0g+OlZZ z8;w+=qK$}$Hq7bx^kHQ(-4YFsp}9GgkBjQwnn1{IFzRT&vKL0B_-w< zy8MBo>N=plNZN*+cS4cexGOzf+B%$R&D+F4cZ&BYT zcgw4GSjF7u)A*A~uHotCWNdQ2T7H9*BwVNJ@I(%sUUXl(bD5s5fCCdR8F)1eT;$^e zsJsaO3B2K6fE%06P6tk5iy&-;huZ2Tk4PoJ$4+7rFPoI3C>=&5?QJ-UAdLeO&M1qT zm+Xn^-KW$P&>==tOGzs>JP>I}%Qke7R5@4NL%WxJ%&~jC>zglZsQ8rJf!EHo@KKP6 zA%X=Zr9l-S3nAC@Z5%x}CmZ9^`0ATPsXDidKo8Qy9I9P4!GG2n_)qb_`k+FL@kD_V z0JP?`tGhd6~EhjBr&)IC!Vtu;h2dCZSNlV3r64eeavki{lW5^Q@O(4jo zh&E#CkUo9C%w!U}z+{*j_L{k$l}U^8wpJu9*1!_k^?UeCCxC(%hd+~xCfo-pE5sV$ ze&%3X_(09NV4otsA-(h?;WSO8V1a|BgIqbd$RiskslHf`@tqtx?f7N?f3f-4Kl%UD z{{Lt7R<7y#R{HAUN2?w+m93x&9rz(_=d@K}{xz2;!WWkwg3#MWNtc}reXK=c4>xi%b!k0l_Ue z2-vzgJ(0OMwJ{cxwwMFXc<*DK13uI_Gu-4W?sl`fj@^T^l{!}0y}kJT>dLi2U7Dq5 zktXA7Opqo&`|qE<;>+KCQUY?p~4J$V84NY{Q$DD>;l9f# z0ih?siAa{gZsNrALT#K1SPhY9#gUcgOSimDW<=vTV9EkBMGKS#=45Eodc~v)XdsOF z-&c?1<{Z|bD5sDn4n3*#{Q3i$&bTmSP&7#5069>#V)cjM$XCYPn_A`l@t17-k0UM>dEC>3VGr}>iM25@IUCOrzEc7$nIyY67g`>u2xDk(quW9g$_e$*pAye294 zc~8rpM95i_lpm#6x~dD&AmwK1St%c1VT8Mv+*S3*+9C|$bn zAOr)ENUhbIYJf$9(s!LB<*G*Hq*h^-GIebwTQg=0OkqKNGJ{M4rGG?Bw-%8eJmQ3^ zaD$RG%poB(j`jvUlKXR5gOooF^D@!uaI4Wisqtt-lq&8Q_enQ8_4gbuBadq@(^6G< zs#Ym~?_=-%lAB%DKyo%O2Mv-8IVQ{&pBWDqClG8W>2t=~VUyrU{bOsE-NHNCJ}ZW& z@oS(~r|UJYD%qvbSr*PU#YE#ly+c+Lj8b~_a5^;ri@pzK z38?PU!3MVuy|u3CqC2C%0PS?DpvlN(a&U4TPUJwstJuuHgx-bzA@qK{{$K0!DNf~M zvJ?$)*=yyY^>K$BIv(j*9}Y;avK!#DWDgK)GV7N91EvLfhE!v+anzUS(;7U;e9A^Z zSyi1RQYJr{5#L%7BHh~^`qDSo-{wKzZE%O?r$bZA7Z}_2R6=qgPzlKsDH$ealr&Bg z!|kvuEknnUvPlr$qJ8jI9WLX*#(2AaUiHN{sC=!mY)#6QW-*BEOP70YNY^XGrr@x| zP&q;ak^LatrxOu;wgWk!NtRar36lV5tJRb_N4eE31%s%`oUxraxgQY5O?BnQ33RfW zrKH9zYJN1nho(?JR60I$A@nx({Bk{r_j2f|=oee}>5t-TOdR|PgE)3)8!?F2=v7~< z!?(J=@4gY`Bq24)>@m86N-yo+iRjuuFcbZypzCj zv0qXqaxrnuBTS5--J%7TR$kVes8Cv}$;snkLyC*aNDjp9;Ojo|)@QxoQC+T3 zS|G5ykRQ+Ptl%)!n#qm0Ht|~UXU$e^LeO77Q9%h-lEibda zuykVU;OH>ou&qN)Py$i}`fy`SOH(6@f779y7};S!WW$#>t9-q-$~XC0X{T=ECX4U# z@k%_4@0pWv`t*V3&P%~99#(#hhe}-jMu$X)qO0KawSl+fa;7JHVgqR?B#A@+9qk{8 zwp@6!QBVQXNlVlYXIf%vI(@0lu&a4~3rgP2hgOat)wNTd-2 zR;s=$+2J@K-xNb1A@FZPEu^;%-0geo?sDIcHJ~cyRkso-zifxC4I8zJ7jPSMUG*cR zq@HOUj6B1eVKUPTGlUH&+cnt1^AMa`SMiHEC7?K8GSxSrFiU}R&=8zr%?qupP?ZZV zu0qxR6~}=MGf*^mO)ekcv_R9eJz76hSYWCQ1X6~RtuiZ6r&5xm6$9=P6WAb>^!Cwd z2alNz3zhFlGOLddWt%*a3`I)oGMN>Jk~~g^>7qeh^a%BY7qbssSYTbU#0JnnJ1-;F z*a3`bfqEy8z)MQ*U#|D}S{*Lt(9@#SlNNU*S1|EB>EC1O049~JE*4f3bQtyw`6%pK z7JQxoU$FPCbASHvBlh{>+~9qdQUV8em{Q?{Ojxk?L>SbZND`bKIe z#8WXiJ$YS>51{@5*-&o*edjq({rY3PK7AJ?vlybyH+O;9jcCl0&rNP4W}>6Aj{sP8 z7Yh^6UCvXo=gpDBI^=9`elb&f*mtSekKcEarO>B6f&6&_7df&7+jN|e)WN1}3&SxT z2wGQZNgq1QgvLI6dEj_7UBOurs5u|{Gh_^I$daJyCS1O!va7q#>N}pul0$^=W(+vW ziTSxjIqw#!E&LG=U^*;|qZt5AYl{ESC!h8F$K0pE#h90=bT9_+%>p38MKZ}WF?lfp zhP>+#!cm~`M&&L{mUiB|XmsIiNzO`BJ~Gm?I8*L*l)4cdeXfJ)g2Pui3s8(9bRqO| zq0yKX2p)GHXvTbxNyJ)yf$4}HK@T`*)_Xjz7Am&2R^A zs@OBat}(5PMy`}uj_{FDQ)OoLAaf1FNQ9a(+0VP{Ic((6a}U%F{+b1;P-{W)X&uUb zf}*_*pdp$vtNh#YD(j2?;AGQ&#aH-exsm2SIXk!bFOD?Qv|q>sf?wm)m2N4%Zcd7% z>r?9+KN&>DN4Ur&e#%BPs9;G+$tZr(AniazV$cYW@CH*sR8|vGVAroXccH2?yjZ|W(TOK!>5lPAvx>Rvv$0{#^fh0KQJ*-F5wLjXhUUbG&E&@pikQTep2U%x(Sz@dCv%h@G}B2fk|)CCY5=_^ zZ!u#UF8dq=FWVo;uDqT~?bEFZT^nSB_(*II)>30Co)Y{7lnEG0Y1L!HD9$kl+%a>0 zm6w#q8L&DtPRV6@E+8-CcD_h*`3fB_(%~{4{*l8U)M)%E?fJR$1h4; zzM{j|b@*Oa!Rc!^XY-CBI1T1K;wmZKHv-3@zRwYJMIeZSblHP#ZH3KhwVNKPOo+5- zmIkTDokqLyWRZ! zRc}A*e?Qxh0V#1mUzOsqPlm{h0q-+uXd@L^@E=4N1U^B)D!Y&3e zYJ?%^EkM2TF9{$uHz4wk7$l%5nrbttMvbzdg_?vWO}DxWVcr%eMGN-1j+V?!Lb7`s zQJJ><8#f&O%xC}pMwh2c#eF)85SbzYSZR)nMNy`SeN^@tTn58dz(^$qnd-?@4gjiy z)C9tonHE9bp1^l#CylISK&B0|kRIg5{vuRQOo-rQ;=9N2rU7KL1~wh7h%NYD5?f@O z@Rp&~B^g%^bg0!|+I7%J_Ity3w%@5ol4}Sxcf{;j+(v0*ixfAW4cZt8m*!J2biu@= zN+4fvMdDiVY+i$#W%jVMuPmH#1j~#`sF0 zm-Hac92rQq4FgI^4kfGMHB>5z-r>OOqRP#yNdX416|?Ky`3pyHvmFtoQBn z)0Z?z^;|?PWtade??LWs*fNt#$h6TQOUE2c)+3wrvPoL6IZIOYnk`r-zK%CCi9_GO z3D)zCdR43C_Z#C-v(h>fhnDMfjze3oH@4QynWu1s@*o3(wWeNxIZ|6ASR#66aP8(o zQ_RiDDJiOZYizG>zmtaDYSx~0MY=xB@t1QUipmHV>8hq2L4g`^&NO=t_S%pnHZ%L` zaFKWjQrUAUT=;kd$QeasW_6=dNsYUPnp>_&6&aH5p+ruUN68#9B@K)lN-&SC0?UF1 zk0DcaD+M6h7BU9nVM>_9umGId)Hb^;p>Jdq42@L3S(5S&9X_DL)f{?m@4DY34}M~_ z5@7jc8eG(r27-cHC+P!x1~8311V})m?8>B9`}Y2VbM;ln?$x6mmGOp7Btl<16^~Z} z-wcCV;N_xCojEsDXBG^Y-}!8MQ`Y2%ypZjc#wdQond*R@3HXYi=&i1%3Fcq)xf6ZDtqxmU$@lZ3Z)Ul1W~IdLjm6&`AXoEYTTfy4lY_o00uD(%ryZfMxz{K z68G?D;POxaQJw{+7mxl28ruxYyfR{HX(NpeW3p$1|A(501P#f4)vr@H@S#gIQQFV- zJa#{@x0Kgivo%|SVlj-eVn{6RphL-yxV|`?lL;GRafJTYksQEF>BqieVAj8z{<2)Z zu=rUukM`9}(%sdi$9{9+)laT5?Fhwfcn`(lU0+7ubi(CwLscuDq~YzH1|;a72RWTQ zWv~c%h*2~#O`H})o{_1+)E$o2bOKY(>C9JGHY|jwai%%P;A0s#1|<|z5Wwv!x|?Xl zfDyiA?B$IOCD$UNv~j+mDP%G_?eVc|6mu_E{*mEt4}HgX8U~clb9x9KJeY#XB-omO zgE9u(LZv>VIcr1;1eOfr^Oe8=7w(2Z7O@&f1~IohLfQy4oZ7mo}nD z>L1#O9;N@c>Nhq!`muaWJG#R+>APDeG(?l#3e`W2*L1N$rUXtZ#(aZ0qJ{>}*Jbp% zMJkY;)oZsBsYAr6spOtwec&oMpsWr>2ZJ_2Qqk8`->nSA)`&%)URM4HB5ZK~%v&6| z(2&vgEnZ?1?DyKOPk!h@n{WK{h7e^QB7kZyxgzSJp}EfhT!svMLI0Sn z)z3weaebK%&=_Kzr`Teo4k`yGq<8php5-(ve| z%8cY=WOO6p^wP>#R#zK#zqy~4ScUSo7Kc^n`aOg_p*|dAo)>muXC7d&57^h(X^C`O^i^CSd6JX#IUrdkW<7bVx)oVuPo=Z zM})nJF-YzJGa!$edm0P{K##UXren~6NQs9g#kfqS_OiN7+OG0=8Y04~sb)i8j^rlm z{v)Jl25|KgemMgfT{|W5&m^|3j>7iCJeQobs_GflHgqy2nUMKay}PJ{Je4U z8rvzGn*^mWt3J85>X~NMp1jt@S^O0WzroI#3!J@tf%U~X<_cqm>f&6T9X;lyE}qTV zxy5sI>`FpiJfBZjwyAi5IVleLGoI6JIW$_HLdE2o3c$EBm{GYTpg!aR`J(sDLZ z8K?7FLYOqrt>BHIUEwQE_HtpHQk@cxh z-T&_Q|5^i^O1U>@`$~x1i6wz#!c~9+qsFpWF<}bwnqL!WmSaAQJGIy}$hZ%ACt8~u zEyy!@H_Kb=S)8rhS{pY^7zfYQ+gx>rmGbIZIf+)%R7@5{fkGoCJUj?$H;%Fx-!Gh7 zt}*wsGO#LdYjJ^9*Y7c8#x;>T85&oHBcRF>r(_Zs{=lqKp9wIQvRzeRMa2a0h?@O9 z&4b5Xq+u8k9ytJ*okNm6kwMCC*JFGMhn}0FUNKp-lUaz{v3Q*hC81+GpJbx|nPo1m zE%R1>uE|U{cE{o!{8h6HVlHsS@&(ox@8$$__m#TP(btw8m&JQHJGb~Jj-b0cUi1Te zx_W)ZRpz9)TrcOnU0+V$_)2x`;-y?^lC849VgkYxsG6vXu$_!*LX94}w}4J50xUBY zu$RLFwGQ)I(2Bl9c4WaE2Rs$VPoFZr#hB;Fl%^+2m7JKycO-D0F|cR^a+Ljw1pt7W zxIVQwN+HO3{T|wt>ZWhs@{!+uPlHmOmsqjP;lFcpz8W;dqTA82m6xr9C_Rr%$b2E_S=oUhsrW_KJhez9k39$ z5~%INruAfKsSSnXH@uS432Qb))&6S;B0Bb}H`=8m4#Kfnmz}<}qgc<6N@?tsZQXk6 z)*ai|MQOTY`(CGS&9Xztae!fu(hf9{8Q2xJXGiho3|4uk9+q(JG zbZLiXH{O{ASNDS&A`Npx^&p_UtqzNoP8IYq=nLp$6AkWz&l|s6u!xKw6K1-P(CS8) zjAI7cM{!b}L5>Gcjt%1i+dkx)^s{*ut$=xK9OZd;I({AFhu7gQ=Pk6-nXBH$3RK(7 zu&c3QUwzmWw!Zykln=y?L&#TfO6ip#%yGcfh9j8#VB&@K1sj=8jWUHP9l|%fdr02! zVL{1I=_KWs!wLu>HpIRv%&B}oXw`E!=bdD!$SY%wF<|Z+r)1_mrp?fMuI=*Y5}l)T zI97)Tap>sc^N)Rse9<3U|KK>CKOT>|MN+U$ho^KEg}y%Lq$O@*<>>Px z2f$VsI`TQ`0#L9&8sKu_F+%)A94}$51<|cmj&%5t&g+=riONyH<&Tw9VqZHTK}RuF zhBYTv4q7r%@zDT9g928OkUrLE5jP|}OqsVq{&vMZwELU~9Pp@*UijRGB-=b%7ShH< z@R()VeTndZxRLvyTLkk9Wg4T7A&<~q$Yo!gsjF~VI}yY;IIzX*k(_0$*M*#cMYu>Z zwQ8*1ps>r8Dgalx(#kUH1Sj8qg;Mc)BmqDK!z)Gy9Ga98PLMEZiT-LF31G4#GvcC3 zM}aA3jIJUeOd829z$SD#0~ya9rcwG7;imvx(69PQ3IaDvQMa_l5xY3#zA%?=0%0Wd z0H(%$iJr$hIs8G@tr`)E4LX!f2pyUY8eWrGR z_0>YIZuegy@R(>J`ifo1(3BP;r$Rwmyihbmq&5T~BHndFX^dW4NHZc*(K?d`lvWX? zxHKC$KE|rh*;A4|O|%eTQkxOybho6CHZY?q9WL7Q6a=^7y`do1j?ONzdzr621 z+&JxlsvtUvuo?!1#F!>w)XK%G+*1lo7InPhXH=lgE=7Lujnf4;rZNK4Zr=I*I zco|RJnq@mS?Xi8YQ@5ske$(zOcuS)BS6z$U{jzwg$1o)Sw|jFPS9a z@AaVeJ-D~NT5OVVF@V$SeTt!cm&F)fx}K+A+^y@Q@2dzW(8!4*q&YhcecH#oL`etJ zU59_hEanmP3IWloXF!#G+_5x-w_yZEGm#U9S;QU7VFd9q3g!_iFE&Ypi6TU6(!yeC zm_2#GQ7V|nh>C_Eu^Sj51mp5d&9k+x^7ah}-uJ?IZnx^^X1Y~jDFNonhNC-O>KM*g zh-+vm3b6g2n^Vv?>6gC{n!xzAyE6SS0b3mS+~YoF{q`%J2QoB9S`R~@mMblf;B2<%}2I;6#}F zSs9#{x3!|uvT?(qFZtBg(e6t#G5Y!Gl`sFr)304UMh6Sf_3LoIN$S&L8c|z8YBdc_ zbJ0Ljk`re(L=wm)sDkbgHXgXe__%=7c<&Ngq7TN`FtQxl9MT@L-Y4j3ZsyR__ggHI zO_;&rMLLu~!FHTtqa2xKURhh_0)D38(hvPuypF%((KHB#xxgEjFR;FN6DJg0F4l!6 z1(!E-c5ZPAN0Knzpo+z1e7bsl#oNqDajss@3%b6XzS`EHTF>E1HEjz?z+(@4;4@y?L)#AN{>LrO21Cu|X4pHf zNGzgMTe)Az_khA{`}fK-;cBo=~=%w`A)4dpBo<#Q!8oTUDtx>;|*a zN;O$7IHM-VsHhn=ImSn|s>${nEVX`aSicBznUI08W{^2VF9DexMvA3&213G`ayh*? z0|`Pc!6g(LF@t!(-~}keY<}s1=5(g!E#yP;L*g#wf3P~dOQ6OzY#X@-0IB_9HpmC` zIRAx1Pp!Jx1g1cY#gBC;k&DGo`MCzHYX=@SymYhZZCr-wpf|ez^bkJx@M$X=%sy8)j#mcIuXF%jugb zSZ!Mi5!p=+sXL@M<80O;z0ICaySzC3T}#EE@xNEsouz7uY|%mL&+CJ~yQFcg{Fz*06#w;zoB1oDw zHz9@dOgR?7_2p_LDUhz?LQbb)JO<#+X>ONo3VPbLMFlBS0SV``ZnYDn8 zhVb~ST!e?5dZAx>6*X<&>UKkRN5crI4NpY{@)zI?AfKfUxTSFQVDkDVWh?m@F<;lbuEm?1)K zg8{1$Q9;=5I&`}mowqPMGwxE|iE2}00D3M$vz*9y4rjA=W72`Jo&Vj%ZZOJ}WIM!U zR1uZ=!7GUw5yz{9gM)Ib84HU1bJPU1w=ScWXmaR0`~LFLVZ zc4`_igh(C`I8jAKRDUB4WEZJI>SF=$3}vbpr}D~aT|1%jnlfTv<@HUdlD@_X$%Jp{ zy{?u_Xw;o%rFDiXsa&TsRZ{Eq#%w}#BZf@KNt82H#LrVkrvwA~(S7n#Osw>bU)i8Y z&COLFVo}{+cg~phTbz5*3UW|hbS30q!Wq+kg)$29z!y+LaHwV|s3ZL$z9^_Ty%dH8 z5eQ8UVqxL}xeifzMYxmXl!cX}UqS9DH0xmfV@1Q2whkaZWN0bP0Py_rab|+iy@~;+ zhU1Wo_v!TK<5i54Q`-U7>p)=C@Hou+dn&V3u3zkJ5=#>aZ|7Yy>7)Jlt4hL( z8=8|buIzKVx3TOMpEoDPk%&_9=Q_AL9Mx5_`g;AQwNQ!TiPsN)DueGDH`jRm9t;$e zb|_bQpq{;92B*!+Si98(+Cc_8wulA^^GGp}#flgsiET+7P?^lw1KwNn`r+JkM<_*b zL7L#3)4B%6gSp}4x@bLT7GT-1Rx`uxUjMa6C6}CaY(t)D9@5Nas$d$T0D!kiX^?eb zSSP$wu=talg)lHOMc##(z)F$VY+yrn>5^ML`k(Lo{ySIMr7C5W$e9iQgXZ&)e zuYA*fGr97vu-`(Gyqf*3>><5q^y1YZt*A5v|Ra4ezdKmb?0y(NC z!X>Fz7>bL`8b)cypyEd+F}kJe9$IeOs3)Y>{FV#Ndn!Js!+6M3@kLIS71fvYzrM_2 zThTtXsX6EUfL4ER%6eeCdbs1)j(F-bAJ~JcXPAJyfJK6#xD5HAeU}@~xs^bzSwr9U;6C84 z7v~{2sE4&=0^iE}+rH=0i7Dl_xA8L`@;mrv)g9Vs=*>cB*fccsdbmj~b4Rt00R{VLljw2^UD4o_;<-Akb=FC7exFquccZzPS1m8IzIe4xUZcZnb$A_zZSC`GO1Jxs z`f#J$ZLafXEx491t9QM`WW;z_qp_5?t_1d4V$9@CuZ{oEx zABwcRQsf@~&j`Xta8=k*dX=e}|meeA%nsX*5ZbS^gv7yL^BcH`VyQyjuev zB?&3)Gh<0c83?oh0&VEaVj8$_Xml?uIuHnHUl!Ac`(9-v5tYDBP z5}X!{Kk>lOfwQ-yWm$9dHR)L{Cs@})?6vsN&(Ic|kX)W^_SkeP zROQ}tS~p$kz`5|8f`7F^l`HKifI$q~)c~eaLnO@(s~SlGy1uBE|>4QMHgFr(&+Hw4LYoKZg_F&@+#|#w{k*wdl~;MhqrIz?A+q* z90hXapVif!vHO?vv5DQk!kiQrb$x1mjrTdar>V9of9MW{qQ`Nk#?Bm5TAG+3L?1Xg zFnB?DwY{NTj7Or2iY7dQ0#pqCvvBz|cY>g?Y=}!B+^wqvw1&o=!_=N>da9u}vB|)} zAmGHX3fNw-GVRx{4$j~A_FsD9Q+mYIJOnEa@mq+i1u&y15z` zq<@VoFy>Q!I6rRMYYutZ;xtD&8L_T8ChZ1`0Kk40fm`kg{XAz%zRlEfF8SS;H{SEc zx2V#8)8Mk=OZ?gD0^i^$lvl0_I^~sTwOISor52;2nZk&kGAB`_o752*e@p!`7$y#g zWtf;4wo~o4?T5LeRP|1#FqR|^_3Nd2+*88<5)uzgh$E3t84>-e$Q#!2cS2jfyG1hy)%wDScS3U^m(&W=1WO&-66;4JG&hqFdK`RQ966z%n-$5eW9QYI-}N#Cj=P{W=pFIuU{ z@}ko!a^nVj>p36&*f*ZG)Zq%Hrc4gS1AEj)@Ro3>hYQt;#(X@t4FMH;Rcq6zyMly=Ta@2_1$W**~vfLk!|M+l+}n0`)9h?$&_ z^*VVt45px-5ZY&!hDcek_6izGRR^*GD1Bjs&Y;C-AYpF}`Tt(>@(-N({00xSOdrkm zv!>xt9tY(^2PQFmaa>@{E%Ql)U@4g2k{MQ@g(=4cS9M;1&@5}m^Em6L9bYWT>#H5h zi&omPyy&!cR2ba8bg34ZvEpE()rO*ZP*P!`3o;5!5Tolc;ZCS(PnD#K0bc?hVt4|> zI(cKfwuo7|#4z;bM*x8sE>-(@=4CcR(iksgn0aYnAg4u%f|($3$e7kF%uJT@rFsyr z19o9k;^Z&2P^H^2BpQnPu^Z~u6k9K`eeO0A#grqBL5n=;K9vGPezJ*q(6>6$gRRTq$jfq@bYUrOcx?DpNV${yyRDmA{1vC|`+rZ9U zjr^kll!Nf}0I~5NhF+FYd>&@AeORr|q?)H;z(nD27+%gj;)B^^o9aw4YQ+k3F zyVDcPSo+kYEf%IKcb@*Fu~PN}gr?*_`5%6gnry7-{#XLGszxl;R!D!II@6#L5ojr4 z>_lXR&`euQH^TepRT=x$;I^1MS&4_QZfcq@W#{2HUB5>R3Bk|@T1mGKKzNHDV`6K_ zpCaJOBPjBSZV>RQ(8~zR0A^)+Ba~wrLFz5uCt3;_(iaC z7Bs$d)kKQpbm&|VX?R~|m7|weSzr7GCsZx(qkpJcwwGBXGsXS*cxA|nsTpYU0utDQ`8ap_AjSpFgC(G(cI*+%Dr{(^qfY=Uu7PsbkCBH<&BTqF2V3njOfn^B1Xev6jnsxZ_eg!^(vz z#fuC8y&&rZ&p^)n0VQGjw?x@%MqgJ>B~$)jTvZ6d$RzAA{$fmSagf5R4M&L=0YybD ztB=C~M8iF_Jt%s%lPmN(v}SB9h30;uu)X$ic0`cFJ5MCf6fcHKB5ss!m`jZffv1!) z^~Q2OSg+#|Iz$|LI--k>&oy1$aut}WVrYeu!7$e&d5QJhpi*<3#<%t`T7z2mcW*xvO; z_f`LQ=Sqd9TTIkHU5gG)dPr$PdY%>~IOh{h?9Q2XIC9a{Bhz=%rwUCs9JxCPjl|Vs1#b;4t_;F^4F8ZZKdk+V3 zERtz11!!Lr1xM6j1%-o?iAE77meG=u8BW2cs>|b%Li)&#USAWW??i+qxR1*@0r&Bb zlC4#p*oN(C7BY~>a+Ns_a{Jn)3T8^Vlc;~58DR(=T#-1)nrBFh8&5w?VS=nFNjLB) z=fJ7xQO@S$*kp0@@HhT2by2F&J=Q)>0m!m(H<|t|X9j{Ca6f_vGj;|!PZcU`)38^}jjW8+%llc;xxR)*nXcbs2)vRUyBZ(lM#9>H$^&poCf&Dymw_ZxrM-&O zQ%8tFQu~1={%)uhh}j`}apIrP+!i;dI;tty_WLkX+ufTC)6 zUuKo(*H(EUKX=Du6+8I&M5mo^PKpg(A5C8kW}FIzm=g_#SEASk)1>A5Y3w#pFDyWe4{_)g7)}8N8Ey{pj5wZ=*w|sR z=U{}eu1vM}smf5NnH}ZYnrz%F|H0MIZbAml(_ob7?U3P*1IA$BSb`%#k0v|`C^;-f zWLuPuB)q-i@`mzqg%dP$NNQN0#myq=j5jQm75(-At=X%}wW7KSVVOscCJ)sx9lP{K zltV;%4J6uWZ>Hgq6<9TPw)OIK>*Ym$d*gVLV-+WolX zL&yJZLmt1x;9faH;X9+-jFIOLl~Z zH2Mk`p`OQMgf8YslhgvR zV6k-j#hY{}@4jfi=A2cQvwr1DvlZE$UwWF_y?V~mrHWOl-PA!H#sE!JCfz5ShY6=}!ssj9Z`|0uWaQt_&I%%5;!MFc?s; z^)kJ__v`Qx9X`on4Zf6EvnMJ8`aPzaVF}2p@C60<4^lEWkhOr-$VfOECX2d9(5^Br zJnOMPw(r$r2+J@fl>>b)jtDOV)<^Inimc$RN%+A+pP5>hVg2r9lAE@4Ws}zcm1tVF z_&M)o`#MAI@=Jb{rr0}=PT73&lXh&s!HLnfrKQc$mK`9-Hg9=6zuBCM!}0Vd?bufQ zTCcU$plv@4E_mvq3DI9H9#=9+pag|DppZtDf&TPT!8hz+b8}VqPI-GBE<0}_lf|jN zWMi4L{VUB|dp7mo`F6`a?&enDYwu-D>5_}10Si!0FkKQ=3DFQ)quQorY>@{Rh)+6r zr4(A(v?*yN<{615nx+>M)>kTlncNUIl9FV85k?vBmXa(5C&*7R%V_G%0F0|B$s$)^ zTlknB%tx#~qBkd|j5{Pv4>ixwib~?6-$?{+cpDA}=x`{9o_oALuKo=q6(@TfWI^Z& z!V=kVPGEq6M0*5kL!UV*bksSt%S+AF60_}l{0nz`&oQ_7cta!IJo*cWgYf`d2xl`z z$tnHz;0b9-VYUDSJC;8(C6K}l_cYA@dn!G;u-%T7E-jt9qvnHm^_&|w`(N;aW+*O> z;g{O>Q-*8fhPARnA1jIIpdLK9D^i4h0Kfw^nnA7@B|PU$x*-JU4vD^Abi>XwUuXP? zit=j|?U$Xm(4>W_TxsR=m~aEOUtt`yxB&S8_VdLsk3^if+2W`6aV|4h+{xkAdjQC? zW10bBbI2CZNjZIaFH|@<#|@KpRA0w%DRH0)V>+U@;!iksjLs z7EQYT0eTG&*Wm;XJ$L6~vl(JMES{}HNpo1V(h@cbv03KDwPnud=ZzaC@27Ruva9>$ z{8h79nG3vX`2uv0n=4c_+ON^sghu|PRDKS-)P)X&ZnzaSzKgJil^uuJ)`S8 z>Kpr+s*TVOlh}{aJ_ec6LLlS;T#tY}hsYawVdjT3K`k>a3cRrm&AAwg>D9n@NTAdpZRR@6)`0-A zUZ^5G&f(D@i2y*AH&2{rgbSEJK>RTBC}rvyzN4#v?<$P_^?sDyC%ZnOzA9kS?7yr6 z0?LRPE!d2VBaRy8L_I+I1doZ>3_cTVx{FPNdH`NE z1Yx2A7-9m(iX<#xXQao1XfuQe+3<@q03xNt)F1ow^cU>*4eRo`>7T1-Q~9 zmx^EUzq{xHptQD~^G;!XCV~s;Ocb{-jtz7*wAM1Fqh5t(ZZK0?X~HH7=5Is~6=szC zc#M#g5%XCFJXu`VRnm8rF1Y@dS8j3JIlkHvDj(Nko>r3#$q?mObTUjwBzB=1AXlQ< zR3Q-}oJGmdQ|)!KAF;+v_yfGlNERXr|^g{0??3(*6*R+B=5f6+w+X8 zdhADvv%nRo>XC!R`Z%Id5w$6dyo@WPNE7-d`ZIXWg>A(J+vU6S5-0q-M{xoMQi*&DjmmyF$AISwcW)DCsy7WhJSO94`UF4#t8; zhHp!K=!KGdO_UXT&!Mc)=Zn3RUo?30v(-6qeFhv}hy*V89A3m3+Wun6=BmL~!^UY^ zsX^P%?d@K}MJq*L-u_BOKVf@rzrr}D4T~b#T5{SXyYMC8O=Hib`1U~0h!3)C3hbDV z=mq!GJmo{gG$Oky{zKeIFg}dB8d8~+t5PZY93UmVsNibR-&BEAXr=~*djpt>qAh9S zsR)&(umt*M$Y7M405l}zPeh|Rw=@K53pbkf%va4!AW&`|TBPe?cbu(q@7+dyaP)^xIP1TUZfNnCm+4StAV?s%f=32DLxhiQ z1VBP{0q{RolY!8B1`AGN&yvQhrMCS|UQ5}s_HBMLrETPPW@Ptw^$J(rd!>-lILqJ? zaEU`bm=O$9BC#O*!`J~Ib}5vZ34$ATs=1SjRG}#Erf9$Jyp>i$>Z-3dm6%@xq^|wW z$|n*67bS1)!h%X}0ICo&gAUu0u1+h80_vIu-dXL#5Q?N>2&Oa1HCG^_1ro?sM$(T- zMzAU2)M+19iR*|Kx)B8{M%4*v5l6J6IK(F*btor=Q6%+DuiOvy{(hyy?zdahK3sEn zP^dMr_@NGEKVs3^M%b9RHp~2Sd71UauQ>rC^c(#HM5uMyGJE`cKF%g>5F>MibqW#3 zjg!S*oInq@H~%ctgYCoFxy21QLJ!vQqBr8x)k`dHY)*>X^J0qiUEfY$*XwI~Qd3Ak z&tW`x5Mw}Wye^!VN6(0|MTI|XDx|tZP6&kZ7C=~|=Cx;8= zru;Nf`p&=T;*FqAPO+hI_|wR_mqs>lade&*1Je4~dV2cn2E z;Wi-(B&r{R35)^x56HM@xko~uy(mS#X4$V`2McaHelS8a#wQ)mj|;^o{gtG$gW}J9 zmwq?!8(9#(izrXS)63Av%OoJNlT`sq?Ux=qdNISnTv|u15#xPFHodMvpm8*`Fwqz7hLGJChsLz{8cOzx~MQRI4)r|9rRbHxtW z%jJ~hF}oW#@5Fui^H&kCOf*G`!wH`t_i6l7o3<|cj0qGegSpJ}W1N(^g{LNp&5NbAL1 zzGr|@Z)jCPXCiGNc`b|rew3llpn6Nb49|`7iiaYDn|?G?M`^W7hwOFv;U9d{T)Q+| zN3j@C;9!%`0r+)xmIr|rSU4Ts(Fso#6g51zc# z;R>a0j>zy+%|k8IyTB_6jne=R8i{EM04Wo$s z%$Iw}2-vO(VGbz~6C(@3JO-3(D5`JiY64=lTqb19hA zpm2kU ztj08yQL0y;Lvj$*IR7@%Dxk`MO!mqRlb`m2as`VaOgPjInfFoXAzhZ zlFWM9y_}?PefmKUJnYH_Cuv?@j0zG(ex89qoq7fl&5#;!Oo!@FEL3b|-G#|yFKjP& z^ORoq>)Rdh(sPcPn3$y}Y{Mw+SktJp2k-`Gw*7JZa%S7FLA;NbtaORPfCmMjJ(+VO zbuJDBW?oqX{24exn68l9^@5#AV1f-B*ghX(E}n1(xEO%6hJaUK^mL6|{m}kWT6eC5 z^q5F#%=C{8N_oK^*iw`cQAr@QQwkkmfFTz(Bi{Q->3zSr_oF|u?_4RJb*`A-8DPI* z?em4PihQNhiG$#Zq%VX4M$9XalrBs_8(gY|((*i|NerL5U@<(aRSX;SzqzwnOn-TA z+h^%~+AmTnTOY&)EsRVuF;m+V%0lZ%l$uilfL_z~b7hYzRgO}#x8WN>NbmGuYFf8-d#rG9=7zUGt?UXL6{QO6}ayRqe#-q=OKY#6AzWdXK>hvpk zpUZ*oyE&T;eDCm+hrZ>1f-{zi_elb}=w>%%EHflvLC|7fpN!oLKjp?i;Y0vhhOO+akzkxy612QZqJ+;R(h0g=M z?SZI(Y>DY1H4(Mp%^_{$VKv3IM3elbB zRJ0jdgg;IWh#twDH*Egq2YMRiAMSJtQN6_z*(R&$4M~O$xQOzq;$(8DA5-9_iS81M?1`b@l0>@ z^;W=deFp@5Jn!>1{0v6pcKoxdDs0>-W}$VaZL(aabK7L=^~Qa+0mC692DJ_RNF;jd zkfLykOQLiCJjXBat5oa0-!!7 z8i??@nRzeit6~!nO#s4qM@a~Hml_@_H^Etik&&ny-45ZWgmstScVt7etUOYbQy3&z z#s-BX0>HDoaRiw}Y$)R^13~x#uM=f;?h?hUArK(0Tu6H}GM1r0d?voRL~rB@9X`mR zr&*{s`U+}0*={K;I5{M#1B&=WSCDHE%)yIJM`z;tbZNrDrKi?wyS?qOU%vE(A8hbI z=jCS;hyv{hxMT{`y3988KrRqUH%S8eHA5fHeci>b+-?nK;lf*e=+Oth>y$|t4@X88 zZCzVx_8YvssSu2FS%i5p*b19KzII){jZke}cE)LtbbMa|v8v{8kau$r%K@6XgV}xB zzI3@y@g0aBfQe_2K4K*(kHut62UHh)`hHi6+X%Bz~ zyL2VvhNLPXS04`m&Vo=>^^YbF2BW}%y)&-c`#(Rp*Pl1!gy+R(Fektl!ZRYlz+FJ$ zqjoLX;h4rr21Wuw_yg>Wvw#1PJO1Uj4z6~_;@};3xMSUJ9mW}(qd01AvcZE2J31{(uu)1@E75i$hDSg<2z*`>ZHXa7_Fxa_6~VeOn8Kc_`^6EB7oH8F zx;!ehOl*WtRfJ)eL@%O0f^}G3{Cq*5 z!gx#x+fJ!qP+J4Vw)kT7Vbt>YdQl zC2Mjr9&MzyLYzE?Gm;Z0=v}R*z8jMhW~p^1IZ>|DIXTgKy>W7ahG-g{G)gxWeM?0={cb9p0P0sDyT&hc)^@#=b>vVERkEcd^uBO zNf09IhA}EsS}tSWh|d+l_5nRH*@nuG%&Z|}XfYB)#+|TL!R(M>f|oIcbp{FypJ6>U z<--_q3W{pm5%K`qdNTivuw^_$Lb}ECKrfm%oJc|+ufrKSJY9$9aOkOA>HvYMyaaS4 zG_)}YI-w$u&Lj^5Sb@PjIHmM8C#e(lcR=6ultW*z;ZuF`5}uXAPsCkNj^ou(g9{xh zYZM>s5#Bs*$NyRBb;C`+bHP#Hea!cl)}n3R#w%Km|K7owoXnZ{@2sDF^Rw$uJ-xEy z%lNj-^`2K#Oieqsyl7=q%Zsiws>92VKksq3yTww`euc474TgC{=OrExEyeJHQBrlN z1j$zfF@*nP2KZD)H4ET0WZH=cCa{vK;zO+jNu@#z#)&zWL48*rj|s-83V7h#*k2=$ zj2IMZqXDTt_|e=1Z_lLkuGI7RXAV8(zV2QgSr5Fd6!9KXy-Xk{*Z#x!GfD!i1O2By zjnlTNy!8vb&fos{51)NX5BHK3Cd8JZ3yZ!Q za0eY5#+sz=JG_;$U+jDQBr$Ka6F<-!Tveh=d1FWiRYd?N5G9}r7*`LV8j3nv4^hSp zoudR^)1yGyG;tXl;E__L?@3e&0Gw#~g;R%)6?n&GDSzBwcIw=D7t@h5amc8F=S~Fo zCxi_ME#`zi8OBio#w58HJ5cHxP-d^7{i-Y8ec>68Z79dj%aPI04DAQch;C25xy6^p z0|n+o)e2NY1Qms`AlFl|Iq0xTY!2b4xngrm{%04l0T2ty2}pt!Yj82fU$g{}8iZ5` zXpI$^wI((c&><$k;AD)B!*zg~2S6ST-!=e`1j)=|-oWDD3sN$i(E z_Qi?KVj;Enk=#s@C-*h7Gi~IQkhGlk7TWSSXqTo)PTtq~G;U z8hluDBP+4J<^9Z%Jsx?|7dF1kx$}x-(#E5|eCDe+U-a<{RxdEnMA-G~I1c@L8FCDb zi(W3!CR$>mK}uhrn*%u~rNY;#d{){}2`$Og!a#!qt?8OD-%}i;%m5;mEW2fLESvN+ zAIhQUR;XKFaScqiLJnIa6H3rsjv<^Et?!U_RXD3*h#I;$+eUZw+6piI`UegS{=J9w z4Q(M?D6IfMWkZrvBDrIx6jFFZiIOxhdfc#&%6ljbKb0Z+GToDl+at3r+>1p5^6u(c z&!=(@d}&_NbE!B>Z+um2-LNB`qN_}{=J*;jz>}R{dNyDjU!wsqxYPGvpZ`}b-{E1D zt_w4J=}f{mPFxukO4T^CVjFgbU`dd9g2I{&0#!4Fij7QcY~fkMzc7HHfqGl$(a7Ua zlQ0RT%2kF690y4I6doT`6*4wv;C#5Zi1$8jUkO5Hbk4$Nkqh!lLCe|3l@KpxnO@m zoEysSe%{z~PdWYQ$33qht2)n#hp~}PM$9xjl~5wkT#O8ngS6QRp!UotcWc`%OxniX zJ2he3URjzAd!BZm=Un=(Uvy4!gqa9Vx5`!s|1UQB01z0Ehh(U&Ki@0 z|Mun$7yiS2FQ`=ha&pk+FILyW4f~+HXq6l+FFG4HwJ%*NcsMXX{t?Tj=uxyMdJDo! zTp2nDBkA|ls)yU0x@iJiDd<`eBN1j%dh@~aWU!r*MX{%nIvySL2}A{u*5-nqXg&fX z^gzke;>}Q!fa$N=?x7yUF&uijAa#NNS4g`j0uMZy`E?}u0pYSSK|Y9R!4wPPJa7TH zgr+yLmkaWSXB_;58+@+8D9jUhxM1n(rs@rSIClYhMvRQuC#LPugXH7@vh?+bV((d) zW$5OI%tI`#C0V|iH@NJAJf5?aE{H<<)AX`C_#Wg4GY#(HajBWijP8hWY+?EX${tb{ zHW|Un@g2g81d>Xnz&LmuSaYJ5QhE`YMJ&!$6z#|s$q1=t zrfSkQKo&UlO`lr*9@_g9uDW`k6VBhiAu^iltBx$Xoz;C0E){Qp%nclFq|P+!F{SFm z+$cSXHH&&hiqxQyx~iE**Yy2J&4e)cDo)r$ACfrr-9%=g>h6kirH1O>>V&~-xN>F1 z%PUV?@frne`xQ!KPCFG=4u(0{RtUd{Jy3y|pJ9Y}Ig}HC=ksDKjY(cGpwa=97W8Zk%Qqg{eQox|e zN3@}gB6)@=#yn;`E4T?pOypCd4sOPp$Wv2;5ts;!c${}ZlvX+kr0`tNK`ruu+Q&hk zz)S@vM2tJhnslC!pJUb#AWIm888}~(LhYmHaZ?UG{nt90W6j#OurPOpt%Jh>o#^W4 zzBx+AFvg}wKt^c)w!?rc7zj1l};{IBG!{p7k-?Z^B9;iF0{EG+GPabSe zio5EaIP>1g1=2OOecx*8osrNJL_UGZgwac@-Y6@^ znj8eu5hh^W#hQh0SDE_1<^?W?6c6L9DWrIW-u0@2UTO;z0y8MZ?39@{ZJ;WEq@d7X zh93AWe6X_nxWV5xH?lJQRNl|R(@$N$4z#iS2OU3M=M)}%s?i|Zh;$5Xp^X?t z%hZ^d*%_8I1Sw=ZhuVH@-wU@p;!SUP}d zk!oD5Le-5?gfKTqou04v@G2cHFfnvP3pFVXm5z=m zx5nV{qv^z!=dB(wB1nQXcK3JbAmRWjN3OP4zX$^)8y03F97|7Q{| zSX(9n3{u9wK-7+^pF&C{Fwas1fKrDZ95TJcW0Xhf!vP@)2OprN#6 zo9#CPjk&fg(6&Njx_<32b25;q6mo41*-|7N{k*`_zi{s@uiw4F^3B__G$#V5Cpi@}bVZhrsckT=z|u-@FwKzw5sVk8 z6nfL_k$qO1J+d&@v0df*6;ouT|0VwzB)kAv$l0~Gk>+EWmT@9nf^*d*IjHItd8FZ`>yj74}_iGiQ z22E|QGI7Di*O-!RrSRLA9$Ogj{|+@<3byntLHCH?Zj1E-AP?hFLRiTa^H#nWxftfN z;2l9Kf_DUkv*IkuT^6)fOr>Ldh$(kH%t&W>2}XxttDund1losALsTFF&gZlN!4Auy zC<9kIPEVu!!=26zPPL_mFlU4sPX(a|H!C)w<>(53)i z;o3uRV8rJ>iezO zOgruFwBFjdgAiaLbx;MYDeVbBsziCi*hw1AA}9|;UTFHdp1~uod>((qW`pu@0K^U1 zW||Y;5BVQjeHaY|bByIls;ZBZ`=C5Z4%ITu2`L1#gvo#y;q8F~LLrmEv+EMY^L2QM zx!6u`#Q&%#>HP@WY@Z|sSHVHc3@-|y9)TR4doekJz;q7vvM*1++ih=m@K@&&mCPpT zNr-`%2R+Qg!bQqes`u&JWUw_Oui>;GmB6Hzt3CdBwZPo>b{rPAFRLO{4bRn3hHMZP3UVV3%&_&!;@GxDKPI0PZ9dUHJ%=DxNNYj60c;W3jg7^*y1%?mK}+f!w@fdj z*#pKO8d2nlILyI<_Ly)O?yAgK=-p81XEA(JEC9hN4`ls=Ssft#tn< zS*rFcjNKTtRL1yKwMQbis-i-yMut;tsv6-(?{UpjA_tA2;CI-W$eA>Hg`6zy8&DZQ zvIvpg)X;brdOJrQVm@GUq6A91rez4Zi=p#H~xBkMgxQxi= zG0}?Z)1V6G(#uLnyWy)nM7r7Vk3U1f{H|2cUFEsa$ym>8S{CR#bJmC*oCnh??#f@R zu2CCgzr1KA`{hNaWnbYz`_iS*k210vhzIoE>U&@iFvWcm3=SL@mhCuJZYcRGWsP4C z5{ra8BaL{CV!mZ+D|y5;;?Fd@H8#EBJ9Xb1Hyr+&%YM53I!eb~Zy&HtNJfTSNJ_q# zQDvlcl_+9Iff~TzXJ%S^RUgL~|bTYqO zq=WPX2}}nEiH#CkA&GaCpW5=l(y)8*Q|W;SV)CD~{?rUjPIIvwCt6j7+40OIJmxnL zSo&%Rf-cf1@NNA?wT@lT`<{p@+ry%LN*)nz5$23=f>9fTp&toeL8WmtQ>JKjkiK`| zqYk{um*$hFaUt|;0DwQNS>3e1CA6XQaTRMPmqGhbp0Po>D!-k=w7Myo{0y) zh$$7XY!c857A&Bz?o#5hWL zLdcCs`(uPftmH8rL#feg7xfp=x-Q)S&tH^7!L@Il-r#;l?t zXkv~u=!?=&WU_7zo`|`*Nt&ta_iT#0%}R!rsrr_UbQ|s8Y2IDVcG-5m-O|5v>C}Y$ z1szV$D0@=_i!(GTlVvjM4@4?cU^(WA(QFOREHG#ajDYVcj>Cqcyq!z!5n!~Eca=Nn z61so^g11@bSw1Vm^jr*}Npxn7JADE6N1tjX75_D?^81 zw#)w-MNxi%9A0D))o2r%5((AipsEfBVIuT3m`tZkCw@_K`ky*{PX{egT*slO^H#Tr zH=lCe2*@3ggMv84Z4$>LMx3NCE-&MAaUKnmX}>V);+MVov7?h4Qe0&iHOm?%w3RSC0nTW<=jK@$|8PPi7l0YcByS#ldierozOLVrKg*rgNJhh_T@1zb*eK;fW9-YC7n&4a%9W-gs~VoW{Z>o)#Q=hRVSXmSC0?6Yg~%n)!N4#lK>9R` zn~CAoj!Jx*Uq=Fgkt@d8BTRt{jBZ4x&Vmi0+sc$Qs*mH!llO%%g|1x*1-k>5MYaf2 zo*^;O=p2(gy9W=p+J#stHzf%Tt29n0VV)4?1OP6W1Be5MbeJ2j>MnX2jt=Et?{unI zLHxvU#{rpZRZAE`#vMm6#sy%=2f5`zt!Qf;eSgm~J@WhB_kJ0YjB3q`z~<@`g!S-<>4rkA^Fz|yeY%tcpL zR;p_+sdX@}Uu=5l4fS{WvypO0`{l2GRltgA_ubL1?tG zS0KU%V7?;EWD2Q+5x#j}d!ii5?>ns=ly>QQ-!LKuBO-3LtWv)@D3}mZHqew7ZA2pd z8`e>u1brUqa5~dR8a11XCx{{tgh`Ca0?36YR609)p{MKc_Z-$>`lj+0%;kdi7qnQO z0BeAEl$#xq7`5QcV8|D&6tffIT7$tKFJ1fOBewO}vRHkEebCUm&{EJ6BknuVucEw! z0p&gxTxOl+N}6C<_I`V^`GkjG+PU+%(Mf(I?`Rpt_9o6&qSyejJbw7fkKgAmOT{I6 ztE=wSa!34s&7FOW9AzEHNedRWrB(3-G=Zdu2Xb|G_hw(zgqm84ErMW;7~3ARJF~r= zw%7Cat}isims$+wE>&0saw;U!m}TGvLlcx|=^>NV!I$ z@n{P?+J?Z8R3e|0r5_OucDhW|FHI+x1LI6FgR+VovsAgg`)@73{NbNmCV);Y zEn^iW)?JeOE^-1#a#56%T$r^gNSK+UUB%n;daT9d_IB*Qa>cDTeQ{p1CMS7?cmvA> z^MX7rcn4bRLCkQb0OtadiGf0*r6>Q_-mI->C&d4@;P8)Xc#HvW`=DOWc|&402{X&# z$SmLI^_rD^zk2jvzyA7{>Io**KII?bx57BmKWg6ii>^($9@91wHyZ((jl|_FbtsEM zfNPs1xM`myk6-~g2R#65LXfzLEZ45xMW8j)-=6I+>@GUr5YZJ_QCq6t;+t)${@x*?Ii{U^1qFvP z@Hmef4dRD$LMR~oPMBrLuaas-v>Nsl?Gd4yN<%5IQMCi*bBWk<}{``mi7WSpMLGEh+ZC44g7mq_oI}+U(d;k#GtWWGOl!u4IW7$MhAv%7%7i;^Q6sjfe^T%Rmh<#YvU5( zQQ)z==w)C8G4eHCPugRjIr`0|kEgJrhH%a3Rlo(XQGC6VZN#dj^=`ebEII@T-P+*Cev1D0sKUK*C=Za@XJS*)i zSlTOOR?4}6n?7_0NT$JpNd#ayYNySKz}h8ciYp2Z5*B959L?d6XG;!UY4o!4Z9LQr znk5cV4M7m#G0@lWw3!OTSv`RNXt?m*3B$Y@uQdv8JPYeXb7yy?%|O#ENg+aRTGN36 z6pSP;;97u|xGw3d3C~~n^7r=bicw7I1gWCQp-4T!rY)1S0eA^YnrcfSE13n_57Svt zqx=H}#d%vU&BXsaEtx(ZOLI>DTHcW&xQ;J#1~{>aO*7R(Q~eL-iB$iW)rNO?(r?}D zjMwXEt2gj-YQtMt?lspN9;=P*M#``2=T4&la^)(BZQ7N{@8M14LAc^e(jH3B6YWRz zBm}*gF^k(=WT?#)T;>e>>Xje+(8q6h%gq20yWN1e-6!r=#vHoS8g8MV7a|gp{Pmsw zo@OX9{Rg$wVGpco8ekZZ8ja3a#%h?__8!5N@CADZguiAfU*&4l<^ zXjify;YEY;r5s5P(nfEZT7OCD*KqF;cMtmym2|K>+B-PsEVYoaiMd%i$M6&R@vv}- zmku*Av==03h@vHjTQ&sQ((B~6$zw$08Wf-aTC(LqIv|J;CwCd{Vt6je)8c2+M`Ly- zOOLpyVYP;vHGG1hhtzP-0fR5)Fn@!F#5!CtHeqI|rkN#nJ)3EUXQt`%NA%`X8b&qL z8OFN{xS2)k@6->+_J5hr*`o=2`J*Lg=vx{(OD6hRT|@#g!3`9xE0WP#7AlfL@US<=d1tbEXm(@lm@bPjm2V6C9C&g)mfWn+m7qWD76l zsK>MNm4s*>`~|8yw8HW6bY&>(4vJa)^t8gmYf+`^s9hSnYe=`Sa(b`-{uumUCpeD! z@W9LijscAjR#A{q{^C;YI>lW0z&pUnM(-xHzW(EgS zZ8vn`khh|BftU>Gh5-l_SX}+8dGakBdoUGJIMKb~V8~zc> zrcj<5<+t$1D>U#ELQ4|>Ot>0F)Xal?8!4ZvUExCtI$6VW8>~1$JvawwwZJPVsy`h5 zG~mL7-H7{SD@MFAoSk$xB{tju4{|e{2ylyZNZ@!l1(fFVnF>KUv-?lU5}(oVdkxQO zIK_}?7o3SLqM^99x#kwbEy_7L_MmJ;=DOEso_pt? z-TsF{%~y+;?O^h^aqcIE@B9j2{m{$@PXzt0P=M=dUiR&`b@H9_|$jr9N&G@ zRGaVGOVGaLy6Aqhly~XiOqcQ-b8by>>n-@uu$?OS(AjbR^6xx+`o*WwY3%+9!SW-g zY`MUA`fZ{=!V)}H#{<*{X$?&rJU>!7k>zI>-E3a2DNhtjQ!yDLCQdX|=kanKBxs_9 zS8fK(3!GU{6thqVMC4J@%HkvQkT*bK90WaY)BRY^ka9&tv-ZmHG*<+AFl@TiWFR#l zli-T_802d|Jj8G#(%S@uvTQNciWs@#k6(W5`ItOvr&dA>bv@2qTzbHJtO$_@fqdmQ z=c(1Bt5XOt9x6Ean|1Ok8SGE}4=@i0-u~lHUi!qgb6zE}XX%YR(I^YaTHb4+bP|x7 zh0!6;y0AJ<*uBu(HA1L;i>zd>13hllm}_VTo52;dt{S1?x=$S}7j!Igkoj|_lcwi@ z&>ly+6PTuM1BP%<$$NQexXZ&Twt zpQlJKN5eiI%Zw}f1>VIT4c@L7p1kpiYTJ7A@6lsTYvjR)hDSs2p{+(v$LiqO?$-d1Z6~ zE%=gQH;ya~aNVLug)JL#V0LH$$uT?2T=whN~GwbBc ztVz>+_wp;@neE>fee(hH#y_Kna<SRNLVV4%O;o+edddHa7bB|G|s-f4)BZy4J5kSLyqYTo(V;%p1<{ zOv*x|`l-Q_SKa>86TQr+YOrQ(^#j%|?PX5Y zfX!7N=Eb@