diff --git a/docs/source/visualize.rst b/docs/source/visualize.rst index 45dc1e4..629d4df 100644 --- a/docs/source/visualize.rst +++ b/docs/source/visualize.rst @@ -10,11 +10,15 @@ visualization in PyGAD. This section discusses the different options to visualize the results in PyGAD through these methods: -1. ``plot_fitness()``: Create plots for the fitness. +1. ``plot_fitness()``: Creates plots for the fitness. -2. ``plot_genes()``: Create plots for the genes. +2. ``plot_genes()``: Creates plots for the genes. -3. ``plot_new_solution_rate()``: Create plots for the new solution rate. +3. ``plot_new_solution_rate()``: Creates plots for the new solution + rate. + +4. ``plot_pareto_front_curve()``: Creates plots for the pareto front for + multi-objective problems. In the following code, the ``save_solutions`` flag is set to ``True`` which means all solutions are saved in the ``solutions`` attribute. The @@ -87,7 +91,7 @@ This method accepts the following parameters: 9. ``save_dir``: Directory to save the figure. -.. _plottypeplot: +.. _plottype=plot: ``plot_type="plot"`` ~~~~~~~~~~~~~~~~~~~~ @@ -101,10 +105,9 @@ line connecting the fitness values across all generations: ga_instance.plot_fitness() # ga_instance.plot_fitness(plot_type="plot") -.. image:: https://user-images.githubusercontent.com/16560492/122472609-d02f5280-cf8e-11eb-88a7-f9366ff6e7c6.png - :alt: +|image1| -.. _plottypescatter: +.. _plottype=scatter: ``plot_type="scatter"`` ~~~~~~~~~~~~~~~~~~~~~~~ @@ -117,10 +120,9 @@ these dots can be changed using the ``linewidth`` parameter. ga_instance.plot_fitness(plot_type="scatter") -.. image:: https://user-images.githubusercontent.com/16560492/122473159-75e2c180-cf8f-11eb-942d-31279b286dbd.png - :alt: +|image2| -.. _plottypebar: +.. _plottype=bar: ``plot_type="bar"`` ~~~~~~~~~~~~~~~~~~~ @@ -132,8 +134,7 @@ bar graph with each individual fitness represented as a bar. ga_instance.plot_fitness(plot_type="bar") -.. image:: https://user-images.githubusercontent.com/16560492/122473340-b7736c80-cf8f-11eb-89c5-4f7db3b653cc.png - :alt: +|image3| New Solution Rate ================= @@ -174,7 +175,7 @@ in the ``plot_fitness()`` method (it also have 3 possible values for 8. ``save_dir``: Directory to save the figure. -.. _plottypeplot-2: +.. _plottype=plot-2: ``plot_type="plot"`` ~~~~~~~~~~~~~~~~~~~~ @@ -192,10 +193,9 @@ first generation is always equal to the number of solutions in the population (i.e. the value assigned to the ``sol_per_pop`` parameter in the constructor of the ``pygad.GA`` class) which is 10 in this example. -.. image:: https://user-images.githubusercontent.com/16560492/122475815-3322e880-cf93-11eb-9648-bf66f823234b.png - :alt: +|image4| -.. _plottypescatter-2: +.. _plottype=scatter-2: ``plot_type="scatter"`` ~~~~~~~~~~~~~~~~~~~~~~~ @@ -207,10 +207,9 @@ The previous graph can be represented as scattered points by setting ga_instance.plot_new_solution_rate(plot_type="scatter") -.. image:: https://user-images.githubusercontent.com/16560492/122476108-adec0380-cf93-11eb-80ac-7588bf90492f.png - :alt: +|image5| -.. _plottypebar-2: +.. _plottype=bar-2: ``plot_type="bar"`` ~~~~~~~~~~~~~~~~~~~ @@ -222,8 +221,7 @@ vertical bar. ga_instance.plot_new_solution_rate(plot_type="bar") -.. image:: https://user-images.githubusercontent.com/16560492/122476173-c2c89700-cf93-11eb-9e77-d39737cd3a96.png - :alt: +|image6| Genes ===== @@ -307,13 +305,13 @@ solutions in the population or from just the best solutions. An exception is raised if: -- ``solutions="all"`` while ``save_solutions=False`` in the constructor - of the ``pygad.GA`` class. . +- ``solutions="all"`` while ``save_solutions=False`` in the constructor + of the ``pygad.GA`` class. . -- ``solutions="best"`` while ``save_best_solutions=False`` in the - constructor of the ``pygad.GA`` class. . +- ``solutions="best"`` while ``save_best_solutions=False`` in the + constructor of the ``pygad.GA`` class. . -.. _graphtypeplot: +.. _graphtype=plot: ``graph_type="plot"`` ~~~~~~~~~~~~~~~~~~~~~ @@ -322,7 +320,7 @@ When ``graph_type="plot"``, then the figure creates a normal graph where the relationship between the gene values and the generation numbers is represented as a continuous plot, scattered points, or bars. -.. _plottypeplot-3: +.. _plottype=plot-3: ``plot_type="plot"`` ^^^^^^^^^^^^^^^^^^^^ @@ -345,8 +343,7 @@ of the next graph) lasted for 83 generations. ga_instance.plot_genes(graph_type="plot", plot_type="plot") -.. image:: https://user-images.githubusercontent.com/16560492/122477158-4a62d580-cf95-11eb-8c93-9b6e74cb814c.png - :alt: +|image7| As the default value for the ``solutions`` parameter is ``"all"``, then the following method calls generate the same plot. @@ -365,7 +362,7 @@ the following method calls generate the same plot. plot_type="plot", solutions="all") -.. _plottypescatter-3: +.. _plottype=scatter-3: ``plot_type="scatter"`` ^^^^^^^^^^^^^^^^^^^^^^^ @@ -381,10 +378,9 @@ scatter plot. plot_type="scatter", solutions='all') -.. image:: https://user-images.githubusercontent.com/16560492/122477273-73836600-cf95-11eb-828f-f357c7b0f815.png - :alt: +|image8| -.. _plottypebar-3: +.. _plottype=bar-3: ``plot_type="bar"`` ^^^^^^^^^^^^^^^^^^^ @@ -397,10 +393,9 @@ scatter plot. plot_type="bar", solutions='all') -.. image:: https://user-images.githubusercontent.com/16560492/122477370-99106f80-cf95-11eb-8643-865b55e6b844.png - :alt: +|image9| -.. _graphtypeboxplot: +.. _graphtype=boxplot: ``graph_type="boxplot"`` ~~~~~~~~~~~~~~~~~~~~~~~~ @@ -419,10 +414,9 @@ figure as the default value for the ``solutions`` parameter is ga_instance.plot_genes(graph_type="boxplot", solutions='all') -.. image:: https://user-images.githubusercontent.com/16560492/122479260-beeb4380-cf98-11eb-8f08-23707929b12c.png - :alt: +|image10| -.. _graphtypehistogram: +.. _graphtype=histogram: ``graph_type="histogram"`` ~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -442,8 +436,74 @@ figure as the default value for the ``solutions`` parameter is ga_instance.plot_genes(graph_type="histogram", solutions='all') -.. image:: https://user-images.githubusercontent.com/16560492/122477314-8007be80-cf95-11eb-9c95-da3f49204151.png - :alt: +|image11| All the previous figures can be created for only the best solutions by setting ``solutions="best"``. + +Pareto Front +============ + +.. _plotparetofrontcurve: + +``plot_pareto_front_curve()`` +----------------------------- + +The ``plot_pareto_front_curve()`` method creates the Pareto front curve +for multi-objective optimization problems. It creates, shows, and +returns a figure that shows the Pareto front curve and points +representing the fitness. It only works when 2 objectives are used. + +It works only after completing at least 1 generation. If no generation +is completed (at least 1), an exception is raised. + +This method accepts the following parameters: + +1. ``title``: Title of the figure. + +2. ``xlabel``: X-axis label. + +3. ``ylabel``: Y-axis label. + +4. ``linewidth``: Line width of the plot. Defaults to ``3``. + +5. ``font_size``: Font size for the labels and title. Defaults to + ``14``. + +6. ``label``: The label used for the legend. + +7. ``color``: Color of the plot which defaults to the royal blue color + ``#FF6347``. + +8. ``color_fitness``: Color of the fitness points which defaults to the + tomato red color ``#4169E1``. + +9. ``grid``: Either ``True`` or ``False`` to control the visibility of + the grid. + +10. ``alpha``: The transparency of the pareto front curve. + +11. ``marker``: The marker of the fitness points. + +12. ``save_dir``: Directory to save the figure. + +This is an example of calling the ``plot_pareto_front_curve()`` method. + +.. code:: python + + ga_instance.plot_pareto_front_curve() + +|image12| + +.. |image1| image:: https://user-images.githubusercontent.com/16560492/122472609-d02f5280-cf8e-11eb-88a7-f9366ff6e7c6.png +.. |image2| image:: https://user-images.githubusercontent.com/16560492/122473159-75e2c180-cf8f-11eb-942d-31279b286dbd.png +.. |image3| image:: https://user-images.githubusercontent.com/16560492/122473340-b7736c80-cf8f-11eb-89c5-4f7db3b653cc.png +.. |image4| image:: https://user-images.githubusercontent.com/16560492/122475815-3322e880-cf93-11eb-9648-bf66f823234b.png +.. |image5| image:: https://user-images.githubusercontent.com/16560492/122476108-adec0380-cf93-11eb-80ac-7588bf90492f.png +.. |image6| image:: https://user-images.githubusercontent.com/16560492/122476173-c2c89700-cf93-11eb-9e77-d39737cd3a96.png +.. |image7| image:: https://user-images.githubusercontent.com/16560492/122477158-4a62d580-cf95-11eb-8c93-9b6e74cb814c.png +.. |image8| image:: https://user-images.githubusercontent.com/16560492/122477273-73836600-cf95-11eb-828f-f357c7b0f815.png +.. |image9| image:: https://user-images.githubusercontent.com/16560492/122477370-99106f80-cf95-11eb-8643-865b55e6b844.png +.. |image10| image:: https://user-images.githubusercontent.com/16560492/122479260-beeb4380-cf98-11eb-8f08-23707929b12c.png +.. |image11| image:: https://user-images.githubusercontent.com/16560492/122477314-8007be80-cf95-11eb-9c95-da3f49204151.png +.. |image12| image:: https://github.com/user-attachments/assets/606d853c-7370-41a0-8ddb-857a4c6c7fb9 diff --git a/pygad/pygad.py b/pygad/pygad.py index beb0a4d..436237b 100644 --- a/pygad/pygad.py +++ b/pygad/pygad.py @@ -1,7 +1,6 @@ import numpy import random import cloudpickle -import time import warnings import concurrent.futures import inspect @@ -58,7 +57,6 @@ def __init__(self, on_mutation=None, on_generation=None, on_stop=None, - delay_after_gen=0.0, save_best_solutions=False, save_solutions=False, suppress_warnings=False, @@ -114,8 +112,6 @@ def __init__(self, on_generation: Accepts a function/method to be called after each generation. If function, then it must accept a single parameter representing the instance of the genetic algorithm. If the function returned "stop", then the run() method stops without completing the other generations. If method, then it must accept 2 parameters where the second one refers to the method's object. Added in PyGAD 2.6.0. on_stop: Accepts a function/method to be called only once exactly before the genetic algorithm stops or when it completes all the generations. If function, then it must accept 2 parameters: the first one represents the instance of the genetic algorithm and the second one is a list of fitness values of the last population's solutions. If method, then it must accept 3 parameters where the third one refers to the method's object. Added in PyGAD 2.6.0. - delay_after_gen: Added in PyGAD 2.4.0 and deprecated in PyGAD 3.3.0. It accepts a non-negative number specifying the number of seconds to wait after a generation completes and before going to the next generation. It defaults to 0.0 which means no delay after the generation. - save_best_solutions: Added in PyGAD 2.9.0 and its type is bool. If True, then the best solution in each generation is saved into the 'best_solutions' attribute. Use this parameter with caution as it may cause memory overflow when either the number of generations or the number of genes is large. save_solutions: Added in PyGAD 2.15.0 and its type is bool. If True, then all solutions in each generation are saved into the 'solutions' attribute. Use this parameter with caution as it may cause memory overflow when either the number of generations, number of genes, or number of solutions in population is large. @@ -1133,19 +1129,6 @@ def __init__(self, else: self.on_stop = None - # Validate delay_after_gen - if type(delay_after_gen) in GA.supported_int_float_types: - if not self.suppress_warnings: - warnings.warn("The 'delay_after_gen' parameter is deprecated starting from PyGAD 3.3.0. To delay or pause the evolution after each generation, assign a callback function/method to the 'on_generation' parameter to adds some time delay.") - if delay_after_gen >= 0.0: - self.delay_after_gen = delay_after_gen - else: - self.valid_parameters = False - raise ValueError(f"The value passed to the 'delay_after_gen' parameter must be a non-negative number. The value passed is ({delay_after_gen}) of type {type(delay_after_gen)}.") - else: - self.valid_parameters = False - raise TypeError(f"The value passed to the 'delay_after_gen' parameter must be of type int or float but {type(delay_after_gen)} found.") - # Validate save_best_solutions if type(save_best_solutions) is bool: if save_best_solutions == True: @@ -1228,7 +1211,7 @@ def __init__(self, self.valid_parameters = False raise ValueError(f"In the 'stop_criteria' parameter, the supported stop words are {self.supported_stop_words} but '{stop_word}' found.") - if number.replace(".", "").isnumeric(): + if number.replace(".", "").replace("-", "").isnumeric(): number = float(number) else: self.valid_parameters = False @@ -1323,6 +1306,8 @@ def __init__(self, # A list holding the offspring after applying mutation in the last generation. self.last_generation_offspring_mutation = None # Holds the fitness values of one generation before the fitness values saved in the last_generation_fitness attribute. Added in PyGAD 2.16.2. + # They are used inside the cal_pop_fitness() method to fetch the fitness of the parents in one generation before the latest generation. + # This is to avoid re-calculating the fitness for such parents again. self.previous_generation_fitness = None # Added in PyGAD 2.18.0. A NumPy array holding the elitism of the current generation according to the value passed in the 'keep_elitism' parameter. It works only if the 'keep_elitism' parameter has a non-zero value. self.last_generation_elitism = None @@ -1657,14 +1642,12 @@ def cal_pop_fitness(self): # 'last_generation_parents_as_list' is the list version of 'self.last_generation_parents' # It is used to return the parent index using the 'in' membership operator of Python lists. This is much faster than using 'numpy.where()'. if self.last_generation_parents is not None: - last_generation_parents_as_list = [ - list(gen_parent) for gen_parent in self.last_generation_parents] + last_generation_parents_as_list = self.last_generation_parents.tolist() # 'last_generation_elitism_as_list' is the list version of 'self.last_generation_elitism' # It is used to return the elitism index using the 'in' membership operator of Python lists. This is much faster than using 'numpy.where()'. if self.last_generation_elitism is not None: - last_generation_elitism_as_list = [ - list(gen_elitism) for gen_elitism in self.last_generation_elitism] + last_generation_elitism_as_list = self.last_generation_elitism.tolist() pop_fitness = ["undefined"] * len(self.population) if self.parallel_processing is None: @@ -1676,6 +1659,12 @@ def cal_pop_fitness(self): # Make sure that both the solution and 'self.solutions' are of type 'list' not 'numpy.ndarray'. # if (self.save_solutions) and (len(self.solutions) > 0) and (numpy.any(numpy.all(self.solutions == numpy.array(sol), axis=1))) # if (self.save_solutions) and (len(self.solutions) > 0) and (numpy.any(numpy.all(numpy.equal(self.solutions, numpy.array(sol)), axis=1))) + + # Make sure self.best_solutions is a list of lists before proceeding. + # Because the second condition expects that best_solutions is a list of lists. + if type(self.best_solutions) is numpy.ndarray: + self.best_solutions = self.best_solutions.tolist() + if (self.save_solutions) and (len(self.solutions) > 0) and (list(sol) in self.solutions): solution_idx = self.solutions.index(list(sol)) fitness = self.solutions_fitness[solution_idx] @@ -1884,13 +1873,13 @@ def run(self): # self.best_solutions: Holds the best solution in each generation. if type(self.best_solutions) is numpy.ndarray: - self.best_solutions = list(self.best_solutions) + self.best_solutions = self.best_solutions.tolist() # self.best_solutions_fitness: A list holding the fitness value of the best solution for each generation. if type(self.best_solutions_fitness) is numpy.ndarray: self.best_solutions_fitness = list(self.best_solutions_fitness) # self.solutions: Holds the solutions in each generation. if type(self.solutions) is numpy.ndarray: - self.solutions = list(self.solutions) + self.solutions = self.solutions.tolist() # self.solutions_fitness: Holds the fitness of the solutions in each generation. if type(self.solutions_fitness) is numpy.ndarray: self.solutions_fitness = list(self.solutions_fitness) @@ -1930,34 +1919,8 @@ def run(self): self.best_solutions.append(list(best_solution)) for generation in range(generation_first_idx, generation_last_idx): - if not (self.on_fitness is None): - on_fitness_output = self.on_fitness(self, - self.last_generation_fitness) - - if on_fitness_output is None: - pass - else: - if type(on_fitness_output) in [tuple, list, numpy.ndarray, range]: - on_fitness_output = numpy.array(on_fitness_output) - if on_fitness_output.shape == self.last_generation_fitness.shape: - self.last_generation_fitness = on_fitness_output - else: - raise ValueError(f"Size mismatch between the output of on_fitness() {on_fitness_output.shape} and the expected fitness output {self.last_generation_fitness.shape}.") - else: - raise ValueError(f"The output of on_fitness() is expected to be tuple/list/range/numpy.ndarray but {type(on_fitness_output)} found.") - - # Appending the fitness value of the best solution in the current generation to the best_solutions_fitness attribute. - self.best_solutions_fitness.append(best_solution_fitness) - - # Appending the solutions in the current generation to the solutions list. - if self.save_solutions: - # self.solutions.extend(self.population.copy()) - population_as_list = self.population.copy() - population_as_list = [list(item) - for item in population_as_list] - self.solutions.extend(population_as_list) - self.solutions_fitness.extend(self.last_generation_fitness) + self.run_loop_head(best_solution_fitness) # Call the 'run_select_parents()' method to select the parents. # It edits these 2 instance attributes: @@ -1981,7 +1944,6 @@ def run(self): # 1) population: A NumPy array of the population of solutions/chromosomes. self.run_update_population() - # The generations_completed attribute holds the number of the last completed generation. self.generations_completed = generation + 1 @@ -2064,8 +2026,6 @@ def run(self): if stop_run: break - time.sleep(self.delay_after_gen) - # Save the fitness of the last generation. if self.save_solutions: # self.solutions.extend(self.population.copy()) @@ -2097,6 +2057,9 @@ def run(self): # Converting the 'best_solutions' list into a NumPy array. self.best_solutions = numpy.array(self.best_solutions) + # Update previous_generation_fitness because it is used to get the fitness of the parents. + self.previous_generation_fitness = self.last_generation_fitness.copy() + # Converting the 'solutions' list into a NumPy array. # self.solutions = numpy.array(self.solutions) except Exception as ex: @@ -2104,6 +2067,35 @@ def run(self): # sys.exit(-1) raise ex + def run_loop_head(self, best_solution_fitness): + if not (self.on_fitness is None): + on_fitness_output = self.on_fitness(self, + self.last_generation_fitness) + + if on_fitness_output is None: + pass + else: + if type(on_fitness_output) in [tuple, list, numpy.ndarray, range]: + on_fitness_output = numpy.array(on_fitness_output) + if on_fitness_output.shape == self.last_generation_fitness.shape: + self.last_generation_fitness = on_fitness_output + else: + raise ValueError(f"Size mismatch between the output of on_fitness() {on_fitness_output.shape} and the expected fitness output {self.last_generation_fitness.shape}.") + else: + raise ValueError(f"The output of on_fitness() is expected to be tuple/list/range/numpy.ndarray but {type(on_fitness_output)} found.") + + # Appending the fitness value of the best solution in the current generation to the best_solutions_fitness attribute. + self.best_solutions_fitness.append(best_solution_fitness) + + # Appending the solutions in the current generation to the solutions list. + if self.save_solutions: + # self.solutions.extend(self.population.copy()) + population_as_list = self.population.copy() + population_as_list = [list(item) for item in population_as_list] + self.solutions.extend(population_as_list) + + self.solutions_fitness.extend(self.last_generation_fitness) + def run_select_parents(self, call_on_parents=True): """ This method must be only callled from inside the run() method. It is not meant for use by the user. @@ -2352,6 +2344,7 @@ def best_solution(self, pop_fitness=None): -best_solution_fitness: Fitness value of the best solution. -best_match_idx: Index of the best solution in the current population. """ + try: if pop_fitness is None: # If the 'pop_fitness' parameter is not passed, then we have to call the 'cal_pop_fitness()' method to calculate the fitness of all solutions in the lastest population. @@ -2535,11 +2528,6 @@ def print_params_summary(): if not print_step_parameters: print_mutation_params() - if self.delay_after_gen != 0: - m = f"Post-Generation Delay: {self.delay_after_gen}" - self.logger.info(m) - summary_output = summary_output + m + "\n" - if not print_step_parameters: print_on_generation_params() diff --git a/pygad/visualize/plot.py b/pygad/visualize/plot.py index 3ddeaff..3265de3 100644 --- a/pygad/visualize/plot.py +++ b/pygad/visualize/plot.py @@ -3,9 +3,18 @@ """ import numpy -import matplotlib.pyplot +# import matplotlib.pyplot import pygad +def get_matplotlib(): + # Importing matplotlib.pyplot at the module scope causes performance issues. + # This causes matplotlib.pyplot to be imported once pygad is imported. + # An efficient approach is to import matplotlib.pyplot only when needed. + # Inside each function, call get_matplotlib() to return the library object. + # If a function called get_matplotlib() once, then the library object is reused. + import matplotlib.pyplot as matplt + return matplt + class Plot: def __init__(): @@ -43,7 +52,9 @@ def plot_fitness(self, self.logger.error("The plot_fitness() (i.e. plot_result()) method can only be called after completing at least 1 generation but ({self.generations_completed}) is completed.") raise RuntimeError("The plot_fitness() (i.e. plot_result()) method can only be called after completing at least 1 generation but ({self.generations_completed}) is completed.") - fig = matplotlib.pyplot.figure() + matplt = get_matplotlib() + + fig = matplt.figure() if type(self.best_solutions_fitness[0]) in [list, tuple, numpy.ndarray] and len(self.best_solutions_fitness[0]) > 1: # Multi-objective optimization problem. if type(linewidth) in pygad.GA.supported_int_float_types: @@ -70,18 +81,18 @@ def plot_fitness(self, # Return the fitness values for the current objective function across all best solutions acorss all generations. fitness = numpy.array(self.best_solutions_fitness)[:, objective_idx] if plot_type == "plot": - matplotlib.pyplot.plot(fitness, + matplt.plot(fitness, linewidth=current_linewidth, color=current_color, label=current_label) elif plot_type == "scatter": - matplotlib.pyplot.scatter(range(len(fitness)), + matplt.scatter(range(len(fitness)), fitness, linewidth=current_linewidth, color=current_color, label=current_label) elif plot_type == "bar": - matplotlib.pyplot.bar(range(len(fitness)), + matplt.bar(range(len(fitness)), fitness, linewidth=current_linewidth, color=current_color, @@ -89,29 +100,29 @@ def plot_fitness(self, else: # Single-objective optimization problem. if plot_type == "plot": - matplotlib.pyplot.plot(self.best_solutions_fitness, + matplt.plot(self.best_solutions_fitness, linewidth=linewidth, color=color) elif plot_type == "scatter": - matplotlib.pyplot.scatter(range(len(self.best_solutions_fitness)), + matplt.scatter(range(len(self.best_solutions_fitness)), self.best_solutions_fitness, linewidth=linewidth, color=color) elif plot_type == "bar": - matplotlib.pyplot.bar(range(len(self.best_solutions_fitness)), + matplt.bar(range(len(self.best_solutions_fitness)), self.best_solutions_fitness, linewidth=linewidth, color=color) - matplotlib.pyplot.title(title, fontsize=font_size) - matplotlib.pyplot.xlabel(xlabel, fontsize=font_size) - matplotlib.pyplot.ylabel(ylabel, fontsize=font_size) + matplt.title(title, fontsize=font_size) + matplt.xlabel(xlabel, fontsize=font_size) + matplt.ylabel(ylabel, fontsize=font_size) # Create a legend out of the labels. - matplotlib.pyplot.legend() + matplt.legend() if not save_dir is None: - matplotlib.pyplot.savefig(fname=save_dir, + matplt.savefig(fname=save_dir, bbox_inches='tight') - matplotlib.pyplot.show() + matplt.show() return fig @@ -166,21 +177,23 @@ def plot_new_solution_rate(self, generation_num_unique_solutions = len_after - len_before num_unique_solutions_per_generation.append(generation_num_unique_solutions) - fig = matplotlib.pyplot.figure() + matplt = get_matplotlib() + + fig = matplt.figure() if plot_type == "plot": - matplotlib.pyplot.plot(num_unique_solutions_per_generation, linewidth=linewidth, color=color) + matplt.plot(num_unique_solutions_per_generation, linewidth=linewidth, color=color) elif plot_type == "scatter": - matplotlib.pyplot.scatter(range(self.generations_completed), num_unique_solutions_per_generation, linewidth=linewidth, color=color) + matplt.scatter(range(self.generations_completed), num_unique_solutions_per_generation, linewidth=linewidth, color=color) elif plot_type == "bar": - matplotlib.pyplot.bar(range(self.generations_completed), num_unique_solutions_per_generation, linewidth=linewidth, color=color) - matplotlib.pyplot.title(title, fontsize=font_size) - matplotlib.pyplot.xlabel(xlabel, fontsize=font_size) - matplotlib.pyplot.ylabel(ylabel, fontsize=font_size) + matplt.bar(range(self.generations_completed), num_unique_solutions_per_generation, linewidth=linewidth, color=color) + matplt.title(title, fontsize=font_size) + matplt.xlabel(xlabel, fontsize=font_size) + matplt.ylabel(ylabel, fontsize=font_size) if not save_dir is None: - matplotlib.pyplot.savefig(fname=save_dir, + matplt.savefig(fname=save_dir, bbox_inches='tight') - matplotlib.pyplot.show() + matplt.show() return fig @@ -251,7 +264,7 @@ def plot_genes(self, if num_cols == 0: figsize = (10, 8) # There is only a single gene - fig, ax = matplotlib.pyplot.subplots(num_rows, figsize=figsize) + fig, ax = matplt.subplots(num_rows, figsize=figsize) if plot_type == "plot": ax.plot(solutions_to_plot[:, 0], linewidth=linewidth, color=fill_color) elif plot_type == "scatter": @@ -260,7 +273,7 @@ def plot_genes(self, ax.bar(range(self.generations_completed + 1), solutions_to_plot[:, 0], linewidth=linewidth, color=fill_color) ax.set_xlabel(0, fontsize=font_size) else: - fig, axs = matplotlib.pyplot.subplots(num_rows, num_cols) + fig, axs = matplt.subplots(num_rows, num_cols) if num_cols == 1 and num_rows == 1: fig.set_figwidth(5 * num_cols) @@ -297,10 +310,10 @@ def plot_genes(self, gene_idx += 1 fig.suptitle(title, fontsize=font_size, y=1.001) - matplotlib.pyplot.tight_layout() + matplt.tight_layout() elif graph_type == "boxplot": - fig = matplotlib.pyplot.figure(1, figsize=(0.7*self.num_genes, 6)) + fig = matplt.figure(1, figsize=(0.7*self.num_genes, 6)) # Create an axes instance ax = fig.add_subplot(111) @@ -323,10 +336,10 @@ def plot_genes(self, for cap in boxeplots['caps']: cap.set(color=color, linewidth=linewidth) - matplotlib.pyplot.title(title, fontsize=font_size) - matplotlib.pyplot.xlabel(xlabel, fontsize=font_size) - matplotlib.pyplot.ylabel(ylabel, fontsize=font_size) - matplotlib.pyplot.tight_layout() + matplt.title(title, fontsize=font_size) + matplt.xlabel(xlabel, fontsize=font_size) + matplt.ylabel(ylabel, fontsize=font_size) + matplt.tight_layout() elif graph_type == "histogram": # num_rows will always be >= 1 @@ -337,12 +350,12 @@ def plot_genes(self, if num_cols == 0: figsize = (10, 8) # There is only a single gene - fig, ax = matplotlib.pyplot.subplots(num_rows, + fig, ax = matplt.subplots(num_rows, figsize=figsize) ax.hist(solutions_to_plot[:, 0], color=fill_color) ax.set_xlabel(0, fontsize=font_size) else: - fig, axs = matplotlib.pyplot.subplots(num_rows, num_cols) + fig, axs = matplt.subplots(num_rows, num_cols) if num_cols == 1 and num_rows == 1: fig.set_figwidth(4 * num_cols) @@ -375,13 +388,13 @@ def plot_genes(self, gene_idx += 1 fig.suptitle(title, fontsize=font_size, y=1.001) - matplotlib.pyplot.tight_layout() + matplt.tight_layout() if not save_dir is None: - matplotlib.pyplot.savefig(fname=save_dir, + matplt.savefig(fname=save_dir, bbox_inches='tight') - matplotlib.pyplot.show() + matplt.show() return fig @@ -449,12 +462,14 @@ def plot_pareto_front_curve(self, # Sort the Pareto front solutions (optional but can make the plot cleaner) sorted_pareto_front = sorted(zip(pareto_front_x, pareto_front_y)) + matplt = get_matplotlib() + # Plotting - fig = matplotlib.pyplot.figure() + fig = matplt.figure() # First, plot the scatter of all points (population) all_points_x = [self.last_generation_fitness[i][0] for i in range(self.sol_per_pop)] all_points_y = [self.last_generation_fitness[i][1] for i in range(self.sol_per_pop)] - matplotlib.pyplot.scatter(all_points_x, + matplt.scatter(all_points_x, all_points_y, marker=marker, color=color_fitness, @@ -463,7 +478,7 @@ def plot_pareto_front_curve(self, # Then, plot the Pareto front as a curve pareto_front_x_sorted, pareto_front_y_sorted = zip(*sorted_pareto_front) - matplotlib.pyplot.plot(pareto_front_x_sorted, + matplt.plot(pareto_front_x_sorted, pareto_front_y_sorted, marker=marker, label=label, @@ -471,17 +486,17 @@ def plot_pareto_front_curve(self, color=color, linewidth=linewidth) - matplotlib.pyplot.title(title, fontsize=font_size) - matplotlib.pyplot.xlabel(xlabel, fontsize=font_size) - matplotlib.pyplot.ylabel(ylabel, fontsize=font_size) - matplotlib.pyplot.legend() + matplt.title(title, fontsize=font_size) + matplt.xlabel(xlabel, fontsize=font_size) + matplt.ylabel(ylabel, fontsize=font_size) + matplt.legend() - matplotlib.pyplot.grid(grid) + matplt.grid(grid) if not save_dir is None: - matplotlib.pyplot.savefig(fname=save_dir, + matplt.savefig(fname=save_dir, bbox_inches='tight') - matplotlib.pyplot.show() + matplt.show() return fig