Skip to content

Update fstring #189

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 8, 2023
Merged

Conversation

KelvinChung2000
Copy link

Updating all the string.format with fstring to improve readability.

Minor formatting, such as removing some of the extra spaces.

@ahmedfgad ahmedfgad added the enhancement New feature or request label May 6, 2023
@ahmedfgad
Copy link
Owner

Thanks @KelvinChung2000. It will save much space as the format() method is used over 600 times.

@KelvinChung2000
Copy link
Author

It is more about readability and maintainability. I have another similar changes would like to make. Removing all the logging.error and use a single try except to capture the error. This in theory will make the code much clearer. Do you want me to do this on the same PR?

@ahmedfgad
Copy link
Owner

It is more about readability and maintainability. I have another similar changes would like to make. Removing all the logging.error and use a single try except to capture the error. This in theory will make the code much clearer. Do you want me to do this on the same PR?

Thanks @KelvinChung2000 for your suggestion.

But we cannot customize the error message using a single try-except block. I prefer to keep use logger.error() to customize the error message. This gives a clearer idea for the user about the issue to be fixed.

@KelvinChung2000
Copy link
Author

You will still be able to have your customised message. Whenever you log the error, you raise the same message anyway. You can capture the raised error message at the except block and use a single logger.exception() for all the raised exceptions. Then you can do an exit(-1) to terminate the programme. Doing so, you can also remove the valid_parameters variable since the programme will be terminated at that __init__ point.

Which is something like this:

import logging

try:
    raise ValueError("This is a value error")
except Exception as e:
    logging.exception("Exception occurred")
    exit(-1)
    
# Output:
# ERROR: root: Exception occurred
# Traceback(most recent call last):
#   File "/GeneticAlgorithmPython/test.py", line 4, in <module >
#   raise ValueError("This is a value error")
# ValueError: This is a value error

Just a side note, the current valid_parameters is not providing a lot of utility since it will not be able to validate any parameter change after instantiation. I suggest moving all the check logic into a setter for each variable, so the checking can happen whenever the user is reassigning the variable. Having the separation, which should also improve the readability of the code. We can also add typing to the property, which allows for more tipping from IDE like vscode

Which can be something like this:

@property
def suppress_warnings(self) -> bool:
    return self._suppress_warnings

@suppress_warnings.setter
def suppress_warnings(self, suppress_warnings):
    # If suppress_warnings is bool and its value is False, then print warning messages.
    if type(suppress_warnings) is bool:
        self._suppress_warnings = suppress_warnings
    else:
        raise TypeError(
            f"The expected type of the 'suppress_warnings' parameter is bool but {type(suppress_warnings)} found.")
@ahmedfgad
Copy link
Owner

ahmedfgad commented May 6, 2023

So, you suggest using try-except to have only one logger.error() statement for all checks?

Using setter would be a great addition too but we might consider it later.

@KelvinChung2000
Copy link
Author

Yes, by doing so, you will not have any duplicated messages, which should make the code cleaner to read.

Changing to a setter should make the library more robust, but the migration will be a pain.

@ahmedfgad
Copy link
Owner

Yes, by doing so, you will not have any duplicated messages, which should make the code cleaner to read.

Changing to a setter should make the library more robust, but the migration will be a pain.

I totally agree. Using setter would solve some issues. But I will also introduce some conflictions for the user. This is a scenario.

Inside the class, we expect the value of a property named, for example, num_genes to be inside the protected variable _num_genes.

Sometimes the user modifies the properties outside the class. So, the user might change the value of num_genes outside the class. But it will not be used inside the class as we are using _num_genes instead.

For now, let's keep the setter aside.

@KelvinChung2000
Copy link
Author

The changes will apply though to the protected variable, any assignments using self.num_genes = something will be assigning the value using the setter, which changes the _num_genes value. When the user reads the variable, the value will be returned from _num_genes , so in the user's perspective, this would not have changed anything but added robustness, as any potential false assignments will be caught.

@ahmedfgad
Copy link
Owner

The changes will apply though to the protected variable, any assignments using self.num_genes = something will be assigning the value using the setter, which changes the _num_genes value. When the user reads the variable, the value will be returned from _num_genes , so in the user's perspective, this would not have changed anything but added robustness, as any potential false assignments will be caught.

This makes sense.

But I am worried because our only motivation to use the setter and getter is to validate the parameters. But parameter validation happens only once. If the user have to call the getter method each time a property is retrieved, this would impact the performance. We are already suffering from high-computational time for some cases.

Also I think getter would be perfect in case that a logic must be applied when getting a property. In our case, we are just retrieving the property with no logic applied.

Maybe setter and getter would be applied to selected properties. An example is the gene_space. Sometimes it is given a value like gene_space=range(5). We might apply a code to unpack it whenever retrieved. So, instead of returning range(5), we might return the unpacked range [0, 1, 2, 3, 4].

