From 436a766910dc55c446c7e455ed38ccebab0bdb9a Mon Sep 17 00:00:00 2001 From: benhid Date: Fri, 13 Jul 2018 14:51:50 +0200 Subject: [PATCH 01/26] Added objective functions and labels to Problem --- jmetal/core/problem.py | 42 +++++++++---------- jmetal/core/solution.py | 6 ++- jmetal/core/test/test_problem.py | 23 +++++++++- jmetal/core/test/test_solution.py | 33 +++------------ jmetal/problem/multiobjective/constrained.py | 6 +++ jmetal/problem/multiobjective/dtlz.py | 2 + .../problem/multiobjective/unconstrained.py | 4 ++ jmetal/problem/multiobjective/zdt.py | 5 +++ .../problem/singleobjective/unconstrained.py | 6 +++ 9 files changed, 73 insertions(+), 54 deletions(-) diff --git a/jmetal/core/problem.py b/jmetal/core/problem.py index da10044b..3d7cca9f 100644 --- a/jmetal/core/problem.py +++ b/jmetal/core/problem.py @@ -1,7 +1,7 @@ from abc import ABCMeta, abstractmethod +from typing import Generic, TypeVar from os.path import dirname, join from pathlib import Path -from typing import Generic, TypeVar import random from jmetal.core.solution import BinarySolution, FloatSolution, IntegerSolution @@ -19,17 +19,13 @@ class Problem(Generic[S]): MAXIMIZE = 1 def __init__(self): - self.number_of_variables = None - self.number_of_objectives = None - self.number_of_constraints = None - self.obj_directions = [] - - @abstractmethod - def evaluate(self, solution: S) -> S: - """ Evaluate a solution. + self.number_of_variables: int = None + self.number_of_objectives: int = None + self.number_of_constraints: int = None - :return: Evaluated solution. """ - pass + self.obj_functions: list = [] + self.obj_directions: list = None + self.obj_labels: list = None @abstractmethod def create_solution(self) -> S: @@ -38,6 +34,18 @@ def create_solution(self) -> S: :return: Solution. """ pass + def evaluate(self, solution: S) -> S: + """ Evaluate a solution. + + :return: Evaluated solution. """ + for ith, fnc in enumerate(self.obj_functions): + if self.obj_directions[ith] == self.MINIMIZE: + solution.objectives[ith] = fnc(solution) + else: + solution.objectives[ith] = -1.0 * fnc(solution) + + return solution + def evaluate_constraints(self, solution: S): pass @@ -67,10 +75,6 @@ class BinaryProblem(Problem[BinarySolution]): __metaclass__ = ABCMeta - @abstractmethod - def evaluate(self, solution: BinarySolution) -> BinarySolution: - pass - @abstractmethod def create_solution(self) -> BinarySolution: pass @@ -86,10 +90,6 @@ def __init__(self): self.lower_bound = None self.upper_bound = None - @abstractmethod - def evaluate(self, solution: FloatSolution) -> FloatSolution: - pass - def create_solution(self) -> FloatSolution: new_solution = FloatSolution(self.number_of_variables, self.number_of_objectives, self.number_of_constraints, self.lower_bound, self.upper_bound) @@ -109,10 +109,6 @@ def __init__(self): self.lower_bound = None self.upper_bound = None - @abstractmethod - def evaluate(self, solution: IntegerSolution) -> IntegerSolution: - pass - def create_solution(self) -> IntegerSolution: new_solution = IntegerSolution( self.number_of_variables, diff --git a/jmetal/core/solution.py b/jmetal/core/solution.py index 2ead41d7..6f568bdb 100644 --- a/jmetal/core/solution.py +++ b/jmetal/core/solution.py @@ -1,3 +1,4 @@ +from abc import ABCMeta from typing import List, Generic, TypeVar BitSet = List[bool] @@ -7,10 +8,13 @@ class Solution(Generic[S]): """ Class representing solutions """ + __metaclass__ = ABCMeta + def __init__(self, number_of_variables: int, number_of_objectives: int, number_of_constraints: int = 0): self.number_of_objectives = number_of_objectives self.number_of_variables = number_of_variables self.number_of_constraints = number_of_constraints + self.objectives = [0.0 for _ in range(self.number_of_objectives)] self.variables = [[] for _ in range(self.number_of_variables)] self.attributes = {} @@ -28,7 +32,7 @@ def __str__(self) -> str: class BinarySolution(Solution[BitSet]): """ Class representing float solutions """ - def __init__(self, number_of_variables: int, number_of_objectives: int, number_of_constraints=0): + def __init__(self, number_of_variables: int, number_of_objectives: int, number_of_constraints: int=0): super(BinarySolution, self).__init__(number_of_variables, number_of_objectives, number_of_constraints) def __copy__(self): diff --git a/jmetal/core/test/test_problem.py b/jmetal/core/test/test_problem.py index 62954395..8c538b5b 100644 --- a/jmetal/core/test/test_problem.py +++ b/jmetal/core/test/test_problem.py @@ -5,11 +5,14 @@ class FloatProblemTestCases(unittest.TestCase): - class DummyFloatProblem(FloatProblem): + class DummyFloatProblem(FloatProblem): def evaluate(self, solution: FloatSolution) -> FloatSolution: pass + class DummyLambdaFloatProblem(FloatProblem): + pass + def test_should_default_constructor_create_a_valid_problem(self) -> None: problem = self.DummyFloatProblem() problem.number_of_variables = 1 @@ -23,6 +26,22 @@ def test_should_default_constructor_create_a_valid_problem(self) -> None: self.assertEqual([-1], problem.lower_bound) self.assertEqual([1], problem.upper_bound) + def test_should_default_constructor_create_a_valid_problem_with_lambda(self) -> None: + problem = self.DummyLambdaFloatProblem() + problem.number_of_variables = 1 + problem.number_of_objectives = 2 + problem.number_of_constraints = 0 + problem.lower_bound = [-1.0] + problem.upper_bound = [1.0] + problem.obj_directions = [problem.MINIMIZE, problem.MAXIMIZE] + problem.obj_functions.append(lambda s: s.number_of_variables * 2) + problem.obj_functions.append(lambda s: s.number_of_variables * 3) + + solution = FloatSolution(2, 2, 0, [], []) + problem.evaluate(solution) + + self.assertEqual([4, -6], solution.objectives) + def test_should_create_solution_create_a_valid_solution(self) -> None: problem = self.DummyFloatProblem() problem.number_of_variables = 2 @@ -38,8 +57,8 @@ def test_should_create_solution_create_a_valid_solution(self) -> None: class IntegerProblemTestCases(unittest.TestCase): - class DummyIntegerProblem(IntegerProblem): + class DummyIntegerProblem(IntegerProblem): def evaluate(self, solution: IntegerSolution) -> IntegerSolution: pass diff --git a/jmetal/core/test/test_solution.py b/jmetal/core/test/test_solution.py index dd28c356..f150ff40 100644 --- a/jmetal/core/test/test_solution.py +++ b/jmetal/core/test/test_solution.py @@ -1,30 +1,7 @@ import copy import unittest -from jmetal.core.solution import BinarySolution, FloatSolution, Solution, IntegerSolution - - -class SolutionTestCase(unittest.TestCase): - - def test_should_constructor_create_a_non_null_object(self) -> None: - solution = Solution[int](3, 2) - self.assertIsNotNone(solution) - - def test_should_constructor_create_a_valid_solution_of_ints(self) -> None: - solution = Solution[int](3, 2) - self.assertEqual(3, solution.number_of_variables) - self.assertEqual(2, solution.number_of_objectives) - self.assertEqual(0, solution.number_of_constraints) - - def test_should_constructor_create_a_valid_solution_of_floats(self) -> None: - solution = Solution[float](3, 2, 5) - self.assertEqual(3, solution.number_of_variables) - self.assertEqual(2, solution.number_of_objectives) - self.assertEqual(5, solution.number_of_constraints) - - def test_should_constructor_create_a_non_null_objective_list(self) -> None: - solution = Solution[float](3, 2) - self.assertIsNotNone(solution.objectives) +from jmetal.core.solution import BinarySolution, FloatSolution, IntegerSolution class BinarySolutionTestCase(unittest.TestCase): @@ -100,17 +77,17 @@ def test_should_constructor_create_a_non_null_object(self) -> None: self.assertIsNotNone(solution) def test_should_default_constructor_create_a_valid_solution(self) -> None: - solution = IntegerSolution(2, 3, 2, [0.0, 0.5], [1.0, 2.0]) + solution = IntegerSolution(2, 3, 2, [0, 5], [1, 2]) self.assertEqual(2, solution.number_of_variables) self.assertEqual(3, solution.number_of_objectives) self.assertEqual(2, solution.number_of_constraints) self.assertEqual(2, len(solution.variables)) self.assertEqual(3, len(solution.objectives)) - self.assertEqual([0.0, 0.5], solution.lower_bound) - self.assertEqual([1.0, 2.0], solution.upper_bound) + self.assertEqual([0, 5], solution.lower_bound) + self.assertEqual([1, 2], solution.upper_bound) def test_should_copy_work_properly(self) -> None: - solution = IntegerSolution(2, 3, 2, [0.0, 0.5], [1.0, 2.0]) + solution = IntegerSolution(2, 3, 2, [0, 5], [1, 2]) solution.variables = [1, 2] solution.objectives = [0.16, -2.34, 9.25] solution.attributes["attr"] = "value" diff --git a/jmetal/problem/multiobjective/constrained.py b/jmetal/problem/multiobjective/constrained.py index c0cf034e..71769d82 100644 --- a/jmetal/problem/multiobjective/constrained.py +++ b/jmetal/problem/multiobjective/constrained.py @@ -21,6 +21,9 @@ def __init__(self): self.number_of_variables = 2 self.number_of_constraints = 2 + self.obj_directions = [self.MINIMIZE, self.MINIMIZE] + self.obj_labels = ['f(x)', 'f(y)'] + self.lower_bound = [-20.0 for _ in range(self.number_of_variables)] self.upper_bound = [20.0 for _ in range(self.number_of_variables)] @@ -69,6 +72,9 @@ def __init__(self): self.number_of_variables = 2 self.number_of_constraints = 2 + self.obj_directions = [self.MINIMIZE, self.MINIMIZE] + self.obj_labels = ['f(x)', 'f(y)'] + self.lower_bound = [10e-5 for _ in range(self.number_of_variables)] self.upper_bound = [pi for _ in range(self.number_of_variables)] diff --git a/jmetal/problem/multiobjective/dtlz.py b/jmetal/problem/multiobjective/dtlz.py index 2a908641..cb5fbabb 100644 --- a/jmetal/problem/multiobjective/dtlz.py +++ b/jmetal/problem/multiobjective/dtlz.py @@ -27,6 +27,7 @@ def __init__(self, number_of_variables: int = 7, number_of_objectives=3): self.number_of_constraints = 0 self.obj_directions = [self.MINIMIZE] * number_of_objectives + self.obj_labels = ['f'] * number_of_objectives self.lower_bound = self.number_of_variables * [0.0] self.upper_bound = self.number_of_variables * [1.0] @@ -72,6 +73,7 @@ def __init__(self, number_of_variables: int = 12, number_of_objectives=3): self.number_of_constraints = 0 self.obj_directions = [self.MINIMIZE] * number_of_objectives + self.obj_labels = ['f'] * number_of_objectives self.lower_bound = self.number_of_variables * [0.0] self.upper_bound = self.number_of_variables * [1.0] diff --git a/jmetal/problem/multiobjective/unconstrained.py b/jmetal/problem/multiobjective/unconstrained.py index cd2ade73..34f7a168 100644 --- a/jmetal/problem/multiobjective/unconstrained.py +++ b/jmetal/problem/multiobjective/unconstrained.py @@ -22,6 +22,7 @@ def __init__(self, number_of_variables: int = 3): self.number_of_constraints = 0 self.obj_directions = [self.MINIMIZE, self.MINIMIZE] + self.obj_labels = ['f(x)', 'f(y)'] self.lower_bound = [-5.0 for _ in range(number_of_variables)] self.upper_bound = [5.0 for _ in range(number_of_variables)] @@ -56,6 +57,7 @@ def __init__(self): self.number_of_constraints = 0 self.obj_directions = [self.MINIMIZE, self.MINIMIZE] + self.obj_labels = ['f(x)', 'f(y)'] self.lower_bound = self.number_of_variables * [-4] self.upper_bound = self.number_of_variables * [4] @@ -83,6 +85,7 @@ def __init__(self): self.number_of_constraints = 0 self.obj_directions = [self.MINIMIZE, self.MINIMIZE] + self.obj_labels = ['f(x)', 'f(y)'] self.lower_bound = [-100000] self.upper_bound = [100000] @@ -111,6 +114,7 @@ def __init__(self): self.number_of_constraints = 0 self.obj_directions = [self.MINIMIZE, self.MINIMIZE, self.MINIMIZE] + self.obj_labels = ['f(x)', 'f(y)', 'f(z)'] self.lower_bound = self.number_of_variables * [-4] self.upper_bound = self.number_of_variables * [4] diff --git a/jmetal/problem/multiobjective/zdt.py b/jmetal/problem/multiobjective/zdt.py index 8e05124a..65238261 100644 --- a/jmetal/problem/multiobjective/zdt.py +++ b/jmetal/problem/multiobjective/zdt.py @@ -29,6 +29,7 @@ def __init__(self, number_of_variables: int = 30): self.number_of_constraints = 0 self.obj_directions = [self.MINIMIZE, self.MINIMIZE] + self.obj_labels = ['f(x)', 'f(y)'] self.lower_bound = self.number_of_variables * [0.0] self.upper_bound = self.number_of_variables * [1.0] @@ -75,6 +76,7 @@ def __init__(self, number_of_variables: int = 30): self.number_of_constraints = 0 self.obj_directions = [self.MINIMIZE, self.MINIMIZE] + self.obj_labels = ['f(x)', 'f(y)'] self.lower_bound = self.number_of_variables * [0.0] self.upper_bound = self.number_of_variables * [1.0] @@ -121,6 +123,7 @@ def __init__(self, number_of_variables: int = 30): self.number_of_constraints = 0 self.obj_directions = [self.MINIMIZE, self.MINIMIZE] + self.obj_labels = ['f(x)', 'f(y)'] self.lower_bound = self.number_of_variables * [0.0] self.upper_bound = self.number_of_variables * [1.0] @@ -166,6 +169,7 @@ def __init__(self, number_of_variables: int = 10): self.number_of_constraints = 0 self.obj_directions = [self.MINIMIZE, self.MINIMIZE] + self.obj_labels = ['f(x)', 'f(y)'] self.lower_bound = self.number_of_variables * [-5.0] self.upper_bound = self.number_of_variables * [5.0] @@ -215,6 +219,7 @@ def __init__(self, number_of_variables: int = 10): self.number_of_constraints = 0 self.obj_directions = [self.MINIMIZE, self.MINIMIZE] + self.obj_labels = ['f(x)', 'f(y)'] self.lower_bound = self.number_of_variables * [0.0] self.upper_bound = self.number_of_variables * [1.0] diff --git a/jmetal/problem/singleobjective/unconstrained.py b/jmetal/problem/singleobjective/unconstrained.py index f27bcaf4..ab766c4e 100644 --- a/jmetal/problem/singleobjective/unconstrained.py +++ b/jmetal/problem/singleobjective/unconstrained.py @@ -21,6 +21,9 @@ def __init__(self, number_of_bits: int = 256): self.number_of_variables = 1 self.number_of_constraints = 0 + self.obj_directions = [self.MINIMIZE] + self.obj_labels = ['f(x)'] + def evaluate(self, solution: BinarySolution) -> BinarySolution: counter_of_ones = 0 for bits in solution.variables[0]: @@ -49,6 +52,9 @@ def __init__(self, number_of_variables: int = 10): self.number_of_variables = number_of_variables self.number_of_constraints = 0 + self.obj_directions = [self.MINIMIZE] + self.obj_labels = ['f(x)'] + self.lower_bound = [-5.12 for _ in range(number_of_variables)] self.upper_bound = [5.12 for _ in range(number_of_variables)] From 4ef19945d14813ffbfeed708cd0dc0d2dde6d3bf Mon Sep 17 00:00:00 2001 From: benhid Date: Tue, 17 Jul 2018 14:01:17 +0200 Subject: [PATCH 02/26] New backend for plotting and new problem attributes --- docs/source/api/jmetal.util.rst | 8 - docs/source/conf.py | 7 +- .../multiobjective/nsgaii_full_settings.py | 15 +- .../multiobjective/smpso_full_settings.py | 19 +- .../smpsorp_standard_settings.py | 33 +- jmetal/algorithm/multiobjective/smpso.py | 3 +- .../singleobjective/evolutionaryalgorithm.py | 3 +- jmetal/component/observer.py | 11 +- .../component/test/test_quality_indicator.py | 4 +- jmetal/core/problem.py | 105 +++-- jmetal/problem/multiobjective/constrained.py | 8 +- jmetal/problem/multiobjective/dtlz.py | 16 +- .../problem/multiobjective/unconstrained.py | 16 +- jmetal/problem/multiobjective/zdt.py | 59 ++- .../problem/singleobjective/unconstrained.py | 8 +- jmetal/util/__init__.py | 6 +- jmetal/util/front_file.py | 37 -- jmetal/util/graphic.py | 434 +++++++++--------- jmetal/util/test/__init__.py | 0 requirements.txt | 6 +- .../reference_front/DTLZ1.pf | 0 .../reference_front/ZDT1.pf | 0 .../reference_front/ZDT2.pf | 0 .../reference_front/ZDT3.pf | 0 .../reference_front/ZDT4.pf | 0 .../reference_front/ZDT6.pf | 0 26 files changed, 398 insertions(+), 400 deletions(-) delete mode 100644 jmetal/util/front_file.py delete mode 100644 jmetal/util/test/__init__.py rename {jmetal/problem => resources}/reference_front/DTLZ1.pf (100%) rename {jmetal/problem => resources}/reference_front/ZDT1.pf (100%) rename {jmetal/problem => resources}/reference_front/ZDT2.pf (100%) rename {jmetal/problem => resources}/reference_front/ZDT3.pf (100%) rename {jmetal/problem => resources}/reference_front/ZDT4.pf (100%) rename {jmetal/problem => resources}/reference_front/ZDT6.pf (100%) diff --git a/docs/source/api/jmetal.util.rst b/docs/source/api/jmetal.util.rst index 0e330e90..72e3ae54 100644 --- a/docs/source/api/jmetal.util.rst +++ b/docs/source/api/jmetal.util.rst @@ -1,14 +1,6 @@ Utils =================== -Front file ------------------------------ - -.. automodule:: jmetal.util.front_file - :members: - :undoc-members: - :show-inheritance: - Graphic -------------------------- diff --git a/docs/source/conf.py b/docs/source/conf.py index f2abbff3..0fe35c75 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -19,9 +19,10 @@ # http://blog.rtwilson.com/how-to-make-your-sphinx-documentation-compile-with-readthedocs-when-youre-using-numpy-and-scipy/ import mock -MOCK_MODULES = ['numpy', 'dask', 'dask.distributed', 'tqdm', 'bokeh', 'bokeh.embed', 'bokeh.front', 'bokeh.client', - 'bokeh.io', 'bokeh.layouts','bokeh.models','bokeh.plotting', 'bokeh.resources', - 'mpl_toolkits', 'mpl_toolkits.mplot3d', 'matplotlib', 'matplotlib.pyplot', 'matplotlib.axes'] +MOCK_MODULES = ['numpy', 'pandas', + 'tqdm', + 'plotly', 'plotly.offline', + 'matplotlib', 'matplotlib.pyplot', 'mpl_toolkits', 'mpl_toolkits.mplot3d'] for mod_name in MOCK_MODULES: sys.modules[mod_name] = mock.Mock() diff --git a/examples/multiobjective/nsgaii_full_settings.py b/examples/multiobjective/nsgaii_full_settings.py index a3de7483..69ae5a6a 100644 --- a/examples/multiobjective/nsgaii_full_settings.py +++ b/examples/multiobjective/nsgaii_full_settings.py @@ -1,12 +1,12 @@ from jmetal.algorithm import NSGAII -from jmetal.component import VisualizerObserver, ProgressBarObserver, RankingAndCrowdingDistanceComparator from jmetal.problem import ZDT1 from jmetal.operator import SBX, Polynomial, BinaryTournamentSelection -from jmetal.util import ScatterMatplotlib, SolutionList +from jmetal.component import VisualizerObserver, ProgressBarObserver, RankingAndCrowdingDistanceComparator +from jmetal.util import ScatterPlot, SolutionList if __name__ == '__main__': - problem = ZDT1() + problem = ZDT1(rf_path='../../resources/reference_front/ZDT1.pf') algorithm = NSGAII( problem=problem, @@ -17,17 +17,18 @@ selection=BinaryTournamentSelection(comparator=RankingAndCrowdingDistanceComparator()) ) - observer = VisualizerObserver(problem) - progress_bar = ProgressBarObserver(step=100, maximum=25000) + observer = VisualizerObserver() algorithm.observable.register(observer=observer) + + progress_bar = ProgressBarObserver(step=100, maximum=25000) algorithm.observable.register(observer=progress_bar) algorithm.run() front = algorithm.get_result() # Plot frontier to file - pareto_front = ScatterMatplotlib(plot_title='NSGAII for ZDT1', number_of_objectives=problem.number_of_objectives) - pareto_front.plot(front, reference=problem.get_reference_front(), output='NSGAII-ZDT1', show=False) + pareto_front = ScatterPlot(plot_title='NSGAII-ZDT1', axis_labels=problem.obj_labels) + pareto_front.plot(front, reference_front=problem.reference_front) # Save variables to file SolutionList.print_function_values_to_file(front, 'FUN.NSGAII.' + problem.get_name()) diff --git a/examples/multiobjective/smpso_full_settings.py b/examples/multiobjective/smpso_full_settings.py index 0b9014ea..ee15e014 100644 --- a/examples/multiobjective/smpso_full_settings.py +++ b/examples/multiobjective/smpso_full_settings.py @@ -1,14 +1,12 @@ from jmetal.algorithm import SMPSO -from jmetal.component.observer import ProgressBarObserver, VisualizerObserver -from jmetal.component.archive import CrowdingDistanceArchive -from jmetal.problem import ZDT1 +from jmetal.problem import DTLZ1 from jmetal.operator import Polynomial -from jmetal.util.graphic import ScatterMatplotlib -from jmetal.util.solution_list_output import SolutionList +from jmetal.component import VisualizerObserver, ProgressBarObserver, CrowdingDistanceArchive +from jmetal.util import ScatterPlot, SolutionList if __name__ == '__main__': - problem = ZDT1() + problem = DTLZ1(rf_path='../../resources/reference_front/DTLZ1.pf') algorithm = SMPSO( problem=problem, @@ -18,17 +16,18 @@ leaders=CrowdingDistanceArchive(100) ) - observer = VisualizerObserver(problem) - progress_bar = ProgressBarObserver(step=100, maximum=25000) + observer = VisualizerObserver() algorithm.observable.register(observer=observer) + + progress_bar = ProgressBarObserver(step=100, maximum=25000) algorithm.observable.register(observer=progress_bar) algorithm.run() front = algorithm.get_result() # Plot frontier to file - pareto_front = ScatterMatplotlib(plot_title='SMPSO for ' + problem.get_name(), number_of_objectives=problem.number_of_objectives) - pareto_front.plot(front, reference=problem.get_reference_front(), output='SMPSO-' + problem.get_name(), show=False) + pareto_front = ScatterPlot(plot_title='SMPSO-DTLZ1', axis_labels=problem.obj_labels) + pareto_front.plot(front, reference_front=problem.reference_front) # Save variables to file SolutionList.print_function_values_to_file(front, 'FUN.SMPSO.' + problem.get_name()) diff --git a/examples/multiobjective/smpsorp_standard_settings.py b/examples/multiobjective/smpsorp_standard_settings.py index 8d485365..b2f62bfe 100644 --- a/examples/multiobjective/smpsorp_standard_settings.py +++ b/examples/multiobjective/smpsorp_standard_settings.py @@ -1,15 +1,26 @@ from jmetal.algorithm import SMPSORP -from jmetal.component.archive import CrowdingDistanceArchiveWithReferencePoint -from jmetal.component.observer import ProgressBarObserver +from jmetal.component import ProgressBarObserver, VisualizerObserver, CrowdingDistanceArchiveWithReferencePoint from jmetal.problem import ZDT1 from jmetal.operator import Polynomial +from jmetal.util import ScatterPlot + + +def points_to_solutions(points): + solutions = [] + for i, _ in enumerate(points): + point = problem.create_solution() + point.objectives = points[i] + solutions.append(point) + + return solutions if __name__ == '__main__': - problem = ZDT1() + problem = ZDT1(rf_path='../../resources/reference_front/ZDT1.pf') + swarm_size = 100 - reference_points = [[0.0, 0.0]] + reference_points = [[0.5, 0.5], [0.2, 0.8]] archives_with_reference_points = [] for point in reference_points: @@ -26,11 +37,21 @@ leaders=archives_with_reference_points ) + observer = VisualizerObserver() + algorithm.observable.register(observer=observer) + progress_bar = ProgressBarObserver(step=swarm_size, maximum=25000) - algorithm.observable.register(progress_bar) + algorithm.observable.register(observer=progress_bar) algorithm.run() + front = algorithm.get_result() + + # Plot frontier to file + pareto_front = ScatterPlot(plot_title='NSGAII plot', axis_labels=problem.obj_labels) + pareto_front.plot(front, reference_front=problem.reference_front, show=False) + pareto_front.add_data(points_to_solutions(reference_points), legend='reference points') + pareto_front.show() print('Algorithm (continuous problem): ' + algorithm.get_name()) print('Problem: ' + problem.get_name()) - print('Computing time: ' + str(algorithm.total_computing_time)) + print('Computing time: ' + str(algorithm.total_computing_time)) \ No newline at end of file diff --git a/jmetal/algorithm/multiobjective/smpso.py b/jmetal/algorithm/multiobjective/smpso.py index 26830c9d..c9198e2b 100644 --- a/jmetal/algorithm/multiobjective/smpso.py +++ b/jmetal/algorithm/multiobjective/smpso.py @@ -92,7 +92,8 @@ def update_progress(self) -> None: observable_data = {'evaluations': self.evaluations, 'computing time': self.get_current_computing_time(), - 'population': self.leaders.solution_list} + 'population': self.leaders.solution_list, + 'reference_front': self.problem.reference_front} self.observable.notify_all(**observable_data) diff --git a/jmetal/algorithm/singleobjective/evolutionaryalgorithm.py b/jmetal/algorithm/singleobjective/evolutionaryalgorithm.py index 12c60201..825b902a 100644 --- a/jmetal/algorithm/singleobjective/evolutionaryalgorithm.py +++ b/jmetal/algorithm/singleobjective/evolutionaryalgorithm.py @@ -134,7 +134,8 @@ def update_progress(self): observable_data = {'evaluations': self.evaluations, 'computing time': self.get_current_computing_time(), - 'population': self.population} + 'population': self.population, + 'reference_front': self.problem.reference_front} self.observable.notify_all(**observable_data) diff --git a/jmetal/component/observer.py b/jmetal/component/observer.py index 5a8ff12e..f6783303 100644 --- a/jmetal/component/observer.py +++ b/jmetal/component/observer.py @@ -4,7 +4,7 @@ from tqdm import tqdm from jmetal.core.problem import Problem -from jmetal.util.graphic import ScatterMatplotlib +from jmetal.util.graphic import ScatterStreaming from jmetal.core.observable import Observer from jmetal.util.solution_list_output import SolutionList @@ -76,18 +76,19 @@ def update(self, *args, **kwargs): class VisualizerObserver(Observer): - def __init__(self, problem: Problem, replace: bool=True) -> None: + def __init__(self, replace: bool=True) -> None: self.display_frequency = 1.0 self.replace = replace - self.reference = problem.get_reference_front() - self.plot = ScatterMatplotlib('jMetalPy', problem.number_of_objectives) + self.plot = ScatterStreaming(plot_title='jMetalPy') def update(self, *args, **kwargs): computing_time = kwargs['computing time'] evaluations = kwargs['evaluations'] + front = kwargs['population'] + reference_front = kwargs['reference_front'] title = '{0}, Eval: {1}, Time: {2}'.format('VisualizerObserver', evaluations, computing_time) if (evaluations % self.display_frequency) == 0: - self.plot.update(front, self.reference, new_title=title, persistence=self.replace) + self.plot.update(front, reference_front, rename_title=title, persistence=self.replace) diff --git a/jmetal/component/test/test_quality_indicator.py b/jmetal/component/test/test_quality_indicator.py index 1874f4c3..a24ab7be 100644 --- a/jmetal/component/test/test_quality_indicator.py +++ b/jmetal/component/test/test_quality_indicator.py @@ -28,11 +28,11 @@ def test_should_hypervolume_return_5_0(self): self.assertEqual(5.0, value) def test_should_hypervolume_return_the_correct_value_when_applied_to_the_ZDT1_reference_front(self): - problem = ZDT1() + problem = ZDT1(rf_path='resources/reference_front/ZDT1.pf') reference_point = [1, 1] hv = HyperVolume(reference_point) - value = hv.compute(problem.get_reference_front()) + value = hv.compute(problem.reference_front) self.assertAlmostEqual(0.666, value, delta=0.001) diff --git a/jmetal/core/problem.py b/jmetal/core/problem.py index 3d7cca9f..60e7fa34 100644 --- a/jmetal/core/problem.py +++ b/jmetal/core/problem.py @@ -1,11 +1,9 @@ from abc import ABCMeta, abstractmethod -from typing import Generic, TypeVar -from os.path import dirname, join +from typing import Generic, TypeVar, List from pathlib import Path import random from jmetal.core.solution import BinarySolution, FloatSolution, IntegerSolution -from jmetal.util.front_file import read_front_from_file_as_solutions S = TypeVar('S') @@ -18,14 +16,53 @@ class Problem(Generic[S]): MINIMIZE = -1 MAXIMIZE = 1 - def __init__(self): + def __init__(self, reference_front_path: str): self.number_of_variables: int = None self.number_of_objectives: int = None self.number_of_constraints: int = None - self.obj_functions: list = [] - self.obj_directions: list = None - self.obj_labels: list = None + self.obj_directions: List[int] = [] + self.obj_functions: List = [] + self.obj_labels: List[str] = [] + + self.reference_front: List[S] = None + if reference_front_path: + self.reference_front = self.read_front_from_file_as_solutions(reference_front_path) + + @staticmethod + def read_front_from_file(file_path: str) -> List[List[float]]: + """ Reads a front from a file and returns a list. + + :return: List of solution points. """ + front = [] + if Path(file_path).is_file(): + with open(file_path) as file: + for line in file: + vector = [float(x) for x in line.split()] + front.append(vector) + else: + raise Exception('Reference front file was not found at {}'.format(file_path)) + + return front + + @staticmethod + def read_front_from_file_as_solutions(file_path: str) -> List[S]: + """ Reads a front from a file and returns a list of solution objects. + + :return: List of solution objects. """ + front = [] + if Path(file_path).is_file(): + with open(file_path) as file: + for line in file: + vector = [float(x) for x in line.split()] + solution = FloatSolution(2, 2, 0, [], []) + solution.objectives = vector + + front.append(solution) + else: + raise Exception('Reference front file was not found at {}'.format(file_path)) + + return front @abstractmethod def create_solution(self) -> S: @@ -35,37 +72,33 @@ def create_solution(self) -> S: pass def evaluate(self, solution: S) -> S: - """ Evaluate a solution. + """ Evaluate a solution. For any new problem inheriting from :class:`Problem`, this method should be replaced. + Otherwise, the attributes `obj_functions`, `obj_functions` and `obj_labels` must be declared: + + .. code-block:: python + + problem = FloatProblem() + + problem.obj_functions.append(lambda s: 1) + problem.obj_directions.append(Problem.MINIMIZE) + problem.obj_labels.append('Min') + + problem.obj_functions.append(lambda s: 2) + problem.obj_directions.append(Problem.MAXIMIZE) + problem.obj_labels.append('Max') :return: Evaluated solution. """ for ith, fnc in enumerate(self.obj_functions): - if self.obj_directions[ith] == self.MINIMIZE: - solution.objectives[ith] = fnc(solution) - else: + if self.obj_directions[ith] == self.MAXIMIZE: solution.objectives[ith] = -1.0 * fnc(solution) + else: + solution.objectives[ith] = fnc(solution) return solution def evaluate_constraints(self, solution: S): pass - def get_reference_front(self) -> list: - """ Get the reference front to the problem (if any). - This method read front files (.pf) located in `jmetal/problem/reference_front/`, which must have the same - name as the problem. - - :return: Front.""" - reference_front_path = 'problem/reference_front/{0}.pf'.format(self.get_name()) - - front = [] - file_path = dirname(join(dirname(__file__))) - computed_path = join(file_path, reference_front_path) - - if Path(computed_path).is_file(): - front = read_front_from_file_as_solutions(computed_path) - - return front - def get_name(self) -> str: return self.__class__.__name__ @@ -73,9 +106,9 @@ def get_name(self) -> str: class BinaryProblem(Problem[BinarySolution]): """ Class representing binary problems. """ - __metaclass__ = ABCMeta + def __init__(self, rf_path: str = None): + super(BinaryProblem, self).__init__(reference_front_path=rf_path) - @abstractmethod def create_solution(self) -> BinarySolution: pass @@ -83,10 +116,8 @@ def create_solution(self) -> BinarySolution: class FloatProblem(Problem[FloatSolution]): """ Class representing float problems. """ - __metaclass__ = ABCMeta - - def __init__(self): - super(FloatProblem, self).__init__() + def __init__(self, rf_path: str = None): + super(FloatProblem, self).__init__(reference_front_path=rf_path) self.lower_bound = None self.upper_bound = None @@ -102,10 +133,8 @@ def create_solution(self) -> FloatSolution: class IntegerProblem(Problem[IntegerSolution]): """ Class representing integer problems. """ - __metaclass__ = ABCMeta - - def __init__(self): - super(IntegerProblem, self).__init__() + def __init__(self, rf_path: str = None): + super(IntegerProblem, self).__init__(reference_front_path=rf_path) self.lower_bound = None self.upper_bound = None diff --git a/jmetal/problem/multiobjective/constrained.py b/jmetal/problem/multiobjective/constrained.py index 71769d82..a87fc71b 100644 --- a/jmetal/problem/multiobjective/constrained.py +++ b/jmetal/problem/multiobjective/constrained.py @@ -15,8 +15,8 @@ class Srinivas(FloatProblem): """ Class representing problem Srinivas. """ - def __init__(self): - super(Srinivas, self).__init__() + def __init__(self, rf_path: str=None): + super(Srinivas, self).__init__(rf_path=rf_path) self.number_of_objectives = 2 self.number_of_variables = 2 self.number_of_constraints = 2 @@ -66,8 +66,8 @@ def get_name(self): class Tanaka(FloatProblem): """ Class representing problem Tanaka """ - def __init__(self): - super(Tanaka, self).__init__() + def __init__(self, rf_path: str=None): + super(Tanaka, self).__init__(rf_path=rf_path) self.number_of_objectives = 2 self.number_of_variables = 2 self.number_of_constraints = 2 diff --git a/jmetal/problem/multiobjective/dtlz.py b/jmetal/problem/multiobjective/dtlz.py index cb5fbabb..90d9cab1 100644 --- a/jmetal/problem/multiobjective/dtlz.py +++ b/jmetal/problem/multiobjective/dtlz.py @@ -18,10 +18,11 @@ class DTLZ1(FloatProblem): .. note:: Unconstrained problem. The default number of variables and objectives are, respectively, 7 and 3. """ - def __init__(self, number_of_variables: int = 7, number_of_objectives=3): + def __init__(self, number_of_variables: int = 7, number_of_objectives=3, rf_path: str=None): """ :param number_of_variables: number of decision variables of the problem. + :param rf_path: Path to the reference front file (if any). Default to None. """ - super(DTLZ1, self).__init__() + super(DTLZ1, self).__init__(rf_path=rf_path) self.number_of_variables = number_of_variables self.number_of_objectives = number_of_objectives self.number_of_constraints = 0 @@ -32,9 +33,6 @@ def __init__(self, number_of_variables: int = 7, number_of_objectives=3): self.lower_bound = self.number_of_variables * [0.0] self.upper_bound = self.number_of_variables * [1.0] - FloatSolution.lower_bound = self.lower_bound - FloatSolution.upper_bound = self.upper_bound - def evaluate(self, solution: FloatSolution) -> FloatSolution: k = self.number_of_variables - self.number_of_objectives + 1 @@ -64,10 +62,11 @@ class DTLZ2(FloatProblem): .. note:: Unconstrained problem. The default number of variables and objectives are, respectively, 12 and 3. """ - def __init__(self, number_of_variables: int = 12, number_of_objectives=3): + def __init__(self, number_of_variables: int = 12, number_of_objectives=3, rf_path: str=None): """:param number_of_variables: number of decision variables of the problem + :param rf_path: Path to the reference front file (if any). Default to None. """ - super(DTLZ2, self).__init__() + super(DTLZ2, self).__init__(rf_path=rf_path) self.number_of_variables = number_of_variables self.number_of_objectives = number_of_objectives self.number_of_constraints = 0 @@ -78,9 +77,6 @@ def __init__(self, number_of_variables: int = 12, number_of_objectives=3): self.lower_bound = self.number_of_variables * [0.0] self.upper_bound = self.number_of_variables * [1.0] - FloatSolution.lower_bound = self.lower_bound - FloatSolution.upper_bound = self.upper_bound - def evaluate(self, solution: FloatSolution) -> FloatSolution: k = self.number_of_variables - self.number_of_objectives + 1 diff --git a/jmetal/problem/multiobjective/unconstrained.py b/jmetal/problem/multiobjective/unconstrained.py index 34f7a168..1d2cc9d2 100644 --- a/jmetal/problem/multiobjective/unconstrained.py +++ b/jmetal/problem/multiobjective/unconstrained.py @@ -15,8 +15,8 @@ class Kursawe(FloatProblem): """ Class representing problem Kursawe. """ - def __init__(self, number_of_variables: int = 3): - super(Kursawe, self).__init__() + def __init__(self, number_of_variables: int=3, rf_path: str=None): + super(Kursawe, self).__init__(rf_path=rf_path) self.number_of_objectives = 2 self.number_of_variables = number_of_variables self.number_of_constraints = 0 @@ -50,8 +50,8 @@ def get_name(self): class Fonseca(FloatProblem): - def __init__(self): - super(Fonseca, self).__init__() + def __init__(self, rf_path: str=None): + super(Fonseca, self).__init__(rf_path=rf_path) self.number_of_variables = 3 self.number_of_objectives = 2 self.number_of_constraints = 0 @@ -78,8 +78,8 @@ def get_name(self): class Schaffer(FloatProblem): - def __init__(self): - super(Schaffer, self).__init__() + def __init__(self, rf_path: str=None): + super(Schaffer, self).__init__(rf_path=rf_path) self.number_of_variables = 1 self.number_of_objectives = 2 self.number_of_constraints = 0 @@ -107,8 +107,8 @@ def get_name(self): class Viennet2(FloatProblem): - def __init__(self): - super(Viennet2, self).__init__() + def __init__(self, rf_path: str=None): + super(Viennet2, self).__init__(rf_path=rf_path) self.number_of_variables = 2 self.number_of_objectives = 3 self.number_of_constraints = 0 diff --git a/jmetal/problem/multiobjective/zdt.py b/jmetal/problem/multiobjective/zdt.py index 65238261..5ca0dc31 100644 --- a/jmetal/problem/multiobjective/zdt.py +++ b/jmetal/problem/multiobjective/zdt.py @@ -19,11 +19,11 @@ class ZDT1(FloatProblem): .. note:: Continuous problem having a convex Pareto front """ - def __init__(self, number_of_variables: int = 30): + def __init__(self, number_of_variables: int=30, rf_path: str=None): + """ :param number_of_variables: Number of decision variables of the problem. + :param rf_path: Path to the reference front file (if any). Default to None. """ - :param number_of_variables: Number of decision variables of the problem. - """ - super(ZDT1, self).__init__() + super(ZDT1, self).__init__(rf_path=rf_path) self.number_of_variables = number_of_variables self.number_of_objectives = 2 self.number_of_constraints = 0 @@ -34,9 +34,6 @@ def __init__(self, number_of_variables: int = 30): self.lower_bound = self.number_of_variables * [0.0] self.upper_bound = self.number_of_variables * [1.0] - FloatSolution.lower_bound = self.lower_bound - FloatSolution.upper_bound = self.upper_bound - def evaluate(self, solution: FloatSolution) -> FloatSolution: g = self.__eval_g(solution) h = self.__eval_h(solution.variables[0], g) @@ -63,14 +60,17 @@ def get_name(self): class ZDT2(FloatProblem): - """ Problem ZDT2 + """ Problem ZDT2. .. note:: Bi-objective unconstrained problem. The default number of variables is 30. .. note:: Continuous problem having a non-convex Pareto front """ - def __init__(self, number_of_variables: int = 30): - super(ZDT2, self).__init__() + def __init__(self, number_of_variables: int = 30, rf_path: str=None): + """ :param number_of_variables: Number of decision variables of the problem. + :param rf_path: Path to the reference front file (if any). Default to None. + """ + super(ZDT2, self).__init__(rf_path=rf_path) self.number_of_variables = number_of_variables self.number_of_objectives = 2 self.number_of_constraints = 0 @@ -81,9 +81,6 @@ def __init__(self, number_of_variables: int = 30): self.lower_bound = self.number_of_variables * [0.0] self.upper_bound = self.number_of_variables * [1.0] - FloatSolution.lower_bound = self.lower_bound - FloatSolution.upper_bound = self.upper_bound - def evaluate(self, solution: FloatSolution) -> FloatSolution: g = self.__eval_g(solution) h = self.__eval_h(solution.variables[0], g) @@ -110,14 +107,17 @@ def get_name(self): class ZDT3(FloatProblem): - """ Problem ZDT3 + """ Problem ZDT3. .. note:: Bi-objective unconstrained problem. The default number of variables is 30. .. note:: Continuous problem having a partitioned Pareto front """ - def __init__(self, number_of_variables: int = 30): - super(ZDT3, self).__init__() + def __init__(self, number_of_variables: int = 30, rf_path: str=None): + """ :param number_of_variables: Number of decision variables of the problem. + :param rf_path: Path to the reference front file (if any). Default to None. + """ + super(ZDT3, self).__init__(rf_path=rf_path) self.number_of_variables = number_of_variables self.number_of_objectives = 2 self.number_of_constraints = 0 @@ -128,9 +128,6 @@ def __init__(self, number_of_variables: int = 30): self.lower_bound = self.number_of_variables * [0.0] self.upper_bound = self.number_of_variables * [1.0] - FloatSolution.lower_bound = self.lower_bound - FloatSolution.upper_bound = self.upper_bound - def evaluate(self, solution: FloatSolution) -> FloatSolution: g = self.__eval_g(solution) h = self.__eval_h(solution.variables[0], g) @@ -156,14 +153,17 @@ def get_name(self): class ZDT4(FloatProblem): - """ Problem ZDT4 + """ Problem ZDT4. .. note:: Bi-objective unconstrained problem. The default number of variables is 10. .. note:: Continuous multi-modal problem having a convex Pareto front """ - def __init__(self, number_of_variables: int = 10): - super(ZDT4, self).__init__() + def __init__(self, number_of_variables: int = 10, rf_path: str=None): + """ :param number_of_variables: Number of decision variables of the problem. + :param rf_path: Path to the reference front file (if any). Default to None. + """ + super(ZDT4, self).__init__(rf_path=rf_path) self.number_of_variables = number_of_variables self.number_of_objectives = 2 self.number_of_constraints = 0 @@ -176,9 +176,6 @@ def __init__(self, number_of_variables: int = 10): self.lower_bound[0] = 0.0 self.upper_bound[0] = 1.0 - FloatSolution.lower_bound = self.lower_bound - FloatSolution.upper_bound = self.upper_bound - def evaluate(self, solution: FloatSolution) -> FloatSolution: g = self.__eval_g(solution) h = self.__eval_h(solution.variables[0], g) @@ -206,14 +203,17 @@ def get_name(self): class ZDT6(FloatProblem): - """ Problem ZDT6 + """ Problem ZDT6. .. note:: Bi-objective unconstrained problem. The default number of variables is 10. .. note:: Continuous problem having a non-convex Pareto front """ - def __init__(self, number_of_variables: int = 10): - super(ZDT6, self).__init__() + def __init__(self, number_of_variables: int = 10, rf_path: str=None): + """ :param number_of_variables: Number of decision variables of the problem. + :param rf_path: Path to the reference front file (if any). Default to None. + """ + super(ZDT6, self).__init__(rf_path=rf_path) self.number_of_variables = number_of_variables self.number_of_objectives = 2 self.number_of_constraints = 0 @@ -224,9 +224,6 @@ def __init__(self, number_of_variables: int = 10): self.lower_bound = self.number_of_variables * [0.0] self.upper_bound = self.number_of_variables * [1.0] - FloatSolution.lower_bound = self.lower_bound - FloatSolution.upper_bound = self.upper_bound - def evaluate(self, solution: FloatSolution) -> FloatSolution: g = self.__eval_g(solution) h = self.__eval_h(solution.variables[0], g) diff --git a/jmetal/problem/singleobjective/unconstrained.py b/jmetal/problem/singleobjective/unconstrained.py index ab766c4e..36ea0649 100644 --- a/jmetal/problem/singleobjective/unconstrained.py +++ b/jmetal/problem/singleobjective/unconstrained.py @@ -14,8 +14,8 @@ class OneMax(BinaryProblem): - def __init__(self, number_of_bits: int = 256): - super(OneMax, self).__init__() + def __init__(self, number_of_bits: int=256, rf_path: str=None): + super(OneMax, self).__init__(rf_path=rf_path) self.number_of_bits = number_of_bits self.number_of_objectives = 1 self.number_of_variables = 1 @@ -46,8 +46,8 @@ def get_name(self) -> str: class Sphere(FloatProblem): - def __init__(self, number_of_variables: int = 10): - super(Sphere, self).__init__() + def __init__(self, number_of_variables: int=10, rf_path: str=None): + super(Sphere, self).__init__(rf_path=rf_path) self.number_of_objectives = 1 self.number_of_variables = number_of_variables self.number_of_constraints = 0 diff --git a/jmetal/util/__init__.py b/jmetal/util/__init__.py index 8869c71d..d04ff292 100644 --- a/jmetal/util/__init__.py +++ b/jmetal/util/__init__.py @@ -1,11 +1,9 @@ -from .front_file import read_front_from_file, read_front_from_file_as_solutions -from .graphic import ScatterBokeh, ScatterMatplotlib +from .graphic import ScatterPlot, ScatterStreaming from .laboratory import experiment, display from .solution_list_output import SolutionList __all__ = [ - 'read_front_from_file', 'read_front_from_file_as_solutions', - 'ScatterBokeh', 'ScatterMatplotlib', + 'ScatterPlot', 'ScatterStreaming', 'experiment', 'display', 'SolutionList' ] diff --git a/jmetal/util/front_file.py b/jmetal/util/front_file.py deleted file mode 100644 index 362007cb..00000000 --- a/jmetal/util/front_file.py +++ /dev/null @@ -1,37 +0,0 @@ -from jmetal.core.solution import FloatSolution - -""" -.. module:: Front file - :platform: Unix, Windows - :synopsis: Utils to read reference frontiers from files. - -.. moduleauthor:: Antonio J. Nebro -""" - - -def read_front_from_file(file_name: str): - """ Reads a front from a file and returns a list. - """ - front = [] - with open(file_name) as file: - for line in file: - vector = [float(x) for x in line.split()] - front.append(vector) - return front - - -def read_front_from_file_as_solutions(file_path: str): - """ Reads a front from a file and returns a list of solution objects. - - :return: List of solution objects. - """ - front = [] - with open(file_path) as file: - for line in file: - vector = [float(x) for x in line.split()] - solution = FloatSolution(2, 2, 0, [], []) - solution.objectives = vector - - front.append(solution) - - return front diff --git a/jmetal/util/graphic.py b/jmetal/util/graphic.py index 0619ce03..ac1723dc 100644 --- a/jmetal/util/graphic.py +++ b/jmetal/util/graphic.py @@ -1,21 +1,12 @@ import logging -import warnings from abc import ABCMeta -from typing import TypeVar, List, Tuple - -from bokeh.embed import file_html -from bokeh.resources import CDN -from bokeh.client import ClientSession -from bokeh.io import curdoc, reset_output -from bokeh.layouts import column, row -from bokeh.models import HoverTool, ColumnDataSource, TapTool, CustomJS, WheelZoomTool -from bokeh.plotting import Figure -from mpl_toolkits.mplot3d import Axes3D -import matplotlib.pyplot as plt - -from jmetal.core.solution import Solution +from typing import TypeVar, List -warnings.filterwarnings("ignore", ".*GUI is implemented.*") +import matplotlib.pyplot as plt +from mpl_toolkits.mplot3d import Axes3D +from plotly import graph_objs as go +from plotly.offline import plot +from pandas import DataFrame jMetalPyLogger = logging.getLogger('jMetalPy') S = TypeVar('S') @@ -23,139 +14,122 @@ """ .. module:: Visualization :platform: Unix, Windows - :synopsis: Classes for plotting solutions. + :synopsis: Classes for plotting fronts. .. moduleauthor:: Antonio Benítez-Hidalgo """ class Plot: - __metaclass__ = ABCMeta - def __init__(self, plot_title: str, number_of_objectives: int, - xaxis_label: str='', yaxis_label: str='', zaxis_label: str=''): + def __init__(self, plot_title: str, axis_labels: list): self.plot_title = plot_title - self.number_of_objectives = number_of_objectives + self.axis_labels = axis_labels - self.xaxis_label = xaxis_label - self.yaxis_label = yaxis_label - self.zaxis_label = zaxis_label + self.number_of_objectives: int = None - def get_objectives(self, front: List[S]) -> Tuple[list, list, list]: + @staticmethod + def get_objectives(front: List[S]) -> DataFrame: if front is None: raise Exception('Front is none!') - points = list(solution.objectives for solution in front) + return DataFrame(list(solution.objectives for solution in front)) - x_values, y_values = [point[0] for point in points], [point[1] for point in points] - try: - z_values = [point[2] for point in points] - except IndexError: - z_values = [0]*len(points) - - return x_values, y_values, z_values +class ScatterStreaming(Plot): + def __init__(self, plot_title: str, axis_labels: list = None): + """ Creates a new :class:`ScatterStreaming` instance. Suitable for problems with 2 or 3 objectives in streaming. -class ScatterMatplotlib(Plot): + :param plot_title: Title of the diagram. + :param axis_labels: List of axis labels. """ + super(ScatterStreaming, self).__init__(plot_title, axis_labels) - def __init__(self, plot_title: str, number_of_objectives: int): - """ Creates a new :class:`ScatterPlot` instance. Suitable for problems with 2 or 3 objectives. + import warnings + warnings.filterwarnings("ignore", ".*GUI is implemented.*") - :param plot_title: Title of the scatter diagram. - :param number_of_objectives: Number of objectives to be used (2D/3D). - """ - super(ScatterMatplotlib, self).__init__(plot_title, number_of_objectives) - - # Initialize a plot self.fig = plt.figure() self.sc = None self.axis = None - self.__initialize() - - def __initialize(self) -> None: - """ Initialize the scatter plot for the first time. """ - jMetalPyLogger.info("Generating plot...") - - # Initialize a plot - self.fig.canvas.set_window_title('jMetalPy') - - if self.number_of_objectives == 2: - self.axis = self.fig.add_subplot(111) + def plot(self, front: List[S], reference_front: List[S], filename: str = '', show: bool = True) -> None: + """ Plot a front of solutions (2D or 3D). - # Stylize axis - self.axis.spines['top'].set_visible(False) - self.axis.spines['right'].set_visible(False) - self.axis.get_xaxis().tick_bottom() - self.axis.get_yaxis().tick_left() - else: - self.axis = Axes3D(self.fig) - self.axis.autoscale(enable=True, axis='both') + :param front: List of solutions. + :param reference_front: Reference solution list (if any). + :param filename: If specified, save the plot into a file. + :param show: If True, show the final diagram (default to True). """ + objectives = self.get_objectives(front) - self.axis.set_autoscale_on(True) - self.axis.autoscale_view(True, True, True) + # Initialize plot + self.number_of_objectives = objectives.shape[1] + self.__initialize() - # Style options - self.axis.grid(color='#f0f0f5', linestyle='-', linewidth=1, alpha=0.5) - self.fig.suptitle(self.plot_title, fontsize=13) + if reference_front: + jMetalPyLogger.info('Reference front found') + ref_objectives = self.get_objectives(reference_front) - jMetalPyLogger.info("Plot initialized") + if self.number_of_objectives == 2: + self.__plot(ref_objectives[0], ref_objectives[1], None, + color='#323232', marker='*', markersize=3) + else: + self.__plot(ref_objectives[0], ref_objectives[1], ref_objectives[2], + color='#323232', marker='*', markersize=3) - def __plot(self, x_values, y_values, z_values, color: str = '#98FB98', marker: str = 'o', msize: int = 3): if self.number_of_objectives == 2: - self.sc, = self.axis.plot(x_values, y_values, - color=color, marker=marker, markersize=msize, ls='None', picker=10) + self.__plot(objectives[0], objectives[1], None, color='#98FB98', marker='o', markersize=3) else: - self.sc, = self.axis.plot(x_values, y_values, z_values, - color=color, marker=marker, markersize=msize, ls='None', picker=10) - - def plot(self, front: List[S], reference: List[S], output: str= '', show: bool=True) -> None: - if reference: - jMetalPyLogger.info('Reference front found') - ref_x_values, ref_y_values, ref_z_values = self.get_objectives(reference) - self.__plot(ref_x_values, ref_y_values, ref_z_values, color='#323232', marker='*') - - x_values, y_values, z_values = self.get_objectives(front) - self.__plot(x_values, y_values, z_values) + self.__plot(objectives[0], objectives[1], objectives[2], color='#98FB98', marker='o', markersize=3) - if output: - self.__save(output) + if filename: + self.fig.savefig(filename, format='png', dpi=200) if show: - self.fig.canvas.mpl_connect('pick_event', lambda event: self.__pick_handler(event, front)) + self.fig.canvas.mpl_connect('pick_event', lambda event: self.__pick_handler(front, event)) plt.show() - def update(self, front: List[S], reference: List[S], new_title: str= '', persistence: bool=True) -> None: + def update(self, front: List[S], reference_front: List[S], rename_title: str = '', + persistence: bool = True) -> None: + """ Update an already created plot. + + :param front: List of solutions. + :param reference_front: Reference solution list (if any). + :param rename_title: New title of the plot. + :param persistence: If True, keep old points; else, replace them with new values. + """ if self.sc is None: - jMetalPyLogger.warning("Plot is none! Generating first plot...") - self.plot(front, reference, show=False) + jMetalPyLogger.warning('Plot must be initialized first.') + self.plot(front, reference_front, show=False) + return - x_values, y_values, z_values = self.get_objectives(front) + objectives = self.get_objectives(front) if persistence: # Replace with new points - self.sc.set_data(x_values, y_values) + self.sc.set_data(objectives[0], objectives[1]) if self.number_of_objectives == 3: - self.sc.set_3d_properties(z_values) + self.sc.set_3d_properties(objectives[2]) else: # Add new points - self.__plot(x_values, y_values, z_values) + if self.number_of_objectives == 2: + self.__plot(objectives[0], objectives[1], None, color='#98FB98', marker='o', markersize=3) + else: + self.__plot(objectives[0], objectives[1], objectives[2], color='#98FB98', marker='o', markersize=3) # Also, add event handler event_handler = \ - self.fig.canvas.mpl_connect('pick_event', lambda event: self.__pick_handler(event, front)) + self.fig.canvas.mpl_connect('pick_event', lambda event: self.__pick_handler(front, event)) # Update title with new times and evaluations - self.fig.suptitle(new_title, fontsize=13) + self.fig.suptitle(rename_title, fontsize=13) # Re-align the axis self.axis.relim() self.axis.autoscale_view(True, True, True) - # Draw try: + # Draw self.fig.canvas.draw() except KeyboardInterrupt: pass @@ -165,22 +139,43 @@ def update(self, front: List[S], reference: List[S], new_title: str= '', persist # Disconnect the pick event for the next update self.fig.canvas.mpl_disconnect(event_handler) - def __save(self, file_name: str, fmt: str = 'png', dpi: int = 200): - supported_formats = ["eps", "jpeg", "jpg", "pdf", "pgf", "png", "ps", - "raw", "rgba", "svg", "svgz", "tif", "tiff"] + def __initialize(self) -> None: + """ Initialize the scatter plot for the first time. """ + jMetalPyLogger.info('Generating plot') - if fmt not in supported_formats: - raise Exception('{0} is not a valid format! Use one of these instead: {0}'.format(fmt, supported_formats)) + # Initialize a plot + self.fig.canvas.set_window_title('jMetalPy') - self.fig.savefig(file_name + '.' + fmt, format=fmt, dpi=dpi) + if self.number_of_objectives == 2: + self.axis = self.fig.add_subplot(111) - def __retrieve_info(self, x_val: float, y_val: float, solution: Solution) -> None: - jMetalPyLogger.info("Output file: " + '{0}-{1}'.format(x_val, y_val)) + # Stylize axis + self.axis.spines['top'].set_visible(False) + self.axis.spines['right'].set_visible(False) + self.axis.get_xaxis().tick_bottom() + self.axis.get_yaxis().tick_left() + elif self.number_of_objectives == 3: + self.axis = Axes3D(self.fig) + self.axis.autoscale(enable=True, axis='both') + else: + raise Exception('Number of objectives must be either 2 or 3') + + self.axis.set_autoscale_on(True) + self.axis.autoscale_view(True, True, True) + + # Style options + self.axis.grid(color='#f0f0f5', linestyle='-', linewidth=1, alpha=0.5) + self.fig.suptitle(self.plot_title, fontsize=13) - with open('{0}-{1}'.format(x_val, y_val), 'w') as of: - of.write(solution.__str__()) + jMetalPyLogger.info('Plot initialized') - def __pick_handler(self, event, front: List[S]): + def __plot(self, x_values, y_values, z_values, **kwargs) -> None: + if self.number_of_objectives == 2: + self.sc, = self.axis.plot(x_values, y_values, ls='None', picker=10, **kwargs) + else: + self.sc, = self.axis.plot(x_values, y_values, z_values, ls='None', picker=10, **kwargs) + + def __pick_handler(self, front: List[S], event): """ Handler for picking points from the plot. """ line, ind = event.artist, event.ind[0] x, y = line.get_xdata(), line.get_ydata() @@ -191,132 +186,135 @@ def __pick_handler(self, event, front: List[S]): if solution.objectives[0] == x[ind] and solution.objectives[1] == y[ind]), None) if sol is not None: - self.__retrieve_info(x[ind], y[ind], sol) + with open('{0}-{1}'.format(x[ind], y[ind]), 'w') as of: + of.write(sol.__str__()) else: jMetalPyLogger.warning('Solution is none') return True -class ScatterBokeh(Plot): +class ScatterPlot(Plot): - def __init__(self, plot_title: str, number_of_objectives: int, ws_url: str='localhost:5006'): - super(ScatterBokeh, self).__init__(plot_title, number_of_objectives) - - if self.number_of_objectives == 2: - self.source = ColumnDataSource(data=dict(x=[], y=[], str=[])) - elif self.number_of_objectives == 3: - self.source = ColumnDataSource(data=dict(x=[], y=[], z=[], str=[])) - else: - raise Exception('Wrong number of objectives: {0}'.format(number_of_objectives)) + def __init__(self, plot_title: str, axis_labels: list = None): + """ Creates a new :class:`ScatterPlot` instance. Suitable for problems with 2 or more objectives. - self.client = ClientSession(websocket_url='ws://{0}/ws'.format(ws_url)) - self.doc = curdoc() - self.doc.title = plot_title - self.figure_xy = None - self.figure_xz = None - self.figure_yz = None + :param plot_title: Title of the diagram. + :param axis_labels: List of axis labels. """ + super(ScatterPlot, self).__init__(plot_title, axis_labels) - self.__initialize() + self.figure: go.Figure = None + self.layout = None + self.data = None - def __initialize(self) -> None: - """ Set-up tools for plot. """ - code = ''' - selected = source.selected['1d']['indices'][0] - var str = source.front.str[selected] - alert(str) - ''' - - callback = CustomJS(args=dict(source=self.source), code=code) - self.plot_tools = [TapTool(callback=callback), WheelZoomTool(), 'save', 'pan', - HoverTool(tooltips=[('index', '$index'), ('(x,y)', '($x, $y)')])] - - def plot(self, front: List[S], reference: List[S]=None, output: str= '', show: bool=True) -> None: - # This is important to purge front (if any) between calls - reset_output() - - # Set up figure - self.figure_xy = Figure(output_backend='webgl', - sizing_mode='scale_width', - title=self.plot_title, - tools=self.plot_tools) - self.figure_xy.scatter(x='x', y='y', legend='solution', fill_alpha=0.7, source=self.source) - self.figure_xy.xaxis.axis_label = self.xaxis_label - self.figure_xy.yaxis.axis_label = self.yaxis_label - - x_values, y_values, z_values = self.get_objectives(front) + def plot(self, front: List[S], reference_front: List[S] = None, show: bool = True) -> None: + """ Plot a front of solutions (2D, 3D or parallel coordinates). - if self.number_of_objectives == 2: - # Plot reference solution list (if any) - if reference: - ref_x_values, ref_y_values, _ = self.get_objectives(reference) - self.figure_xy.line(x=ref_x_values, y=ref_y_values, legend='reference', color='green') - - # Push front to server - self.source.stream({'x': x_values, 'y': y_values, 'str': [s.__str__() for s in front]}) - self.doc.add_root(column(self.figure_xy)) - else: - # Add new figures for each axis - self.figure_xz = Figure(title='xz', output_backend='webgl', - sizing_mode='scale_width', tools=self.plot_tools) - self.figure_xz.scatter(x='x', y='z', legend='solution', fill_alpha=0.7, source=self.source) - self.figure_xz.xaxis.axis_label = self.xaxis_label - self.figure_xz.yaxis.axis_label = self.zaxis_label - - self.figure_yz = Figure(title='yz', output_backend='webgl', - sizing_mode='scale_width', tools=self.plot_tools) - self.figure_yz.scatter(x='y', y='z', legend='solution', fill_alpha=0.7, source=self.source) - self.figure_yz.xaxis.axis_label = self.yaxis_label - self.figure_yz.yaxis.axis_label = self.zaxis_label - - # Plot reference solution list (if any) - if reference: - ref_x_values, ref_y_values, ref_z_values = self.get_objectives(reference) - self.figure_xy.line(x=ref_x_values, y=ref_y_values, legend='reference', color='green') - self.figure_xz.line(x=ref_x_values, y=ref_z_values, legend='reference', color='green') - self.figure_yz.line(x=ref_y_values, y=ref_z_values, legend='reference', color='green') - - # Push front to server - self.source.stream({'x': x_values, 'y': y_values, 'z': z_values, 'str': [s.__str__() for s in front]}) - self.doc.add_root(row(self.figure_xy, self.figure_xz, self.figure_yz)) - - self.client.push(self.doc) - - if output: - self.__save(output) - if show: - self.client.show() + :param front: List of solutions. + :param reference_front: Reference solution list (if any). + :param show: If True, show and save (file `front.html`) the final diagram (default to True). """ + self.__initialize() - def update(self, front: List[S], reference: List[S], new_title: str= '', persistence: bool=False) -> None: - # Check if plot has not been initialized first - if self.figure_xy is None: - self.plot(front, reference) + objectives = self.get_objectives(front) + self.data = [self.__generate_trace(objectives, legend='front')] - if not persistence: - rollover = len(front) - else: - rollover = None + if reference_front: + objectives = self.get_objectives(reference_front) + self.data.append( + self.__generate_trace( + objectives, legend='reference', + symbol='diamond-open', size=3, opacity=0.4, color='rgb(2, 130, 242)')) - self.figure_xy.title.text = new_title - x_values, y_values, z_values = self.get_objectives(front) + self.figure = go.Figure(data=self.data, layout=self.layout) - if self.number_of_objectives == 2: - self.source.stream({'x': x_values, 'y': y_values, 'str': [s.__str__() for s in front]}, - rollover=rollover) + if show: + plot(self.figure, filename='front.html') + + def add_data(self, data: List[S], **kwargs) -> None: + """ Update an already created plot with new data. + + :param data: List of solutions to be included. + :param kwargs: Optional values for `styling markers `_. """ + if self.figure is None: + jMetalPyLogger.warning('Plot must be initialized first.') + self.plot(data, None, show=False) + return + + objectives = self.get_objectives(data) + new_data = self.__generate_trace(objectives=objectives, size=5, color='rgb(255, 170, 0)', **kwargs) + + self.data.append(new_data) + self.figure = go.Figure(data=self.data, layout=self.layout) + + def show(self) -> None: + plot(self.figure, filename='front') + + def __initialize(self): + """ Initialize the plot for the first time. """ + jMetalPyLogger.info('Generating plot') + + self.layout = go.Layout( + margin=dict(l=100, r=100, b=100, t=100), + title=self.plot_title, + scene=dict( + xaxis=dict(title=self.axis_labels[0:1][0] if self.axis_labels[0:1] else None), + yaxis=dict(title=self.axis_labels[1:2][0] if self.axis_labels[1:2] else None), + zaxis=dict(title=self.axis_labels[2:3][0] if self.axis_labels[2:3] else None) + ), + images=[dict( + source='https://raw.githubusercontent.com/jMetal/jMetalPy/master/docs/source/jmetalpy.png', + xref='paper', yref='paper', + x=0, y=1.05, + sizex=0.1, sizey=0.1, + xanchor="left", yanchor="bottom" + )] + ) + + @staticmethod + def __generate_trace(objectives: DataFrame, legend: str = '', **kwargs): + number_of_objectives = objectives.shape[1] + + marker = dict( + color='rgb(127, 127, 127)', + size=3, + symbol='circle', + line=dict( + color='rgb(204, 204, 204)', + width=1 + ), + opacity=1.0 + ) + marker.update(**kwargs) + + if number_of_objectives == 2: + trace = go.Scattergl( + x=objectives[0], + y=objectives[1], + mode='markers', + marker=marker, + name=legend + ) + elif number_of_objectives == 3: + trace = go.Scatter3d( + x=objectives[0], + y=objectives[1], + z=objectives[2], + mode='markers', + marker=marker, + name=legend + ) else: - self.source.stream({'x': x_values, 'y': y_values, 'z': z_values, 'str': [s.__str__() for s in front]}, - rollover=rollover) - - def __save(self, file_name: str): - # env = Environment(loader=FileSystemLoader(BASE_PATH + '/util/')) - # env.filters['json'] = lambda obj: Markup(json.dumps(obj)) - - html = file_html(models=self.doc, resources=CDN) - with open(file_name + '.html', 'w') as of: - of.write(html) - - def disconnect(self): - if self.is_connected(): - self.client.close() - - def is_connected(self) -> bool: - return self.client.connected + dimensions = list() + for column in objectives: + dimensions.append( + dict(range=[0, 1], + label='O', + values=objectives[column]) + ) + + trace = go.Parcoords( + line=dict(color='blue'), + dimensions=dimensions, + name=legend + ) + + return trace diff --git a/jmetal/util/test/__init__.py b/jmetal/util/test/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/requirements.txt b/requirements.txt index 14dac7a2..25d55821 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ +numpy==1.13.1 +pandas tqdm==4.23.4 -bokeh==0.12.16 -pytest==3.1.2 PyHamcrest==1.9.0 mockito==1.0.11 matplotlib==2.0.2 -numpy==1.13.1 \ No newline at end of file +plotly \ No newline at end of file diff --git a/jmetal/problem/reference_front/DTLZ1.pf b/resources/reference_front/DTLZ1.pf similarity index 100% rename from jmetal/problem/reference_front/DTLZ1.pf rename to resources/reference_front/DTLZ1.pf diff --git a/jmetal/problem/reference_front/ZDT1.pf b/resources/reference_front/ZDT1.pf similarity index 100% rename from jmetal/problem/reference_front/ZDT1.pf rename to resources/reference_front/ZDT1.pf diff --git a/jmetal/problem/reference_front/ZDT2.pf b/resources/reference_front/ZDT2.pf similarity index 100% rename from jmetal/problem/reference_front/ZDT2.pf rename to resources/reference_front/ZDT2.pf diff --git a/jmetal/problem/reference_front/ZDT3.pf b/resources/reference_front/ZDT3.pf similarity index 100% rename from jmetal/problem/reference_front/ZDT3.pf rename to resources/reference_front/ZDT3.pf diff --git a/jmetal/problem/reference_front/ZDT4.pf b/resources/reference_front/ZDT4.pf similarity index 100% rename from jmetal/problem/reference_front/ZDT4.pf rename to resources/reference_front/ZDT4.pf diff --git a/jmetal/problem/reference_front/ZDT6.pf b/resources/reference_front/ZDT6.pf similarity index 100% rename from jmetal/problem/reference_front/ZDT6.pf rename to resources/reference_front/ZDT6.pf From 7fefd307e6c398f832004bad7c849f9d22c2b0d8 Mon Sep 17 00:00:00 2001 From: benhid Date: Tue, 17 Jul 2018 14:14:37 +0200 Subject: [PATCH 03/26] Change image --- docs/source/index.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/source/index.rst b/docs/source/index.rst index 586a542d..8c6ae1bc 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -56,4 +56,6 @@ Basic usage pareto_front = ScatterMatplotlib(plot_title='NSGAII for ZDT1', number_of_objectives=problem.number_of_objectives) pareto_front.plot(front, reference=problem.get_reference_front(), output='NSGAII-ZDT1', show=False) -.. image:: NSGAII-ZDT1.png +.. raw:: html + +
From 97e978a445054674ec20756cb587648248b01950 Mon Sep 17 00:00:00 2001 From: benhid Date: Tue, 17 Jul 2018 14:19:00 +0200 Subject: [PATCH 04/26] Change image --- docs/source/index.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/source/index.rst b/docs/source/index.rst index 8c6ae1bc..594c8450 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -57,5 +57,6 @@ Basic usage pareto_front.plot(front, reference=problem.get_reference_front(), output='NSGAII-ZDT1', show=False) .. raw:: html +
From 472e23f7d2888a5d7496da5d3842f8884f6e54e6 Mon Sep 17 00:00:00 2001 From: benhid Date: Tue, 17 Jul 2018 14:29:29 +0200 Subject: [PATCH 05/26] Change image --- docs/source/index.rst | 20 +++++++++++++++++--- docs/source/runner/nsgaii.rst | 4 ++-- docs/source/runner/smpso.rst | 4 ++-- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/docs/source/index.rst b/docs/source/index.rst index 594c8450..da7f24fd 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -39,7 +39,7 @@ Basic usage .. code-block:: python - problem = ZDT1() + problem = ZDT1(rf_path='resources/reference_front/ZDT1.pf') algorithm = NSGAII( problem=problem, @@ -53,10 +53,24 @@ Basic usage algorithm.run() front = algorithm.get_result() - pareto_front = ScatterMatplotlib(plot_title='NSGAII for ZDT1', number_of_objectives=problem.number_of_objectives) - pareto_front.plot(front, reference=problem.get_reference_front(), output='NSGAII-ZDT1', show=False) + pareto_front = ScatterPlot(plot_title='NSGAII-ZDT1', axis_labels=problem.obj_labels) + pareto_front.plot(front, reference_front=problem.reference_front) .. raw:: html
+ +.. code-block:: python + + problem = DTLZ1(rf_path='resources/reference_front/DTLZ1.pf') + + # configure and run NSGAII (same settings as before) + + pareto_front = ScatterPlot(plot_title='NSGAII-ZDT1', axis_labels=problem.obj_labels) + pareto_front.plot(front, reference_front=problem.reference_front) + +.. raw:: html + + +
diff --git a/docs/source/runner/nsgaii.rst b/docs/source/runner/nsgaii.rst index 3a693b43..523e4587 100644 --- a/docs/source/runner/nsgaii.rst +++ b/docs/source/runner/nsgaii.rst @@ -17,7 +17,7 @@ NSGA-II with standard settings .. code-block:: python algorithm = NSGAII( - problem=ZDT1(), + problem=ZDT1(rf_path='resources/reference_front/ZDT1.pf'), population_size=100, max_evaluations=25000, mutation=Polynomial(probability=1.0/problem.number_of_variables, distribution_index=20), @@ -39,7 +39,7 @@ NSGA-II stopping by time return [False, True][self.get_current_computing_time() > 4] algorithm = NSGA2b( - problem=ZDT1(), + problem=ZDT1(rf_path='resources/reference_front/ZDT1.pf'), population_size=100, max_evaluations=25000, mutation=Polynomial(probability=1.0/problem.number_of_variables, distribution_index=20), diff --git a/docs/source/runner/smpso.rst b/docs/source/runner/smpso.rst index 140f5535..4b92f3eb 100644 --- a/docs/source/runner/smpso.rst +++ b/docs/source/runner/smpso.rst @@ -18,7 +18,7 @@ SMPSO with standard settings from jmetal.component import CrowdingDistanceArchive algorithm = SMPSO( - problem=ZDT1(), + problem=ZDT1(rf_path='resources/reference_front/ZDT1.pf'), swarm_size=100, max_evaluations=25000, mutation=Polynomial(probability=1.0/problem.number_of_variables, distribution_index=20), @@ -47,7 +47,7 @@ SMPSO/RP with standard settings ) algorithm = SMPSORP( - problem=ZDT1(), + problem=ZDT1(rf_path='resources/reference_front/ZDT1.pf'), swarm_size=swarm_size, max_evaluations=25000, mutation=Polynomial(probability=1.0/problem.number_of_variables, distribution_index=20), From 5bc613dba719242171107a17663820ec24628d8b Mon Sep 17 00:00:00 2001 From: benhid Date: Tue, 17 Jul 2018 14:42:16 +0200 Subject: [PATCH 06/26] Docs updated --- docs/source/index.rst | 41 ------------- docs/source/runner/nsgaii.rst | 60 +++++++++++++++---- docs/source/runner/smpso.rst | 26 +++++++- .../smpsorp_standard_settings.py | 2 +- jmetal/algorithm/multiobjective/smpso.py | 3 +- 5 files changed, 73 insertions(+), 59 deletions(-) diff --git a/docs/source/index.rst b/docs/source/index.rst index da7f24fd..3935d6b1 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -33,44 +33,3 @@ Via Github: $ git clone https://github.com/jMetal/jMetalPy.git $ pip install -r requirements.txt $ python setup.py install - -Basic usage ------------ - -.. code-block:: python - - problem = ZDT1(rf_path='resources/reference_front/ZDT1.pf') - - algorithm = NSGAII( - problem=problem, - population_size=100, - max_evaluations=25000, - mutation=Polynomial(probability=1.0/problem.number_of_variables, distribution_index=20), - crossover=SBX(probability=1.0, distribution_index=20), - selection=BinaryTournamentSelection(comparator=RankingAndCrowdingDistanceComparator()) - ) - - algorithm.run() - front = algorithm.get_result() - - pareto_front = ScatterPlot(plot_title='NSGAII-ZDT1', axis_labels=problem.obj_labels) - pareto_front.plot(front, reference_front=problem.reference_front) - -.. raw:: html - - -
- -.. code-block:: python - - problem = DTLZ1(rf_path='resources/reference_front/DTLZ1.pf') - - # configure and run NSGAII (same settings as before) - - pareto_front = ScatterPlot(plot_title='NSGAII-ZDT1', axis_labels=problem.obj_labels) - pareto_front.plot(front, reference_front=problem.reference_front) - -.. raw:: html - - -
diff --git a/docs/source/runner/nsgaii.rst b/docs/source/runner/nsgaii.rst index 523e4587..c38e4b9c 100644 --- a/docs/source/runner/nsgaii.rst +++ b/docs/source/runner/nsgaii.rst @@ -9,24 +9,58 @@ Common imports for these examples: from jmetal.operator import Polynomial, SBX, BinaryTournamentSelection from jmetal.component import RankingAndCrowdingDistanceComparator - from jmetal.problem import ZDT1 + from jmetal.problem import ZDT1, DTLZ1 -NSGA-II with standard settings +NSGA-II with plotting ------------------------------------ .. code-block:: python - algorithm = NSGAII( - problem=ZDT1(rf_path='resources/reference_front/ZDT1.pf'), - population_size=100, - max_evaluations=25000, - mutation=Polynomial(probability=1.0/problem.number_of_variables, distribution_index=20), - crossover=SBX(probability=1.0, distribution_index=20), - selection=BinaryTournamentSelection(comparator=RankingAndCrowdingDistanceComparator()) - ) + problem = ZDT1(rf_path='resources/reference_front/ZDT1.pf') - algorithm.run() - front = algorithm.get_result() + algorithm = NSGAII( + problem=problem, + population_size=100, + max_evaluations=25000, + mutation=Polynomial(probability=1.0/problem.number_of_variables, distribution_index=20), + crossover=SBX(probability=1.0, distribution_index=20), + selection=BinaryTournamentSelection(comparator=RankingAndCrowdingDistanceComparator()) + ) + + algorithm.run() + front = algorithm.get_result() + + pareto_front = ScatterPlot(plot_title='NSGAII-ZDT1', axis_labels=problem.obj_labels) + pareto_front.plot(front, reference_front=problem.reference_front) + +.. raw:: html + + +
+ +.. code-block:: python + + problem = DTLZ1(rf_path='resources/reference_front/DTLZ1.pf') + + algorithm = NSGAII( + problem=problem, + population_size=100, + max_evaluations=25000, + mutation=Polynomial(probability=1.0/problem.number_of_variables, distribution_index=20), + crossover=SBX(probability=1.0, distribution_index=20), + selection=BinaryTournamentSelection(comparator=RankingAndCrowdingDistanceComparator()) + ) + + algorithm.run() + front = algorithm.get_result() + + pareto_front = ScatterPlot(plot_title='NSGAII-DTLZ1', axis_labels=problem.obj_labels) + pareto_front.plot(front, reference_front=problem.reference_front) + +.. raw:: html + + +
NSGA-II stopping by time ------------------------------------ @@ -39,7 +73,7 @@ NSGA-II stopping by time return [False, True][self.get_current_computing_time() > 4] algorithm = NSGA2b( - problem=ZDT1(rf_path='resources/reference_front/ZDT1.pf'), + problem=ZDT1(), population_size=100, max_evaluations=25000, mutation=Polynomial(probability=1.0/problem.number_of_variables, distribution_index=20), diff --git a/docs/source/runner/smpso.rst b/docs/source/runner/smpso.rst index 4b92f3eb..a20ef5c4 100644 --- a/docs/source/runner/smpso.rst +++ b/docs/source/runner/smpso.rst @@ -36,8 +36,18 @@ SMPSO/RP with standard settings from jmetal.algorithm import SMPSORP from jmetal.component import CrowdingDistanceArchiveWithReferencePoint - swarm_size = 100 + def points_to_solutions(points): + solutions = [] + for i, _ in enumerate(points): + point = problem.create_solution() + point.objectives = points[i] + solutions.append(point) + + return solutions + + problem = ZDT1(rf_path='resources/reference_front/ZDT1.pf') + swarm_size = 100 reference_points = [[0.8, 0.2], [0.4, 0.6]] archives_with_reference_points = [] @@ -47,7 +57,7 @@ SMPSO/RP with standard settings ) algorithm = SMPSORP( - problem=ZDT1(rf_path='resources/reference_front/ZDT1.pf'), + problem=problem, swarm_size=swarm_size, max_evaluations=25000, mutation=Polynomial(probability=1.0/problem.number_of_variables, distribution_index=20), @@ -56,4 +66,14 @@ SMPSO/RP with standard settings ) algorithm.run() - front = algorithm.get_result() \ No newline at end of file + front = algorithm.get_result() + + pareto_front = ScatterPlot(plot_title='SMPSORP-ZDT1', axis_labels=problem.obj_labels) + pareto_front.plot(front, reference_front=problem.reference_front, show=False) + pareto_front.add_data(points_to_solutions(reference_points), legend='reference points') + pareto_front.show() + +.. raw:: html + + +
\ No newline at end of file diff --git a/examples/multiobjective/smpsorp_standard_settings.py b/examples/multiobjective/smpsorp_standard_settings.py index b2f62bfe..c19741d4 100644 --- a/examples/multiobjective/smpsorp_standard_settings.py +++ b/examples/multiobjective/smpsorp_standard_settings.py @@ -47,7 +47,7 @@ def points_to_solutions(points): front = algorithm.get_result() # Plot frontier to file - pareto_front = ScatterPlot(plot_title='NSGAII plot', axis_labels=problem.obj_labels) + pareto_front = ScatterPlot(plot_title='SMPSORP-ZDT1', axis_labels=problem.obj_labels) pareto_front.plot(front, reference_front=problem.reference_front, show=False) pareto_front.add_data(points_to_solutions(reference_points), legend='reference points') pareto_front.show() diff --git a/jmetal/algorithm/multiobjective/smpso.py b/jmetal/algorithm/multiobjective/smpso.py index c9198e2b..bac7e31f 100644 --- a/jmetal/algorithm/multiobjective/smpso.py +++ b/jmetal/algorithm/multiobjective/smpso.py @@ -265,7 +265,8 @@ def update_progress(self) -> None: observable_data = {'evaluations': self.evaluations, 'computing time': self.get_current_computing_time(), - 'population': self.get_result() + reference_points} + 'population': self.get_result() + reference_points, + 'reference_front': self.problem.reference_front} self.observable.notify_all(**observable_data) From a93bc6a4dd70bffed28a0a609b51c38984e11ea0 Mon Sep 17 00:00:00 2001 From: benhid Date: Wed, 18 Jul 2018 10:59:25 +0200 Subject: [PATCH 07/26] Added parallel coordinates plot --- docs/source/examples.rst | 10 +- docs/source/examples/nsgaii.rst | 104 ++++++++++++++++++ docs/source/{runner => examples}/observer.rst | 0 docs/source/examples/smpso.rst | 103 +++++++++++++++++ docs/source/runner/nsgaii.rst | 85 -------------- docs/source/runner/smpso.rst | 79 ------------- examples/multiobjective/nsgaii_dtlz1.py | 37 +++++++ ...nsgaii_full_settings.py => nsgaii_zdt1.py} | 10 +- ...mpso_full_settings.py => smpso_dtlz1_5.py} | 18 +-- ...p_standard_settings.py => smpsorp_zdt1.py} | 9 +- jmetal/problem/multiobjective/dtlz.py | 4 +- jmetal/util/graphic.py | 69 ++++++------ 12 files changed, 306 insertions(+), 222 deletions(-) create mode 100644 docs/source/examples/nsgaii.rst rename docs/source/{runner => examples}/observer.rst (100%) create mode 100644 docs/source/examples/smpso.rst delete mode 100644 docs/source/runner/nsgaii.rst delete mode 100644 docs/source/runner/smpso.rst create mode 100644 examples/multiobjective/nsgaii_dtlz1.py rename examples/multiobjective/{nsgaii_full_settings.py => nsgaii_zdt1.py} (80%) rename examples/multiobjective/{smpso_full_settings.py => smpso_dtlz1_5.py} (63%) rename examples/multiobjective/{smpsorp_standard_settings.py => smpsorp_zdt1.py} (88%) diff --git a/docs/source/examples.rst b/docs/source/examples.rst index e56a766a..6e8e04a6 100644 --- a/docs/source/examples.rst +++ b/docs/source/examples.rst @@ -1,15 +1,15 @@ -Examples -============== +Getting started +================== .. toctree:: :maxdepth: 2 :caption: Algorithms: - runner/nsgaii - runner/smpso + examples/nsgaii + examples/smpso .. toctree:: :maxdepth: 2 :caption: Further configuration: - runner/observer + examples/observer diff --git a/docs/source/examples/nsgaii.rst b/docs/source/examples/nsgaii.rst new file mode 100644 index 00000000..7c9d1c8c --- /dev/null +++ b/docs/source/examples/nsgaii.rst @@ -0,0 +1,104 @@ +NSGA-II +======================== + +Common imports for these examples: + +.. code-block:: python + + from jmetal.algorithm import NSGAII + from jmetal.operator import Polynomial, SBX, BinaryTournamentSelection + from jmetal.component import RankingAndCrowdingDistanceComparator + + from jmetal.util import ScatterPlot + +NSGA-II with plotting +------------------------------------ + +.. code-block:: python + + from jmetal.problem import ZDT1 + + problem = ZDT1(rf_path='resources/reference_front/ZDT1.pf') + + algorithm = NSGAII( + problem=problem, + population_size=100, + max_evaluations=25000, + mutation=Polynomial(probability=1.0/problem.number_of_variables, distribution_index=20), + crossover=SBX(probability=1.0, distribution_index=20), + selection=BinaryTournamentSelection(comparator=RankingAndCrowdingDistanceComparator()) + ) + + algorithm.run() + front = algorithm.get_result() + + pareto_front = ScatterPlot(plot_title='NSGAII-ZDT1', axis_labels=problem.obj_labels) + pareto_front.plot(front, reference_front=problem.reference_front) + pareto_front.save(filename='NSGAII-ZDT1') + +.. raw:: html + + +
+ +.. code-block:: python + + from jmetal.problem import DTLZ1 + + problem = DTLZ1(rf_path='resources/reference_front/DTLZ1.pf') + + algorithm = NSGAII( + problem=problem, + population_size=100, + max_evaluations=50000, + mutation=Polynomial(probability=1.0/problem.number_of_variables, distribution_index=20), + crossover=SBX(probability=1.0, distribution_index=20), + selection=BinaryTournamentSelection(comparator=RankingAndCrowdingDistanceComparator()) + ) + + algorithm.run() + front = algorithm.get_result() + + pareto_front = ScatterPlot(plot_title='NSGAII-DTLZ1', axis_labels=problem.obj_labels) + pareto_front.plot(front, reference_front=problem.reference_front) + pareto_front.save(filename='NSGAII-DTLZ1') + +.. raw:: html + + + +
+ +NSGA-II stopping by time +------------------------------------ + +.. code-block:: python + + from jmetal.problem import ZDT1 + + + class NSGA2b(NSGAII): + def is_stopping_condition_reached(self): + # Re-define the stopping condition + return [False, True][self.get_current_computing_time() > 4] + + problem = ZDT1() + + algorithm = NSGA2b( + problem=problem, + population_size=100, + max_evaluations=25000, + mutation=Polynomial(probability=1.0/problem.number_of_variables, distribution_index=20), + crossover=SBX(probability=1.0, distribution_index=20), + selection=BinaryTournamentSelection(comparator=RankingAndCrowdingDistanceComparator()) + ) + + algorithm.run() + front = algorithm.get_result() diff --git a/docs/source/runner/observer.rst b/docs/source/examples/observer.rst similarity index 100% rename from docs/source/runner/observer.rst rename to docs/source/examples/observer.rst diff --git a/docs/source/examples/smpso.rst b/docs/source/examples/smpso.rst new file mode 100644 index 00000000..38be5010 --- /dev/null +++ b/docs/source/examples/smpso.rst @@ -0,0 +1,103 @@ +SMPSO +======================== + +Common imports for these examples: + +.. code-block:: python + + from jmetal.operator import Polynomial + + from jmetal.util import ScatterPlot + +SMPSO with standard settings +------------------------------------ + +.. code-block:: python + + from jmetal.algorithm import SMPSO + from jmetal.component import CrowdingDistanceArchive + from jmetal.problem import DTLZ1 + + problem = DTLZ1(number_of_objectives=5) + + algorithm = SMPSO( + problem=problem, + swarm_size=100, + max_evaluations=25000, + mutation=Polynomial(probability=1.0/problem.number_of_variables, distribution_index=20), + leaders=CrowdingDistanceArchive(100) + ) + + algorithm.run() + front = algorithm.get_result() + + pareto_front = ScatterPlot(plot_title='SMPSO-DTLZ1-5', axis_labels=problem.obj_labels) + pareto_front.plot(front, reference_front=problem.reference_front) + pareto_front.save(filename='SMPSO-DTLZ1-5') + +.. raw:: html + + +
+ +.. code-block:: python + + pareto_front = ScatterPlot(plot_title='SMPSO-DTLZ1-5-norm', axis_labels=problem.obj_labels) + pareto_front.plot(front, reference_front=problem.reference_front, normalize=True) + pareto_front.save(filename='SMPSO-DTLZ1-5-norm') + +.. raw:: html + + +
+ +SMPSO/RP with standard settings +------------------------------------ + +.. code-block:: python + + from jmetal.algorithm import SMPSORP + from jmetal.component import CrowdingDistanceArchiveWithReferencePoint + from jmetal.problem import ZDT1 + + def points_to_solutions(points): + solutions = [] + for i, _ in enumerate(points): + point = problem.create_solution() + point.objectives = points[i] + solutions.append(point) + + return solutions + + problem = ZDT1(rf_path='resources/reference_front/ZDT1.pf') + + swarm_size = 100 + reference_points = [[0.8, 0.2], [0.4, 0.6]] + archives_with_reference_points = [] + + for point in reference_points: + archives_with_reference_points.append( + CrowdingDistanceArchiveWithReferencePoint(swarm_size, point) + ) + + algorithm = SMPSORP( + problem=problem, + swarm_size=swarm_size, + max_evaluations=25000, + mutation=Polynomial(probability=1.0/problem.number_of_variables, distribution_index=20), + reference_points=reference_points, + leaders=archives_with_reference_points + ) + + algorithm.run() + front = algorithm.get_result() + + pareto_front = ScatterPlot(plot_title='SMPSORP-ZDT1', axis_labels=problem.obj_labels) + pareto_front.plot(front, reference_front=problem.reference_front) + pareto_front.update(points_to_solutions(reference_points), legend='reference points') + pareto_front.save(filename='SMPSORP-ZDT1') + +.. raw:: html + + +
\ No newline at end of file diff --git a/docs/source/runner/nsgaii.rst b/docs/source/runner/nsgaii.rst deleted file mode 100644 index c38e4b9c..00000000 --- a/docs/source/runner/nsgaii.rst +++ /dev/null @@ -1,85 +0,0 @@ -NSGA-II -======================== - -Common imports for these examples: - -.. code-block:: python - - from jmetal.algorithm import NSGAII - from jmetal.operator import Polynomial, SBX, BinaryTournamentSelection - from jmetal.component import RankingAndCrowdingDistanceComparator - - from jmetal.problem import ZDT1, DTLZ1 - -NSGA-II with plotting ------------------------------------- - -.. code-block:: python - - problem = ZDT1(rf_path='resources/reference_front/ZDT1.pf') - - algorithm = NSGAII( - problem=problem, - population_size=100, - max_evaluations=25000, - mutation=Polynomial(probability=1.0/problem.number_of_variables, distribution_index=20), - crossover=SBX(probability=1.0, distribution_index=20), - selection=BinaryTournamentSelection(comparator=RankingAndCrowdingDistanceComparator()) - ) - - algorithm.run() - front = algorithm.get_result() - - pareto_front = ScatterPlot(plot_title='NSGAII-ZDT1', axis_labels=problem.obj_labels) - pareto_front.plot(front, reference_front=problem.reference_front) - -.. raw:: html - - -
- -.. code-block:: python - - problem = DTLZ1(rf_path='resources/reference_front/DTLZ1.pf') - - algorithm = NSGAII( - problem=problem, - population_size=100, - max_evaluations=25000, - mutation=Polynomial(probability=1.0/problem.number_of_variables, distribution_index=20), - crossover=SBX(probability=1.0, distribution_index=20), - selection=BinaryTournamentSelection(comparator=RankingAndCrowdingDistanceComparator()) - ) - - algorithm.run() - front = algorithm.get_result() - - pareto_front = ScatterPlot(plot_title='NSGAII-DTLZ1', axis_labels=problem.obj_labels) - pareto_front.plot(front, reference_front=problem.reference_front) - -.. raw:: html - - -
- -NSGA-II stopping by time ------------------------------------- - -.. code-block:: python - - class NSGA2b(NSGAII): - def is_stopping_condition_reached(self): - # Re-define the stopping condition - return [False, True][self.get_current_computing_time() > 4] - - algorithm = NSGA2b( - problem=ZDT1(), - population_size=100, - max_evaluations=25000, - mutation=Polynomial(probability=1.0/problem.number_of_variables, distribution_index=20), - crossover=SBX(probability=1.0, distribution_index=20), - selection=BinaryTournamentSelection(comparator=RankingAndCrowdingDistanceComparator()) - ) - - algorithm.run() - front = algorithm.get_result() diff --git a/docs/source/runner/smpso.rst b/docs/source/runner/smpso.rst deleted file mode 100644 index a20ef5c4..00000000 --- a/docs/source/runner/smpso.rst +++ /dev/null @@ -1,79 +0,0 @@ -SMPSO -======================== - -Common imports: - -.. code-block:: python - - from jmetal.operator import Polynomial - - from jmetal.problem import ZDT1 - -SMPSO with standard settings ------------------------------------- - -.. code-block:: python - - from jmetal.algorithm import SMPSO - from jmetal.component import CrowdingDistanceArchive - - algorithm = SMPSO( - problem=ZDT1(rf_path='resources/reference_front/ZDT1.pf'), - swarm_size=100, - max_evaluations=25000, - mutation=Polynomial(probability=1.0/problem.number_of_variables, distribution_index=20), - leaders=CrowdingDistanceArchive(100) - ) - - algorithm.run() - front = algorithm.get_result() - -SMPSO/RP with standard settings ------------------------------------- - -.. code-block:: python - - from jmetal.algorithm import SMPSORP - from jmetal.component import CrowdingDistanceArchiveWithReferencePoint - - def points_to_solutions(points): - solutions = [] - for i, _ in enumerate(points): - point = problem.create_solution() - point.objectives = points[i] - solutions.append(point) - - return solutions - - problem = ZDT1(rf_path='resources/reference_front/ZDT1.pf') - - swarm_size = 100 - reference_points = [[0.8, 0.2], [0.4, 0.6]] - archives_with_reference_points = [] - - for point in reference_points: - archives_with_reference_points.append( - CrowdingDistanceArchiveWithReferencePoint(swarm_size, point) - ) - - algorithm = SMPSORP( - problem=problem, - swarm_size=swarm_size, - max_evaluations=25000, - mutation=Polynomial(probability=1.0/problem.number_of_variables, distribution_index=20), - reference_points=reference_points, - leaders=archives_with_reference_points - ) - - algorithm.run() - front = algorithm.get_result() - - pareto_front = ScatterPlot(plot_title='SMPSORP-ZDT1', axis_labels=problem.obj_labels) - pareto_front.plot(front, reference_front=problem.reference_front, show=False) - pareto_front.add_data(points_to_solutions(reference_points), legend='reference points') - pareto_front.show() - -.. raw:: html - - -
\ No newline at end of file diff --git a/examples/multiobjective/nsgaii_dtlz1.py b/examples/multiobjective/nsgaii_dtlz1.py new file mode 100644 index 00000000..3fbb6f32 --- /dev/null +++ b/examples/multiobjective/nsgaii_dtlz1.py @@ -0,0 +1,37 @@ +from jmetal.algorithm import NSGAII +from jmetal.problem import DTLZ1 +from jmetal.operator import SBX, Polynomial, BinaryTournamentSelection +from jmetal.component import ProgressBarObserver, RankingAndCrowdingDistanceComparator +from jmetal.util import ScatterPlot, SolutionList + + +if __name__ == '__main__': + problem = DTLZ1(rf_path='../../resources/reference_front/DTLZ1.pf') + + algorithm = NSGAII( + problem=problem, + population_size=100, + max_evaluations=50000, + mutation=Polynomial(probability=1.0 / problem.number_of_variables, distribution_index=20), + crossover=SBX(probability=1.0, distribution_index=20), + selection=BinaryTournamentSelection(comparator=RankingAndCrowdingDistanceComparator()) + ) + + progress_bar = ProgressBarObserver(step=100, maximum=50000) + algorithm.observable.register(observer=progress_bar) + + algorithm.run() + front = algorithm.get_result() + + # Plot frontier to file + pareto_front = ScatterPlot(plot_title='NSGAII-DTLZ1', axis_labels=problem.obj_labels) + pareto_front.plot(front, reference_front=problem.reference_front) + pareto_front.save(filename='NSGAII-DTLZ1') + + # Save variables to file + SolutionList.print_function_values_to_file(front, 'FUN.NSGAII.DTLZ1') + SolutionList.print_variables_to_file(front, 'VAR.NSGAII.DTLZ1') + + print('Algorithm (continuous problem): ' + algorithm.get_name()) + print('Problem: ' + problem.get_name()) + print('Computing time: ' + str(algorithm.total_computing_time)) diff --git a/examples/multiobjective/nsgaii_full_settings.py b/examples/multiobjective/nsgaii_zdt1.py similarity index 80% rename from examples/multiobjective/nsgaii_full_settings.py rename to examples/multiobjective/nsgaii_zdt1.py index 69ae5a6a..dc684e6a 100644 --- a/examples/multiobjective/nsgaii_full_settings.py +++ b/examples/multiobjective/nsgaii_zdt1.py @@ -1,7 +1,7 @@ from jmetal.algorithm import NSGAII from jmetal.problem import ZDT1 from jmetal.operator import SBX, Polynomial, BinaryTournamentSelection -from jmetal.component import VisualizerObserver, ProgressBarObserver, RankingAndCrowdingDistanceComparator +from jmetal.component import ProgressBarObserver, RankingAndCrowdingDistanceComparator from jmetal.util import ScatterPlot, SolutionList @@ -17,9 +17,6 @@ selection=BinaryTournamentSelection(comparator=RankingAndCrowdingDistanceComparator()) ) - observer = VisualizerObserver() - algorithm.observable.register(observer=observer) - progress_bar = ProgressBarObserver(step=100, maximum=25000) algorithm.observable.register(observer=progress_bar) @@ -29,10 +26,11 @@ # Plot frontier to file pareto_front = ScatterPlot(plot_title='NSGAII-ZDT1', axis_labels=problem.obj_labels) pareto_front.plot(front, reference_front=problem.reference_front) + pareto_front.save(filename='NSGAII-ZDT1') # Save variables to file - SolutionList.print_function_values_to_file(front, 'FUN.NSGAII.' + problem.get_name()) - SolutionList.print_variables_to_file(front, 'VAR.NSGAII.' + problem.get_name()) + SolutionList.print_function_values_to_file(front, 'FUN.NSGAII.ZDT1') + SolutionList.print_variables_to_file(front, 'VAR.NSGAII.ZDT1') print('Algorithm (continuous problem): ' + algorithm.get_name()) print('Problem: ' + problem.get_name()) diff --git a/examples/multiobjective/smpso_full_settings.py b/examples/multiobjective/smpso_dtlz1_5.py similarity index 63% rename from examples/multiobjective/smpso_full_settings.py rename to examples/multiobjective/smpso_dtlz1_5.py index ee15e014..13b0afb3 100644 --- a/examples/multiobjective/smpso_full_settings.py +++ b/examples/multiobjective/smpso_dtlz1_5.py @@ -1,12 +1,12 @@ from jmetal.algorithm import SMPSO from jmetal.problem import DTLZ1 from jmetal.operator import Polynomial -from jmetal.component import VisualizerObserver, ProgressBarObserver, CrowdingDistanceArchive +from jmetal.component import ProgressBarObserver, CrowdingDistanceArchive from jmetal.util import ScatterPlot, SolutionList if __name__ == '__main__': - problem = DTLZ1(rf_path='../../resources/reference_front/DTLZ1.pf') + problem = DTLZ1(number_of_objectives=5) algorithm = SMPSO( problem=problem, @@ -16,9 +16,6 @@ leaders=CrowdingDistanceArchive(100) ) - observer = VisualizerObserver() - algorithm.observable.register(observer=observer) - progress_bar = ProgressBarObserver(step=100, maximum=25000) algorithm.observable.register(observer=progress_bar) @@ -26,12 +23,17 @@ front = algorithm.get_result() # Plot frontier to file - pareto_front = ScatterPlot(plot_title='SMPSO-DTLZ1', axis_labels=problem.obj_labels) + pareto_front = ScatterPlot(plot_title='SMPSO-DTLZ1-5', axis_labels=problem.obj_labels) pareto_front.plot(front, reference_front=problem.reference_front) + pareto_front.save(filename='SMPSO-DTLZ1-5') + + pareto_front = ScatterPlot(plot_title='SMPSO-DTLZ1-5-norm', axis_labels=problem.obj_labels) + pareto_front.plot(front, reference_front=problem.reference_front, normalize=True) + pareto_front.save(filename='SMPSO-DTLZ1-5-norm') # Save variables to file - SolutionList.print_function_values_to_file(front, 'FUN.SMPSO.' + problem.get_name()) - SolutionList.print_variables_to_file(front, 'VAR.SMPSO.' + problem.get_name()) + SolutionList.print_function_values_to_file(front, 'FUN.SMPSO.DTLZ1-5') + SolutionList.print_variables_to_file(front, 'VAR.SMPSO.DTLZ1-5') print('Algorithm (continuous problem): ' + algorithm.get_name()) print('Problem: ' + problem.get_name()) diff --git a/examples/multiobjective/smpsorp_standard_settings.py b/examples/multiobjective/smpsorp_zdt1.py similarity index 88% rename from examples/multiobjective/smpsorp_standard_settings.py rename to examples/multiobjective/smpsorp_zdt1.py index c19741d4..bd137237 100644 --- a/examples/multiobjective/smpsorp_standard_settings.py +++ b/examples/multiobjective/smpsorp_zdt1.py @@ -37,9 +37,6 @@ def points_to_solutions(points): leaders=archives_with_reference_points ) - observer = VisualizerObserver() - algorithm.observable.register(observer=observer) - progress_bar = ProgressBarObserver(step=swarm_size, maximum=25000) algorithm.observable.register(observer=progress_bar) @@ -48,9 +45,9 @@ def points_to_solutions(points): # Plot frontier to file pareto_front = ScatterPlot(plot_title='SMPSORP-ZDT1', axis_labels=problem.obj_labels) - pareto_front.plot(front, reference_front=problem.reference_front, show=False) - pareto_front.add_data(points_to_solutions(reference_points), legend='reference points') - pareto_front.show() + pareto_front.plot(front, reference_front=problem.reference_front) + pareto_front.update(points_to_solutions(reference_points), legend='reference points') + pareto_front.save(filename='SMPSORP-ZDT1') print('Algorithm (continuous problem): ' + algorithm.get_name()) print('Problem: ' + problem.get_name()) diff --git a/jmetal/problem/multiobjective/dtlz.py b/jmetal/problem/multiobjective/dtlz.py index 90d9cab1..00ac7c42 100644 --- a/jmetal/problem/multiobjective/dtlz.py +++ b/jmetal/problem/multiobjective/dtlz.py @@ -28,7 +28,7 @@ def __init__(self, number_of_variables: int = 7, number_of_objectives=3, rf_path self.number_of_constraints = 0 self.obj_directions = [self.MINIMIZE] * number_of_objectives - self.obj_labels = ['f'] * number_of_objectives + self.obj_labels = ['$ f_{} $'.format(i) for i in range(number_of_objectives)] self.lower_bound = self.number_of_variables * [0.0] self.upper_bound = self.number_of_variables * [1.0] @@ -72,7 +72,7 @@ def __init__(self, number_of_variables: int = 12, number_of_objectives=3, rf_pat self.number_of_constraints = 0 self.obj_directions = [self.MINIMIZE] * number_of_objectives - self.obj_labels = ['f'] * number_of_objectives + self.obj_labels = ['$ f_{} $'.format(i) for i in range(number_of_objectives)] self.lower_bound = self.number_of_variables * [0.0] self.upper_bound = self.number_of_variables * [1.0] diff --git a/jmetal/util/graphic.py b/jmetal/util/graphic.py index ac1723dc..69ff361a 100644 --- a/jmetal/util/graphic.py +++ b/jmetal/util/graphic.py @@ -5,7 +5,7 @@ import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D from plotly import graph_objs as go -from plotly.offline import plot +from plotly.offline import plot, offline from pandas import DataFrame jMetalPyLogger = logging.getLogger('jMetalPy') @@ -198,62 +198,67 @@ class ScatterPlot(Plot): def __init__(self, plot_title: str, axis_labels: list = None): """ Creates a new :class:`ScatterPlot` instance. Suitable for problems with 2 or more objectives. - :param plot_title: Title of the diagram. + :param plot_title: Title of the graph. :param axis_labels: List of axis labels. """ super(ScatterPlot, self).__init__(plot_title, axis_labels) self.figure: go.Figure = None self.layout = None - self.data = None + self.data = [] - def plot(self, front: List[S], reference_front: List[S] = None, show: bool = True) -> None: + def plot(self, front: List[S], reference_front: List[S] = None, normalize: bool = False) -> None: """ Plot a front of solutions (2D, 3D or parallel coordinates). :param front: List of solutions. :param reference_front: Reference solution list (if any). - :param show: If True, show and save (file `front.html`) the final diagram (default to True). """ + :param normalize: Normalize the input front between 0 and 1 (for problems with more than 3 objectives). """ self.__initialize() - objectives = self.get_objectives(front) - self.data = [self.__generate_trace(objectives, legend='front')] - if reference_front: objectives = self.get_objectives(reference_front) - self.data.append( - self.__generate_trace( - objectives, legend='reference', - symbol='diamond-open', size=3, opacity=0.4, color='rgb(2, 130, 242)')) + trace = self.__generate_trace(objectives=objectives, legend='reference front', normalize=normalize, + color='rgb(2, 130, 242)') + self.data.append(trace) - self.figure = go.Figure(data=self.data, layout=self.layout) + objectives = self.get_objectives(front) + trace = self.__generate_trace(objectives=objectives, legend='front', normalize=normalize, + symbol='diamond-open') + self.data.append(trace) - if show: - plot(self.figure, filename='front.html') + self.figure = go.Figure(data=self.data, layout=self.layout) - def add_data(self, data: List[S], **kwargs) -> None: - """ Update an already created plot with new data. + def update(self, data: List[S], normalize: bool = False, legend: str = '') -> None: + """ Update an already created graph with new data. :param data: List of solutions to be included. - :param kwargs: Optional values for `styling markers `_. """ + :param legend: Legend to be included. + :param normalize: Normalize the input front between 0 and 1 (for problems with more than 3 objectives). """ if self.figure is None: jMetalPyLogger.warning('Plot must be initialized first.') - self.plot(data, None, show=False) + self.plot(data, reference_front=None, normalize=normalize) return objectives = self.get_objectives(data) - new_data = self.__generate_trace(objectives=objectives, size=5, color='rgb(255, 170, 0)', **kwargs) - + new_data = self.__generate_trace(objectives=objectives, legend=legend, normalize=normalize, + color='rgb(255, 170, 0)') self.data.append(new_data) + self.figure = go.Figure(data=self.data, layout=self.layout) - def show(self) -> None: - plot(self.figure, filename='front') + def save(self, filename: str = 'front', show: bool = False) -> None: + """ Save the graph. """ + plot(self.figure, filename=filename + '.html', auto_open=show, show_link=False) + + def export(self, include_plotlyjs: bool = False) -> str: + """ Export as a `div` for embedding the graph in an HTML file. """ + return plot(self.figure, output_type='div', include_plotlyjs=include_plotlyjs) def __initialize(self): - """ Initialize the plot for the first time. """ - jMetalPyLogger.info('Generating plot') + """ Initialize the graph for the first time. """ + jMetalPyLogger.info('Generating graph') self.layout = go.Layout( - margin=dict(l=100, r=100, b=100, t=100), + margin=dict(l=150, r=150, b=150, t=150), title=self.plot_title, scene=dict( xaxis=dict(title=self.axis_labels[0:1][0] if self.axis_labels[0:1] else None), @@ -269,19 +274,21 @@ def __initialize(self): )] ) - @staticmethod - def __generate_trace(objectives: DataFrame, legend: str = '', **kwargs): + def __generate_trace(self, objectives: DataFrame, legend: str = '', normalize: bool=False, **kwargs): number_of_objectives = objectives.shape[1] + if normalize: + objectives = (objectives - objectives.min()) / (objectives.max() - objectives.min()) + marker = dict( color='rgb(127, 127, 127)', size=3, - symbol='circle', + symbol='circle-dot', line=dict( color='rgb(204, 204, 204)', width=1 ), - opacity=1.0 + opacity=0.8 ) marker.update(**kwargs) @@ -307,7 +314,7 @@ def __generate_trace(objectives: DataFrame, legend: str = '', **kwargs): for column in objectives: dimensions.append( dict(range=[0, 1], - label='O', + label=self.axis_labels[column:column+1][0] if self.axis_labels[column:column+1] else None, values=objectives[column]) ) From db8f42af764adab0143abca6e787ee59c2741fb6 Mon Sep 17 00:00:00 2001 From: benhid Date: Wed, 18 Jul 2018 11:12:34 +0200 Subject: [PATCH 08/26] Updated docs --- docs/source/examples/nsgaii.rst | 4 ++-- docs/source/examples/smpso.rst | 6 +++--- jmetal/component/observer.py | 16 +++++++++++++--- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/docs/source/examples/nsgaii.rst b/docs/source/examples/nsgaii.rst index 7c9d1c8c..e656d79c 100644 --- a/docs/source/examples/nsgaii.rst +++ b/docs/source/examples/nsgaii.rst @@ -39,7 +39,7 @@ NSGA-II with plotting .. raw:: html -
+
.. code-block:: python @@ -74,7 +74,7 @@ NSGA-II with plotting clearInterval(waitForPlotly); }}, 250 ); -
+
NSGA-II stopping by time ------------------------------------ diff --git a/docs/source/examples/smpso.rst b/docs/source/examples/smpso.rst index 38be5010..d432245a 100644 --- a/docs/source/examples/smpso.rst +++ b/docs/source/examples/smpso.rst @@ -38,7 +38,7 @@ SMPSO with standard settings .. raw:: html -
+
.. code-block:: python @@ -49,7 +49,7 @@ SMPSO with standard settings .. raw:: html -
+
SMPSO/RP with standard settings ------------------------------------ @@ -100,4 +100,4 @@ SMPSO/RP with standard settings .. raw:: html -
\ No newline at end of file +
\ No newline at end of file diff --git a/jmetal/component/observer.py b/jmetal/component/observer.py index f6783303..4e791089 100644 --- a/jmetal/component/observer.py +++ b/jmetal/component/observer.py @@ -3,7 +3,6 @@ from tqdm import tqdm -from jmetal.core.problem import Problem from jmetal.util.graphic import ScatterStreaming from jmetal.core.observable import Observer from jmetal.util.solution_list_output import SolutionList @@ -22,6 +21,11 @@ class ProgressBarObserver(Observer): def __init__(self, step: int, maximum: int, desc: str= 'Progress') -> None: + """ Show a smart progress meter with the number of evaluations and computing time. + + :param step: Initial counter value. + :param maximum: Number of expected iterations. + :param desc: Prefix for the progressbar. """ self.progress_bar = tqdm(total=maximum, initial=step, ascii=True, desc=desc) self.progress = step self.step = step @@ -38,6 +42,9 @@ def update(self, *args, **kwargs): class BasicAlgorithmObserver(Observer): def __init__(self, frequency: float = 1.0) -> None: + """ Show the number of evaluations, best fitness and computing time. + + :param frequency: Display frequency. """ self.display_frequency = frequency def update(self, *args, **kwargs): @@ -56,15 +63,18 @@ def update(self, *args, **kwargs): class WriteFrontToFileObserver(Observer): def __init__(self, output_directory) -> None: + """ Write function values of the front into files. + + :param output_directory: Output directory. Each front will be saved on a file `FUN.x`. """ self.counter = 0 self.directory = output_directory if os.path.isdir(self.directory): - jMetalPyLogger.warning('Directory {0} exists. Removing contents.'.format(self.directory)) + jMetalPyLogger.warning('Directory {} exists. Removing contents.'.format(self.directory)) for file in os.listdir(self.directory): os.remove('{0}/{1}'.format(self.directory, file)) else: - jMetalPyLogger.warning('Directory {0} does not exist. Creating it.'.format(self.directory)) + jMetalPyLogger.warning('Directory {} does not exist. Creating it.'.format(self.directory)) os.mkdir(self.directory) def update(self, *args, **kwargs): From 0812f1b3d2e4e98127f456fb2b1423cffd88da6d Mon Sep 17 00:00:00 2001 From: benhid Date: Wed, 18 Jul 2018 14:44:27 +0200 Subject: [PATCH 09/26] Solutions can be selected to show information --- docs/source/examples/nsgaii.rst | 14 ++-- docs/source/examples/smpso.rst | 14 ++-- examples/experiment/NSGAII-SMPSO for ZDT1.py | 6 +- examples/multiobjective/nsgaii_dtlz1.py | 6 +- examples/multiobjective/nsgaii_zdt1.py | 6 +- examples/multiobjective/smpso_dtlz1_5.py | 10 +-- examples/multiobjective/smpsorp_zdt1.py | 6 +- jmetal/__init__.py | 2 +- jmetal/util/__init__.py | 4 +- jmetal/util/graphic.py | 79 +++++++++++++++----- 10 files changed, 95 insertions(+), 52 deletions(-) diff --git a/docs/source/examples/nsgaii.rst b/docs/source/examples/nsgaii.rst index e656d79c..3259a6b2 100644 --- a/docs/source/examples/nsgaii.rst +++ b/docs/source/examples/nsgaii.rst @@ -9,7 +9,7 @@ Common imports for these examples: from jmetal.operator import Polynomial, SBX, BinaryTournamentSelection from jmetal.component import RankingAndCrowdingDistanceComparator - from jmetal.util import ScatterPlot + from jmetal.util import FrontPlot NSGA-II with plotting ------------------------------------ @@ -32,14 +32,14 @@ NSGA-II with plotting algorithm.run() front = algorithm.get_result() - pareto_front = ScatterPlot(plot_title='NSGAII-ZDT1', axis_labels=problem.obj_labels) + pareto_front = FrontPlot(plot_title='NSGAII-ZDT1', axis_labels=problem.obj_labels) pareto_front.plot(front, reference_front=problem.reference_front) - pareto_front.save(filename='NSGAII-ZDT1') + pareto_front.to_html(filename='NSGAII-ZDT1') .. raw:: html -
+
.. code-block:: python @@ -59,9 +59,9 @@ NSGA-II with plotting algorithm.run() front = algorithm.get_result() - pareto_front = ScatterPlot(plot_title='NSGAII-DTLZ1', axis_labels=problem.obj_labels) + pareto_front = FrontPlot(plot_title='NSGAII-DTLZ1', axis_labels=problem.obj_labels) pareto_front.plot(front, reference_front=problem.reference_front) - pareto_front.save(filename='NSGAII-DTLZ1') + pareto_front.to_html(filename='NSGAII-DTLZ1') .. raw:: html @@ -74,7 +74,7 @@ NSGA-II with plotting clearInterval(waitForPlotly); }}, 250 ); -
+
NSGA-II stopping by time ------------------------------------ diff --git a/docs/source/examples/smpso.rst b/docs/source/examples/smpso.rst index d432245a..8e9aec39 100644 --- a/docs/source/examples/smpso.rst +++ b/docs/source/examples/smpso.rst @@ -7,7 +7,7 @@ Common imports for these examples: from jmetal.operator import Polynomial - from jmetal.util import ScatterPlot + from jmetal.util import FrontPlot SMPSO with standard settings ------------------------------------ @@ -31,9 +31,9 @@ SMPSO with standard settings algorithm.run() front = algorithm.get_result() - pareto_front = ScatterPlot(plot_title='SMPSO-DTLZ1-5', axis_labels=problem.obj_labels) + pareto_front = FrontPlot(plot_title='SMPSO-DTLZ1-5', axis_labels=problem.obj_labels) pareto_front.plot(front, reference_front=problem.reference_front) - pareto_front.save(filename='SMPSO-DTLZ1-5') + pareto_front.to_html(filename='SMPSO-DTLZ1-5') .. raw:: html @@ -49,7 +49,7 @@ SMPSO with standard settings .. raw:: html -
+
SMPSO/RP with standard settings ------------------------------------ @@ -92,12 +92,12 @@ SMPSO/RP with standard settings algorithm.run() front = algorithm.get_result() - pareto_front = ScatterPlot(plot_title='SMPSORP-ZDT1', axis_labels=problem.obj_labels) + pareto_front = FrontPlot(plot_title='SMPSORP-ZDT1', axis_labels=problem.obj_labels) pareto_front.plot(front, reference_front=problem.reference_front) pareto_front.update(points_to_solutions(reference_points), legend='reference points') - pareto_front.save(filename='SMPSORP-ZDT1') + pareto_front.to_html(filename='SMPSORP-ZDT1') .. raw:: html -
\ No newline at end of file +
\ No newline at end of file diff --git a/examples/experiment/NSGAII-SMPSO for ZDT1.py b/examples/experiment/NSGAII-SMPSO for ZDT1.py index 17697176..31b60e14 100644 --- a/examples/experiment/NSGAII-SMPSO for ZDT1.py +++ b/examples/experiment/NSGAII-SMPSO for ZDT1.py @@ -1,10 +1,8 @@ from jmetal.algorithm import NSGAII, SMPSO -from jmetal.component.archive import CrowdingDistanceArchive -from jmetal.component.comparator import RankingAndCrowdingDistanceComparator +from jmetal.component import CrowdingDistanceArchive, RankingAndCrowdingDistanceComparator, HyperVolume from jmetal.operator import NullMutation, SBX, BinaryTournamentSelection from jmetal.problem import ZDT1, ZDT2 -from jmetal.component.quality_indicator import HyperVolume -from jmetal.util.laboratory import experiment, display +from jmetal.util import experiment, display algorithm = [ (NSGAII, {'population_size': 100, 'max_evaluations': 25000, 'mutation': NullMutation(), 'crossover': SBX(1.0, 20), diff --git a/examples/multiobjective/nsgaii_dtlz1.py b/examples/multiobjective/nsgaii_dtlz1.py index 3fbb6f32..a40aed44 100644 --- a/examples/multiobjective/nsgaii_dtlz1.py +++ b/examples/multiobjective/nsgaii_dtlz1.py @@ -2,7 +2,7 @@ from jmetal.problem import DTLZ1 from jmetal.operator import SBX, Polynomial, BinaryTournamentSelection from jmetal.component import ProgressBarObserver, RankingAndCrowdingDistanceComparator -from jmetal.util import ScatterPlot, SolutionList +from jmetal.util import FrontPlot, SolutionList if __name__ == '__main__': @@ -24,9 +24,9 @@ front = algorithm.get_result() # Plot frontier to file - pareto_front = ScatterPlot(plot_title='NSGAII-DTLZ1', axis_labels=problem.obj_labels) + pareto_front = FrontPlot(plot_title='NSGAII-DTLZ1', axis_labels=problem.obj_labels) pareto_front.plot(front, reference_front=problem.reference_front) - pareto_front.save(filename='NSGAII-DTLZ1') + pareto_front.to_html(filename='NSGAII-DTLZ1') # Save variables to file SolutionList.print_function_values_to_file(front, 'FUN.NSGAII.DTLZ1') diff --git a/examples/multiobjective/nsgaii_zdt1.py b/examples/multiobjective/nsgaii_zdt1.py index dc684e6a..df08db8c 100644 --- a/examples/multiobjective/nsgaii_zdt1.py +++ b/examples/multiobjective/nsgaii_zdt1.py @@ -2,7 +2,7 @@ from jmetal.problem import ZDT1 from jmetal.operator import SBX, Polynomial, BinaryTournamentSelection from jmetal.component import ProgressBarObserver, RankingAndCrowdingDistanceComparator -from jmetal.util import ScatterPlot, SolutionList +from jmetal.util import FrontPlot, SolutionList if __name__ == '__main__': @@ -24,9 +24,9 @@ front = algorithm.get_result() # Plot frontier to file - pareto_front = ScatterPlot(plot_title='NSGAII-ZDT1', axis_labels=problem.obj_labels) + pareto_front = FrontPlot(plot_title='NSGAII-ZDT1', axis_labels=problem.obj_labels) pareto_front.plot(front, reference_front=problem.reference_front) - pareto_front.save(filename='NSGAII-ZDT1') + pareto_front.to_html(filename='NSGAII-ZDT1') # Save variables to file SolutionList.print_function_values_to_file(front, 'FUN.NSGAII.ZDT1') diff --git a/examples/multiobjective/smpso_dtlz1_5.py b/examples/multiobjective/smpso_dtlz1_5.py index 13b0afb3..c98fac9e 100644 --- a/examples/multiobjective/smpso_dtlz1_5.py +++ b/examples/multiobjective/smpso_dtlz1_5.py @@ -2,7 +2,7 @@ from jmetal.problem import DTLZ1 from jmetal.operator import Polynomial from jmetal.component import ProgressBarObserver, CrowdingDistanceArchive -from jmetal.util import ScatterPlot, SolutionList +from jmetal.util import FrontPlot, SolutionList if __name__ == '__main__': @@ -23,13 +23,13 @@ front = algorithm.get_result() # Plot frontier to file - pareto_front = ScatterPlot(plot_title='SMPSO-DTLZ1-5', axis_labels=problem.obj_labels) + pareto_front = FrontPlot(plot_title='SMPSO-DTLZ1-5', axis_labels=problem.obj_labels) pareto_front.plot(front, reference_front=problem.reference_front) - pareto_front.save(filename='SMPSO-DTLZ1-5') + pareto_front.to_html(filename='SMPSO-DTLZ1-5') - pareto_front = ScatterPlot(plot_title='SMPSO-DTLZ1-5-norm', axis_labels=problem.obj_labels) + pareto_front = FrontPlot(plot_title='SMPSO-DTLZ1-5-norm', axis_labels=problem.obj_labels) pareto_front.plot(front, reference_front=problem.reference_front, normalize=True) - pareto_front.save(filename='SMPSO-DTLZ1-5-norm') + pareto_front.to_html(filename='SMPSO-DTLZ1-5-norm') # Save variables to file SolutionList.print_function_values_to_file(front, 'FUN.SMPSO.DTLZ1-5') diff --git a/examples/multiobjective/smpsorp_zdt1.py b/examples/multiobjective/smpsorp_zdt1.py index bd137237..1e157083 100644 --- a/examples/multiobjective/smpsorp_zdt1.py +++ b/examples/multiobjective/smpsorp_zdt1.py @@ -2,7 +2,7 @@ from jmetal.component import ProgressBarObserver, VisualizerObserver, CrowdingDistanceArchiveWithReferencePoint from jmetal.problem import ZDT1 from jmetal.operator import Polynomial -from jmetal.util import ScatterPlot +from jmetal.util import FrontPlot def points_to_solutions(points): @@ -44,10 +44,10 @@ def points_to_solutions(points): front = algorithm.get_result() # Plot frontier to file - pareto_front = ScatterPlot(plot_title='SMPSORP-ZDT1', axis_labels=problem.obj_labels) + pareto_front = FrontPlot(plot_title='SMPSORP-ZDT1', axis_labels=problem.obj_labels) pareto_front.plot(front, reference_front=problem.reference_front) pareto_front.update(points_to_solutions(reference_points), legend='reference points') - pareto_front.save(filename='SMPSORP-ZDT1') + pareto_front.to_html(filename='SMPSORP-ZDT1') print('Algorithm (continuous problem): ' + algorithm.get_name()) print('Problem: ' + problem.get_name()) diff --git a/jmetal/__init__.py b/jmetal/__init__.py index 213468e7..0dcbe600 100644 --- a/jmetal/__init__.py +++ b/jmetal/__init__.py @@ -12,7 +12,7 @@ # create a file handler file_handler = logging.FileHandler('jmetalpy.log', delay=True) -file_handler.setLevel(logging.INFO) +file_handler.setLevel(logging.DEBUG) stream_handler = logging.StreamHandler() # create a logging format diff --git a/jmetal/util/__init__.py b/jmetal/util/__init__.py index d04ff292..8b07c4af 100644 --- a/jmetal/util/__init__.py +++ b/jmetal/util/__init__.py @@ -1,9 +1,9 @@ -from .graphic import ScatterPlot, ScatterStreaming +from .graphic import FrontPlot, ScatterStreaming from .laboratory import experiment, display from .solution_list_output import SolutionList __all__ = [ - 'ScatterPlot', 'ScatterStreaming', + 'FrontPlot', 'ScatterStreaming', 'experiment', 'display', 'SolutionList' ] diff --git a/jmetal/util/graphic.py b/jmetal/util/graphic.py index 69ff361a..665598d2 100644 --- a/jmetal/util/graphic.py +++ b/jmetal/util/graphic.py @@ -1,12 +1,13 @@ import logging from abc import ABCMeta +from string import Template from typing import TypeVar, List import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D from plotly import graph_objs as go -from plotly.offline import plot, offline -from pandas import DataFrame +from plotly.offline import plot +from pandas import DataFrame, merge jMetalPyLogger = logging.getLogger('jMetalPy') S = TypeVar('S') @@ -193,14 +194,14 @@ def __pick_handler(self, front: List[S], event): return True -class ScatterPlot(Plot): +class FrontPlot(Plot): def __init__(self, plot_title: str, axis_labels: list = None): """ Creates a new :class:`ScatterPlot` instance. Suitable for problems with 2 or more objectives. :param plot_title: Title of the graph. :param axis_labels: List of axis labels. """ - super(ScatterPlot, self).__init__(plot_title, axis_labels) + super(FrontPlot, self).__init__(plot_title, axis_labels) self.figure: go.Figure = None self.layout = None @@ -221,7 +222,8 @@ def plot(self, front: List[S], reference_front: List[S] = None, normalize: bool self.data.append(trace) objectives = self.get_objectives(front) - trace = self.__generate_trace(objectives=objectives, legend='front', normalize=normalize, + metadata = list(solution.__str__() for solution in front) + trace = self.__generate_trace(objectives=objectives, metadata=metadata, legend='front', normalize=normalize, symbol='diamond-open') self.data.append(trace) @@ -245,13 +247,44 @@ def update(self, data: List[S], normalize: bool = False, legend: str = '') -> No self.figure = go.Figure(data=self.data, layout=self.layout) - def save(self, filename: str = 'front', show: bool = False) -> None: - """ Save the graph. """ - plot(self.figure, filename=filename + '.html', auto_open=show, show_link=False) - - def export(self, include_plotlyjs: bool = False) -> str: - """ Export as a `div` for embedding the graph in an HTML file. """ - return plot(self.figure, output_type='div', include_plotlyjs=include_plotlyjs) + def to_html(self, filename: str='front') -> None: + """ Export the graph to an interactive HTML (solutions can be selected to show some metadata). + + :param filename: Output file name. """ + html_string = ''' + + + + + + + + ''' + self.__export(include_plotlyjs=False) + ''' + + + ''' + + with open(filename + '.html', 'w') as outf: + outf.write(html_string) def __initialize(self): """ Initialize the graph for the first time. """ @@ -271,10 +304,12 @@ def __initialize(self): x=0, y=1.05, sizex=0.1, sizey=0.1, xanchor="left", yanchor="bottom" - )] + )], + hovermode='closest' ) - def __generate_trace(self, objectives: DataFrame, legend: str = '', normalize: bool=False, **kwargs): + def __generate_trace(self, objectives: DataFrame, metadata: list = None, legend: str = '', normalize: bool = False, + **kwargs): number_of_objectives = objectives.shape[1] if normalize: @@ -298,7 +333,8 @@ def __generate_trace(self, objectives: DataFrame, legend: str = '', normalize: b y=objectives[1], mode='markers', marker=marker, - name=legend + name=legend, + customdata=metadata ) elif number_of_objectives == 3: trace = go.Scatter3d( @@ -307,7 +343,8 @@ def __generate_trace(self, objectives: DataFrame, legend: str = '', normalize: b z=objectives[2], mode='markers', marker=marker, - name=legend + name=legend, + customdata=metadata ) else: dimensions = list() @@ -321,7 +358,15 @@ def __generate_trace(self, objectives: DataFrame, legend: str = '', normalize: b trace = go.Parcoords( line=dict(color='blue'), dimensions=dimensions, - name=legend + name=legend, ) return trace + + def __export(self, include_plotlyjs: bool = False) -> str: + """ Export as a `div` for embedding the graph in an HTML file. """ + return plot(self.figure, output_type='div', include_plotlyjs=include_plotlyjs, show_link=False) + + def __save(self, filename: str = 'front', show: bool = False) -> None: + """ Save the graph. """ + plot(self.figure, filename=filename + '.html', auto_open=show, show_link=False) From 368143cd86c2559bef25a9df47955d89d3fe1bdc Mon Sep 17 00:00:00 2001 From: benhid Date: Thu, 19 Jul 2018 14:41:53 +0200 Subject: [PATCH 10/26] No longer useful --- CONTRIBUTING.md | 27 --------------------------- 1 file changed, 27 deletions(-) delete mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 6afbb71f..00000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,27 +0,0 @@ -# Development guidelines - -Contributions to the jMetalPy project are welcome. Please, take into account the following guidelines (all developers should follow these guidelines): - -- [Git WorkFlow](resources/pages/workflow_git.md) -- [Follow style guide for python code: PEP8](resources/pages/code_style.md) -- [Object-oriented programming](resources/pages/poo.md) -- [Incorporate the new features of Python 3.5](resources/pages/features_python3.md) -- [Respect the initial structure](resources/pages/project_structure.md) -- [How to create auto documentation using compatible code](resources/pages/auto_doc.md) -- [Performance analysis of Python](resources/pages/profiling.md) - -# Documentation - -To generate the documentation, install [Sphinx](http://www.sphinx-doc.org/en/master/) by running: - -```bash -$ pip install sphinx -$ pip install sphinx_rtd_theme -``` - -And then `cd` to `/docs` and run: - -```bash -$ sphinx-apidoc -f -o source/ ../jmetal/ -$ make html -``` \ No newline at end of file From b26fdd3ef067f6bd124afd251a23378967ebc458 Mon Sep 17 00:00:00 2001 From: benhid Date: Thu, 19 Jul 2018 14:42:50 +0200 Subject: [PATCH 11/26] Install requires updated --- setup.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index ee867b5a..9bc9d270 100644 --- a/setup.py +++ b/setup.py @@ -25,14 +25,14 @@ 'Programming Language :: Python :: 3.6' ], install_requires=[ + 'tqdm', 'numpy', + 'pandas', + 'plotly', 'matplotlib==2.0.2', - 'bokeh==0.12.16', - 'tqdm' ], tests_require=[ 'mockito' 'PyHamcrest', - 'pytest' ] ) From 8d30760d0c086dc57a984a09c80785c82a62a108 Mon Sep 17 00:00:00 2001 From: benhid Date: Thu, 19 Jul 2018 14:43:02 +0200 Subject: [PATCH 12/26] Refactor problem classes --- jmetal/core/problem.py | 41 ++++++++++++++++---------------- jmetal/core/test/test_problem.py | 19 --------------- jmetal/util/graphic.py | 17 +++++++------ 3 files changed, 28 insertions(+), 49 deletions(-) diff --git a/jmetal/core/problem.py b/jmetal/core/problem.py index 60e7fa34..cdf283ee 100644 --- a/jmetal/core/problem.py +++ b/jmetal/core/problem.py @@ -22,7 +22,6 @@ def __init__(self, reference_front_path: str): self.number_of_constraints: int = None self.obj_directions: List[int] = [] - self.obj_functions: List = [] self.obj_labels: List[str] = [] self.reference_front: List[S] = None @@ -71,30 +70,12 @@ def create_solution(self) -> S: :return: Solution. """ pass + @abstractmethod def evaluate(self, solution: S) -> S: """ Evaluate a solution. For any new problem inheriting from :class:`Problem`, this method should be replaced. - Otherwise, the attributes `obj_functions`, `obj_functions` and `obj_labels` must be declared: - - .. code-block:: python - - problem = FloatProblem() - - problem.obj_functions.append(lambda s: 1) - problem.obj_directions.append(Problem.MINIMIZE) - problem.obj_labels.append('Min') - - problem.obj_functions.append(lambda s: 2) - problem.obj_directions.append(Problem.MAXIMIZE) - problem.obj_labels.append('Max') :return: Evaluated solution. """ - for ith, fnc in enumerate(self.obj_functions): - if self.obj_directions[ith] == self.MAXIMIZE: - solution.objectives[ith] = -1.0 * fnc(solution) - else: - solution.objectives[ith] = fnc(solution) - - return solution + pass def evaluate_constraints(self, solution: S): pass @@ -106,16 +87,24 @@ def get_name(self) -> str: class BinaryProblem(Problem[BinarySolution]): """ Class representing binary problems. """ + __metaclass__ = ABCMeta + def __init__(self, rf_path: str = None): super(BinaryProblem, self).__init__(reference_front_path=rf_path) def create_solution(self) -> BinarySolution: pass + @abstractmethod + def evaluate(self, solution: BinarySolution) -> BinarySolution: + pass + class FloatProblem(Problem[FloatSolution]): """ Class representing float problems. """ + __metaclass__ = ABCMeta + def __init__(self, rf_path: str = None): super(FloatProblem, self).__init__(reference_front_path=rf_path) self.lower_bound = None @@ -129,10 +118,16 @@ def create_solution(self) -> FloatSolution: return new_solution + @abstractmethod + def evaluate(self, solution: FloatSolution) -> FloatSolution: + pass + class IntegerProblem(Problem[IntegerSolution]): """ Class representing integer problems. """ + __metaclass__ = ABCMeta + def __init__(self, rf_path: str = None): super(IntegerProblem, self).__init__(reference_front_path=rf_path) self.lower_bound = None @@ -150,3 +145,7 @@ def create_solution(self) -> IntegerSolution: for i in range(self.number_of_variables)] return new_solution + + @abstractmethod + def evaluate(self, solution: IntegerSolution) -> IntegerSolution: + pass diff --git a/jmetal/core/test/test_problem.py b/jmetal/core/test/test_problem.py index 8c538b5b..c77be09f 100644 --- a/jmetal/core/test/test_problem.py +++ b/jmetal/core/test/test_problem.py @@ -10,9 +10,6 @@ class DummyFloatProblem(FloatProblem): def evaluate(self, solution: FloatSolution) -> FloatSolution: pass - class DummyLambdaFloatProblem(FloatProblem): - pass - def test_should_default_constructor_create_a_valid_problem(self) -> None: problem = self.DummyFloatProblem() problem.number_of_variables = 1 @@ -26,22 +23,6 @@ def test_should_default_constructor_create_a_valid_problem(self) -> None: self.assertEqual([-1], problem.lower_bound) self.assertEqual([1], problem.upper_bound) - def test_should_default_constructor_create_a_valid_problem_with_lambda(self) -> None: - problem = self.DummyLambdaFloatProblem() - problem.number_of_variables = 1 - problem.number_of_objectives = 2 - problem.number_of_constraints = 0 - problem.lower_bound = [-1.0] - problem.upper_bound = [1.0] - problem.obj_directions = [problem.MINIMIZE, problem.MAXIMIZE] - problem.obj_functions.append(lambda s: s.number_of_variables * 2) - problem.obj_functions.append(lambda s: s.number_of_variables * 3) - - solution = FloatSolution(2, 2, 0, [], []) - problem.evaluate(solution) - - self.assertEqual([4, -6], solution.objectives) - def test_should_create_solution_create_a_valid_solution(self) -> None: problem = self.DummyFloatProblem() problem.number_of_variables = 2 diff --git a/jmetal/util/graphic.py b/jmetal/util/graphic.py index 665598d2..622cdca3 100644 --- a/jmetal/util/graphic.py +++ b/jmetal/util/graphic.py @@ -259,7 +259,7 @@ def to_html(self, filename: str='front') -> None: - ''' + self.__export(include_plotlyjs=False) + ''' + ''' + self.export(include_plotlyjs=False) + ''' -
+
SMPSO/RP with standard settings ------------------------------------ diff --git a/docs/source/index.rst b/docs/source/index.rst index 3935d6b1..812705c2 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -33,3 +33,15 @@ Via Github: $ git clone https://github.com/jMetal/jMetalPy.git $ pip install -r requirements.txt $ python setup.py install + +Features +------------------------ +The current release of jMetalPy (v0.5.1) contains the following components: + +* Algorithms: random search, NSGA-II, SMPSO, SMPSO/RP +* Problems: ZDT1-6, DTLZ1-2, unconstrained (Kursawe, Fonseca, Schaffer, Viennet2), constrained (Srinivas, Tanaka). +* Encodings: real, binary +* Operators: selection (binary tournament, ranking and crowding distance, random, nary random, best solution), crossover (single-point, SBX), mutation (bit-blip, polynomial, uniform, random) +* Quality indicators: hypervolume +* Density estimator: crowding distance +* Graphics: Pareto front plotting (2 or more objectives) diff --git a/jmetal/algorithm/multiobjective/nsgaii.py b/jmetal/algorithm/multiobjective/nsgaii.py index 5f8d99cc..746c4b4c 100644 --- a/jmetal/algorithm/multiobjective/nsgaii.py +++ b/jmetal/algorithm/multiobjective/nsgaii.py @@ -36,14 +36,14 @@ def __init__(self, NSGA-II is a genetic algorithm (GA), i.e. it belongs to the evolutionary algorithms (EAs) family. The implementation of NSGA-II provided in jMetalPy follows the evolutionary - algorithm template described in the algorithm module (:py:mod:`algorithm`). + algorithm template described in the algorithm module (:py:mod:`jmetal.core.algorithm`). :param problem: The problem to solve. :param population_size: Size of the population. :param max_evaluations: Maximum number of evaluations/iterations. - :param mutation: Mutation operator (see :py:mod:`mutation`). - :param crossover: Crossover operator (see :py:mod:`crosover`). - :param selection: Selection operator (see :py:mod:`selection`). + :param mutation: Mutation operator (see :py:mod:`jmetal.operator.mutation`). + :param crossover: Crossover operator (see :py:mod:`jmetal.operator.crossover`). + :param selection: Selection operator (see :py:mod:`jmetal.operator.selection`). :param evaluator: An evaluator object to evaluate the individuals of the population. """ super(NSGAII, self).__init__( diff --git a/jmetal/operator/crossover.py b/jmetal/operator/crossover.py index 5967d125..ff6e4be3 100644 --- a/jmetal/operator/crossover.py +++ b/jmetal/operator/crossover.py @@ -6,7 +6,7 @@ from jmetal.core.solution import Solution, FloatSolution, BinarySolution """ -.. module:: crosover +.. module:: crossover :platform: Unix, Windows :synopsis: Module implementing crossover operators. diff --git a/jmetal/util/graphic.py b/jmetal/util/graphic.py index 622cdca3..bcc3e27f 100644 --- a/jmetal/util/graphic.py +++ b/jmetal/util/graphic.py @@ -247,7 +247,7 @@ def update(self, data: List[S], normalize: bool = False, legend: str = '') -> No self.figure = go.Figure(data=self.data, layout=self.layout) - def to_html(self, filename: str='front') -> None: + def to_html(self, filename: str='front') -> str: """ Export the graph to an interactive HTML (solutions can be selected to show some metadata). :param filename: Output file name. """ @@ -285,8 +285,12 @@ def to_html(self, filename: str='front') -> None: with open(filename + '.html', 'w') as outf: outf.write(html_string) + return html_string + def export(self, include_plotlyjs: bool = False) -> str: - """ Export as a `div` for embedding the graph in an HTML file. """ + """ Export as a `div` for embedding the graph in an HTML file. + + :param include_plotlyjs: If True, include plot.ly JS script (default to False). """ return plot(self.figure, output_type='div', include_plotlyjs=include_plotlyjs, show_link=False) def __initialize(self): From 010e286cd37011a46098d4177c0c21ec4d97cf20 Mon Sep 17 00:00:00 2001 From: benhid Date: Fri, 20 Jul 2018 11:04:23 +0200 Subject: [PATCH 14/26] Minor changes --- docs/source/examples/smpso.rst | 2 +- jmetal/util/graphic.py | 16 ++++++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/docs/source/examples/smpso.rst b/docs/source/examples/smpso.rst index 35511fec..7dee6bd8 100644 --- a/docs/source/examples/smpso.rst +++ b/docs/source/examples/smpso.rst @@ -44,7 +44,7 @@ SMPSO with standard settings pareto_front = ScatterPlot(plot_title='SMPSO-DTLZ1-5-norm', axis_labels=problem.obj_labels) pareto_front.plot(front, reference_front=problem.reference_front, normalize=True) - pareto_front.save(filename='SMPSO-DTLZ1-5-norm') + pareto_front.to_html(filename='SMPSO-DTLZ1-5-norm') .. raw:: html diff --git a/jmetal/util/graphic.py b/jmetal/util/graphic.py index bcc3e27f..b66d4852 100644 --- a/jmetal/util/graphic.py +++ b/jmetal/util/graphic.py @@ -13,7 +13,7 @@ S = TypeVar('S') """ -.. module:: Visualization +.. module:: visualization :platform: Unix, Windows :synopsis: Classes for plotting fronts. @@ -22,6 +22,7 @@ class Plot: + __metaclass__ = ABCMeta def __init__(self, plot_title: str, axis_labels: list): @@ -197,7 +198,7 @@ def __pick_handler(self, front: List[S], event): class FrontPlot(Plot): def __init__(self, plot_title: str, axis_labels: list = None): - """ Creates a new :class:`ScatterPlot` instance. Suitable for problems with 2 or more objectives. + """ Creates a new :class:`FrontPlot` instance. Suitable for problems with 2 or more objectives. :param plot_title: Title of the graph. :param axis_labels: List of axis labels. """ @@ -287,11 +288,18 @@ def to_html(self, filename: str='front') -> str: return html_string - def export(self, include_plotlyjs: bool = False) -> str: + def export(self, filename: str = '', include_plotlyjs: bool = False) -> str: """ Export as a `div` for embedding the graph in an HTML file. + :param filename: Output file name (if desired, default to None). :param include_plotlyjs: If True, include plot.ly JS script (default to False). """ - return plot(self.figure, output_type='div', include_plotlyjs=include_plotlyjs, show_link=False) + script = plot(self.figure, output_type='div', include_plotlyjs=include_plotlyjs, show_link=False) + + if filename: + with open(filename + '.html', 'w') as outf: + outf.write(script) + + return script def __initialize(self): """ Initialize the graph for the first time. """ From 1168530f7d470136c28bc66c0460bc39703763e4 Mon Sep 17 00:00:00 2001 From: benhid Date: Fri, 20 Jul 2018 11:21:06 +0200 Subject: [PATCH 15/26] Docs updated --- jmetal/component/quality_indicator.py | 3 +++ jmetal/util/graphic.py | 10 +++++++-- jmetal/util/laboratory.py | 31 ++++++++++++++++++++++----- 3 files changed, 37 insertions(+), 7 deletions(-) diff --git a/jmetal/component/quality_indicator.py b/jmetal/component/quality_indicator.py index 4ce33951..11794810 100644 --- a/jmetal/component/quality_indicator.py +++ b/jmetal/component/quality_indicator.py @@ -22,6 +22,9 @@ class Metric: def get_name(self) -> str: return self.__class__.__name__ + @abstractmethod + def compute(self, front: List[Solution]): + pass class HyperVolume(Metric): """ Hypervolume computation based on variant 3 of the algorithm in the paper: diff --git a/jmetal/util/graphic.py b/jmetal/util/graphic.py index b66d4852..06f1d7be 100644 --- a/jmetal/util/graphic.py +++ b/jmetal/util/graphic.py @@ -33,6 +33,10 @@ def __init__(self, plot_title: str, axis_labels: list): @staticmethod def get_objectives(front: List[S]) -> DataFrame: + """ Get objectives for each solution of the front. + + :param front: List of solutions. + :return: Pandas dataframe with one column for each objective and one row for each solution. """ if front is None: raise Exception('Front is none!') @@ -251,7 +255,8 @@ def update(self, data: List[S], normalize: bool = False, legend: str = '') -> No def to_html(self, filename: str='front') -> str: """ Export the graph to an interactive HTML (solutions can be selected to show some metadata). - :param filename: Output file name. """ + :param filename: Output file name. + :return: Script as string. """ html_string = ''' @@ -292,7 +297,8 @@ def export(self, filename: str = '', include_plotlyjs: bool = False) -> str: """ Export as a `div` for embedding the graph in an HTML file. :param filename: Output file name (if desired, default to None). - :param include_plotlyjs: If True, include plot.ly JS script (default to False). """ + :param include_plotlyjs: If True, include plot.ly JS script (default to False). + :return: Script as string. """ script = plot(self.figure, output_type='div', include_plotlyjs=include_plotlyjs, show_link=False) if filename: diff --git a/jmetal/util/laboratory.py b/jmetal/util/laboratory.py index db00fb6f..015ad895 100644 --- a/jmetal/util/laboratory.py +++ b/jmetal/util/laboratory.py @@ -12,15 +12,36 @@ """ -def experiment(algorithm_list: list, metric_list: list, problem_list: list, g_params: dict=None, m_workers: int=3): - """ :param algorithm_list: List of algorithms as Tuple(Algorithm, dic() with parameters). - :param metric_list: List of metrics. +def experiment(algorithm_list: list, metric_list: list, problem_list: list, g_params: dict = None, m_workers: int = 3): + """ Run an experiment. For example: + + .. code-block:: python + + algorithm = [ + (NSGAII, {'population_size': 100, 'max_evaluations': 25000, 'mutation': NullMutation(), 'crossover': SBX(1.0, 20), + 'selection': BinaryTournamentSelection(RankingAndCrowdingDistanceComparator())}), + (NSGAII(population_size=100, max_evaluations=25000, mutation=NullMutation(), crossover=SBX(1.0, 20), + selection=BinaryTournamentSelection(RankingAndCrowdingDistanceComparator()), problem=ZDT1()), {}), + (SMPSO, {'swarm_size': 100, 'max_evaluations': 25000, 'mutation': NullMutation(), + 'leaders': CrowdingDistanceArchive(100)}) + ] + metric = [HyperVolume(reference_point=[1, 1])] + problem = [(ZDT1, {}), (ZDT2, {})] + + results = experiment(algorithm, metric, problem) + + :param algorithm_list: List of algorithms as Tuple(Algorithm, dic() with parameters). + :param metric_list: List of metrics. Each metric should inherit from :py:class:`Metric` or, at least, contain a + method `compute`. :param problem_list: List of problems as Tuple(Problem, dic() with parameters). :param g_params: Global parameters (will override those from algorithm_list). :param m_workers: Maximum number of workers for ProcessPoolExecutor. :return: Stats. """ + if g_params is None: + g_params = {} + with ProcessPoolExecutor(max_workers=m_workers) as pool: result = dict() @@ -30,8 +51,8 @@ def experiment(algorithm_list: list, metric_list: list, problem_list: list, g_pa problem = problem(**problem_params) for a_index, (algorithm, algorithm_params) in enumerate(algorithm_list): - if g_params: - algorithm_params.update(g_params) + algorithm_params.update(g_params) + if isinstance(algorithm, type): jMetalPyLogger.debug('Algorithm {} is not instantiated by default'.format(algorithm)) algorithm_list[a_index] = (algorithm(problem=problem, **algorithm_params), {}) From 63e9886e5d214e0a2b21e9bcf710a355bded3a6b Mon Sep 17 00:00:00 2001 From: benhid Date: Fri, 20 Jul 2018 13:32:18 +0200 Subject: [PATCH 16/26] Updated travis configuration file --- .travis.yml | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4e90f15c..a3034efa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,17 +1,15 @@ language: python -python: '3.6' +python: "3.6" install: pip install -r requirements.txt +# command to run tests script: python -m unittest discover -branches: - only: - - master - - /.*/ +# deploy to pypi deploy: - provider: pypi - skip_cleanup: true - user: ajnebro - distributions: sdist bdist_wheel - password: - secure: "pJhh2ZwuDaMELZO7kmNGWa7sRaUi6s4By4GdBxf1hPjIBkB0GkOGHKxuivnWitAYLbmzpSoMcp2rHETcRiSqYTPlyovLA8A7YpY1HXIcNBrrmqnOpzc9bN3Ka90HMu0ySw2uYZgaQ2neFMm5CvD6W47IG0UUaFPYl68aZ8lEe8t7Tea7kFLU4UgXZxp3BXLTHF7xx7V29Ba5aKv2" - on: - branch: master + provider: pypi + skip_cleanup: true + user: ajnebro + distributions: sdist bdist_wheel + password: + secure: "pJhh2ZwuDaMELZO7kmNGWa7sRaUi6s4By4GdBxf1hPjIBkB0GkOGHKxuivnWitAYLbmzpSoMcp2rHETcRiSqYTPlyovLA8A7YpY1HXIcNBrrmqnOpzc9bN3Ka90HMu0ySw2uYZgaQ2neFMm5CvD6W47IG0UUaFPYl68aZ8lEe8t7Tea7kFLU4UgXZxp3BXLTHF7xx7V29Ba5aKv2" + on: + branch: master \ No newline at end of file From cebe2b76718b8ee4b140b35962f9bdcf71601217 Mon Sep 17 00:00:00 2001 From: Rod Date: Fri, 31 Aug 2018 18:40:57 -0400 Subject: [PATCH 17/26] Update __init__.py Fix problem when importing class RandomSearch for peforming an experiment --- jmetal/algorithm/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/jmetal/algorithm/__init__.py b/jmetal/algorithm/__init__.py index 839f7f76..258d9252 100644 --- a/jmetal/algorithm/__init__.py +++ b/jmetal/algorithm/__init__.py @@ -1,9 +1,10 @@ from .multiobjective.nsgaii import NSGAII from .multiobjective.smpso import SMPSO, SMPSORP from .singleobjective.evolutionaryalgorithm import ElitistEvolutionStrategy, NonElitistEvolutionStrategy +from .multiobjective.randomSearch import RandomSearch __all__ = [ 'NSGAII', 'SMPSO', 'SMPSORP', - 'ElitistEvolutionStrategy', 'NonElitistEvolutionStrategy' + 'ElitistEvolutionStrategy', 'NonElitistEvolutionStrategy','RandomSearch' ] From 83c396875d76bfa34dd1204368df8ecead19c549 Mon Sep 17 00:00:00 2001 From: benhid Date: Mon, 3 Sep 2018 12:12:23 +0200 Subject: [PATCH 18/26] Fixed sorting exception --- jmetal/component/quality_indicator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jmetal/component/quality_indicator.py b/jmetal/component/quality_indicator.py index 11794810..9892ab15 100644 --- a/jmetal/component/quality_indicator.py +++ b/jmetal/component/quality_indicator.py @@ -174,7 +174,7 @@ def _sort_by_dimension(self, nodes, i): # build a list of tuples of (point[i], node) decorated = [(node.cargo[i], node) for node in nodes] # sort by this value - decorated.sort() + decorated.sort(key=lambda n: n[0]) # write back to original list nodes[:] = [node for (_, node) in decorated] From e6606eb3b0cc0526a626edfdaa4c3b39059063e5 Mon Sep 17 00:00:00 2001 From: benhid Date: Mon, 3 Sep 2018 13:48:21 +0200 Subject: [PATCH 19/26] Examples updated --- docs/source/examples/experiment.rst | 12 ++++++++++++ examples/multiobjective/nsgaii_dtlz1.py | 4 +++- examples/multiobjective/nsgaii_zdt1.py | 4 +++- examples/multiobjective/smpsorp_zdt1.py | 4 +++- 4 files changed, 21 insertions(+), 3 deletions(-) create mode 100644 docs/source/examples/experiment.rst diff --git a/docs/source/examples/experiment.rst b/docs/source/examples/experiment.rst new file mode 100644 index 00000000..59082f04 --- /dev/null +++ b/docs/source/examples/experiment.rst @@ -0,0 +1,12 @@ +Observers +======================== + +It is possible to attach any number of observers to a jMetalPy's algorithm to retrieve information from each iteration. +For example, a basic algorithm observer will print the number of evaluations, the objectives from the best individual in the population and the computing time: + +.. code-block:: python + + basic = BasicAlgorithmObserver(frequency=1.0) + algorithm.observable.register(observer=basic) + +A full list of all available observer can be found at :py:mod:`jmetal.component.observer` module. \ No newline at end of file diff --git a/examples/multiobjective/nsgaii_dtlz1.py b/examples/multiobjective/nsgaii_dtlz1.py index a40aed44..6abec7dd 100644 --- a/examples/multiobjective/nsgaii_dtlz1.py +++ b/examples/multiobjective/nsgaii_dtlz1.py @@ -1,7 +1,7 @@ from jmetal.algorithm import NSGAII from jmetal.problem import DTLZ1 from jmetal.operator import SBX, Polynomial, BinaryTournamentSelection -from jmetal.component import ProgressBarObserver, RankingAndCrowdingDistanceComparator +from jmetal.component import ProgressBarObserver, VisualizerObserver, RankingAndCrowdingDistanceComparator from jmetal.util import FrontPlot, SolutionList @@ -18,7 +18,9 @@ ) progress_bar = ProgressBarObserver(step=100, maximum=50000) + visualizer = VisualizerObserver() algorithm.observable.register(observer=progress_bar) + algorithm.observable.register(observer=visualizer) algorithm.run() front = algorithm.get_result() diff --git a/examples/multiobjective/nsgaii_zdt1.py b/examples/multiobjective/nsgaii_zdt1.py index df08db8c..a8042b37 100644 --- a/examples/multiobjective/nsgaii_zdt1.py +++ b/examples/multiobjective/nsgaii_zdt1.py @@ -1,7 +1,7 @@ from jmetal.algorithm import NSGAII from jmetal.problem import ZDT1 from jmetal.operator import SBX, Polynomial, BinaryTournamentSelection -from jmetal.component import ProgressBarObserver, RankingAndCrowdingDistanceComparator +from jmetal.component import ProgressBarObserver, RankingAndCrowdingDistanceComparator, VisualizerObserver from jmetal.util import FrontPlot, SolutionList @@ -18,7 +18,9 @@ ) progress_bar = ProgressBarObserver(step=100, maximum=25000) + visualizer = VisualizerObserver() algorithm.observable.register(observer=progress_bar) + algorithm.observable.register(observer=visualizer) algorithm.run() front = algorithm.get_result() diff --git a/examples/multiobjective/smpsorp_zdt1.py b/examples/multiobjective/smpsorp_zdt1.py index 1e157083..6af6cb5f 100644 --- a/examples/multiobjective/smpsorp_zdt1.py +++ b/examples/multiobjective/smpsorp_zdt1.py @@ -37,8 +37,10 @@ def points_to_solutions(points): leaders=archives_with_reference_points ) - progress_bar = ProgressBarObserver(step=swarm_size, maximum=25000) + progress_bar = ProgressBarObserver(step=100, maximum=25000) + visualizer = VisualizerObserver() algorithm.observable.register(observer=progress_bar) + algorithm.observable.register(observer=visualizer) algorithm.run() front = algorithm.get_result() From 12b346493a0643e21e79c582c060e183ba1e4119 Mon Sep 17 00:00:00 2001 From: benhid Date: Mon, 3 Sep 2018 13:48:47 +0200 Subject: [PATCH 20/26] Updated --- README.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 7b2547de..3c989c1b 100644 --- a/README.md +++ b/README.md @@ -34,13 +34,14 @@ Examples of configuring and running all the included algorithms are located [in ## Features The current release of jMetalPy (v0.5.1) contains the following components: -* Algorithms: random search, NSGA-II, SMPSO, SMPSO/RP -* Problems: ZDT1-6, DTLZ1-2, unconstrained (Kursawe, Fonseca, Schaffer, Viennet2), constrained (Srinivas, Tanaka). -* Encodings: real, binary -* Operators: selection (binary tournament, ranking and crowding distance, random, nary random, best solution), crossover (single-point, SBX), mutation (bit-blip, polynomial, uniform, random) -* Quality indicators: hypervolume -* Density estimator: crowding distance -* Graphics: Pareto front plotting (2 or more objectives) +* Algorithms: random search, NSGA-II, SMPSO, SMPSO/RP. +* Benchmark problems: ZDT1-6, DTLZ1-2, unconstrained (Kursawe, Fonseca, Schaffer, Viennet2), constrained (Srinivas, Tanaka). +* Encodings: real, binary. +* Operators: selection (binary tournament, ranking and crowding distance, random, nary random, best solution), crossover (single-point, SBX), mutation (bit-blip, polynomial, uniform, random). +* Quality indicators: hypervolume. +* Density estimator: crowding distance. +* Graphics: Pareto front plotting (2 or more objectives). +* Laboratory: Experiment class for performing studies. ## License This project is licensed under the terms of the MIT - see the [LICENSE](LICENSE) file for details. From 44acbda7cdf8732a53aab9a441e17542a00ce70d Mon Sep 17 00:00:00 2001 From: benhid Date: Mon, 3 Sep 2018 13:49:49 +0200 Subject: [PATCH 21/26] New Experiment class for performing studies --- docs/source/about.rst | 3 +- docs/source/examples.rst | 1 + docs/source/examples/experiment.rst | 24 +++-- docs/source/index.rst | 4 +- examples/experiment/NSGAII-SMPSO for ZDT1.py | 23 ++-- jmetal/util/__init__.py | 4 +- jmetal/util/graphic.py | 19 ++-- jmetal/util/laboratory.py | 107 ++++++++++--------- 8 files changed, 105 insertions(+), 80 deletions(-) diff --git a/docs/source/about.rst b/docs/source/about.rst index c5482847..20e8b3a2 100644 --- a/docs/source/about.rst +++ b/docs/source/about.rst @@ -7,4 +7,5 @@ References -------------------------------- 1. J.J. Durillo, A.J. Nebro jMetal: a Java Framework for Multi-Objective Optimization. Advances in Engineering Software 42 (2011) 760-771. -2. A.J. Nebro, J.J. Durillo, M. Vergne Redesigning the jMetal Multi-Objective Optimization Framework. GECCO (Companion) 2015, pp: 1093-1100. July 2015. \ No newline at end of file +2. A.J. Nebro, J.J. Durillo, M. Vergne Redesigning the jMetal Multi-Objective Optimization Framework. GECCO (Companion) 2015, pp: 1093-1100. July 2015. +3. Nebro A.J. et al. (2018) Extending the Speed-Constrained Multi-objective PSO (SMPSO) with Reference Point Based Preference Articulation. In: Auger A., Fonseca C., Lourenço N., Machado P., Paquete L., Whitley D. (eds) Parallel Problem Solving from Nature – PPSN XV. PPSN 2018. Lecture Notes in Computer Science, vol 11101. Springer, Cham \ No newline at end of file diff --git a/docs/source/examples.rst b/docs/source/examples.rst index 6e8e04a6..9062be6e 100644 --- a/docs/source/examples.rst +++ b/docs/source/examples.rst @@ -13,3 +13,4 @@ Getting started :caption: Further configuration: examples/observer + examples/experiment diff --git a/docs/source/examples/experiment.rst b/docs/source/examples/experiment.rst index 59082f04..817de569 100644 --- a/docs/source/examples/experiment.rst +++ b/docs/source/examples/experiment.rst @@ -1,12 +1,24 @@ -Observers +Experiments ======================== -It is possible to attach any number of observers to a jMetalPy's algorithm to retrieve information from each iteration. -For example, a basic algorithm observer will print the number of evaluations, the objectives from the best individual in the population and the computing time: +This is an example of an experimental study based on solving two problems of the ZDT family with two versions of the same algorithm (NSGAII). +The hypervolume indicator is used for performance assessment. .. code-block:: python - basic = BasicAlgorithmObserver(frequency=1.0) - algorithm.observable.register(observer=basic) + # Configure the experiment + algorithm = [ + (NSGAII, {'population_size': 100, 'max_evaluations': 25000, 'mutation': NullMutation(), 'crossover': SBX(1.0, 20), + 'selection': BinaryTournamentSelection(RankingAndCrowdingDistanceComparator())}), + (NSGAII, {'population_size': 100, 'max_evaluations': 25000, 'mutation': NullMutation(), 'crossover': SBX(0.3, 20), + 'selection': BinaryTournamentSelection(RankingAndCrowdingDistanceComparator())}) + ] + problem = [(ZDT1, {}), (ZDT2, {})] -A full list of all available observer can be found at :py:mod:`jmetal.component.observer` module. \ No newline at end of file + study = Experiment(algorithm, problem, n_runs=3) + study.run() + + # Compute metrics + metric = [HyperVolume(reference_point=[1, 1])] + + study.compute_metrics(metric) diff --git a/docs/source/index.rst b/docs/source/index.rst index 812705c2..9a65b7e1 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -39,9 +39,11 @@ Features The current release of jMetalPy (v0.5.1) contains the following components: * Algorithms: random search, NSGA-II, SMPSO, SMPSO/RP -* Problems: ZDT1-6, DTLZ1-2, unconstrained (Kursawe, Fonseca, Schaffer, Viennet2), constrained (Srinivas, Tanaka). +* Benchmark problems: ZDT1-6, DTLZ1-2, unconstrained (Kursawe, Fonseca, Schaffer, Viennet2), constrained (Srinivas, Tanaka). * Encodings: real, binary * Operators: selection (binary tournament, ranking and crowding distance, random, nary random, best solution), crossover (single-point, SBX), mutation (bit-blip, polynomial, uniform, random) * Quality indicators: hypervolume * Density estimator: crowding distance * Graphics: Pareto front plotting (2 or more objectives) +* Laboratory: Experiment class for performing studies. + diff --git a/examples/experiment/NSGAII-SMPSO for ZDT1.py b/examples/experiment/NSGAII-SMPSO for ZDT1.py index 31b60e14..bc0c7d99 100644 --- a/examples/experiment/NSGAII-SMPSO for ZDT1.py +++ b/examples/experiment/NSGAII-SMPSO for ZDT1.py @@ -1,19 +1,22 @@ -from jmetal.algorithm import NSGAII, SMPSO -from jmetal.component import CrowdingDistanceArchive, RankingAndCrowdingDistanceComparator, HyperVolume +from jmetal.algorithm import NSGAII +from jmetal.component.comparator import RankingAndCrowdingDistanceComparator from jmetal.operator import NullMutation, SBX, BinaryTournamentSelection from jmetal.problem import ZDT1, ZDT2 -from jmetal.util import experiment, display +from jmetal.component.quality_indicator import HyperVolume +from jmetal.util.laboratory import Experiment algorithm = [ (NSGAII, {'population_size': 100, 'max_evaluations': 25000, 'mutation': NullMutation(), 'crossover': SBX(1.0, 20), 'selection': BinaryTournamentSelection(RankingAndCrowdingDistanceComparator())}), - (NSGAII(population_size=100, max_evaluations=25000, mutation=NullMutation(), crossover=SBX(1.0, 20), - selection=BinaryTournamentSelection(RankingAndCrowdingDistanceComparator()), problem=ZDT1()), {}), - (SMPSO, {'swarm_size': 100, 'max_evaluations': 25000, 'mutation': NullMutation(), - 'leaders': CrowdingDistanceArchive(100)}) + (NSGAII, {'population_size': 100, 'max_evaluations': 25000, 'mutation': NullMutation(), 'crossover': SBX(0.7, 20), + 'selection': BinaryTournamentSelection(RankingAndCrowdingDistanceComparator())}) + ] +problem = [(ZDT1, {}), (ZDT2, {}), (ZDT2, {})] + +study = Experiment(algorithm, problem, n_runs=2) +study.run() + metric = [HyperVolume(reference_point=[1, 1])] -problem = [(ZDT1, {}), (ZDT2, {})] -results = experiment(algorithm, metric, problem) -display(results) \ No newline at end of file +study.compute_metrics(metric) diff --git a/jmetal/util/__init__.py b/jmetal/util/__init__.py index 8b07c4af..68f54013 100644 --- a/jmetal/util/__init__.py +++ b/jmetal/util/__init__.py @@ -1,9 +1,9 @@ from .graphic import FrontPlot, ScatterStreaming -from .laboratory import experiment, display +from .laboratory import Experiment from .solution_list_output import SolutionList __all__ = [ 'FrontPlot', 'ScatterStreaming', - 'experiment', 'display', + 'Experiment', 'SolutionList' ] diff --git a/jmetal/util/graphic.py b/jmetal/util/graphic.py index 06f1d7be..0f5d71d5 100644 --- a/jmetal/util/graphic.py +++ b/jmetal/util/graphic.py @@ -258,8 +258,10 @@ def to_html(self, filename: str='front') -> str: :param filename: Output file name. :return: Script as string. """ html_string = ''' + + @@ -274,15 +276,16 @@ def to_html(self, filename: str='front') -> str: for(var i=0; i < data.points.length; i++){ pts = '(x, y) = ('+data.points[i].x +', '+ data.points[i].y.toPrecision(4)+')'; cs = data.points[i].customdata - if(cs == undefined) cs = ""; } - - swal({ - title: 'Closest solution clicked:', - text: cs, - type: 'info', - position: 'bottom-end' - }) + + if(typeof cs !== "undefined"){ + swal({ + title: 'Closest solution clicked:', + text: cs, + type: 'info', + position: 'bottom-end' + }) + } }); diff --git a/jmetal/util/laboratory.py b/jmetal/util/laboratory.py index 015ad895..2ac48d28 100644 --- a/jmetal/util/laboratory.py +++ b/jmetal/util/laboratory.py @@ -12,72 +12,75 @@ """ -def experiment(algorithm_list: list, metric_list: list, problem_list: list, g_params: dict = None, m_workers: int = 3): - """ Run an experiment. For example: +class Experiment: - .. code-block:: python + def __init__(self, algorithm_list: list, problem_list: list, n_runs: int = 1, m_workers: int = 1): + """ :param algorithm_list: List of algorithms as Tuple(Algorithm, dic() with parameters). + :param problem_list: List of problems as Tuple(Problem, dic() with parameters). + :param m_workers: Maximum number of workers for ProcessPoolExecutor. """ + self.algorithm_list = algorithm_list + self.problem_list = problem_list - algorithm = [ - (NSGAII, {'population_size': 100, 'max_evaluations': 25000, 'mutation': NullMutation(), 'crossover': SBX(1.0, 20), - 'selection': BinaryTournamentSelection(RankingAndCrowdingDistanceComparator())}), - (NSGAII(population_size=100, max_evaluations=25000, mutation=NullMutation(), crossover=SBX(1.0, 20), - selection=BinaryTournamentSelection(RankingAndCrowdingDistanceComparator()), problem=ZDT1()), {}), - (SMPSO, {'swarm_size': 100, 'max_evaluations': 25000, 'mutation': NullMutation(), - 'leaders': CrowdingDistanceArchive(100)}) - ] - metric = [HyperVolume(reference_point=[1, 1])] - problem = [(ZDT1, {}), (ZDT2, {})] + self.n_runs = n_runs + self.m_workers = m_workers + self.experiments_list = list() - results = experiment(algorithm, metric, problem) + def run(self) -> None: + """ Run the experiment. """ + self.__configure_algorithm_list() - :param algorithm_list: List of algorithms as Tuple(Algorithm, dic() with parameters). - :param metric_list: List of metrics. Each metric should inherit from :py:class:`Metric` or, at least, contain a - method `compute`. - :param problem_list: List of problems as Tuple(Problem, dic() with parameters). - :param g_params: Global parameters (will override those from algorithm_list). - :param m_workers: Maximum number of workers for ProcessPoolExecutor. - :return: Stats. - """ + with ProcessPoolExecutor(max_workers=self.m_workers) as pool: + for algorithm, problem, run in self.experiments_list: + jMetalPyLogger.info('Running experiment: algorithm {0}, problem {1} (run {2})'.format( + algorithm.get_name(), problem.get_name(), run) + ) - if g_params is None: - g_params = {} + pool.submit(algorithm.run()) - with ProcessPoolExecutor(max_workers=m_workers) as pool: - result = dict() - - for p_index, (problem, problem_params) in enumerate(problem_list): - if isinstance(problem, type): - jMetalPyLogger.debug('Problem is not instantiated by default') - problem = problem(**problem_params) + # Wait until all computation is done for this problem + jMetalPyLogger.debug('Waiting') + pool.shutdown(wait=True) - for a_index, (algorithm, algorithm_params) in enumerate(algorithm_list): - algorithm_params.update(g_params) + def compute_metrics(self, metric_list: list) -> None: + """ :param metric_list: List of metrics. Each metric should inherit from :py:class:`Metric` or, at least, + contain a method `compute`. """ + results = dict() - if isinstance(algorithm, type): - jMetalPyLogger.debug('Algorithm {} is not instantiated by default'.format(algorithm)) - algorithm_list[a_index] = (algorithm(problem=problem, **algorithm_params), {}) + for algorithm, problem, run in self.experiments_list: + name = '{0}.{1}.{2}'.format(algorithm.__class__.__name__, problem.__class__.__name__, run) - jMetalPyLogger.info('Running experiment: problem {0}, algorithm {1}'.format(problem, algorithm)) + counter = 0 + while name in results: + counter += 1 + name = '{0}.{1}.{2}({3})'.format(algorithm.__class__.__name__, problem.__class__.__name__, run, counter) - pool.submit(algorithm_list[a_index][0].run()) + results[name] = {} - jMetalPyLogger.debug('Waiting') + for metric in metric_list: + results[name].setdefault('metric', dict()).update( + {metric.get_name(): metric.compute(algorithm.get_result())} + ) - # Wait until all computation is done for this problem - pool.shutdown(wait=True) + print(results) - for algorithm, _ in algorithm_list: - front = algorithm.get_result() - result[algorithm.get_name()] = {'front': front, - 'problem': algorithm.problem.get_name(), - 'time': algorithm.total_computing_time} + def export_to_file(self, base_directory: str = 'experiment', function_values_filename: str = 'FUN', + variables_filename: str = 'VAR'): + for algorithm, problem, run in self.experiments_list: + # todo Save VAR and FUN to files + pass - for metric in metric_list: - result[algorithm.get_name()].setdefault('metric', dict()).update({metric.get_name(): metric.compute(front)}) + def __configure_algorithm_list(self): + """ Configure the algorithm list, by making a triple of (algorithm, problem, run). """ + for n_run in range(self.n_runs): - return result + for p_index, (problem, problem_params) in enumerate(self.problem_list): + if isinstance(problem, type): + jMetalPyLogger.debug('Problem {} is not instantiated by default'.format(problem)) + problem = problem(**problem_params) + for a_index, (algorithm, algorithm_params) in enumerate(self.algorithm_list): + if isinstance(algorithm, type): + jMetalPyLogger.debug('Algorithm {} is not instantiated by default'.format(algorithm)) + algorithm = algorithm(problem=problem, **algorithm_params) -def display(table: dict): - for k, v in table.items(): - print('{0}: {1}'.format(k, v['metric'])) + self.experiments_list.append((algorithm, problem, n_run)) From 419f0e5309c60bbfa185c86476f87f7cab0c1b8f Mon Sep 17 00:00:00 2001 From: benhid Date: Mon, 3 Sep 2018 14:46:47 +0200 Subject: [PATCH 22/26] Configure number of runs for experiment --- docs/source/examples/experiment.rst | 43 ++++++++++++----- examples/experiment/NSGAII for ZDT1-2.py | 42 ++++++++++++++++ examples/experiment/NSGAII-SMPSO for ZDT1.py | 22 --------- examples/experiment/SMPSO for ZDT1-2.py | 50 ++++++++++++++++++++ jmetal/util/laboratory.py | 44 +++++------------ 5 files changed, 136 insertions(+), 65 deletions(-) create mode 100644 examples/experiment/NSGAII for ZDT1-2.py delete mode 100644 examples/experiment/NSGAII-SMPSO for ZDT1.py create mode 100644 examples/experiment/SMPSO for ZDT1-2.py diff --git a/docs/source/examples/experiment.rst b/docs/source/examples/experiment.rst index 817de569..4ff868c0 100644 --- a/docs/source/examples/experiment.rst +++ b/docs/source/examples/experiment.rst @@ -6,19 +6,38 @@ The hypervolume indicator is used for performance assessment. .. code-block:: python - # Configure the experiment - algorithm = [ - (NSGAII, {'population_size': 100, 'max_evaluations': 25000, 'mutation': NullMutation(), 'crossover': SBX(1.0, 20), - 'selection': BinaryTournamentSelection(RankingAndCrowdingDistanceComparator())}), - (NSGAII, {'population_size': 100, 'max_evaluations': 25000, 'mutation': NullMutation(), 'crossover': SBX(0.3, 20), - 'selection': BinaryTournamentSelection(RankingAndCrowdingDistanceComparator())}) - ] - problem = [(ZDT1, {}), (ZDT2, {})] + # Configure experiment + problem_list = [ZDT1(), ZDT2()] + algorithm_list = [] - study = Experiment(algorithm, problem, n_runs=3) + for problem in problem_list: + algorithm_list.append( + ('NSGAII_A', + NSGAII( + problem=problem, + population_size=100, + max_evaluations=25000, + mutation=NullMutation(), + crossover=SBX(probability=1.0, distribution_index=20), + selection=BinaryTournamentSelection(comparator=RankingAndCrowdingDistanceComparator()) + )) + ) + algorithm_list.append( + ('NSGAII_B', + NSGAII( + problem=problem, + population_size=100, + max_evaluations=25000, + mutation=Polynomial(probability=1.0 / problem.number_of_variables, distribution_index=20), + crossover=SBX(probability=1.0, distribution_index=20), + selection=BinaryTournamentSelection(comparator=RankingAndCrowdingDistanceComparator()) + )) + ) + + study = Experiment(algorithm_list, n_runs=2) study.run() - # Compute metrics - metric = [HyperVolume(reference_point=[1, 1])] + # Compute quality indicators + metric_list = [HyperVolume(reference_point=[1, 1])] - study.compute_metrics(metric) + print(study.compute_metrics(metric_list)) \ No newline at end of file diff --git a/examples/experiment/NSGAII for ZDT1-2.py b/examples/experiment/NSGAII for ZDT1-2.py new file mode 100644 index 00000000..e92f105b --- /dev/null +++ b/examples/experiment/NSGAII for ZDT1-2.py @@ -0,0 +1,42 @@ +from jmetal.algorithm import NSGAII +from jmetal.component.comparator import RankingAndCrowdingDistanceComparator +from jmetal.operator import NullMutation, SBX, BinaryTournamentSelection, Polynomial +from jmetal.problem import ZDT1, ZDT2 +from jmetal.component.quality_indicator import HyperVolume +from jmetal.util.laboratory import Experiment + +# Configure experiment +problem_list = [ZDT1(), ZDT2()] +algorithm_list = [] + +for problem in problem_list: + algorithm_list.append( + ('NSGAII_A', + NSGAII( + problem=problem, + population_size=100, + max_evaluations=25000, + mutation=NullMutation(), + crossover=SBX(probability=1.0, distribution_index=20), + selection=BinaryTournamentSelection(comparator=RankingAndCrowdingDistanceComparator()) + )) + ) + algorithm_list.append( + ('NSGAII_B', + NSGAII( + problem=problem, + population_size=100, + max_evaluations=25000, + mutation=Polynomial(probability=1.0 / problem.number_of_variables, distribution_index=20), + crossover=SBX(probability=1.0, distribution_index=20), + selection=BinaryTournamentSelection(comparator=RankingAndCrowdingDistanceComparator()) + )) + ) + +study = Experiment(algorithm_list, n_runs=2) +study.run() + +# Compute quality indicators +metric_list = [HyperVolume(reference_point=[1, 1])] + +print(study.compute_metrics(metric_list)) \ No newline at end of file diff --git a/examples/experiment/NSGAII-SMPSO for ZDT1.py b/examples/experiment/NSGAII-SMPSO for ZDT1.py deleted file mode 100644 index bc0c7d99..00000000 --- a/examples/experiment/NSGAII-SMPSO for ZDT1.py +++ /dev/null @@ -1,22 +0,0 @@ -from jmetal.algorithm import NSGAII -from jmetal.component.comparator import RankingAndCrowdingDistanceComparator -from jmetal.operator import NullMutation, SBX, BinaryTournamentSelection -from jmetal.problem import ZDT1, ZDT2 -from jmetal.component.quality_indicator import HyperVolume -from jmetal.util.laboratory import Experiment - -algorithm = [ - (NSGAII, {'population_size': 100, 'max_evaluations': 25000, 'mutation': NullMutation(), 'crossover': SBX(1.0, 20), - 'selection': BinaryTournamentSelection(RankingAndCrowdingDistanceComparator())}), - (NSGAII, {'population_size': 100, 'max_evaluations': 25000, 'mutation': NullMutation(), 'crossover': SBX(0.7, 20), - 'selection': BinaryTournamentSelection(RankingAndCrowdingDistanceComparator())}) - -] -problem = [(ZDT1, {}), (ZDT2, {}), (ZDT2, {})] - -study = Experiment(algorithm, problem, n_runs=2) -study.run() - -metric = [HyperVolume(reference_point=[1, 1])] - -study.compute_metrics(metric) diff --git a/examples/experiment/SMPSO for ZDT1-2.py b/examples/experiment/SMPSO for ZDT1-2.py new file mode 100644 index 00000000..92a74285 --- /dev/null +++ b/examples/experiment/SMPSO for ZDT1-2.py @@ -0,0 +1,50 @@ +from jmetal.algorithm import SMPSO +from jmetal.component import CrowdingDistanceArchive +from jmetal.operator import Polynomial, NullMutation +from jmetal.problem import ZDT1, ZDT2 +from jmetal.component.quality_indicator import HyperVolume +from jmetal.util.laboratory import Experiment + +# Configure experiment +problem_list = [ZDT1(), ZDT2()] +algorithm_list = [] + +for problem in problem_list: + algorithm_list.append( + ('SMPSO_A', + SMPSO( + problem=problem, + swarm_size=100, + max_evaluations=25000, + mutation=Polynomial(probability=0.5, distribution_index=20), + leaders=CrowdingDistanceArchive(100) + )) + ) + algorithm_list.append( + ('SMPSO_B', + SMPSO( + problem=problem, + swarm_size=100, + max_evaluations=25000, + mutation=NullMutation(), + leaders=CrowdingDistanceArchive(100) + )) + ) + algorithm_list.append( + ('SMPSO_C', + SMPSO( + problem=problem, + swarm_size=100, + max_evaluations=25000, + mutation=NullMutation(), + leaders=CrowdingDistanceArchive(100) + )) + ) + +study = Experiment(algorithm_list, n_runs=1) +study.run() + +# Compute quality indicators +metric_list = [HyperVolume(reference_point=[1, 1])] + +print(study.compute_metrics(metric_list)) diff --git a/jmetal/util/laboratory.py b/jmetal/util/laboratory.py index 2ac48d28..15092c6f 100644 --- a/jmetal/util/laboratory.py +++ b/jmetal/util/laboratory.py @@ -14,25 +14,23 @@ class Experiment: - def __init__(self, algorithm_list: list, problem_list: list, n_runs: int = 1, m_workers: int = 1): + def __init__(self, algorithm_list: list, n_runs: int = 1, m_workers: int = 6): """ :param algorithm_list: List of algorithms as Tuple(Algorithm, dic() with parameters). - :param problem_list: List of problems as Tuple(Problem, dic() with parameters). :param m_workers: Maximum number of workers for ProcessPoolExecutor. """ self.algorithm_list = algorithm_list - self.problem_list = problem_list self.n_runs = n_runs self.m_workers = m_workers - self.experiments_list = list() + self.experiment_list = list() def run(self) -> None: """ Run the experiment. """ self.__configure_algorithm_list() with ProcessPoolExecutor(max_workers=self.m_workers) as pool: - for algorithm, problem, run in self.experiments_list: - jMetalPyLogger.info('Running experiment: algorithm {0}, problem {1} (run {2})'.format( - algorithm.get_name(), problem.get_name(), run) + for name, algorithm, n_run in self.experiment_list: + jMetalPyLogger.info('Running experiment {0}'.format( + name, algorithm.problem, n_run) ) pool.submit(algorithm.run()) @@ -41,19 +39,12 @@ def run(self) -> None: jMetalPyLogger.debug('Waiting') pool.shutdown(wait=True) - def compute_metrics(self, metric_list: list) -> None: + def compute_metrics(self, metric_list: list) -> dict: """ :param metric_list: List of metrics. Each metric should inherit from :py:class:`Metric` or, at least, contain a method `compute`. """ results = dict() - for algorithm, problem, run in self.experiments_list: - name = '{0}.{1}.{2}'.format(algorithm.__class__.__name__, problem.__class__.__name__, run) - - counter = 0 - while name in results: - counter += 1 - name = '{0}.{1}.{2}({3})'.format(algorithm.__class__.__name__, problem.__class__.__name__, run, counter) - + for name, algorithm, n_run in self.experiment_list: results[name] = {} for metric in metric_list: @@ -61,26 +52,17 @@ def compute_metrics(self, metric_list: list) -> None: {metric.get_name(): metric.compute(algorithm.get_result())} ) - print(results) + return results def export_to_file(self, base_directory: str = 'experiment', function_values_filename: str = 'FUN', variables_filename: str = 'VAR'): - for algorithm, problem, run in self.experiments_list: + for tag, algorithm, run in self.experiment_list: # todo Save VAR and FUN to files pass def __configure_algorithm_list(self): - """ Configure the algorithm list, by making a triple of (algorithm, problem, run). """ + """ Configure the algorithm list, by making a triple of (name, algorithm, run). """ for n_run in range(self.n_runs): - - for p_index, (problem, problem_params) in enumerate(self.problem_list): - if isinstance(problem, type): - jMetalPyLogger.debug('Problem {} is not instantiated by default'.format(problem)) - problem = problem(**problem_params) - - for a_index, (algorithm, algorithm_params) in enumerate(self.algorithm_list): - if isinstance(algorithm, type): - jMetalPyLogger.debug('Algorithm {} is not instantiated by default'.format(algorithm)) - algorithm = algorithm(problem=problem, **algorithm_params) - - self.experiments_list.append((algorithm, problem, n_run)) + for tag, algorithm in self.algorithm_list: + name = '{0}.{1}.{2}'.format(tag, algorithm.problem.get_name(), n_run) + self.experiment_list.append((name, algorithm, n_run)) From 9b480f5a1f7ce7e20b9635c5ff69fab7010c43cb Mon Sep 17 00:00:00 2001 From: benhid Date: Tue, 4 Sep 2018 11:03:06 +0200 Subject: [PATCH 23/26] README updated --- README.md | 6 ++++++ docs/source/NSGAII-ZDT1.png | Bin 52697 -> 0 bytes docs/source/visualization.png | Bin 0 -> 220018 bytes 3 files changed, 6 insertions(+) delete mode 100644 docs/source/NSGAII-ZDT1.png create mode 100644 docs/source/visualization.png diff --git a/README.md b/README.md index 3c989c1b..d6747482 100644 --- a/README.md +++ b/README.md @@ -43,5 +43,11 @@ The current release of jMetalPy (v0.5.1) contains the following components: * Graphics: Pareto front plotting (2 or more objectives). * Laboratory: Experiment class for performing studies. +

+
+ Visualization +
+

+ ## License This project is licensed under the terms of the MIT - see the [LICENSE](LICENSE) file for details. diff --git a/docs/source/NSGAII-ZDT1.png b/docs/source/NSGAII-ZDT1.png deleted file mode 100644 index ac19d046bf6d460586084aa1530fa140690367c0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 52697 zcmeFZWn5KV_b$9aKm-&EB?T2kQbJHcLQy0nBn3$UDFJE8y)9G(2?;?;=}ijK5{lB@ z(%s#?-?8@ge&T=L^L{!X&WCsXe)pq~d(F9KjJU=%#++}J74ICTpr=5gP={q@ZmFP9 zr0~}RW#nY=hedCj3;aQ1e^XYC93FRa<2UgC4?U65vPYrL{6zjEvTkw|N1<3yvbS!i zJ&l;{b8%1`+pAn^jI3j&>+p}0(^4S6ajN{nMT_4U$uxFv1`GLPdCdFMUx{yiO~QOp zIqiKaRGWtF)@ScG>7#% z8;cvA2gA;LHmvkkW^&gahY=yq1G`&=gukIspQTXbgui#u60s2eW?)2uA^Z*FdkjTP z_^Yt=|BWvSua#3vm@SW1i!6L!2?`2g4XtG>=QF1Da$TJ~Hnj{xKYD=#gF>}QsV3ZA zI(UuUll|^Tx7EDC7=5*?%C`O8K@blkOOjPw{xT2Awk=B-> zFX2nee^crD(o6BZRn_2-kdE<@shx$=&e=cNLUyAvYqNh;=el!LM=E@2n3*$Ll_Yd^ z3muw;I(1@SpK+D3>`YHnXEt%Ueuj;em1H-p?jwJ!#)imP+91bKhO6oNkGKcjoCJQJPk^^v?R+hsKGNR`D-CYA==Q%ojbumeTC$y6?>8 zsq39MaUxdQmp<8~He|ZLxF|h;n4-^lz(c#xA^G|lCFSV&bcxEh1JvT1F*^EZ{O&}1 zZYM{%jMMem)$&ZYr-VK@ z>WJIhn3-&j9qX5HT}^s_Mhx3mUlk+hBWtoD4f}A6hL@3EUMA9(| zPp_X*h`O$IsbuqEWPj((nnhQp;XdEU%dW2b@rj9C*RE-Wm?S49$vnujR+p81TUx)r zw{zfT688E^oglrK>$mTEHf1xsq4K5?wqAjOful}E>S}6nFb=cED6xIv0gsKy3rbO` zQ>}@Xd)sSr3JPh>rm2&Svnd%REinD|JevXfJ}!E0t*l!EXk0OC_0W^a;*EaQM~@!8 zOrh}YNPA#e<$pG(WWVXN)b{3x-vK?#o?NS(PRGfn8|LQbL>Wxt?)P^#7CNKE+*U{Y zB*?ytTXt}pn3`IyPBy!44!s_;E2!xc{Mh)_u$&A;Gex?S^}+?R2%Emb;6-W_hRh3f z6oq046LZU*FP!0ZoN9@0n7?M;5{FsLFsghXF5-ygEt(Z5=$psn>SbuobY^sZx&3Z+ zdnUuOHiYNKnk~rQOqglZQZ$3t=alq9xx9YYlm-c$yO4Eney`Ag=T2Hb8s~uws2!^w z8292J_4MG67ed?aq@<+#Gm(mk0)m2T^F<4CB4?;NDp8rUS(B8!`DM97(GKpyJux?4 zSU9GcXcg`**B;PgxMCeac7*Z%Tu%9G>dqhzRZb5y=*0G7IrUm2dSBJDBQ=0^%UxV- ze{*CThL~g4eC}H0Gu%o2+{aoud+!;qsbrfrrZ)=(zkTs%k29oN<`T=?tO7 z#f@aIZLiOz9AtJ^{v>SG;!VSajr864bwY{cCEBVdw;f)^uh!z=4;n9Y%aZ9_pZiLE zO>J#!Vzj6B*vdLq05(i3gl%f`F}8Gf?9%?mc>O2Mp}+|5U;eK(;BB4O?%qzq=)bWup(;6BTs{p?mK(la_bc zaas-MA}8k!)1E*}Kh&icFn$AapQyh#t-0SBh2D1og>ZB1*W^q8zAWds&>#5~^!LC` zM1K$Wg83r4@j$_uB)twcjkuvv0=FwtuyBa_`XUCMO2@%lS z7bTBC%=agfK|XGV>N=;y1ks9?=d#0*A6L|{7SOL6=DYX?y%PZkPi z(p7bIBH~6GwL}d;-?|GOE$`2G2Q`ZC+y$G|hB%^Y_Ty$C4I)nlF>-Qp-|Qz8i*frq z^q^WI4wErqf~Fmt`|>HM%acdc9v9U<; z4<9}pXwXc(Z!5RKW!e1YHqo=;qFwQ#AHFYxkobgWMH@|0E_(hIO5q&2H>?by_b8glI=!;^|_{{_cdt zOoQk`>OEiZ%&mYCxLYsNFfgQm(c}~qRE-pMPPfx|sLQXQiry?|CKgT%<1>5?TX|}^ zL@9qRw}-gF-rl~v;|dC8mXGL6X26{@F2B&$8M|!6F-jItdw|u z#wZ%SUl8?l_z^OJuq$;0vdQmGNO@5;!~7Xr z#Fd3&-m4im1n)ncGXK;RRXQOk$b3+uj1@?7x>8XWB=~*eSBXy;1X zJ^a9GNZ51tDb9Wm)J+e)=jh8UQKG{V8Wxt^o}zj1B_{TcU&pfp2eES_;Op$JGP)wh z0a}0~XC2&IpX;d|vupp6nA`=BfiClIiPY;O;jnRx?-%#&ud=bR4Qjmr*GOrJ1N35e z4n=}0d$hlvSGpzTyHJYLQxe}YPFp}W9TZ?Q+LxUPePpmEn9H$}$vNqc-x-VUZ^;c} z8#;Y16OrK(XisHBfAjF}Jez3TcZE;FIq6@8H8S+(1_pl(Ju%x{92)DsY}s*FQ^)od z?t95&G8I*1&nz>F$0bU)#yV-O!zzxugyFV?n?@-J|!JGz9>WVaC|YtRExE?pFXXKEI8(#|n`m z?GlzYxc$Av-H2@P3|xT6Rwf!XH8euNEpS;q(9ucN)N{Fw%)nvY(AL(r%;gWAm-?R) zkD`!E`g&tFj*V)f4wK4dK6zPZ2dc@aXk&7%dgKD##iO&bvtMAd#Q_@n;_?BVOt&Sg zDtGF7ZjJ4a;6TTT&OW>iVwiA7%(X4ON?SldwG_9vaKOnn>Zoqe=gF7ZJPBH*q6^)BsAg%^$oj|9tGc^k(Bs zxpkS4x-jDmtVL6&iLvpf-@AA3a_lD>IC~P~;$E604rEd2m#How5v`{_IP{Y(~vam&j0Ol#9A8D1W(e%`Zv zMV)9JCn*>iM)~b-DXgfZ48Ti^pDz#XuYjIS0?eh*LIdt>1s|Srm_e9?`#cW^NAi;c zzCl4JaZ8>0Xr>U80Dbgc4iZt}wx9uabaa#x-6*!viwU!I>Y<8@i~EIUWME)`!CdMQ zchTCY{z>=UV{fgK=#%GVt8O)$;x&z?#&D|m=g*&G=r90_kEKEse)%MRi@RX6_84?N zCL|Xvl!%UcBf@e(ZwGK=ZO+UdNrJQe<12u*2UjPza6%Tr-C(Qs(02e_09csrEwF#dB{~oGn&N6H zIHYr`5KWsY<6Q=SUQZf_YQkn?D7V%Z@LaFH4?rxDBKzS2-mnH+3L2DQFe?zMczsI% z;WlrGxR&T14L;!D<8vwlkxt#_`y+rTNj>_it!n$(RqnR7G4C%Pq&azVQ@;p7NWW<` zZ)IX7VdZqnT#x{Q6ZG=%AtDxl63!#OSIFu-w`=wHk1vZKE+;2PMJk#W2y1C+ZG%gm zlR)opO^hu)y{XZP;CqN9wj05dX%DTCaP91sGw% z|N2Gp*lTerF?!nfNgO*hBB#_j$wjc)&D-fGdS@8~q!^_hlCW3#c=%jE}v6PYsm z`B?!qE4&P18xh&?G3-&2cI1z7S5s-vQVp1;vOYm?BxR(b8#s_s@Wlzt;#~ zD&5VJ@V`tIt3hP&`0?Yi?9uVMu=CN4nj<^67|QuBgWJWq+_>=!PE+C zhzM4qsp$l6K{QM05*fS&`&VPN9q{2ptYG7H!G}V!m8QD@cV<9FI@v;XzTkFBaVw>v z!NKtWEu}L>0BJM@p;@R=sLXf>^dvz5UBLibIz%>3s5&|2f{ky_wR*4ykQsJB9qe<^ z$B#)ZEh=+=vhN^3r~m1QuiYmp;0DeQYN6RhfjtQd4NZjUZD)7@WcUU_U!>c7K}}tq z8b~nG8tm&NSb=UpmcrJ(Nw8Uy61Xj;vOUB-KC#vc(Q}<*!)*calj1bj-9Fhk_!3-s z2RZ|ZL%kpus(J9pI)Q*oeg= zzrGC5HLm(_A+DS1%o`IDxN8Qjevj(Ir(n4{5L#%)?UAwZB;dcaECoeH#j=%M)+uC& zVDvp9YR8l8|~fswnn~8qPe{n*ts;*sqR;}_c?ZwI+l7*Ni1x|P|@$Z+uR9Ja^~_3maAb>cqB1`+vDqeFaP z#bi3ubsftOUm@x91bPqPe{h8_Ln4Ilq(YX-jx!zfqE4y8R^2DnHl1(2JH>6Y`$D&Fnb%IXnMhTc#Ne;SnE`qx~A@!ggy{v zAO-3(JGVao3sfH|s+m!|9$x4)r)^#YE}+k2qpzm1Q8Q67^5=a+2YZo7i{>w{04Zk7 z_Z3+JeFO>^u915GGlG`ivPZq~2A&0V`L36GU%5L9h4OS7tvZH4K+cO7o4}$uZY~;L zi&PW>M^?JOmH~LrG#Q|+WA2KDq2XtUqt?LESqzkT0Gz4+bVc*w!!IJv^QpYLg;i6= zH>55;?#We%c3*zGKHrxT7tGkP38w?;+80)N0`V8l&?kkjxy_Ce4D z@?`Q8OCo=wP-PW}slh)C=Rub6&;S1-u_R$7{{Q9qKQ9#a(uJTH;?YVHyNfSa5$4)X zgbpAA@_a6%lDzecT4EPL|5(61`z(J&*3r*gQ}S?7&{Gqw5UO%zod~X$F;wEqC8{FB zyXA_sjhPG;muFut`E0+T!{EC@{a>HAfMNch9OFIXh5lmg?@zxXfrUJ8!7Z@Y0#s4} zQb(%-&Q93_4?%zZX(mwh$(x%`b8H6m5Iz+mr<*OzgcbW)OE*Ql_zpak*kcdKSLk}I zwYyG4Iuq;R0OqaMaAlZ5C*haP1A*OU%nc{BQr`GTL>rILSwCJJd_B8=rO)Am#BOa} zUE9xBlsRBPi#LbgO*egh0nioLh1Cr79+$BYp)JcQw7rcCSlnYfNhw-fWN)=~Z7y$s zlZ`FDVMnXLE*^p#cG;Smnie1clJWdM1g8#vOhS{L7y7qhTnO`~YhNz}2Kjbm=RIbv ztkGai-hy+wNR|$eCjjx-s=bW?<`B;_=v`acK(P6MT1KdGiZY6dNRa(yCZkjjf%TM^ zzQ-($RKAYSHB&dH;TWjX{2Z$+Ue2C4re9KTze8(KZX+K z`~Ca({zz9>7iUk!@84T~VrP`9EV8)eLrh-H?&r|N(*C~B&nYCAVq?iHRm3jVPSNq*!ghL0ffxTUxClANk4D#H>^MSVikrht;vOG@1~tk>px zGy{!krPX*Ng^VYuxNi6OOgnFhmCFT`4+exM=~GE%aZ7o9PTD;}EcE-MS0rDQ;T0eA zu0tbqYsZKT4zxnD3}@?D>*~jNZCE1Rx?r&y>sDf*Wow(B6zSdDI{kSr{*}5l$*{ka zn6B7K(_0g!DC|STj=kw#U{O{jbmdfz@A=*UpGopF4u(4QynA>4Xt@iyBt%ED(#h61 z7}s#7J=M-rx8P_ENM=3HQ5Q_`+$672na?C?}?nq~SuY4g{Ynj}<;p>nyV-bI?<($r0{65-o2mI*Gs5OT7~ne~vbW0eJ; z0FbaqPG0^%KW?d#SqL;+R^{f+=hg*dA^sgY%qO*O*YMQ0bG^Uj+8!3G$!H(+AUx$< z%Egv=!Wh)vvX$K%g+N>dA{g8*BuQ48PoF$FITosa#7x3&v`Tvo;%$_!bDtvuTu?iOq|aGle#s4mBGdv5cx*@GwxYE+3=iJcXudS0PkJ{DKHC1%?1y3*e$iMs zzmnuXvn%S;&N6i+ij$?Y+S1GM6^M?YkA=PF)vRIO7G zpgK<#Oyn3F_lv7G3lbW`!}RNl1DOZ^hjx7=B3Jvlv7)4#N3aXz?8K5W#~Y&2(}g_v zflFQmk~x@QuZAnID?qAyqJwMG?wUogo zk2vZBP7P5I=FC}{PAYjc(1+Ee!Wg@HKy~A8_(lDpSydW-wIH)~UE1P1#rXw{BIL>qk-S{*KP+VDzSdxVt{Igww4o%5~T|rPp z1mpN$4D=jw)FjEkvOZSnS~Th$ zz8Ea+L^Di1Xv}jR*eG!oapSC(@pgDi80y$j6!8sC zKFV(`UsXa3tEaB-x7!v*b|+YM>lNz4dgDz58~$BMmM9`}lFwC~EpGiA#&jBp2U?5T z5Tnt${m+)fSirZBF4w!$@k<9=`Or#jJ@Wj>Nhlm4KSIQ!e7H7C&FmCedu|hVkcs+r zd4l6(IZlpxX0bW?p>dJ4vtzM3aj=`v=H&sT%|xSwW8W#lGB6@<(t3SXJ1>-!n=Fl` zik&tk{rmC>B9@~ssqT0&w!7;x+FdLUIF|6A%Xzk1MkV3As)qS*2KSHRstf%a=~>ZL zNB;4i>VR^e2Z60*xbppT{kto*GfQ%T-_K+SU+PaKJ%8G)$J+|R>91m46@ci-@wMMt@p^cfupDn zbpQQqNFSkq+_&t0O5l4ust$9bmywfG0Z!)C0z`%o(Y-GCs|;AsA0a4b2&K@sf&AV_ zE`mrVoq-iXv^RNiEu&;x4w6M3K6HXVb&lctlpI6)1UqLvNa;1|<1AOPr0PUW5Wwb_ zx|nD57kPQtnc1E0s#UDf$0` zsW;7@J%a1?N~r8N-aPrK(1euM1rc-8I>mSnp69`Y-C(RdZV=<;!y2ozsTpE&AIun_ zsaFL8_+ep|kzs9DEUCv24IE0iJN9YI5KAaOLO_t@;rm&u`Xi+`a(KQ`m}qId;Ue^z zL=TfaAigy!=>8*5{fQM}32wduZ6ivIu+#oA7df)K1zA*hSOSqx@EIo+w~Hs`{$_Y* zLM#1W{YdH7Sqr3MUx>AuS`0~1maF~eipi;cY2cY+Z#C7}ESVAvc0;GHTlFOl4-fNc z61qS7=3o#}PE)mG0zH%M%qt~5x{!RSrs7EN`oDkqM zj=T`PXm6Lm*r1=~5ZV*JD&K0deHVbEoPFHwOoGj6YT`d|H0jmw@IyAIyWFSugU6nT ze9Ll(_fGxt&yW`>!6u)ARE&^QN0(fA=?U&~m69;#1T5iWH6Z^MKLX=*zZN~Wcwk0T zla+c@^Dj&Usvzc+y9BPy@d5s2Od%A3wLMo=yp}e&-ejw-c zys-E*EfZ`=dp7;=&OU^l~Xb}OE?K+BqD;^1U z3ytj&?Bg&0kr{n3Rf#cZOWzTVU-2U7{Gl3 zKgi1lvv*Mu#K49YDAlV&7HauS-3P%24*lvSCUm5&Vzs}qETqR%D}3lq{CS9%CCL}y zjUv84`VRQpyMb+~R)Quv(Vv5eIP3~^mtDVim)I5chwZ$tc=MMChf2`}N;lGYtAyoJ4sA5)VgsBvP|oRejL(aNe`><01A(kw>B z*BX{Nig}8ffdMDS2|i0ws^S8RBvaQPchmnxMFy`DOAej{SA8>uFDaK);~94?h3##^ zE|vlrs2H#?5pD2MAg8mNVfe2F^aKr#I0u&W$9DIw$jf%Z+a%!c6ax%t<@i;eTbJtg zXps?I>rbc+N;;tH!+Rqytvb*U|1w4u@_cR2T76r`$d_k~UDD`z=m>`7Dh+Hb6Zm#> zX>DbzJnn&W<$O2)8lW;;P2` zbeZHbVcWr`u0k%~s{lx2c$$=9)f_L@2PC9KXqvAK*TYr>vVxfcGi~{!inKfT;U`uO zc6Rna6TG3okiCo~$B1+!Q7Le|A(Em0C;kPzNW7H%a6#d@Mdc$F{J*iZfo(bZq^J+LHvO$D zRMm6~L7x5WFY6@ZMmeNSt&1Kp<6Ao_IBn(vH;=r zdgS$Cry`-*TtkcX%11;)`dVJ)O%vNX4JwJQ6{}SCukb^OIXg&)ktClJv$BbYS1i+V zIJ3Ne!g3;ynHe&TYtz@TAmBIha9OLhM>X&UA4Sgz;N+Ih5^q6odQ8u0tTF^$ndk7W znvUYX9i+;yakptsUY#@A{cI{h!!O^0&3=P;=Q|cnbCD7_d;?S)8+7)vP_*!1pKv?g zA|OdRc<}F&1Eiatn$eoe-UIPiv+sDF%!&WC$sZK{Y{!+7j6#>fY5}ezL<-T#y*u`6 zHFcUlb?ET#m_at{8Q zf)FMQK<&bQ=W)>soFm&*@l-PZc$MHDgpUC1W{`vbM%fW$?v^caZ*lqmHa^w)1miQ~ zxG*EaKF+x2Ge!6Iuhb}HlF*W8pZN5QmEa>xu2QAtbXa&yZS!doc5wD3>gXFxV@$EaKvN=BE=$`@{$8s11b(gK2=lkS|uV-_cprF9n|9 z-vVQx2~t6Fbxh6Y;dASSoxlD$CIM-H1O#%23yWLrYN`|Z@IA$xLVlP!(<(NZoBUpl z&gQgPvLLh5FFK)Wlh=q~*uroZ(SR1@s#bsiBDOjQg_>R#ELQ;L46XHC9%?0;}H1SOdkg1}y~k`r>fp^e zf~odb*WM#zmiGCqBdB>>;~p(1;md0@0KrWlB&!j~b}*=XX3-Na5NtF4`ZGbJmGzOy zCaJq~35lhdD_c>f-PF=p6C}e=MzZ`CiZ*_KX8<=1Y2OxLu#zFs!v%HN9^0$UMt))m zNSTlJh|$O6jmA~%jvu(9bu>1H|K$A2-K` z>R}-t)QOd?%cDO%MA|ged11TqD3kcSNe; zej;o2n3Fj$yp(dMlghZ?7+2%9j-}=(N;&}6RVJ_vQmbo_ENpRtV!I9I487vQiaiG? zQwYS@>-8{Rf7&D=x_Ha&mb9g_<=)zE>2!Qu=<0aAP0bTp0Y>RpRtg9qgpZ`;@SPt` zLzrz?J2qJmV>^+^;9OBCbgk7@J(1Vv^obQJ*pq&mR0Kk;ZJXWPv1`E625=|O1nZ7H z(OA)u46C`aQm#hurEi>q6Yr9zKnQ{JDVmOY4dvdHP|JP)&U?mYpb{;5I~A_<#Ubz%OAL*#1lMH6$&6a4 zCWyye>CHIU-oYOIo^hp$?TpO3n%j2Q4}32BH-#(f!PNNpeoN*tl=NF<_O=jz=gdKU z6|3wSzeJ5atKR&3Fy-z{L#bB_8E_f{Nq-}mSST~?#QGTk65r0ETU!#~(F!@X9?npC zMZ;V+`Qh+Gtd*q;&u?0On)x$S5l5?+s9q8S$^G-G*I!X=yF-GxcTQZqAI(^oUIc}V zP<4JdEg4c@%1|7E)afG0Wu(CT1=(R0NdH2mQ7jY;PL=G;w|;k7`DW41nRfr{O{Ajk z9&+9|J1Iure)OZ3% z=*UU`2ayNTbPFupUgZz&88M`+Z7_r~R9!^{pB_1Qgi#r4|JSP7qmhck{f(Y)=CKk` zbv_MMJ`LhKkC7bvmEI>W;8X{$Eh&0B%ILm^PDF~H!tGpcc`w6TapO@MN;HgI{>N0; z(8ekU6Q;N78>hCj(~-z0VOp8_e{K!Jt{+x?`Q;XH4}pR24+^zg#VjGGK@OCKU$;~W z1kj#<)OZFuKrtuO-J3W<{vJ|Vp{$67*oJaN%Qotp=c{E}Bqt=p#ItLh(w~Ov=<2xc zb@c8lLL1AH9vEnK!DnN9@iJM@nS~6dg3@L}F+f#|bA}m0X zee$z<ozuwl=*S90(OJlX6GfL>~56Hoku<<3|3rLx7^^if*Ee0gCU3C5K zYPbF2AHw0*`7^VpOkCGzlhL@H0jQ77KXT@J(%Td4A(kJaqSD#qLy}-DInyJMr)Y-~ z^Hs=lOfL?VBV`$WOCZ-FErUH{e^g?X8>by1_o@i-lvG71cg%wcDX%F%g z5fcshz6AIh=YNRbBs7$K+G`;c|jB$2+yOCZLLfTJq^J|bTX<)7vE2vq2sb6&q2 zP;W3$!j2@#1LJ_QNP>dpW9b*6Ci-9C1iSy7?;^tQ>Tt-9mDVIQs?F~ebg$0~Lr7dW zKRt8#QQb#!wYO#`eMji7()@Q8iuo%EZPEn5IlIt&A&)JG_#=0N@f4RjtibmC$U@ux z?t0;q;aB^e_VV_Bz3o&B0nX6^))sE+?$b7EJd;u@cqtU2UfwD(kvTS%9VDw6+BJEb zSv%Kq%JLnB9A(f{m0Ec0x_mXE?OvH6is+MBYl5#f>o@<5u0(*(CW;MI$ z^#JvgxA5#KR(dSx%7?V zx40z!*DeMk7K8I%hLgcA8C7FPtl2pS8VPJ{SqT)ye6pqE4P$$xY{gUC@;aV*nVZlG zsg22`E9YzDC{b;)%Cks06~wFjOzY?{A_)JEGomc)j{7**}VaD}zUY`|hf6P(nYnNxx}hW%t>TcaNgu@v~$2<6^{f?Om0QNfs&qfM(}HN_H|JGt|% zP#Bj$C-~@P!A-(vIIquAnz8)9N(V0688>vV-MJs9A6-p#79%0>R&P5^?RenPx%vB+ z@wdwTltWaEii3H3E){rkd;r;4Uo-xfu@m37hXlX_vF zK{%;Zy8bEk;Z54O!LB5A4gIl_1y0RV| z23r_U($34pTB0?VyPsU;mMw%***hsZ_|fy-gea4L^gJnZb2aX2ay`0KYu~+e_9IV$ zhd#rR*N0H}t*Ih>u>h=q@=y|Xjr%a@0Xl)m5ACLW9xzTl=I5UO^Vj_syoSiB!Od;GP&xc@6F%%ehu>P6OsGetDpsqS2%Iu z?$w#S+ES!7VX>HVAw+1w*;I|h8_zTsbGS1Z+eQ;lAPPE6w@j)Q# zOb}bAt%=W+z0cppQB9wzvE#`-h>vmo&vp@04Kn^Nd8kz%B8+y<-re$4AJ9+p?)E0C zylU5W(xyc=GKlAa=hmMycp5;G%z%^yBW~4|c_W@JKFcUqZam~Ey}jBUKv2Kd4e;X@ zASHrV=!~HLLRk075-G(=5-*Qv!92nzF{==^H(fIF5x1>p4>Tn@5_Qlj+l-MA--A~%5T=o6aZy_RdQxA9d8O_=y9@Hl))5B0x}la5Df zl21zP5&CL*hdPR3A9ESgzHA+EWViC!Xc$H#FJ6Li|3%_b=;U!aBNZ)zm6<$HYm0iS zG0PQF+|x0B@Jem;LMT%<4L2nsj5QO?n;VA~CYc-yBVjkpu>_YTlL}wGwYIi~jnLrO z3F0YG>D=cn?%AG?8PO?lAf%-XPJ;2IJ#OT)zow5{^SKf?T=P)LXo`femfK+LX|n*M zAto!36;tAmfs zi%(8%tL`Yu26A^k{A-}if2uH|$~E-$GZ188BLA*NTvgbB-JsIRVYK*s_sId)2f4e; zDQ!WVqm<2bw8+|HPCkUaH59D4$arL?<-0tM7EOSWsE6LfFQ#f4)kg}lQAB378bv+n z6;ESDBHlZM;8V0^AK`@0;CI%WWn~M*7CPNYxlC5of<$T7xtRf4C={UKK!o3Z`swfwK)p{J+RIxHr2hyl}2K%gDP0?aukOGAv|W} z(+p*IYRbwivda@YG#AOA(pyIF+v$e#{=mQG!%~PkKnoTKIT_=&J{!Q44O$a*v+VlV zl-7h6lLK`N8)0pZ{i2XWolh?*(W9ZE3FKnx^=NASm_OXTFrO$-*pl<$N6X%bu3Rq* z;o-4+Y+5#RuzOxU)ByjX25J~ikW9qE%^Q^my;Hqc8Hfwta~aiPHop9AUJg6TV&cOe ze$77N$Ymzwkrp7X%s#YR$p)rc%Ug+dpImtBR(M+XIbP;z&xB)8GRa$MoqX(H~-!tZ1bO-6+$9kO7R0B}Yn#lmxMggr+ss~V=m~N{S>9G1-+q*gSP~W6KpR{1 zsE@7EiR^pBSEd|mt6OI!c(o$fTGUOnQHe<`L zf)hC$C9K1Y|%dgaoy`{HQ@gwrrf*~6RfvMD})A|f&v@dbGUgQ*gByY;S_(=qf z#d}iR2=Su_x!rwEj=x0JMpEvDWjsE~c@SyOBKVb*RV)89+;*iHGZhKN|NI&!X zQ^if^3;*m-I453DSysThRml*XI~#IF|1 zThvj>Q+yR>kJj$uRdsDnDK7<-H^lNQ4PK%7#*wZm4j(=&vOdgzEmv^R@%`LI+)sC3 z-1bxO#w`h(7j6$bkl}a9N0$VH%6iT(&7f(c))Q;B=Y>)14e+^a(h)T3@+|jpPSM;y z(o0~;j1lS8DyNNgbk1l^p)SGxV-)|x5OyGjFqH%^%syhs2jm{I4HikbY5unb99^N_ zJzpOXdn%Dlfl!X%PlxOxlabHQp-j$1^%`*#`9QoMW^jzXOyHk=IFJri1V~a-gCqu` zudmqf{Ug;C6v)`PjuD3h2eCCCVDxFXGKVv+sl;=O`K5=@lqoA$`orBuNtYdd z^qn>vN%r;rWlo^CsyvXlqWGoJLOU6wT@5jv7wBJo#N7Lrb5IM#Z(vyx7;FC%1o58W zAi4elP{`|G3<@4hFT@Zmra=?IGffgqDn0LW!9D8{-?;N$J0e6a6fbaPNdV9MRW;C- zIsJQoiV&ahY5cKDueTe0XGg2F(NUx$Nk#Dg{;J0#pUr5rCoc})1S#pSAob~x_%8mt z9WM|5o+KEQ-FtrNoSdBQwS}USel8D)%p4z3;8;^3>W|MS&>RS0kZf4qa>p|2PvI5X zF8;~Yh+!=98I;T=VoB7sIr;%10f7U0BVF$Sk6*xjhvP76p?Hy(>;{umWqvzB#@@qz z=P(gd&pl)HTe>?%feQ%2g=Uxtd(<#Qv#0c^ ze7lD2NJ}$)g+2rjJb)Fs{A7yvG|~)n3|7qG?nQyy*y@z#*+bcm<*5wDky)(HgZwWs z_&0RQP8pz#yx|N|`#K461?=5v@i4n`KY0r(?045Zv>=3dS!aAJn=K`N)({a&8aD;^>~n7NdAvqkbZ4@Esk^K z@j!{Ldc=1}bUAwE^-gt8f@s9b7HLVxgwKhdNv$QQ0`7#f%&! zLawg~bbv!dNL}rDSDf10->!|007fMS9+=)AJ<{ozV_bc_xOv?R{JahOBP9)#9Y;o zId7C~h!rpXq_nWM?uc~m=a99qvC*`$N`uUR@a#myRok1MPDok^dZQWlt|p((q1ypas0MZ8U**`w&g->^{Y>Jce;OQhbI!CgzgY>%9*pMEW|`)4=Yd(xK?KqG9v6 zatW5xv__Epzx#(@)a63V>2w`HSRASN5C)Td4E4w#&c+ICx&FyA@z5Jt*;1$Wb@zloI*+mon1JvjeD3bErCPU+*L(Uw;zz8BXw zdhG+_fY+RcTR>(u3jbuAvg^ZTC@WH~UNeQ;Ta9R5S>(P1Z*SMUv)#FAkW-w3L$lt$ zA4>5s(SA5?N_@s`=_6y$k~pt{TIpv!e7O5l64?$(Ib^hO%275Dj$e@VIz7UPw9b@ME8FOI41iFdzOAy1f+oGgoIEL7Qw$C=6$D~OXV51A7G zsUskY>48*@=A0K-HWvL>QoiJ#+cN!cyv7|oMvP3jf|j_9$`%eFkIumP?8B}e=TO%@ zcQ^f`KHT49m&Gko<6`b}Cy-K}N=i>x{H!bk_eZo}@?4_ym=GOkMh;~o&Ig;Cs%B+1 z2aP5dXKr&p;yA_ND)^z~z&1Vc9rHy>1YLIo4U>h|ugyR3?{^M-$vJ&@$1GcYoV zootC#&`CHL%Pw0u8YsUSirxy{{vfe+qCQIO!TXWPo`L;bI0kwl?Bt$0I>ePa_Doy#L1C0Ksxd035 z!z>}bRhXD)+B9>*SA>Fs!fTIF7RfCL?24*7I_8|>)k%tmbgwJiowN#Zz5aHq#GYzn zBi&=mWw>!D4eFZZr~Aaiuup8`=*}0U2k_T~hX?4X3aP(p`0ZN%Jz+gcc+9sEVmpp* z#`ZQB)iGv?O}{>=aRv2HVFGeIXf>Rk@`Ge0X12c*r z&nTphu4L)vmrm%w#W_goeW!R`Fna55meC^IWpX_Z^(51tsxc^L0#y4 zF>j|yp-!lIdZ~1~J(fn4?t{cYhWQnxmCOCYxe=H&VpacToYW6@>2yQI^P&H?q{a+| zDAPA^h$tl~X`?RV2fZ0stAo|3Cl^iUJT|v~+$sq&qUYc>s~%}fN)eU2O@SOvzIM|OI0(%-6QPj91w!G={|-?3f2+|`Tq_s9}a zhSNq4g*zqK*1TMVgoLC#8{i}un=I&y zI^3vJ+9@O~d_kZ6r!1-*yk?qYmKM{;la-RhAUJ=(#)B9e z_vDT@MrT0%#Y|`Ee$KgoHzjaVcV>TQA(R!adozRj-g4Ipq)-E{+}a6@>*&xxc#%(6 zY&y6yzJHNn{ZhI=Q>y6o8WPr@AzggsT=d~GW=-Ofoowfvf=AhBaCIY5qI=zc<1<+0&df>^<{5=7_o zMC=;m^YiS0*8YEH4#|7YLmn z6lGMa1&tnxJUl#zr&hmr@AIJ(7bao3JJ=<*WRVL9p|%_g9-v|88hR%WouZH1?nLhU zOSl{M1*{Na?+f7;IV2&Slb1K+#b8bQOo?Q${CNG1aCG}@f#{RJ5D$uNr>CZ5ZC?>S zZK!i3=;y_&aDn2o&JW1j&UkaB(~fz6fBT!eAf3y|XL;qI{#VkcOOfc^_vyRLnl%#( zIA?+Pb4j-5v4!i-vVm|+X|>nB(Hx5Q$~}2k=nA~9`Re@qw?_H`Uyh1>yq2dy-Nn@P zmFc#|?ZBaE&>C9+73Pqyp5i=#48=bl;I%=}S|?N2bZ%R2tb(sRgJaB;WVBJ<<3BfJ z&mixVVaxzN)3G!&J%uyr+PJMX5!c})$Qig?jXTSDgzJo7DU(@|h5$$0(6tQ2vHK{1mUt!BsG}{?Cq|9fXUU zRzcgJ(x2d_o}OFITYE7gp&Y})Qo7426i=~r6S!55biAGLPevw6$BBpSQ(xZk(l2)Z ze+CM4$c+4-!K_(cur-iAwzdvw?A-91HE>ZD;<6lF+1T^=ho0b+$44N>7l|cNRm2sv z>YADxTg6So%B4g4&k-tGo>LBwMvSi_ZyX1PU_zLTpTHdbi(lj;1NuNRrD2OCqaw0NvR7q`><}s|JA3oJ-se`&^Lc(he>~40-RHi~d7trd z5WqkTUrn@$o_9LDXKx^Vm(LBDWb>r%YLu%J$wsb2|C@3X8L344UikYc;DSBCB?va0 z>0Isp42yE3()sNA@yA><{Gub00+mtB26D%0@vo1g<-~}XeE9kK-`rA-Iv#oZW&fo= z$=BQL^&fK_ggVl|TcohPB=8hXRarUY7$@hA%CY{i?`0a@VIRcEIotMyz+&p0E^oy{ zo+dA}-MshG-}%pCu7(Y+b{`U#j zu8WZmgu3gWw34H*mZm2b$7o0Zxy!!!?MTtg*jXGON0{zx0ku23UQek9J&-Lw-^InM zpbJX3p{1l@2H5243FCWV--<8LZ#`>RX)e?Bw(6upUzc+yzwzoJL|?F>&+FgKB;Jix z?1rp;Scha<4zERjTP(i5Kt%2Mt!78ML`@4$o@8;YD+p)Re zyijUFq)!2Er8%B)baLJPb&vA|WWBFGD^`cTH}7}_tm)DJEPDM`W5}avZumArzQnK7 zJSV&2G1s+4V`O~)xBP6?;Qd^b$KIWZlQIZ6_I*bcPf2_@M}CEN>~%dW=SMis*cN-iKl{36NS>RUW`8) z#Yli9u=f}VrV_25cCqh($p@`{z;2H3GPlz*?P`p#c@X?QHM(mIJC*EcL;q*zY5k78@hS(<`o^Cs5@mZSgKRvJQJ%Mv?og;n#B=#JuG)}`Q|Q@N%kD#-yG3jxX2=U$r| zKsb3xKJk3S1jS`0Fwt*YYI7B$UQD+uq7P9sX=*nY5NTZ)md{;IA(K zQv$~=TD;;(g5&g^cxl(?%7bDb>{CqWdiH)9U9F#TZ3`2ouYo{8XmIj+E=DgsmfauF znI<3q`5oN5<_@m2p0S>_UcG@(fos`t4aRGajtDQ;!$Br_h0}@VmpTN0#1K;UqFuv* z^OXw$u0iO77_m53x^x0@V99UaPNyua5m4vJeO4XY@tijiK{;nE!RA8JRHlC=7@edg zPu*PWRye^)7toi`WTACpK9a|po0#Yz@B;Bgx1|>x$NSA5&3#f*IgCE!D^CjVY>(Zb zX{q6r=TU>NUD@C_bQv3fTfdMATM-E$?RdjW*X z3^{?^KD4>9)=>{Lb7H_rk26mZw21W|H}E|!W|g-~!~wXw%e1r0Ku=h|28XzjFuj4m z+>=Di|MI+vJov7)-UenF<7oZz<;Ad5^VaU|WBAT69a&Ahy4ZIw=}l@NIgL#4|Cqy5 z=wbRPh{Ykn{<#0mlw7MV>C+>WqcoSwfihfKA28eqd%+(jqdyaRq(apacrfo?Tn zEiQw4|7?Nnf8Hi;91YPARgp)+ag}}!8~9eqw7sBzERV-Pt#i?w9Y0YS>bN> zkqZKqr==>wRYGwch@dwWtn|B?C0A$H=Ds8?)r3u_jWdFl2esx7j<7zwT!y}%cN>H| zm?ZFAfE7c7q38U@^@r%ithCi~1~`xh6j>Wh2;JBiF#Ax+Ruv|nppUNlwH2*fYz<8; zM9H;01=?F37-#thi`*tnfI&9>R#{Q4HtBT7b~}Csi_w{ugicY-5c}JWl^d+tZ@+xo zTnd=3SY@25a|GoAR<#Y-6YV0!@^q?&gTzAbV;!`5UlabFf{2I-V=|yIc=s#+BBHl@ zoaX6)ql)X{+vv1^muRIK`-tDDcb=t(mW(Z{z3e)1!&w3W?TNU$ayL4Xx_sote+w zT9<1eehd;qOnsWC+RZv z<=o$Y9LKPzBKsu*mm%l}=RGhR&iiw42kPqV2F?vjGX&~7-^apy&ETWhrM(1W+|PoX z-do)x+g1TqeT}r{1cdtVi&l$ab)Jn&`?T1SzK;-hydB<;OczrE0Ek#19~~VXe075U zwJZO#OOM^Q6Mt_mL)fE%D1^lX`|7yM{y^3}ne;jb@_h-%1kt@1(-3SxfWxRt)V|@| zWsikhyA9z)RH>1|0!PCBc{O@Kg{2=tbb=PZq%rrR^H7I_A;?pl8&=JbhrZ!=ERAd4 zmPcp{3cah0_#q4Lf450fMitnP{E%X3bD1n;&kOcg{UWp_=G|q}pSce?(r_t;Bm+8p zf(+(@zs$gB~x9_kp zKxBYap@cv&UFcGcWM3cL5++`PUZMf8c|csYr0EpATb~)VoBgB+`1$ge`W}6!-;uv3 z<@^tg`%kL92EQEzYfIwH)t_CzOEF z93U3Qu*M zMeg!Zg#%?NvJsYWqH*x%TQ6y02CzC*$vBkbdyE)9PZoHF`u5W5Krr9$Y)H86%ti$# zWAf-gQKgy2+HSX!i>G|P!hl%7vYk3QPc_=Su3(mvrIl8{bMnw1B5(PShvM+X<>v|O zlHh0%`@R_;p%<7`i(*;c`)K2Q2i9EfUi=mA%a7-^RtA=Jz&1k!4ebB;>D7NR?H^qJ zgWT#cqB|+2L2dm@ckh38q_w~GroW|sn1@T={8&${=mAnTFit~vJ&djUkN&)%a&x;T zSa#TdtY;va^t{Gf@y1=gXhf}*gN9<>Mdzl~o8I3%Wr#k$5l2u?$dGzs7=_lWbJCDc zjf^aBBlg3&iy~dIrTpt6nfp~x+M=HVv>WzaIEt23O-+rhcj1o_Dk)8ma1ubchboT2 zRU9Q2$1uqUKpEdY153Kel7UuYGe7~9gwzYZ5tF1KV!|-yUn#Ag&ByF+FPu~8d-b`| zkZ;ew7)bpKXukv!KWzOsRR-%OGFr6BM(PxtBJ33!612UgLFIEQt>?|p_DBEbT$fkZ z;go*eiwxiQLi|h5SI}_h-wHy}hC{R)JA|jL7Ks*8&*^ZR??p5T%|w6$K;;>C#Njeb zVvunhtUk?-%}mUGqBGHl(Me;nXhwIsPAuJ@Q7jEMB$SBQK;v<3)9naa2(&@@gm?sv zfvwH>XJ^wDxyse`SJtv!%=SBfSbvXUKOxwBj_0?jhyj{;EpjG!j+qQ9=dgQbJg5cn( zFyFT`-Ft=Y)>XQv({p}#3NmTE{~vmmSqJ68o?OUY0X$?*o|Zh*!^EP&pz^tfZfVtR zBXpul@zcXM&EtovafPDc9%69}J6ZKCnW%`=3%sQJ#}ZPU?I#quiyuG%ArpyH=K=G$ z;5^po3F<$32EraY`BgmX4jzve(hFPEw#kgL;BqL>6IEiJu=Ji@tmjK99k#zWc%O2s zR(adasJHJFS#IwzvBJZyvXGjN0#XVEvFVEQ}kJ$dWyMuKhK<7@8wDu6|YKU_Ksazx_Zm)nkgoDmp zFMJSBq&~dB*>m%Eg2z^#ER=rnz{R%WgUndn6=+4>1l?y;H8m4~7(7NpF#=OD+9fDO z;^582_djD-h*klGukQ&!Eoz={a5(M4n6m3Bhy#io&A%Kk!wr6Ne~pd4hUuk_b;yn| zLvmooQ0NHEn*d-?sj2etB`r{$^rCll@N_;fc6Pw-B*n%We2OW6%0hbKU*nAKm}i|a z4)Ck4UbQOu7e>J{{AI?cKY!xI!up$~Uk}<)+QDQ8Y)2jH1kL06V}}q7nn7*hCfz1o zoPXS-Zu#~tr!jqE_={Tjq&FDp15k;eT17X zM|xjR1k;MjWS-l@<7JzG?({OJWnoiuO>u8tiLB;l_)$#qfX{+qI5dux>hA8IURg5* z1QOE3X%}eKt?J~I2g4cM0o6ugDCqF|-ARm(Api{(P!VQmhd%OiUkw8M&~yMAH$d{b z-3P|44@v2**F2qb9vyA+p&aZ}sy6>ZY}(dm8kh{i8$BtG6)P33!;{PY?H@LO}uJ`pB7lYK+MxvV-FcE$Ob8 zJ0R0JmRD3Mtpe%zA8#jq{|Hvcghm~)KpXw)$;l1Jikcd+TV0oYY5hS_V+tW6kjyG> zZfhBnBv_R$<1S*#ap4xr%I~C=_AU9@b14hZsSdIy@G{Xg*63_gkjRUl{FJDiFbkZG zY%t75dPJ1Y4NNuMn%&P&&|U^-6vkb$ z%8%O^Npc{S&SZ^FKxrdck?0<5m&5DwoZ>m|qLqmP#G-?yLNLXJ!Vr4gMyLCgbCq8o z>aAm;H$Qv4J#_Ijmo?E0?b_0M)W(|v_4Ux>Df!~BV~(o=74TR7gv=65ESi_iUj8&5 zb7g7Ox?En{y&WKMj^*%`6I)cD1m{2dN9X~R^3bZQ<>#Kj=WyoG3IR{b|Ff#0;VFxq zg9GJ~CK}rWg-QJjD|65V{C11FVQw1aq*B>r>CUyb1Nm%brew<>T`R=bm&$zRfPx?W zac?8efIR>*-r!1;li&wiQ&NH&CY80VWQugWDZ_VN>?TRFH%e^brk{%V*S+APlRqqS zXYJvUObxkUkRbEM4Sl+P0;C0cA*1;?-M9VTeYH1I-WKxEjyp%dZP`|)sHq{{kGK@gNzUaF%P-eK;PTijCT%V&3 zVUQ!8?dPf;J#;>l5 zt8rgMEPi-O674U_8Cu=doDKWOkfqx|L(Hdb>3ZpDY3n(d&jFw1of~_#`$l$dh(e6& zYnup6_5MLV>zy`qd3Tq5&4EEZ4D)r;U%fhFXK%l&Q@jrf@ubN-B?O_=u`NZDSq62i zdvDp>GbHfPB?dG%&><|8MaOygHRwAg0;9KJRrJQE$TcCsM$NKCkK-c8e7%;3rv8?@ zN(4}}-Q)>IK^t}TgbK;l#>T^4?*|qZ*2Ef~ZTx~k3CZ_oeLv0r1Pa#-=Knyed^k5| z&7J~4yZ-eP4$_u(S(aHCm>GSR8^6^#K0Jo^3Ho^YHv@f~ zD)9etJ)sWiFvF*$9h!NwFD(P9CFcYjP9#Sr-EWXneSz+L(XPEH5H7N_%gN`qW1Lij z>k_go@l(tQTyP!~#)qWC0HmQa{&*A5rgCb%l!%CzQ87<`<(~cGfrbRhG=-|`K69M} z#+SCohZyVU+Du0Y$fNaZAd8&o4qEtMO4}b$30D3!5dDfdAvrpso4_99 zrom~rDi(o1hr9MO@?g-Ua$yS z@3%T~jQ<`%34Fga_fU$r;_d$m;$pRfPG@RNYN}Mw3=C~0W5f)32l<-H>~v6(GSWw@ zt8Z{_{0&lPqp9N03pfp(D!cx_S`KC?8^AoDEZq(X)aU)$a1W<77kq^j>Ad0jFbRZh zV@eZsOc?-vM^3KHtZm=#xUw_f!u)f}vqTLAU%YU_b1TAN|7iK^-|OR~h44MkHV)aC z`~_ua{`oh=#o}*eZ@*4cdbzB54F!C@*AQ!hnausLDDx5*$2adTQH@}aNIj}-s_G2h zMpnmy$zCdfMwPH#@OeX4VsS{PkL^A89Tmw#%AySr42X?A4S+zMdRAOwtSvIsm3-VT z{uL^NV=_YlrKjQm0TXoVo45Qmkj(?gY5rx)oFXp(F7h|D6QZM|Ri2>mZ1ej^0Bd7h zi3u2f`}BD8^Npo=09b+nn?lTyqr$i5B2giiw@J6&G=V=f*^hR?12!i3HE+o8fcdjc zc1iwMS{VJO@WO?3J8}N(h^*b*P`YS>qBNnhYbpV*$4>4*conXrAMAWS*D{}=BA9xz zgZ1lY=bYte7BH#qSZx0?FJH$wC&1=4HnX7Wo^mV z2iiRn3$2_!=38DjyPeP_7>vu#XuTkj#SBA_D91$<(se)cNC;aBGpXs}FkL&B+~YDX z(%^S~`6sHvJ4xuT_l$4>tATIDQpGSVNoz|>9F<2-lTB>kdZGQaK)LhvV3u|}eEB^i z#rMJ22hKrYobf#?zkVt1eOJk$48}pAv`$IZb%C!Dtv`_p7F**Khs0R~Y=x!^Z*#5Sk!<`B_pT0@T?G1_tNK$IGlY|M&NEnN2hf!l?AJ5)u&VOv~N#k znzR@BwH6h0b-9nWG2~2`ID7bA%PHvk+SbydoIO|RHgc;ew*2PZ3mPvTo|>SykUz}L zTKefwlSK(##*u2QU7&q#&Aa|CDH$2f2VP;uXpyZ(X-nEMW(^4}PE$@EJs7VtzT17X zO0T}~duq`VHPoF*xmvkZWg~R?goJp*oFNzAhyXv>KCDy@kqOwzup63J3!GD1E`Lj() zV}4~jt3a4od;0VBA^By?m=#OHPx#`7!^m$-8*z8ePf^u&mC+Tk#p0gzk6pp+)zbE# z;2N<(nntfTx149$G(~0o>&^E!F<7X7*$z#gBYv_Y#~3VyW+a5G0`|F0^=89_NYXc@ zL~$l2r>4gHu`+Hw)0&$eQmw^z)puEtawelu9ou0n|U&kH8{Qf zp-^L7g*p9ALP<#z84(kF=<@099f3vXJRFxO-!i|CE6XC%(lc+};jwbZwWFFo#(Y)s zbV~6#5)u++!5eU6OdJT1#%PXE`JEqokm^v-13&o;)H(=8haqT*F~aaEUSfMNKv?9B zw|?h8%>5|)az)9~G97eAD!RH1y?cH+MVmLo%b{~OM0yss8n*XA6lJc_XFAyKzp95~ zR2uy*k{4jI-o{%_O`0>zJHyrIbMDP%Z_EhT8QIkYF~_)qqd1m%>51iZ!Zh-MypJ;h z^uzQi`b_pW<-S(zw&?!Yqg7~{*(R2)E0yIB*T`7+|9aqXLACK1RH-O0ql7KI=qobc zfpHqKf%_)NMG7ac4d{jPc%8<-F4%rSQHCnW|^2J1X z!o|{p#Nx1mR*wX>=h^k8nu>KEPw0IZvvfK*SnHA`_rWkSiBh&4nX(l9N5+t8dv^Hm z)rbF)^H%&CW)w?ZIn|v|x>|aa`qE>y%vzl1C3w=AeP=QPp zRPMEOyBU#Z@KmyUq=_X?x#o#-L)+QMpybjC=zeW5Nb?yTTwaqg46Y52)7Y({rIlP$ zb7>24f&;10n*Z2?sFpMKpV|QLY93)sQS%vo0R>Rt;P5F_Oc(2S5wE$I^vg&c^kZM=RQTlv7zc5YZ@VBbq`Lju~A#|t5?hiOI$fLUr+>Z_#Cin8C z-+B9@LfNef3@xwQb&mae^+$%sf=W*286Ebwn#~?bHyQSmr4mq#xC__mtB|70hdd#m z2}^HIC{jqykH6ZxQMNEQ&)5@o{rN(j{=Ps%Au^Zf1lHuIN0Z*fHOrsxj2+*8^&IrJ zRbFI)tHVu>q7fQreExOPPR1GhJ`-_|#z4|FitR9bh=!bq35*9nP3H+mXrLbaL?tUQ zGuPU#O7O`e4tR1V2?VNQa?A$4(J*uj3M4M+DD{O^+&!%S_Os~K<*O>iue9L^B}N{D zZ!8%% zWa9}N;572dATX^viSG?*zA1Vy2q%l>7dUvuc+=Pp|5(0VSn&I&>fop8ti%Jx%XRF z=8e#Rv@-a376}m(2EZ?0#TyVeHi#`19$0lg_hI)JJ8gkmCMphEIV_3&VZnEQ=&kZu z&n5p#KXVZ6FK@&fVWkw;{#xGXdXbUp$skLAg}>)<3N$8H-&rdv^}TnpFn#>q>J<7Z zfEdumzji=l$GfSwejo#A&2Dx6RX~|oki6n+<|dA zcRSPQFf`8j0wa-5F8(}|PZsbgG&$*JX!HZom+Y?;-ki9U1<&O93}M-@LkNcgv1btR z$)Am=7^4%WYoM4|eH==relfXa;(dZ?J_b8c3Sa4y(&nf2K?*a`?Hv?+VYEjNkfwoD3Td~N%1jQU2_@m`NS_tl4t$*J)cN(RNoJs?ilJAmFI9}y7~ ztPYpHuj0iw=T{ESk1p7iC}?WluKR-S0E?q07Kac<}CwYKu$5 z;qcGl!i0Z@GS{82557C>;$6Se?PR@7R_mm^XPbR4Jzk(E%}449AI@qaHuE~A`(Du@ z+95&oHzVVfHT4m8dSXd=mG-Ua?WLvSkBoqe@vJ7i=K-$Trp{KgxjN zN~)--#lzOWBMu+q$DJ@HlOXFY*0nfrsu>OQhIL5M@b7z`cqMCzcp%!GE z*>?xZ()yCDWMlKY@*5o8rjp99ZnQX4`8*nEn3D}Y)b}qZwT`4HS@gJ%E~G zx#tnAVl6B4;WtkOU-W#ZORMo*b&!M;+LuRhlfe6WTE6&>=DE}iwW@jZ)`|4i8A`GA ziHRQ|-y*z<@oeWly~-Nzu0K95@fk#8;$(#T7`l5xvP13tvGY_P^T{d~tz99WWp|}i zPowLWv~$u4i8>CSt$-?1bP`U#)2wQGE!&rV7R3%CiJ3ZiLj8v##`Y6I_)JCwTMCPZ zT5%Xw`VP|lZ1awjpU-+k`=2@#HJi)r#(hjrG0n@%YkwUSf9V?``V=dD_h_8#8_Wa+ z&4q3E>S(_Z`PHrL)iI;$G2IC;5%8AAHfdy|c)PyfmB1#bX1KOJk-Ibg{>_hfJ#;Lj z%mvr{`(>bC9Dw=XkZxzeY8;!ZjQ~T7>3{nKso{mEsqMIkI;gitThSM`0h$K3>7gV3{CR1q*o`i(P zcJqw2bsvQ3vAyoxKWM%F9`?R;{FLb*-Fe?r{+9BkAyC5v9w)q9=1&p#gxCEl?WW2e z&()VX^j|n6<88d2UHmmEsVlifvFk*?MANI#$H8=Be^g&_qVp6gfq7se{^1jnKQ~W+`muO0fVb;WYKAc}f2!Y>lA0>Qyv@9`@kxe>^jqP+;in`ea^?K6 zpX%{A8Z&&7~yo~XDz!DIE^M3-$0@eE0y>E69*l(G^@_x_lbA4%s0jd*F| zY~Usj#)g-Ou-+5c)F^(Xc! zrJcm>M^uZ3#M!M2^Yai_$qfvQZG%lXbbyEneu?)^*7JK@TJPlmm42==r2mi!!lg); zCm1{j6j5*Sf?~Yrp+vCw3Zffvk8W|%W$&r#i1KbYu!EQbc@kwmJfsm1zJBdHUw~+A z8{_*LJIP!0!b(HmFI+#TZ(_nxKn#cc32xc2ifWqJttG23j*B{G_6RQeM^=3o^dLXG zm7U`N+5G%Gv^{ltE20%W>{GD$#n1(6O#)jfDWd5N%WaEza6=Bvo*lBq`y+2 z;xK&`%p&FQ?7M{2F8}PUze9~r&~+B8k>qXiGCR2cIz`WcIN^`U9>!sjEWywaC4K`;4}I$__TfV zZiQ&~!8N%6DModJcR_=PA=yzP^@QIoDgu5 zp>do;PE!+QAmbH*E2a01VRFjQw740yWPL6B)D%XyZ6_tphvk`rZppu(mNbNJ1^&Cq zKqdn9eieEOKkc1VcXuxaAO;Qe9i{YGlz0>k?-|>7!|;j4jZp+k;Glt5v!#EmNma*c zp8H&f+xG4QO=2yp-qE+CzwB!)+lWQb!Lv%o=pp$9mfP_t*>BUbR8>%DcrZy#O`V*d zfA!+Ui+v0QP*R9S`#QV0NEO|Jf!cVrA5^5>`nDil0d{rwnVgh#No@58 zAy@X z8`fr=s$N{pxUlM#HN{8w3?B2@13s*|PQ7@V3dDh+0)Xm2u$5fvJs?Ct)&AWuatnrKza&2P=)nwC(YZtg6X>__NHP|R#tT~7+A1`` z?O-NsZGw)V{p_5ZK29&}bmgh8N8+lDS;ZOcwolh>tO%DzxrrpZ8wvBhss;?y5H4y{0SLD)k9ncAKbrcFB4L+GcdSQ{% z%_g(l$GGvPqN0N1nJh1juSa)d;|bOUcv$ff5aYhTo*Lab`vkZ1DK5&8R`ApGZ-!I0 z<{O!DU{&#iq@}AX8CpX?xVq>PJybnUwy_76py?oMsYySOjzKJFVC?JHugTLnd3XXm z)}RNjcCuRPr6ujxb+egTw>JhM9Nk#Tc#L;To95BH!6r)dUyzDj5St{_SkaXwUoHIf?1<*sklr-P=?K`hW; zt9nQ_@-s|^wf$D3Pu(j*lO=N$sYlN1=pPSM5T%2zpa@uM{sfzwdP9hZ z$FgqJfJooc((=+!V{dOd5(~jb4vw>QpLxEIj=k+WbDm4*$Yc(?ZH&!lt5?&)4@+u~ z?8}G_GSO(RVo}bEX7pH z>I~|naTap!-~GBQi(V`?NXBTyIsRJp7}QX+EiW(s)MIbf;bv!-3I=Vol28DT$5O2` zsGM14P_a9zwRz$%wzpx%sP0Fjck^t&lTk$-t@Iyj^=xF$wa+ivE>hN zZL<1wCHvXyoZ5_P03Gms5Ti9|threIxtpTC{e-`eT)_ujpER4N$zM3-mTdF~zyHg2 zh4UF-ULAee-MaUoz;M;)&+;9I)(7;~FN!TU2C&FL^N?%?7%)v9AfRS44b{pnZes?F zA^aL)CzWd?e1n7=s%Sok3z9tivr0pzB0Ri1W}h1ZrBT){Qw|ZSJ39%je9^_cjJkw+I*7bmnxA6&gN#>A@=o8Ifww#f# zd&VcVwB+qUM3?MU0jjZmlvt%ro9>ab_G4e37SFQEG~IZr6Q7?i#I2E`-_!?5gTB#2 z0gA-rWMx7~`u$+vuS1d2W2@o_7QH>O2a69v_A@wcejaLQLVo9?K^;@+>`gBn-avWx zIn>M-%pMwj4qL-&ya%?t_~64WUQiV?#gu^q;qo0K+IglAPQ9rez0?8Vm%bsXr%69p zV0aNdE06V7kNmI$0n}p;H}EHy7u}&(;_{fW7-yvwYAlTIR=3dcLmwP!rYwq`#FK#f zxi3tX$ijF$S_Lh$_q)!LbRnGT^N(e~-5yLWDhpUZQ~4{XZ49dnvbb|q%mEYy6?Qsq ziys-)WMv=2Ao251wXZe3Qt2@Wqq?`EBJL=7VIZjU8W|l`y?Rw>*jTy^+;HDOZB$eg zpHcfYkCM-tlvOk-#)fol{3m6E(uaT*86W-@>p(Jzkfzbxsp+1F8O-maDMUG(l+MX` zO*oGo$Bo#J!cvOhdNVp2o;E(CBlfVW0k9ME!KbpyK%@U$U67_+Yj=fy8j5gt1Z`Z# z?@42^70trr2^b42`qOO}y-?hhqe#k#BATSMG|_|~j9^-g(tu5~C^V0q5T3kjC-U|6 z2#=bAsrm~DJt50|tj^u`H-hZDeTE0b!!0j%+>Ti4Stw3`_O@z4(igyDH-YMD|6i{! zg3s@OzVO{JhqaHPBsNw8S|!A;FII|O3SJ+MS(iZzwTn?S@;4u~j&0EK{9msgilAur zq!J9BI^|{&BHYlNRpP3~o_6NM%P3D^R|+ZXm2cbd{ut89GqpV^unE5H6UV}hMB%iK zK-K+2Xr-VA)rwG4D*rK!!09j#Ff>59`=<5gAOis`q#AU(4ajggR- zmtP)HFG+%@1Ra`FKJ@uPI>ceE-sLb5672*BBfo%BaL?SAuge%011OKK>O0mo>Ek)6 zCgP<=7)k`^4$e`|%DzJEXy3VgyXAI37XCNV0W;vr;(TGwe(gcQ3RwKUFiH9@4>^yb z$@CD0nY@jq$TSU)d)w6y)I1%HRji^2;xMBN)MyBQMo6uASs!IRqT(e&o2+L`(OYXS z2Ys-}MoS|sgSNufV(KwOLG!*Dvd<~x3n2;mz-njm{ zEHk*fb1mC>{l`#icejY67dyShnWUdrros(?XstNOTs(yE2`@=V5qv$N7d3L*f(E{C zQ_WTH9#bJuW}ALgx!FJdM8l3*ZJuk3)Fl;;omaC|aSlfa02c-#Cb-no2CCd(zoN1j z5cyKy(20XIfgH(_sq9|K_!Zq=Iv>i-sSJYLn%Y`&5+ZCj;W`tj1%ht(sSSDyPOTd{ zMOXFAL+LYFcSi60x_muZcd?7QzxMLP>=6K!izCa-TwGirS|tbKHH!fUny_%SOnNWq zil~TUVt0e|tDRS8>$@hUqbpjELL6ni9JRPWh%`c21_lNaK_vx~4wUDPg7w(U)ung9 zPV#x5oECpMRO=Y%8-Mzp58jhTNqPT1^RMx7Kz3+fmq8W^4XNQ##XBF~^UAz`*s^$W zFx>Av!Ly&J^xxtYxo;a5&W)oKSX?S_&0_`)re*2WzJSvbFm8yarH4qJquIN%2As&> z?YZjLb!!E3~cS$M6`7LS0CPT3F-HN=R$%p{g2p89Rc@+aYzH>!ahbC6gZgFn8j zW09p`yH(ZJ6K#Hz0v!A;RG;FkT@9^zjtiCp6X#*v|XL5=7h05 zW!fXP#BK|P0Ru>s50iSrqhUsVLG|PmCA-5QwRW!udT-KPv@hE*f9vUdKN$zgc>;uA zn8`zi=^xrW@uQJ$m%slpoY%vLHj(u4IzG~nVTQ4q*26=Aw|@5q{*E}FE-Qa4jNg`p-QJVj1?o;V4d!%5 zWKhF`Zpl2MB9aqo=fC}tT3o8OA;ye&h{e%!1imAvN1lx4C#9vfN%Xo&ck6sSyHz%1 zl%deFR}>wh+EKa#>pQ794@X-LW&tq5%Yjo8pcMmhFFUzCY5sNd5NZAM)04ht*Soz# ztn3%Q10fX}7G^pcol5?(gho9r;#>Tb7-ZZ8#u)k=TpuF61&!jMw+6>J0~a%Qq5C!K zn1v6B81EWRmckVBA`dDM3%>CEWrRI{;;)J$n#G^)X+{qX zi_GY*y5LAw%b#VASd{>m$-r8QFEcT-9p(Je-pRaQw?@?vMb7#OaxNOapDu93^8H4p z^Oa6IVB!SI0QxY~b&4#amUR$)1^@2!@7$%@t@|3Fl|Ey%jVJAkpTm)}8W;(*Ir`dW zD`v>nXZ46HzU;r)f|F$R@MKP3$-~dpGHGT91Jg7bs`yh(4EMI#7)=Q)AY7}!Asw83 z3W(x@c=)}$y`qSu9QeNlsXWy`dwuj-cEg*r_sE7$t(4dAIK+8DSRJJtDaLgdHaCIt z(f7=}`?r^WEQOPsSg?{}Lo8A~Yy&$}_Sk9&;s@RAV*q*Cud(+_XpT@+NSEz%bQJYg+T4Ij|EJ^u*?`4t?zy zBJFXV`3y69>c2mawte^Grc&SC(U05rZ~6J7SNbLmF-Cv~JxvTX!%kUr#-XYMK*a$s zGDTqg+1tK2vylR#mA&^prpyLR%|5H`q@ehuHv^~lv||`81f@9C4KTv@vFY#X8*8dM z%j!@4X(sYEfw9RpoSN|XF@5itmRkIsfImPWR}a@+=$g!gm$-l%p?i3Gr=m3bw|$+> zc~X;PJ&3FRlNK4-<=6!jT&oHekX0#8)TWPTUKfHPz^L#AVN{;{*@QEy!cW(lQ@p~h zDyElOEUmY?HpNe2wR|Yl@)+YxO6igjjJG9X+Vn73TpqwE7R#M%Mbp#6HpHxc$=9l? za}?O@)V?1c`eK-{1KVVvL8tzy#@mdbp@JUn9INNgB;G$fR5V!_H__PC{;DbYk}AlT zEa}dF+>M2I5R928Et~sgeMCjfj`75_KIFX2VovjRBWA&@+>JAst_xyjp2`FQe*lt) zy;NyjPk7p;&^Np!_)L8=zKWX?!}lFO1`^`BdUKklo>a3EdkZDwEx4_YEl)ncnJefh z`9>OD_>6EN6UisK`?)yj)7sWDa(m_)gq6~C!kigrRD=i5Knz8E(d*ieUW7beIEXd^ zA1NVv5GTvTl@6`*;QwE~Jlw+{XCkExzXoC}wQ6owgHsgX#VkcPKJ(RYhsFf##Nx32 zSk)DY=l8!AP2HkjoiD>Ra|pbdn7_q+HScKR)DFv|DwEZ^u(kG(5{uKt;?xv&Qk!d= zc9xMJ68NMRt3_=D%?#9vjHZ;nzlCC0A94#j zw6Glgeyh4%+qk&7TOkf7ukj|*r^7X4;KedEoNgc8A3Wp)UCYZTX?zyizLPq}OB&Fw--|S7ku*Bx^ws~v( zezDjS`!;X*HW^fUKDqUUeBtiA?S$gw9bHYqwv79S_ho|12CZ7_waY#}{)gzN{NHSb z%BoM2q=>N1`L=zv+Fv!}w=_crl~jxE<(_4JhlVNL~VV?E(#CM2aP1c?P!8u#f#X=i8W-2!5q#Ekwe z!WHv6RX_{U+$3bu@fZffo?VPb*@6(hk)QYyP{A|T zx6zns{B-Jxo7jRqySrB(-J5s~tpO76L*!4b`!#jEi&3fBpN*QOV+N| z%ZDJxPXxLv7+N+a#I&qk;MU~c?s84g`X&=GCPCmuFkWbLf3;{?xw9zo;zwf$6>%*< z!BxHDf#Yivy&mlCR_y#?^qB**XFuwCY`k3<6z4G7G)Kl{@&YeL7Y)S~8-)z|RY&4Az+Rv@#8rSI{ z>Ka!_$O4nZCk*n(YKw@LNFseUf=R~Yb{P0tzqOvF^q8SszPqtc=?=wTjn{%Ei8P); z>XwMknT4jG$#UTZ*Hw3b!0lo2-T&e01@}nN&(Qo9F>9ZY6Va2+x^?`A*Pr>g7ERo#>MWG3Zdfz z*zU#Ay!(fBblZouuCP%sk!-%ez&0qHxpy8~V)~(=9ZJMd@vMl52>8~xR|g+UT9EEF zV+uC^7zlfDu2sQZb#oiMlA|fDGZJ?9^D?=;x@d)Z4hOe%bR@#O0h4G&>*bZL9y7+i zikB~28~b6yk2Yxqgr3QjN6ZWJFp$j`rc1SdbNT$>hi;0Ow3Lp|#LYtHMC)tDr9n3| znf;BcU&%R!^%?UcFa}^SuyT=K)Bd5&mzNkl(rUe**8UL?RUUk>ILOk=`{`-aVoXiu z$sbEb#h^G5Zg@Poo4+FN*DzF)5t0QiV#11Uk`M_ko!G;|DRBs|)w+j^(|+@c$~O!cUTVqDRLlpd-M##Y)M}iEXPDy4mlt ze?{f=1_^V38iq2=?RLxFYdB`)^mP>AIJMh0zkM5o{wLLLd?LKhvja7Cfj`eIitasU z-U%=M{_zolh%;POkFEYl8r&*Iy;!~O#sra_-P*phg`=USxx9l~`3MCQ>g9;8rYV=c z_I;m&$jjREPj_!#f?;*k9qkG5#=|V=$cA+0E<$?^$OfRX`?fFFWTS6lLpnVxh zsMy-MPbubErd(L0B=HFU9Z6KDi|cH`h24=n%bAgoAQjw?6W&dS^^b%#G__IZj8Idq zpSQC^yzXF%I3{xmsg z1m_#|fhACYt_Gd?cFEAvJdtW4^@`OS$EH5m%?e~ODld+{;Iu3V&LU1IGuLhiU)nrQ z;*b71u!sn{wUl9FYfIZobJCLsz}S|XJ<4`%w$>DV3~@WlJ$ldZ1Moo50FUd5^zLYa);hsJ4;uagja|;0#>6i4J&n zcoY0CRv+XVlCH0Ig)@Z;Mi>e;H08L@aMLf26S-xKc^gcOJzpu#lj%<)3r>dV=&IL5ZxLT62)Q7~ zv9f=7o8ZJ6y-rg~qlE6wBZ+bY|9v1TiUx&Xb+|EEKGOQc9b7#f2V#df`c3;DXmp+R zL)ZlXBc`4 zs{t{t$V#no>)emy&$#%;gy3uwYAU8b6WsFZ*JoY?FzVj`BZu1oY>0r30z7sBLU96u z(>Zp^NmSwTCq*eYpG{zQ^H3-q)*q`YeR==B7a_dX9bw#7Do8-FKnc#*r0E5He2Uif z(qoEm5*w4TDe3Cg`-|(7p}vsl&*;rd<1K2?vKPovV9ZsRb_cQujNiPpURQeJlRarz z7@H_3F7f4VILInbdb^%BU_yEflPuj9w36J5uet>l%LLHl@!suE-S>j~k(%tylF@pc zJ+X5%b+6{ly5AF(nB-;vRmv5Fj(}bl^jBJI?_>P{?Nro2%W=1YGw(GEfZu(85INq7 zDT8i*V+oTJyrF+K{f+wLd!nz<+>E=(NzH0!BCGd!0tengCRx5syiablc#_;KFL#ui zj15hPfIm;ou`%8ZkcdEyb8B@7yVP1FitcY&%kvUK=c&sdoSb5}dO@rONZuQj&;uk{ z<-b;JanoO#0xcq8mI65Y%^>Y=`(}Ad?xu1$V2c-+5IFsy8ZV`7Kbh&G%e5uhTpQMkdJ4Y5byD5Glmrg^*#p38|lOE72US(bH3)Jd&i6D z(^CJs!U#?bbiI{@>Rj7qKTi#(FRa-q&DgBZ#^_at9wEu1 z!bQm7Pr?cD%G^kD`pDpO%eKUNWpIN>+huO6#r%l2p5{BoCgBXq~y$ zaJO}B-YE#FvVIavElD7eDA~jZH#!JZSZuPLyp5};g%s*yYzdt)Jf)=&-!KHI6^CQ zWqbR1Lq?*iZKR&~W%@OK`^j#>{F(0VhtC~x1vHyOeKY}DzZ??_Fbx5{@Hmgk@8IuD z!u4yY3fNnwAyj0411Ve}y5I)}s4K@(T|(j`?KOQwRuvEx-~~A{y_Pp=zIJtS%1jgB zcS)H&UbHoiDP!-lK&{m6Jeo>Qd4ZciJwAH$h|QG}dei4X18~&XeV^{kJs#I{XkI)Q z=bYfwf1*|;_khGNy<&?#UMD041*@YZh~hZg_)hLL!(~jM)~dKr{(tRVXH-+`nhqYj z0xF`?L4zU)f`W8{4GdN3Qbm*&@d(mO;0QLPXrxI;kP;w>5b21Wq8 zKIAZJ3Ka&E8@d$)oQ{AY#_&L2TGMwliaY_gY5n$2r4`zgduwK`==R!et~2wMr-tZpumbMVIGl7u{aW! zd!zr_sDEuu9Re@oy)^i+jwzQ4Ntpj)4%!g^)CgLH+Pn>U7@v=sKPEP9mVzW+JCTX? zTQAn#BD|$^BEQU4tM11kb);2B%sET)sg4MX&Jlp;3ikH{BAyV>(3@9Fr{4CUvHmb> zN>`U0$;UG>1E6Ot35CD|u(t+-Eggx)&D;4e*b&m;=ZWVpZzu)_JA2wwgh}Ub<*$Ek zd$pmG!mD|9l9y^fn63s=sFHjx4c9)7#k|)WB-(Sns=nld)M@-TRbIa4i*f)}1VI05 zxGuTW-cgTrf1gEuUPG^S=aquEpdd{_AyuKx96K+(BVy1g*XJ`*%x_BG6Q>;5a`r-g zh@c*?>mE+gYg`io`-y^_8N!8Z!%-$0wDTmakaUf;cqd%|!h}4h)X=nI!eVTH|LFFz4t?F5I_bwcjRx+>- z#T9YOt6W!)+HOiu9)l!$$TZ{o|urO=gGRyx;KnZj7WfiSYSr>si@cRfb`;Mes4uW6zH^q~ST11c>RJdy+eF~-8=CP%(6#C*-{JJw%H`Z(*d*B9Ec{Aks;bgc>E%pQ7gN1*0Kklsc-2F-k<(p zIhlw59mls{FN7=m-twKYgB>{Mp-ciE7tFoZK6hVgeAEVHlP8A+!5GH$WJ}#^m<{Bc@ zzv{95qG~v1C&#MHdMst*6}gEz)Es+`T)$za>o+$o{%G~Y>)~N5v_}hRGcB*qN4F2I zu_2Fs?;6qbp}?p`axd!4o@dXVb+|i86p4$7JT)k*zrUKRf2Af#L8jO0^_$t**FBEM z-$kJnx!r7J`l z&n=s7L-t=l&*S>xQvA9~^lk6pI}1q#l(mCX6%PW^8$@JI7)p_1yklf8E9PiBsg`=o zNMIFwmtHIJ>qZWNez@iYWypDik&pAKdAYeUbA=s=LQ+%Hu&B4qhAsjQ29FP_6_)Wa;G99_9r>I zPIg_%&(BwIqqeg$=WL;T2P(#!eC8OR=Yk?(j(^ZcO3rF zvF1Imkq3Zs&7A`34^KYGBaonMTZ8cO<;#fJSgJm2bp5NsLTPNL@zU^*AD(>C&!5}m zqO>$Mhtw)IUgeU@IOA2d#GEo$5j#w0nsJ_`_gKn9H1^5ClSW|XXxl*hM8+y}QIlYa zU%q+&ey?X>3s7}cdlRQKucVfDypVH#dG6dfG#1#UFJHOR!kTxGlhXm*9>FE{vF1rkmj5vah)WBk|zz@+507gfJhO%pH8vzv*>g^DJ z0IOCwzcO-h=pm_d%gf6kH@dZL;AWA}o~3;Jc&TclAy!5c7Nf0Bz(1$FTnS-8kqrf8 zpAaCA8TZ%#6GoxhF-E8}s^=fEry`=F(!07$WE|Ts!Ln*-YUqH`v2X9yK-lC2+F}Oz%l2?+vsu} z{SgrnaQ0!tf|y_=vfa`APr@kvhx_y>;1!5UM~H|Vn#&9Gc3xZY!j-{>g)eWsdI z-`aohpmBejs`9|E*bYpLdF>(n{^zQ*P!!jZsO*Er0ybQwc$8ogXbjU_r2hj%B~1P1 zrSN|ix;5C_i};a!0|Otb6sQ+3TnHjOe?H;EjfZRb>eB}g{e3;|aC>v>oWl&cSO4JQmaeW1m0<3rtwK*(%-{;yZ| z>?o9ABS){)eToQbkO-+x4ILf8g|5h`uC5NCEU(a-WL(ECfB5hLwx_wXvop7XYo-b` zHv~{n)Hg}HcI_eqlp_L!1>Z9Qoef{x&%u$}SK)&L+&%>$xeEx}T>OP}*A;}xeFhke zBGL82uyWXk@& zE)1{YJ{gm)lR$)36%`fLu(q~#{QBkS6&74!u2Lrc%c?_n1w}47{EzQl+(h?Q6^Zn;Ayk)6t7D7%$ z_WSncrY0@if8_{rYXb}aP+s{{$>g28b~UDktC-$YF6wczZHU?19INVP!tyBj8Ilcz zBJ$5oO~wVbjncS|6HqUref8>9Zbf&1jD${|)`9ndZgi?))kHZ|&m!Uqodx__2rm<0 z#F1dFk^d6XEpWQw|j|KQq{6DGcVmPGD9#gjY#HMP!2f( z6pL+7h0dd~0H&sAY8s35)&%eg-0S4?_?803Xy?~8c!%7E1}&_fqa%^hhUl>jodf`g zA%T1!t6|6F6v(4wGiNsMdtHB?UHJz31?Z^)n5#I;B(N9hxQ4jjE60sP!R3gB-yHdb z78ZU1yO_uFq=<+l7K`mrqFxu?b?>~zA`LLt8X?or2=*V5x4>msW5}f>1l^XUe+)G7 z^j*H>dwio~Gc-@7bi1ZRHG&s_5NiASDv6=??s2Eno}8?-&piy_d)UY!sN>Xza?XCY zJ>MqBMxjH-j2VORuk4fp6ue-W5nv4anaw5%0EV!(+;>MBxNJhCkQaT<830S%fq%H0 z>$(zu6Kh@)#=q~Ia~+==;c;;ADVU|5CQiwsn*+A*gx;j89FAF7wDPdyVq&Ku3Q(X^ z4H%iU5}gm*oUun06?-OPCMR#<8bAv8kyyGYO=KYB)HS*LwdAf{LVEy>VyxQ~C);%C z6cqht96x?s2z5afu7apYN4O+Veo=FEcxFG7@L^$LX9!yGiL@b^mt1cg2Kq2s$<_gF zi!A|xTUp=Sqk+Ej*ypP?~h+ znt>qQA%uYhgjG;2l)JZeEnp|iKQQS)h|1Hm*wSZ8X8q`{_ID(FrTbJYI?JXm%rMNM z<>h@-vZGs#XR=VKH=PJgSqAeBFxDR^J#WPc+&jo3c&{2T^w3xc zqZtB0UN-OIYz@0v4@4vBS+&3{2lic7+=9ME0#u<>yYg*RC@iqj-J-{j=f6FF?i@93 zYkZ!HxA)4@9K>b-g%Xpi&hPajYGI3ktvZGW@G0$*umr~Zcxp@x(1X+swLk#H4X)<- z(iBiNsemzFk;BiAwTRaJ7#*De?mXF$ZQL)x!!r?bbL-#5Bjb2Od^a0MDhkDV8o>qN z{+e&|5UdlY7rzGoPoA88r1 zTjYxuSz}}N0RPxDvx@42=|@1b!3ITRfi0l};)3&_GQ<=VOn2gTbOIH-%G zs35D!>2Y9W#R2gpAq^N$vw@TvKecVPmV_s*47VOiMWSW`e)G@h^X}t}om24Z@xg}z zN5>vmO)6o)p6o6SSwcu&Vd=ghKX)7q|VkJN>RUo}O)WTvpbYAPqcxtX!wi8{;m>kcA>Z z=*TchNl7s+zLR1u5#0nsw=u>X+`r$XM_5>x+L@OG{14ebe%!eK@FB7S2uyRK`;=)( zZzpD2H$fqCTlo89?*1cP5YLK|upmOT($h~_QPB*d*a&u`H}+_dl#AtF9L2M9j4-Rj@2;!bzPVjm|0 z%`nUr(iV~EGd<_@QdPXY&W;TWwT@w(+oY721_HUy+=@XvK<)>r-_%8<@=8NKcyaXU zUNfXV9 z{5GWLrc!%f<}1chLrbX}jt)eRpYwm)y8HE8kIAk@G(WC7N5Gw>|&H1ToD4w~BJ*s4IpPgYADQGa=BUS-EwM5|X^1=9flXis7h!wCVx2l;H)cM=Yu zvNCY`VD(@Ek;9T_Bf4O5`Ev67yen2O?u=7=hOR)8qmbuUN>gMV(|0%@Lr-f^*FW_>;^5=_N1=W7pdQ^wG6XXN{i}) z{-A=qV=m4Vy`3qWsdTJcnEcvS%K8ay zN}>&%S;Md%M4BcL2;cQXA!;vr=FCF+s%HVx?4}31EAr{n0e!xAVR+IBF)>Z(O^&V` zx}v2Og1`goBq%IbUT3Z_Ga5oggz|@dk z$MvwC7?X_jbcR6Vjd)DyxNA!rV6Zbul9d)ownvda)c&@@mIee(=3Byu`oe#XhQaIUm3U3&EF z27y4(vbG+C$i;yJv1w^&9KV$x-B6H?_3Xzm{FnOr(=fkk1P4$!&}*R?5WZ>YBPOPeKQDiIwdg;+qr&_@{>h~*JhKBCnxoIFGOqpCtp0m092ksew#R$N> z+CYyE1LWdzaG&X$a^rvT;zjM0I}bJ;YzuHDCXLuxCBd|#FVkuOahEvdWfSu0^XE6U zO3+wVJ2dxgmI2455$s)S^oz_)k-LB#XQs3-*)}Imtr;X2djtUq{~Si5D-cZ52P+r? z77>YwBzDZoWkN(uOeP3Yrr{AOsVn;FS}ic_$hf#{h(&29C>VlFXob`Q!pH?_1S|mv zTdV!}2Z~CkM-dd>cL6M=F2liS2MB&Rf_(&i^Jc?7Ah=RNrYw1=ZufSNPh}~CW8u_Y zh}@dqs(I1@%poczI~wuS5G@N48Pojh_Ya&f5yU!zlhg+p4aA1mv{<}rU=V>=V+biO zNKp!s`2cF!OsgtDk%aeLM*fg7=Gn4z1b;{Ef}u}GK&;O5PS+r>iM>bi{$*{+EnRIlbbY$&va|K+RT zvRb*JlG;5s?dWo#Qz_}-eRkwY^{;DcX-NZ1!cRtDH%S7qpHtPtNpBvYPun&e!ju0L z&#nhUqTbeNoygd-A0)|_7K-&2!A+k?c>HLBM@}|-`6?Nxjc)aplG(!cyiX%BOUlc? zX~oO66q}PAF}R@n_g^4_jV*k?jw~qFB$a+|{W#8mS^(^uXi*o)b?Lud6xY=BniCTz zn7Nzl)CB;bK>Q|TMS=D}1RP+*JE4|C&1t_LYf(?`hbITcjvP4;J>S8hp@ykyq5FAw zvLPIJ)7BOi@8XK%V>7TVe{+8c(Iv2ld44ZXiI3-1W=<9$mKqqwriJz?;9FCnd9W37 zAg=&!S`eikbj_%9b<(8-fyl>!$U&6&JOVupvEQ9LcH}dbku)-R{74#l>+HvXQt%XQ zZr->trt8mm4r(F;HVe4lYSiYGKs@A+?160{R?0@rPnYBDpEtE+!dZX&E8cO(%x1^0 zFv$NDGx=W!SN?rrz+aR5uVYXDo}Pa(JqzoTS@ZNr=E=Lr)L}2`Tp*phcJDs`y{2xr diff --git a/docs/source/visualization.png b/docs/source/visualization.png new file mode 100644 index 0000000000000000000000000000000000000000..b400c9be0bbca8a22ed241e6b8112373bc918441 GIT binary patch literal 220018 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%0Dp$ol))-Nu~nO-zWxrEG0&H=2e92cx2+ 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 MYnGP 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 p4Pg9|v>5Ve~ zZ|`=#R^eNq=o>AS$g6>hk#m@%KxVEz` z4e`RuutK%9;Y?9Y!d0w7_Zsk_t#sGv3p2ZYi;fT^{UBmxr9k~9qIEPkQ=U}cDr`<0 zY!e^^f%#*uhne}Cp?Iep2hio({O2mNAy>xBi?G;s4;GSOFtvy`8Szu0#la^6C;ly} zc=c%R(tdau6THN&te`d_2K9JL(UTwCSeW6jmWE~$wRM1`2Td!iCa)tRfNi$z@LnI9 z?^c(*g#)KXA|W*;(c^pY+;I!=dK452Ce#RX&7r_xf%{tky6cq-6jWqBje`P}S@x3y z@29N{NJ>f&oa3;1+4VUB_H(1T!^KGoh0L}Y6iB)sROigqA15 zW@iRqIs%d5_6FB^oKLYff|*17^=l0|ZK1oC3ok9_?qgye9@I62ChMzbB2&tBqzmtC zh{J?EeBar5tSA`FsX9E7YeES$$}Zg)sVw4f*@EQ!YA>jRV4vV|e1yUBiD5zvbS}1yYFbt7;$()~ioG~^1#khxj_xs*FR9GNm=2$l`VGv7{cGuYhqELum zU8^Q1iVD6@*dxILcjN_xtojpQaB(Z0?y=hE0MF#++MicIyXlkb->gh=eM{L&<6l)M zz{QVewqUYJFT3iG_zJV}l9_oFq}g}G zoVJx>#Ras&OXJ(F5xbYQMx=9b@6<{s$SI};q?DADfD|qvA+c6`tt&$rwZnHVzu#l; z*ZU48A(9-SQ2BI)_$2d!+J<}Q?z6Kd!J(qa(7V9z&VMsg%;Q>PJw5d%DPLt-fYwqX z(htU<=c^iPX$v7z2+w$U-1irnoMYDZcl=a zKU3w~z$2UT$$lfxNS1di+0NEmcWAN%ww3Hp^1ec1_|E``v%vU+$AEbQLw+N#ZoSHI z0WNLP3N`ijNj>{t%_>#2I*mawbgrusiu&YARtyj}0M6z>B3wn4DvjDkAJeHx{PHa1 zbo$)jl%2xE#jlG;gYbQp=s)@#WWfskoA~A%HSqEvrW{2DX%Q&NINkyS7*vI4zW|FO zFj?T9r!5 z2Z9g_#-cbyL`ET}@G}|^vU@dN^uYfvow{8TyTdjfG5=?1ZqOG7dS_+j{>1#$Gz@?L zv6wU5jxSN=gA_86uMg3-9YER#C#Nc1ff_ogXHS)R$Y2WT`S6=J-&?(7=?%G*AHJ5j zPygmwc6O^tM>i?j^FPOv;C)n7WHtFxTO(Rt`=n6q;OIA~D@}(|8g+9EP4QgHs1M;P zAreX$e$^mPGuJ~M^XDW7*v|IG5lJDvQRg!N4WBda1%Lxcb=ptEiTgOL7ON&jT0Fs6 zsicP2bogWbQdH?t%K>t|Gd^P@P<)t;9=>=n4Ed`xepdavuI?c~Rg=Pnn$6|Tt2Q)9 zr~*(T{s}k;NM4CdO}$+b0$58%2EyA(hNrv|j7=iau@cu8igFmsE9dQ)@TFmnf{9tZ zM54U>XvVy)U#37OX=evkzkY#ebab$Yf*>~na1AmvVUwb# zQGu@gWlWVq=?)b&C-&v#m8_4M*4Nf>ViN5h8u`27tiLCE@gxN8zCuF&ts!DxzsQmN zey6=(qXNDV@9b%jcEGhREJ>axVFmj8$28nR*V4wu282I=XH}?PYQ~xf&BNdbuOk5X zok@2XF~mRMn866==<-0SILcI^g+1=t`|7zZGQDHFV!xfw$_vl_KEymHfYUBAE{=+{ zQ=p0Qmg(@~ClrpKpO=x4YQOLUN+%|YdawPlK-2!M1_tS15|4WJ@SC#>eiNah@eEzQ zimJ{;HjgV#4ofL;*aQdje&BAXkBn2lx38w;!)5QX^9B^!G(Ks3hCipjLJlQXt}mJO z??O7a4=KQ!--eVg`M5wgrTI$9Y(!*g zX8Fhz13i27ou5Cra-VQVLo6x)Jx@8;LUDCV3ib^5dBAggo&9BC2D0= zKKU&bApXNOtM-!q`n`tbtyVx^+;BC0QPF18X;v)#KAt=uscg{>Y&t3>cx!*KvkpZ=ozHD*KiB;(nxNPLyz43k`58S zeG~CTK59d&we>abhwc*_Jtca;7+Xm2u1A??_Hcu=!9d`EhD5VDyMgI%F z!f9o04s~pNZt*+2zoAK%Iw(1L@ptg?w(aVDR8*$72=s40c8JW>$Zw5^`US4^F~S&p zupEL=W=*=k5XztfP3Caniij537(AFNU*6I1xT#|`!Ajxlqmno1!Lq&Kb$3yC`wSj3 z7#P6KyS{Y&vsfWThM=4au&=CoN6>z76cwXWLd_>RpM3M88&y-mEeaV}jnc@CYWKEJ1$jZSK4_I&!qSv+O%RJ?)0*Hz3 z@$~8p$e%0nk(`I{OGd(Uply69vvAVl%}W_0&vL57MTvXZp&yYJud`E}Vo~fd|8&GE zJ2x)xFIq^+hQvYsg8-jdxrueQf_wFK+;q`KkAAJ~^L{c8p^(J9%6wFJ=P!X=W&cZj z2i`)CtD&fr6k<|R#>>7rqA7>tEmg95`wa{h7-7_lHQ8ur9=zV2sX|8<*3?Xd=%|TF zUiI9&Lyc#C)TLw8nD`tCGElo(Q9<62fZ*b3CIFycXm(CXPNr2Q;#%olSh(RjHz9#0 znS9kl(AxOws-)yds=nx&B4zj1IaF zS9rL=F&k!iUYl$xd9;O4fO0EMDej{fTGxApp)@2p+YyMm>HC#xKu4W@Zo-N?T$R*&eH@HChmeg+fEd z6OJ%Ti(1bQpem{BWn0d=%yF9bV4KE)oe7v*^w9@x13y?X%S0I~Ybr=5lOC z!t^mmZjDm*&sL#QlbcF0;q+{#{YJ&imyIu19xpX1WD_YKJ=QlA=CE#S^)66tXlTur zeu@vIX4+Kr0#$MpltdQs0`>ip7oQRcREufXR=?(LKIq*<_!I&oFGy4!U3S_}kduSb7x>rCc)N+4-#8`=5{ zIO-Mz$Pcj)krBR^=m3tvaq}ZC9b9)dk4%Ab1CuTc)}5%}kSn)JfmPJ;zC3gg7HdLs z4n`9~PKAer?|%Gf3m2eWMS<#0m{nlk!Prvtga!E!Q4`L+2`x~?QBX1!$u(P2lUN0Z zQkR+i*?|$;hnMf>_;00huetm0&(ZLhmcaIAVso!iysSEun$@HSaSkuUIT)LY3TJe5 z>{isObCa$|aZCmHD)!-AxOn*ML@$&=@^EF){zm46;~)H+D&}UC=g>nDKGYKopAJY6 z%p$XMuyb;-D>0%T82nL}pyNWrOjrCpUPtBaqse`o==v#BzPV(H^hZL9CtA01a2ioa z+L1`Tc-5~BHv+DK8|7qf$AYh4UuO=VU0f_&{)Lv-`8sdTcLsyFAhy{Xf;-@|SKscF13$roG@bLB@H zU7^^N+bIi02Jf3@<3Rv4p;eieo5!)f3j%5K-^(`sBL?v^XFP;26i@kR*~~;PPrU!Z zsUd(;$EE9~Q&Hg~1-8O3O?(dAM_b1|D3M1F5MxO5Vu+T4%+re(a~1FJ5fMFv1LMO7 zcpk!g9KMK?Q{%e_Ee8Q#v^*C+;FEel5QbrEY-zuF1@Khz@*bzeP%dCO^VQZckz3X6 z{Qbo;n(4&Lg=+Nt{5++5It>P055d)4s16%Dm}8*cE6IAqoVlB6EPjl5)17Z z5&(y*vmV=%GJV90lMl(n;gu{GM}YB!>CtEa^c8@l0Wuo8Tj0=y8jbMt`F@%HME|Z( zmbJTerW{~QU{S%SWcn?bAZ0NQkZwwPr7L!`Wxb7zhQdl)QSjZu5n z-}}()*MmSO9^zj)bFrEygxg$y!cP+je8Tps%EA;5gxXr07Eh?30jONDhVatTSR>c* z{!n|sa3~S4Q_)y0bJNwK&g}X(R>0voZg&``NiHl%NE>EigZaNqKd1Ra=TtcYxMP6r z6UP9}7tk_pzp=S2`nw&ob8mjCT6Swzz45jg+?8owVIlbEaH97UEYyTU4KzrKA1;E9 z9OjzQIXJSiZ8rv!6QE~cFKQ}`cttwyXDu_!C;)kyczSp3aP5w1OLaqMYv5XQ)Zx@= zN5W2}c79o#qs!aPpiithF+4BRH`1msc==XI`P@#U&(Z1rZo#Qvf$`KjzgxOV`=$wtMlH84~7gVva zI9eJSG%*D&`nR{Jt3N!ZFLECdxT-O;u((%SUq(A8-VQ;WoL;LM!f4;De=R^ifNoMc zI8|Z#0IwOD5;YFsx@~Q32}8vS$5XlN*SE$C)6>&$Zf?ACHR-*IDpcg z@$vig=z9zfe%WIH77!AxKQxIFz?W8~c}=Ro`Xd*6I$s=$QdkM$hjIRM=L^qf+GfAw zYEOjeXbvQlCh`M>avLnPVB6N zw6s1#g1fV$0E4)_S>3+uG1c#S2#OslT4od#6p{eWf_A6xY#FV(y6evpO#1C@J#BaC z)ZUI!Kj#^{t57Ie+_gS1*HF_lnDbwf^i$B+<83Z-Uve^ud=V%1~YhmaMH-*-_+6@7INL$lUbMpY(e(+!l%5K>TU9ccbwJc)w^EIob6v9*D-|Mmv z{yyH0v$V8=3b)Xwy=>+qg{84|?$kJCWnbCMYi$iHs{GSPi*?ZO@sW^6pi#S_hKa?u zk_9fq^^U+ofydanoz)y%&;f1ziVzSFLkaEApR<}M+-ns(JyicDMX!eu8t^0hK=A>!e$cXnsN4t)P1g}M3Jd4yb`o)>^OAWNpxVDno^Bgo}70)(VJ z2so@Dc560~1!YGXZJ|U_Lf&-hoU~HW(Kh}mzPNT*oEW45F}Px%u@Jh#jg8)DW++oJ zTayG}WM@ljWC}X5gE?%W>GxWkzgmdTU1+j`;#mkQZHu)OCZA${lTwwaG8?O+v$G2# z`28EMPrxDfMijMW1{+-n9;;K40~9HG>8!2-@GG0yaC3ql0!!@yis2+4$F#C3Qf%h2 z=r}A8Z#5g2THRRreg_y-dJO^PhNi#x0j5m(n3tEDF+7|kyDJomVo5l$z=y$rvEM`m z#0%*-fHQXxSxW!~#g3w)0y-oiyu9gmFQIum1HMNr(=eknT<@&y$?3j!O=z68RBi-))^Cq0=;~4w6nWzfp zk;)xSaeQZdeCuCkj%{X>!Femc4KBL{GQ<@UXpfm<@|AFv5@=PQdl|fu$Q*X5Z9s8r zm@)Cww2AO$pvry!ZtcBM7XIo6ycK=k<**~(yx-7&Uoh6Lk|T9ZzB669arAo>d}E>T zJb>=D@Nj&gsX6O5Q)6Q)`D5VJicp}qKrjX9w;RtFC97XXRCcYgYOjzNG@3HbSc9uQ_{gyyvJDvJuMGiJVc(rEpid{#}PsHWEI!BOqWv z4+??|nAZ*Zy%p_d86I2wjL{$S1%o@c%K|6-rQWP&hgx* zMPR&8d}GM)+Re#66S6@4G7xq{*jWe_3*9_?4;FO}>&QwLKup4_>SVX~T?U^!5K(z} z412zad+Gu%TksA5VJ1sg3QMz~cCum#2!rI3O4V2{;2{=!v0ffc>VJ}R}mKkgTg1#N2PkpjqY)$6g zTu;vu@wq>`!QXz2ANb2tSf<}MVHBTXv0)#IIjSsNPYq!)2J%a`v`Q`p0#L-;1E;$J z8l3!dH|7WC=3!4`VbRl#qDjxoXZqI&axFQs)YPxr1G2`I+_;xl;O>n4Rc#c0&gn{^ zzQYdpx%b5hZYUvNtMxrEZyRMzXy5o*$Fj3a*Sx~Xqb!01+^OKl=f{)1lW{nxupD)D z{pP`iCU|r6=ai}#PvGiffhsg2?#=~Iy#ZZB>c!o=5|Lk`JN`AL%zS&Wv_wslu3G5w z^V#q>8mE0RgJs|Mwzw5$xQseK03K&H`OmK>Wk5$q$Q2nzDlUe6brnRAnhMxnxU}dt ziBy?UAc6Xn=4lWW+!dDn{rj&E>L1kF&=JtkJ_V;Q8}ugyW>J6#9`zoj9B5QqGcj=b zDsKLGHSx;<-V5@H;o-Dod;SbB)E4*?3+V7Ke3(W@w!5qwp>FndTI)>zLrpd1q0P z4qqBW+(V?Lb?Y3ZTGx1~r5vGu)~xft?PPv!Ge3Sz4^Ol&jEIqc3PD51DynXF(~3(< z($ZGR!ol*n5&h-(KMstm0c|J=`u~VJ3$QHLv|Xc!gmg+P-7Ou0bV+xJ(%l^@0s_+A zN{C2zgLFuDcS?w)2*SQy`~UY|#~d?jW-%^j;CtWax$o;b&#^oO=##kZO%bGb0!vB1qLuX@G^jE^R;dj*IJsiMT?N{NW)$%Jp>tD2Nhqzt+C*?!D=+ zQuNQMn0}-vu@4*6UG7kKcB<$+1anMVTYU645$^@1dxou<)q2*GKMKy5Ejz*f>-Z72 zDS&4QV@lI9J23CKZ$Ec>$aUO{lgS?wf{qhGD)xJ;rKQUIiHrc6D3Xrr~m_pkc{ozxDNXni%Cul)5U$HUbyI%8`7_Ot@ z9o<}ku(9tz`1wZ413sVgvc7jcd=@Eb&+-?7>n05QPS z@L)?zgXb3*4oUg4t<=>Rupi1wJYtVBA6Q9!qFb6jqtkR@q>HTX$f1;uqM*?GSc|~< ziWfo&EG#|y=aI)e(;x|(+;)w3{b_s!#5?}W} zh8{d}v0(YyTnlgAK)4BlU^g>!VSnLoBVN0?jrDareEeJ$hDb^=3PM6U?T|hxIVi1x zQEZT668ZS{bx05?F*EZ-v4W9Nl@;F41ytDNKdPOdB!G{Q9tprxzdb#^$dAXT;EQB8 zc<~x?3H^<3Q$?$5FZF7bLCvREgO5kYfkVLn^5$;#)}xiS_722K^R~7-VB>BJgm07Y z`h6p>AV3CveDq~frydtL5m*y*@i{6V?G2&)n2`awA=}xv^R*CR2nq&Nx%>W8Uty3Z z96bV$LFyB$35L}J3>ldsgFavoW*n$?Q!zDGn?a*;rUWP#Y*g`UJ&AfG*h{ zK56mW-(x=CHz_6roD95K_ZdVpdp@jspFvRdr~rw@M~(I49eks^d(oow`7PwC_1QvEqk zfX_bP(j!t*GSPuX_tj2RbW%XV_Ot~G+ay9a9-JaF5!uPf6<7?MID1pS_DaK{RCQvW zLg_g96>bFMpMiu0X`>I)V8w>bF^X1USGCRckPuu)rgpzrYo6|Qz)zSX2N`+2yPcq0 zSVsrA;sOH;d@i^cPevXe&Zs&&XFsv&thV8Ix*HP%4cwEh@(l87Yv6pgpoP#l$QO_IZWnj z2-|6#!0jM0Hc84iUTX9lG0nE9YS=4X<$n^4%iWMGQle9Lb*)NEBSnylEitHH0|>i= z1J=_|DjORc`1tsBjg8;unE_?%aR^#a@cV%oBH*VNb9gJ4Bu&0Ld_u>@)au)MEO}BD z=`Ih?f7fz7M28ST#n-di{j1j3>FHzwFRrqdVz$Fwwuf}}^}$87y}4=F;w!+#RpFbc zUu$nDE!{ox>Dldklv?l@?k9~z@=vA{2_%~RjV`@8??{P#e0Us`B}V?7rN`g%xw`t` z5HJ7kor>f=R$VH;n|gc-HY%!}eu5Tv3~HG~B+uoBplX|fIvv0jv9~~M0jPgKboCW( zZ|~Q0P^9zyQ=m~w0l8<`F6SwLbrCI77t*)6H2!Cru<+9^Mg#C$eeAMt<(giaGY<&^ zK99{mvM|Y$?7=it$VI}8b*UNVuaOi9b5+1i!+7|}eSd{cskcAOegVc$UyKY5AZ%3d ztk!;Y*HG%iAGOfSxIMkg}#Rk=1`Q5@oFbOvEPimLt z+uF>2J$-IE&)edR1bkB_g7=`uVq}CbDbx>wSC`{qnJFst_D%H@L;40` zhAb;D;mVUl|Mv3F%U>_uotfe|OyLkeP8a!s<@*CZJ{d)HR^vz!pWMgKBI73ebX(Gdv%I zF$&}fK#2gCnB?AS>lleUdAQZq;wVP7m~?nSL9kw7)qT5f3%)H~-I=D8LM^!Y!cQ@L zFW%mTy&}n=UxsdE3wAf*JY^ZL$qC)99V4?X4hRrRDvYDyr*4626<-ywfbuu`k&2OT(3us^-zYhw_ux_L&k;M0ycu@`>Xbg zJNvrIN_hYh-n3{5LMkzq@fD_`Liq;Sqwnl+J%qX3j^$U!8Je7>M=@&w)IeCrlh?dN zRb^Zd{`oIUs0N|sUSnPsQ+xZvwHJGvA7O~5*`{Nl@oh^v$W&KFCp{yLo><g zbz9}Z|Gub6Ve!gA5^?9w9XLC0=2PN8HNmLS9pg3JRbRe*38!heH}~`LdjqaoUjFZ& zGnk>@a?{&+l~jVjoSVC{tPCQD0DFLg_5DEcSen4LN44T2zP*r6WM=*jmC!%Ct!{t8 z2p;!}hlCklRuu=Om%j5M-p>Oe^Tk4n!7#C zydiL$%X`&YyqIWGc6#CZF22>FBX#g<2+c+ttEZy$=L59kNo($iEhk#ymKMu}UGZ-mEc;S>S6Um~#4J;We z^#*SO0R0#lZEOWEHVTMk1*3l&&-WUp@ZV$oRVD-WBYxNSFe1G^#nUbhW6~`16_Aef z9hv?t(bS}(*Y9(#(h)pS_IT^xg&MegF)^^UO(#1)#UC0{Dby__kUTnm8;55%*9q?5 z7;1xjOmd<7v%|kbH!KWvTb&n!KHySjFuC7eEmHSjGZHZD09jERQqw(eC0lVO4 z1Fyjq6+l7(FoVL|yc0;o-rvUe{Or_Y2OIvLJ(SDUpJ*j48S-)=hEk%MW6QZ*_E;$` zI8On#;lElP2q|bdal(mQOlg+9LTYSHG9owP?GwK{atZhLd#;aG`(QAJ<-`u^5PQDzuku$9MH&>vD{G_!??ZVF zn&BNCa#ON}TC@QHsRD5hi`hGt@JXAihF2fZ4wwl3oWUp6)SPbei@1r&+63d!z&kA; zV~^xO#EMGr+}KYQ*%lb9L7L&R9f?9juXoWJ8WJMj@687vIbc&LLrbGCPEoSi8xD62 z_&QAw3kuTH6kyqRevoC2ee7VU*J$0bDRe)85-X1*W0CY~e(Nt%``q;68co{a2^IQi z@V&kc*d)98VDrJl2eq-Bd_6-MWVjNbl4uKr)kn6^1@hmO@1sPY9rP==R=V`iQ)BJJ)A?VsY6FX_+Xtw$^Vu1= zXy|ASm0MC$GR^Hv%B#w-hsR$z{qOPNwNWWO)vW5-QWlt6WMq0^3JtcLfd>=2QIjkvg{A0~(}50s zs}x20s%aJ@hn6x8vPd6->5QKXNh^-}NJlqes9aR;$~>WB{Svr?Rqr?~LQiq>PuweUjRMZ@v6xVY;9$yBM#U@COU0+9hV z^%?+M+M3D|dUCQa2+R|?k+VV_T~6p=$`(ZtRVc^E)(r^c>OH@p7xJL*f^`P0 zL=p)4)>0!dpuyWInJGqTWCU|_nDDLl%v$3AlXL6GaKT99-U;lM-gUito(j#oRK_1B)IF{d_%0Y zpqTnDN5*U5p|ksEFlBghq@+=E#Wpo@IDUk2=h?noq4xTA*6wyzeHpgjA2_f8YUlU0 z3&g_6MF&(>;f2Ml?RZEQj72nG>(c1X2$|7;iooW^ZT<)_QBV}*Ax6kelQHt!X z84pfLJeG?!DrFbzyM1Dxlc1s2Cl=n^oCE(5FC8A}AL?b3WoU@g*NKhRwN>nOY|Yqv zS?X3+J#NkFkef@QL3vJii+qM7z7e9$J^Z4fbHGy%gcIU zCZCnoaQQr@Y3%=EJopkEF6QQ+jY^XcbKz*RQIT=GZTuox5g-yx$2;Zn6yMs~Ojf?V zftdoV{184!zk)HMCX?oJiyzb?pwkJ%c|-yGTk;S0LrwO!l9@k9;l7*e){S#MHaE%H zia>zt&JR~zdS!eHK~ozG7;vz$1*Z@f6{{Mc2i?`u!oG`%`RFb;8TqHlvG+KDXMj0v zE{&v=$pXKsMrR0Sn`!%OB?M1DeTt@mECq!~s`=pB&sGakQWhhYfdPtzMmi-1CNzkO zV30)12N84ULK7z>C*yhgw~2zW{45|X%q~-kit1F{b7-brSjcpLs7Pnfa8E8f%YW_D zv)%9PR@a`8jIml0mU87mfH9iI@YwG7_4sp$rM$fL=dlSEbn9eqm4*5wAxkuvPQE8z z6?riSD;06i@XzUdUi++pmR#ScXSeLhU*Uvj!_CCTru_UlI~$w77LNJJwgS1(7^LjC zr2G07%GVdgBO|1&7^$c-uj%sgl6gXf+$Bv-yYOT$4y#@wwfxnxULx~2{Exl4vR4Yn z4hsIG!_tQlu;61i-uks3RO&E`<>!0U{@`>qMA$}ab_9?n$sFSHvM&UJkYAUw=jA$1 zmwa|w2a84f06L#Tb{Y2Utf{?n1S#+^;im7*b@}@v1OcvH?3XhU3>w~zsK`o(>dwpv z=th6)L@}{iw{}0%XyE+n#s3;ss{kq#E6g86M4lPKjJoZA(T96}CS_`h#Qwzc>DE@2 zWlVgkyt8GJUW8GzY+iej?m|8m^aF9IElKl6ZVH z99-uM5xD=K?Rjk}?wnnZDP-$%cXq$rRxWskY&~~cqLG^ZL9;M--`4kU^~)=Fs|$N& zrLN8Ia6`Ym8o&8FQAp(L3*frm^24YuQY9}ObmRwVpcWPxFxEGy$)Q?L0T0-0du&)t0pO`nse+uoGzhVP zV<>({Uq=O|FGqJaHynd36b#6^qmr(#0a1KmBpLPgfdcD7v4QKQ_5)W0fd+4~`RY;j zCL;nLZt%}0CTbZJF%vB_Guvm%Ffqflv10AB#LboofGnr9^!O}%96YoYxOHZQwn_1& zX!ERerD|Q!;E931vB1_ltj?%8b-PgMY}N&}D6NI-^)DJsL_|bX^0KlzmF5w(wH`V; zI%X#dUTOpg(3o^^FkaH2T}Fc^hjRWab0{C&9%S|o0IDtlbMSetFr^`is^|&yFclOO zU`?Nuae*IJls3)YZODwndxt`qww{ZF^n)&amYo`6cm^k#jTl~oudRCf}E)AI>mexe-Xu7|rQfuR+J?+8r167mgw;h$fByF$G17m_#it4|ugN`Cv(` z9MpCp9#FKjxUS#l;{Lw>B|%I}CmN>FL(sh63uetGtgfR^wRHX~K8y${dH+ z`Ed#LdO4&29AvSl@IOUf9;`g3`5Q8JE(au#Az1BT-Z_hNa(UJfT;(T(Nnsjr%jJ_g zq5^wDfPP+kqG3uKQ4kS`+doX_i)wCKS$TTr4g4Y*^UMwSQ(XbvkJF%;1*ue_c0S)SzLr*<6CxtA{AA&J2UdZ(Qn?A@SIxjtSuL&W zd54|3vrz3siKHz5RmGQD1t_QIT!aLN%VqoWEt+Rfv4m`j<@21KE7+L0tax~>&!A=k zVJ29_K{(UEKsFP3U=S4qnoFJ_>Y>xtZt%j}+JZJz0c631brBq0CD*$FbpbWcGizRw z-3_@C&G4~t0iJkhrp3lV^bqW2$e8dJ>ef&qBRR8_tS2*Am> zh^7DgIuxI5i4wA+RC9hGn<=Jy@~Hv&FY_NSLN-sriBr%WpH*)JPQa~{5hRP`V;iP` z&VZx?K!{t#;7-iT>-lo?V@&wkuu(&dp;}kvSK<$$^n4Bi3F?KPTLYVmQ`Agey|R1t zs$hKA*4Fml%p>dn%{;_-SaP)r7Z(?r4EEED2M6vcz+#Bgo`6b-`U!0EG#nhOs=3C$ z+{U1M{a@h2o3*ty=p;a(2d*Xeh3S5r+)~1=0bzrU>G3<;e8AuaVZ&-MmgX^#RsQ_} zZL7-<7g?=RghWJ1AwAk^eSy(qi+zvj*WIhX+@C`8lPe!Juo;RqHQ8ISEKIn(dl4s`Ai3iDutbQY85 z&I~8S1m4~i^unDkS>2F>AHO>O&Yp2?W4R~RBU8n7j&~9hfjbDeAm~L}wbJNah{9%Z0V4-nxq3^}WoPeN?e!pSuaA!I{Z(>~R(ed{v(#aI= zyz>J_nhmG<uZBz%r9_9fe$~n&=AIeGf>{57 zQcw_#ir_dedC#g^R-q%F5vpUQEx3i+Ct(CdTH_&r&3hHXp<$S88d2tCY?33$vgP==QT}im9D&f5o$zO;0yeuwQm9B-~xkW$R_;ola%G zSLLpy?0#N>>+$R5Qi1wZ5<9b-pu3|dQ|)>Pz;jpHSnerhvcLiLt7d_CV8t81TffMt z{3x-{r7b|;@5+`MYYPNS;pOjdqcP6TM;%zgPT2rW3dTrJ?H3izonAjODKb*JkLY(n zE+aKstgn`@3{?^??$MfJXlYM;d4oqEozycmHDWzl#|hUmg`_%rLpYo?H6g6*sDl3b zpWWj#imoVc3NSqY(x3crY!{lR!I-RZ?0$amSkx|6uBnNp*`MoL_Uk!3g!fCfyPXe?-9e9EibGzqrb+Ss!j^zH3q>g%NoUWX0{f30Iem5h%!jn94QVeva%AMLray5%>hpa(lauORTwgVZ`g6bK_8F% zdnS7_!H-6HHh;+@(jkT}glq4g(cG>palph+* z6ZMllWM#5wyL*1zUTt&QM-S1M&&qkz#`Fla4$vto(R_fwLjvXXBS%5^4_FisLdM4n ze0R_HBm$a{uG^SkGHTv`qSWtS0WmIuWb=rV20@X&S6xf$Zh!3X0X3z)= zL6H3Y3yyCuUSo4Se8JPcKo)y114sw_-tjp(1-jgTIqB(6i-z!$A4Jx9i@;r`1 z1qt^6F%0+(44wY)Pzv|@T-*gpgHoEby*=2Fp+YsF2nYl<+$Hb50+uk!XHXQgvMQJ# z=kwN-RK_IXOEe$K4JTSXjO>(z241hG2LPgojE{2VOkbV6lY%og3|f|bemd3A4FCj? zn+xDXxucIbV*+kc;8YL7c(AQMhOvGqUKaKvza8vSqd`i4otlpkQ9TdXlOe(l==rHB zS;Y-Tby(v8`o2HygZ9vcs~Y$HhYZIE!eef)0F;6G5y$U~AEmK6>y zF~I$F4mU9X5LpaJ&-806l8A&w;InLF|fATjgxdt ze?#Ei)s!MW#MHB(N7g1HvLGT_VQ(E{4oVAwap(;Pi0#vPtj4~xx2DI%xq$vk+t}~y z;?EXIM7Lq1J4_-q3$>I^@RwfS21L{BP8I8uL~LDn_w+}x(Z$J?PA5SV8iOmA21qO1 zS6Jv3G;**E1tdd)l3KzwaU_|XDibRe6;n$Kyv`%Z)8jf=^~1U&D1Fb)?I6klBx9}O zSRlUU(lp%&LK^R1?vn+KwDW1kUQ%#Ap5@w|;RJ)Py?vD%GYD5`p$iN!y@#R(Bs{Y5 z;gdjt+g3iDjk8}^8By7vM|N@1(W7ESrIuN`nDnz^1RNd%Td+KmIfO{ZXu;Bq;C-I^ z@zIX!$X>Z;Ika7+qv(W$8r&YlcBb(nu-3ju+YG@i$g9XC+&^ViSqbaWsh z0ui;bG5E-M{E*ZxDKmO*VuFJ9w(nlV{n&dC9_Z+{S-uNGDk}cYRk@Kt^R~&_>&1bZ zMtt#f(%kG{#?`G5N>8w5XMVt=|2E+7ZmO zvuLKE4Di)nsP`uiCzJ!6HhBBoS+n@X&(F!oBaW@#%K&5rj6R@2g4$Fz6)%ZgF@zF3 zw+4YO#%HgHi5VZ;-t4pNmsMhz_x7&sEET-cMrEMD#OpwahNPwrpi02h0zt9bPT`5Q z@ZobYyj42dpn$&w@QKK2NgCZfTJjkKjSi&te%18#%~XX(k-d=*nkt4tyj69bA8Dnz zc=6U}C95sP0&RA9Rt<~Bfj5GX73EuBoUkd14NsGIH89`_n13$_z;0AdPNy}~VgQu( z|AFb?#<;KmQA0d>=x_O4w4{YpJJS*og8b_4w{(G`@n6OI5G3}$^)srLkFR|xf@5yG zJk%2vNNi#P0MmH+XU@kMSXj>!`qAMz3cyEWGN*obPxSXWrLVhu5X~dUl|_5D`ka7Lrwl^F&Q#SlHO!S)=;_v=1Y< ze_k5>`}{({7eKXOS-ZU>zDoR(hXmf!kbt5*icH8tOMp0GLHwp{T%U%40n0Xfud#nJ zY#>rn5^9le8e7Qvhlgvcs){u1m@eZuPGV_wS}DkdoU5%h*cOfJVNoT(9wp=^ks+Af zo&v%u`-%5sA|74q2ta(fY-$;}KO8RoBb-2k_SPR0Gtt1XtHBM* z9s>ii1+WFQwJr1EE7Z_Xd)p+;@vJl?gPaDnzJA17w^YW8u}Y7l!}8rI3Kc*9(}H(> z_wsN{N^-~BmML*?es>sir7H6KlR316R$4$2xKMlaqxh^s5_?DY=e4ud=8dhFFf8QR zar&xc^7x-mbMV)%o(o+rAD>q<91wR6xFF}V|1c~TA|Vcoc3N6cM6Tah?oGjUAB)V= zdgLigI>45-nPjg(+KG=m*)&M<>5OX8X5VkKoHab#-^kD{Uq8`*XC`1wJ*+ zg1bIGkY~~=X5b~9EEM|^DdNKGAe|xTsrcleNSBO4xmBV1&dNF>GV)xd%<;*y!24!H z{L0DzumhYBpn*mnmD>=(@5^euvo{8!dysJco}^kx$cPW6#G#D1ubMdyEsqi2ya9K~ zdbg9rUC?i21t?1ZcYLWjyrX^R4q^eYfM+-`F}J+WK&nxs15*oA^VmXWcH^OEA);PS zPfnbEb2uyykzl-h`Ne6S%kBB0K1cv(PBd&{lk4pbkB{G$I&|ql zQ3vjM9l?)z;(6`kgR$7>FO|~Z`&Oz)>;GFEoD``X;#?^lc6zgg`N~d%sg^S;=oAAf zDI2TeXM5DVgoWA)dLrZ$6&I`~gNy+e7rxQcrR)$EMWz%UJD7X{tx@3sQA`TSlPwM60K9cj$Y0n)?eP73E|0V^wGWi+ZU^!uB~fXF@(Pf+;Xo``jg#sS1C`zhyvyqI5=hwxFquWH5`C9${qdaX5)QfSWpEz*}lOD z|Gx>1ZaK|9Ca(Z2Cqq#IDzrEXJ|v`Mz9?jL2A6KXCF|oTJRX)1gaq;CtOIRFnp{Wo2H_OJpe;9kZolSE+x5?qA)e`}8#NRtP z6+KrU&|%(td2%_(Ck6hGO|;uEav8pI%t)lvWu{S+Z1BIE28tq`s~uxEe+?=>XZv_Si6nyb zQMT7fowX*vfT~I~zq6#E%Y85m-`>EyYjzeWv17p)*M0P1=7W}_R3+*mYwKQz1~nBD z?!3YxoYwK4{FmIKnMrJ2^)AAe(Qtx+4Yi05NC%N}tZl-_^8s_;V_bs(xfCq14c$te zJc;F&wUc2+;q;B;!&uyBs;aYcMlckN`Zp9j_GQAqtOcE zP7~D9xKi?Ze+Egl)yIE6ln=8lVj+-tZ^2Xn|Gj_I$7>U)7$|3nlH=h$lMKP4EYT{S z`}q~Syt4ADxmfeoi$)G0B?$*1w_z7aE}aBuLGpRAe&Bx|IQ4E;&Ik7EfGx*E*dUlu zZ)_ik>g(w-l|IWH*ZsRZJX>jzuas8f`D?AIN!ZwU*@sjXG75FhoBec3slqHnhfB>q zTLUT3ON4D@OH0dUzp{zRDr7NmJN)vFb$^vjPPG zh{o4maHFlZI^iFlgUHFp*YN2CJJJLr1Q!2=+5)a)qsILBp7ZL}1JLLG$Nu|Zb3HF~_F=CRpUtdBo z5f+u8;?k?GND2#wACXeMF2lw!qh=<;OzR1N2?~giJLg2y@^Nt;%{#z|7j{DucQXX2 zb#+uiFyTr6`xiKQ3>)B9u(4g)q|!tbbl3U`56R3NoFi*$9NmLMkcEX;4lpPwJ$*(1uxRz{`?XNcsPeRVZlFmVGlO6PsEj{sMNZQ|KUM;Jj_bsm#uzyIP;B%M;M z_bImZ6BZUQ@j>>;+#KXNos#&3+2V+EbuoL>0UN@9w}aCDUg;M2=FYmP;l|R@D$k zB8~W>8aR|`#?G!$pw_m!&=d++>}CH~RM2JyhJvuj&-wj7ZnjId4X%to2Z%|FwMHg8;(owLfl< z2M0Q}zW&&*<1Q||5Cq8&7ut5mfm;Dl7Abk{`@(tx@@L$LwrWwJypfgDwPf=B*^Ocr$CPj zxQ^K#hK)wcZ;8`Au(4hw>@y|=zkR#buTij_^j~_~CTJ~d-I$XF8bICM-5ry&)1wP* zYK(Gf%J!K{mi^o*^oxvB1Y3(kO`6e3HZ~_8-`)27Z?LihL|J4a-V=+9f9I-gP*G6_ zQ=b%)b&CbsDpJ!+|HTLZegNDWU=T=VK$}K}2zCtEK~$ukq|<0_Zg0=e&T1+f2jCDB z6E6qc`gwZR!RaR@MHdDKN4JD(;MG_tGXc$1$JYerd$;RQaXi@?Bo+1b%#n^rWH&wp z_|kDJy9t11Gvnhm@|DRScR~{pA598c7|VVdQPQQuwY7ZAEIi9C|4)BERItV#9>iK9gG6t=Yzt;2E3?(J*2ArovM@S^GypevU z`Soc5@VXQr)t)5^G-xy=*!LBaf@`|G96-Q!^Ex@=8G`(7XiNn5i)$WBpVadmFS)L- z4+-;uA5@7978yMfBVU}whM2~yMKeV$LReXWrDsxmWBsJAuv+hlUTtJEgpNkgP(L9e z|NiX+Jryz$c=MauDG?J-L?ow?@|SgQm)6>Z1}Vta7HNq7>iX#2>38UM_v8O^yd6TUs{=V^7mmD+UP-5F(JJPygdT*i1!NRu=gG_5EhvcDHM)xJa#Sv3czU zV*=XwISR^CMa5a@+M_$n2k(yOeL(kK$H2YI#s+Y?JH7o9w>QEjZ~1+buTF6wgMagv zjI<%$AYTbj+NY85zqXBRE@$Hn(_nXaH46Q8zSl z!4YlaP&UKf*~=EYQ*ZF1RNG)=W_H0)0UiLE&;P4DYBS&mh~1W$sHPSi*cM%j7#fh- zlI<4_P7u}$D$5SHI;~4fdz<^)mvpyFHRuF>P9zT~BfuqrEB<6Ya0>r$>7RBp5Q9ozO5uduco+8#pDW&m3snUICq^-aI z2lNQx;Z$uiooFoRahRT;pC1$?6@p3r=lo!AW8*X=aQg<${pXNzrF6AZPLLM!*$>+6 zoW8!$_I3uQF!cM-cLTMtzo?hz%2M3n7%lt|0Zq@BpHgh-U=CizkEzf}Xu3MFabCXc zolKapmXuMiwuT55wCY5BW)?F*iR`hQ(BMu`+Nw;%e*6|`;R_C6!t%4=tpL^nm+g-l zYsY3V00Z(mgqUNfVg5H=`dpqCld^k%*7u4R)Q?F<-es&Horm0j+pfglt2n*C?`+2V z(Vz@z7Bf9{MEp0Ai0SBa}@?Zh{%3 zI)upfb`gSZU;O>xV%zNFwXF=d^$=u=bvIrcLLq)Qjg5`Zw!by5rWPk_T?Vb9)z)V#SD@9HHr9>hFdtYn=Se{03xfkao=7p(|zcEZe|J*_(nA2J-k zuKc238@7D?L(`}sz9*8PwUF2KuQ$b$uzaN)2hWuLCiH zBosc$8A6<16(FQ6C;%9%w+QS;Qc@=JqX6_cS(t~2ymL^y8$@K;h4wC3l#L7hbq@9J zS)Jn>n@K7U57$mVy>SNEa7 zGgS;oi_T8j`_GIH=6IFTn0$R9zXHU`xY(7o{C+!`2;z#5jZ|(CJ8hPA+J=~ zC{$k;Ege|9Yz})G%qio!!um z5KgzR8alFWdoZ>|Lt~)ZYDrBJ@&G#NSb_RNX%;|$D=NH?(43t<0e(+UuWiD_b_`(BXGda$v+C50|jXW1T{r}Kb4eRml+;4HNjpiHSFUIvky`)>Ksdf91uc z2P1v}>FtmS7+Y<$EiCwaiqj&-#uQ^R;`ED{SqTZfy8fiqx}4EUrPPiAM5(_1l9Ipb zf07Vlff*Twiw$la?d=psvC!NKn4i3eib+c=oZhpuv&+h|QAc^)xC+rK2x!9}y49n* zuAAhV&hm@PD<9NVR6fGOve4`{01H5m=0K@;_w~Jor`6%4<^I6|yJ5ow{F{)<^x+I4 zdO0;yQ<~R)SLg7z*}2^`_wn?kp`|^TDW?Y5Y_S&djnVB`Ycib5BPsbBU3%JwqsYno zhA@wTM0`g~?5lRv@d;ctvV?jBf!W@FFOq<{&dY_Woj%LaUlfkicM+{_e3u~6cKR`# zs9wLeF)lL@F*}Emj|c$DTC7f|4AX_$iBamsB#^^v@tbKR8XZ0Q)%ILWyqGTNF0e~B zxD5*=HSp8ZIqb>o7W?lb3c3TGkH!KqDK0fED`0DH+Upwv4*B0Yx>6q}cWGrzcvc-b zg0&z=b<(h|?55`{*B3>HYrw zQ5=yg1solK1rWb8JG*M4pVcJXbj5~6p$M1$7f*Af%_5lcpF>W_|Dd|34;dml{rJ$( zY;$7X9wG-I#B`wymXK3!-s+;q>I$5=MLj2&2*9%P?jE=_S65BVf}|rXhsdl}B>+%5 z!GMO9Z}$E7v@y&HNjN3y8YDgeXtm`g6+VYTLZIr4AblSW5&#@=6zH}&P=MV94e34h z!}nI>{9I}p1$Qs$_okt?0IEf2mzb1L7+X%W&CGmkH_sDeT8FOwxRaH^}a5|-Dh+Pa_^A~Wk08M_6W(NMZdp0lp$O^ z1;L)W1}Z8DaYN*y#SfZvVd1N5Z)N<>zI`Zd-Qu?3aLNV*{{F%0RxsQK`*0zvwd||D zKN}cB4bLEOGlQD<-byE`QFWMcfT8P;PUo`}JtbaV9t+H30|4KQS&!D1sxVz{$O*c> zSCBV-Mg|c1fdR`ygWYb7n?oUxpdulaRpkQmcQFHcW~OE)KaC8UAUzY4uK4K_D2QMt z^XC){tVT8H70Fckex=645u^ZjBKW=7%Yg6b(F6Yvl|2^m{e9;SoSF;`c19jkh-FR} z$9>f%0`$b6i~QHGuh<%FrX8>Ofkpw6B1;CqbqcyZRf&GgUOKkEB>)}mg6M#JiC;-4 zr4)pD@@9FH-VJle8csE})fE+EgamsI1mk({Y~=bFlh}o<3RT$t`*+{C=vx9CL40AJ5Qya{_6DG(Lkda;UQI5VUs~!Hi zIA5eh>LLdWR$)65(un;7!*i)-i8m`Re*7~U!Ce4GV};rP1gPRU9Fr(32Q>j65bp}sdi7{ZoIY@A@pE7GXLWlxi zqsCx#uN?56b4c|+J7!Z-`2xzWA2Xuj(Ur*uVTs{mRpAQ9=v4zCeW6L$+&xE&S~wkq zJx-j4t;(UH`v(}+seloB)dMbCPA@KaLG#*WuH3n1pdhq2eZR6TbNq&${-ntZ#4RNy z8yl{CT>m*1GG4v}%sLUiN~w`qT-)icS{iRu9G0`oKkGw2*56XheQ`B)uYP<;2MHTg zy6is$oEo5e6p~~pG!EwC)8NEkkxvhgyWQRRl)jE&03CTOt$x+C`h9e@G&}HN|A3KK zEA0KF1T+&9*taY!uI|VpEG(3Huvh8DQa2qO?Dpc&&^R`_-+PMr4HNV8`N8gvs!=r> z4vt16uierB2C3+)RXXZGEQ$?LKgl3oIZ|HGsRrKHH29$YSikm|)Zu6#MS_rx5*XD; z$m28R5IBddRQoa|RK{N;fJzl$*Yz%5i-Is5vXpe>ka*Sw;F4$reSM8$rp9grD4W1g zL_pyA7fkg+h&#CL@zVvfutK{X#Cm8*N*PSL`s%Ld%p)-DR=qDGOdwneo^`c|_`V2!|oGj%K3{v=Jyx;)e7}BwluW-QMCmKwDR*^Yv z5>LBHshGmxX-nzsRZ2!`X}G6g{Bu9}HWco7RwZx&#`fZ#$cGh+!9+nA(~V*QvjgZu z?<1}kI)boRm_8^GE+3;wzik|H}{xQM0H#^YB8J|cv zKLb%};nM%csHIvAH-a~K9TFrF^_6isjd8_s2`Sam4?lDl4WJv!S*aH6D!;*u?aO7? zOk<3Yjyq|VeSdq&pAPJ=tQ`GpC;$fq z_-;Zp;a}?uAi$VHypNvz z#6}CIAwZ8sxYR4rEKD4KCiMWiVBz86HshZ$K#cgjv92zjbk=sc1u9|90C-(M)kBxJhg+T!s2(He~DwTBgbsz2Kk)NL-J*YDhIX>x#p@woV6)VR0=TCLX zP%H|?11WH$>tkf(bYajFaM%zM5WZ}?Ygw(K@vh$GvKs>c!oZ*(|Ll4r3?>HS<;Dc+ zaKc~x2@i!Iiul|_(w;W|{xkMt63(Og`gh^_%b&cq4&4)a`rJG??7dk_<2CO>$%n4S;> zMiXKy4ZW}e^?ErxVR@E1cMSEt{EGMpkqewx>Qytz=B z4|jTrJLa}?oviw59l@?!=u;(KUsPRxJ9do<2T0eY@SuWBjNGuTjsX9t%IbQ~ruA9t ze06xh?>F>Hprpz=VulnzV`6i&YHdlfaLRBN^j?z=g5&UD9B4=|Ox;%A*p6$xUB5{WK_(aO)ni5a7o6X%UEWA#hqb0&ZOh(4 zMq+TWez`RPJ?$r5ZgdL8YU@>#?N@TK$H#|w_NE$4_wIcJdc9r(+?fE$&20hR-7OBJ zZ>DexJ%T!?w05<b;&HOz>58Vj`I-Z@$X)o2In9V5vzWD&X@Nx#DD_% z>tG%}$^`gjmE2aS9`9w)sQT z>C0tDUBlSvu0c-sVveXwaQ^dc0T)PD!sIGS3{5~q(Z9OxVgB=z^8@g!up3Jk>wy~p zzL||e1(PGY;O1gCly7t=e)~2oBg0Nr*XjS}sd@t>K6##BCk0EPn3!1YuDu}L0fp|G zWr^y8YOTC#g@K91e#VA`%uL5iDg|j$=Hj*TW2OITidLF|^%D{n&<2GXIXJAv1Meq{ zy9IH$0HRH=_@>qap^sS8Sup{o5O88+O4#Jogoe4ZF#)$&MBOz46gTvWYsTib99Do4 zRbE~W@EABNgoXp59>sFx;~Qa4$Z9*xR?tGzbT!%0!Qm&ZLLxo^0WBS!QJv!_Xr*p4 zpFqQFcRatRs|(<;v@lkI2Zg2?_0)DC02&ke6)|h##*NzBzW~9U0n9YF#}BqQ9?|65 z{xY`--?0pfl3DM5d7uX`VoY>!Z$rGO$(a_G0d#mC2Y)__9e!3ykA6Lr0WGb=Nd-@@ z$29MR(KreY3%58;H)0(FjuD{B(tpMPbF>`X^_pEog&gPFm{2NYHZ z2k^>;>xmT)kGE@z*8ycm_kANH9xpB#nIxna1sZU5eEG`hjH#jmG@ITcU7aH(hEO)D zuP;}Yk%^K|OniPM=ATTD#>YiS2ww{c%)D5j`@#CO9SIPh&EBp*@RJhNTK#jisKqG7 zZr+y|QW9B#9N`g+

-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 Date: Tue, 4 Sep 2018 11:03:18 +0200 Subject: [PATCH 24/26] Style updated --- jmetal/util/graphic.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/jmetal/util/graphic.py b/jmetal/util/graphic.py index 0f5d71d5..41a6c0bd 100644 --- a/jmetal/util/graphic.py +++ b/jmetal/util/graphic.py @@ -265,8 +265,18 @@ def to_html(self, filename: str='front') -> str: + + + + ''' + self.export(include_plotlyjs=False) + ''' ''' @@ -316,19 +330,13 @@ def __initialize(self): self.layout = go.Layout( margin=dict(l=80, r=80, b=80, t=150), + height=800, title=self.plot_title, scene=dict( xaxis=dict(title=self.axis_labels[0:1][0] if self.axis_labels[0:1] else None), yaxis=dict(title=self.axis_labels[1:2][0] if self.axis_labels[1:2] else None), zaxis=dict(title=self.axis_labels[2:3][0] if self.axis_labels[2:3] else None) ), - images=[dict( - source='https://raw.githubusercontent.com/jMetal/jMetalPy/master/docs/source/jmetalpy.png', - xref='paper', yref='paper', - x=0, y=1.05, - sizex=0.1, sizey=0.1, - xanchor="left", yanchor="bottom" - )], hovermode='closest' ) From 13aee5f2161e21051ce018e66e1160ec1d1a243c Mon Sep 17 00:00:00 2001 From: benhid Date: Tue, 4 Sep 2018 11:03:26 +0200 Subject: [PATCH 25/26] New minor version --- setup.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 9bc9d270..fe462e60 100644 --- a/setup.py +++ b/setup.py @@ -7,12 +7,12 @@ setup( name='jmetalpy', - version='0.5.0', + version='0.5.1', description='JMetalPy. Python version of the jMetal framework', author='Antonio J. Nebro', author_email='antonio@lcc.uma.es', - maintainer='Antonio J. Nebro', - maintainer_email='antonio@lcc.uma.es', + maintainer='Antonio J. Nebro, Antonio Benítez-Hidalgo', + maintainer_email='antonio@lcc.uma.es, antonio.b@uma.es', license='MIT', url='https://github.com/jMetal/jMetalPy', long_description=open('README.md').read(), From 4ec8db159e86794471c2bcc28a6afd7201e9248e Mon Sep 17 00:00:00 2001 From: benhid Date: Tue, 4 Sep 2018 11:10:42 +0200 Subject: [PATCH 26/26] Updated deploy settings --- .travis.yml | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/.travis.yml b/.travis.yml index eefff044..ee0d6c52 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,16 +1,13 @@ language: python -python: "3.6" +python: '3.6' install: pip install -r requirements.txt -# command to run tests script: python -m unittest discover -# deploy to pypi deploy: - provider: pypi - skip_cleanup: true - user: ajnebro - distributions: sdist bdist_wheel - password: - secure: "pJhh2ZwuDaMELZO7kmNGWa7sRaUi6s4By4GdBxf1hPjIBkB0GkOGHKxuivnWitAYLbmzpSoMcp2rHETcRiSqYTPlyovLA8A7YpY1HXIcNBrrmqnOpzc9bN3Ka90HMu0ySw2uYZgaQ2neFMm5CvD6W47IG0UUaFPYl68aZ8lEe8t7Tea7kFLU4UgXZxp3BXLTHF7xx7V29Ba5aKv2" - on: - branch: master - \ No newline at end of file + provider: pypi + skip_cleanup: true + user: benhid + distributions: sdist bdist_wheel + password: + secure: pkZQushcLk4eEq06foPEesrrtzxJz907zrDHJghTR5/OLaJ3JrhASG7sa5s1xRwWjAYpf8GWgZsUbM6YGe+r7EASoQ4VdbGZEaTYJCS0AkBawqe7K0KjX2llkgXdHRerTRoFe50mrxA5atE2kVsmaP1ZD0oZ8HzBcTmQbFy7/ljWLnVsyCgczTFHiSfLAdL1MPy1emGAPz6BtMV18b8GLdwBl2KzKVDWi1PwE/l5eG01YoaPT8Eq+/Sy3ZRg8Nx+jdr6D+/1jVNb3SChy27B4kC1EmaCcSwnUg9cGhjziAf6wfEr48+7ZfhEKHTSsJbeRRoDMrAk8f68inuXp627LaUPOBXQsItNwNwRZGfP1LnvoZUbVbEEm2fT6viRLHR1P4LqJcka/6JVD3UgobTvv4sTTaIReeIRJtWe+ofHL2PA+nLi1oRuFcexKfohjyOhreYbJ0jp9O/hWyM5Lak1yEb1CWUuiNy69VMxwm0i00uSyoTZD0LNf/0BBjbFZ02TxHlkKj+LXzI8IiBhhNWY7nRVAJi/MVow14KKmmpk+Qo+dnv+sS7r1zhiEr01akdsLH+3+cfu7K9lJNkhSG2ayoBOe/lKxxOw4G3D+Nm/mNL+Pu38WlmulWdMwbPdHDKmuBzzA2ADsnDIb5BoPwFDvghTbYBA8Slaum35CW/2yzA= + on: + branch: master