@KelvinChung2000
Copy link
Author

I am unsure how this will affect the performance; I will need to look this up. But I would guess this will induce some overhead. Let's leave it for now since this will be a substantial change which should not be included in this PR. Would you like the changes on the logger? If so, I will work on it later.

@ahmedfgad
Copy link
Owner

Right now, we do the following when an exception happens:

  1. Raise an exception with the error message.
  2. Log the error message using the logging module.
if ...:
    msg = "..."
    raise ValueError(msg)
    log.error(msg)
if ...:
    msg = "..."
    raise ValueError(msg)
    log.error(msg)

Both steps use an informational message that helps the user debug the issue.

If we are to separate the 2 messages so that we only use the log.error() only once, then the log message will not have the custom error message.

try:
    if ...:
        msg = "..."
        raise ValueError(msg)
    if ...:
        msg = "..."
        raise ValueError(msg)
except:
    log.error("Generic error message")

Some users would log all the messages to a file. I prefer to keep the error message in the log file so that they have a clear idea of what happened.

@KelvinChung2000
Copy link
Author

You will still able to retain the error message. You can try out this example and you can see that message is being captured

import logging

try:
    raise ValueError("This is a custom value error")
except Exception as e:
    logging.exception(e)
    
# output
# ERROR:root:This is a custom value error
# Traceback(most recent call last):
#   File "/GeneticAlgorithmPython/test.py", line 4, in <module >
#   raise ValueError("This is a custom value error")
# ValueError: This is a custom value error
@ahmedfgad
Copy link
Owner

Great. Then please add it to the pull request.

@ahmedfgad ahmedfgad merged commit fcca50c into ahmedfgad:master May 8, 2023
ahmedfgad added a commit that referenced this pull request Jun 20, 2023
PyGAD 3.1.0 Release Notes
1.	Fix a bug when the initial population has duplciate genes if a nested gene space is used.
2.	The gene_space parameter can no longer be assigned a tuple.
3.	Fix a bug when the gene_space parameter has a member of type tuple.
4.	A new instance attribute called gene_space_unpacked which has the unpacked gene_space. It is used to solve duplicates. For infinite ranges in the gene_space, they are unpacked to a limited number of values (e.g. 100).
5.	Bug fixes when creating the initial population using gene_space attribute.
6.	When a dict is used with the gene_space attribute, the new gene value was calculated by summing 2 values: 1) the value sampled from the dict 2) a random value returned from the random mutation range defined by the 2 parameters random_mutation_min_val and random_mutation_max_val. This might cause the gene value to exceed the range limit defined in the gene_space. To respect the gene_space range, this release only returns the value from the dict without summing it to a random value.
7.	Formatting the strings using f-string instead of the format() method. #189
8.	In the __init__() of the pygad.GA class, the logged error messages are handled using a try-except block instead of repeating the logger.error() command. #189
9.	A new class named CustomLogger is created in the pygad.cnn module to create a default logger using the logging module assigned to the logger attribute. This class is extended in all other classes in the module. The constructors of these classes have a new parameter named logger which defaults to None. If no logger is passed, then the default logger in the CustomLogger class is used.
10.	Except for the pygad.nn module, the print() function in all other modules are replaced by the logging module to log messages.
11.	The callback functions/methods on_fitness(), on_parents(), on_crossover(), and on_mutation() can return values. These returned values override the corresponding properties. The output of on_fitness() overrides the population fitness. The on_parents() function/method must return 2 values representing the parents and their indices. The output of on_crossover() overrides the crossover offspring. The output of on_mutation() overrides the mutation offspring.
12.	Fix a bug when adaptive mutation is used while fitness_batch_size>1. #195
13.	When allow_duplicate_genes=False and a user-defined gene_space is used, it sometimes happen that there is no room to solve the duplicates between the 2 genes by simply replacing the value of one gene by another gene. This release tries to solve such duplicates by looking for a third gene that will help in solving the duplicates. These examples explain how it works. Check this section for more information.
14.	Use probabilities to select parents using the rank parent selection method. #205
15.	The 2 parameters random_mutation_min_val and random_mutation_max_val can accept iterables (list/tuple/numpy.ndarray) with length equal to the number of genes. This enables customizing the mutation range for each individual gene. #198
16.	The 2 parameters init_range_low and init_range_high can accept iterables (list/tuple/numpy.ndarray) with length equal to the number of genes. This enables customizing the initial range for each individual gene when creating the initial population.
17.	The data parameter in the predict() function of the pygad.kerasga module can be assigned a data generator. #115 #207
18.	The predict() function of the pygad.kerasga module accepts 3 optional parameters: 1) batch_size=None, verbose=0, and steps=None. Check documentation of the Keras Model.predict() method for more information. #207
19.	The documentation is updated to explain how mutation works when gene_space is used with int or float data types. Check this section. #198
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
2 participants