Skip to content

Commit

Permalink
Allow configuring the default logger
Browse files Browse the repository at this point in the history
  • Loading branch information
dhadka committed Sep 26, 2024
1 parent a79f20e commit 34b0f93
Show file tree
Hide file tree
Showing 12 changed files with 199 additions and 171 deletions.
8 changes: 5 additions & 3 deletions platypus/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@

from .algorithms import (CMAES, GDE3, IBEA, MOEAD, NSGAII, NSGAIII, OMOPSO,
PAES, PESA2, SMPSO, SPEA2, AbstractGeneticAlgorithm,
Algorithm, EpsMOEA, EpsNSGAII, EvolutionaryStrategy,
EpsMOEA, EpsNSGAII, EvolutionaryStrategy,
GeneticAlgorithm, ParticleSwarm, RegionBasedSelector,
SingleObjectiveAlgorithm)
from .config import PlatypusConfig
from .core import (AdaptiveGridArchive, Archive, AttributeDominance,
from .core import (AdaptiveGridArchive, Algorithm, Archive, AttributeDominance,
Constraint, Direction, Dominance, EpsilonBoxArchive,
EpsilonDominance, FitnessArchive, FitnessEvaluator,
FixedLengthArray, Generator, HypervolumeFitnessEvaluator,
Expand All @@ -44,7 +44,8 @@
experiment)
from .extensions import (AdaptiveTimeContinuationExtension,
EpsilonProgressContinuationExtension, Extension,
FixedFrequencyExtension, SaveResultsExtension)
FixedFrequencyExtension, LoggingExtension,
SaveResultsExtension)
from .filters import (crowding_distance_key, feasible, fitness_key, infeasible,
matches, objectives_key, rank_key, truncate, unique,
variables_key)
Expand Down Expand Up @@ -78,5 +79,6 @@
PlatypusConfig.register_default_mutator(Permutation, CompoundMutation(Insertion(), Swap()))
PlatypusConfig.register_default_mutator(Subset, Replace())

PlatypusConfig._default_logger = LoggingExtension
PlatypusConfig.default_evaluator = MapEvaluator()
PlatypusConfig._version = __version__
167 changes: 12 additions & 155 deletions platypus/algorithms.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,175 +19,32 @@

import copy
import functools
import inspect
import itertools
import math
import operator
import random
import sys
from abc import ABCMeta, abstractmethod

from ._math import (EPSILON, check_eigensystem, choose, lsolve,
point_line_dist, tql2, tred2)
from ._tools import coalesce, only_keys_for, remove_keys
from ._math import (EPSILON, POSITIVE_INFINITY, check_eigensystem, choose,
clip, lsolve, point_line_dist, tql2, tred2)
from ._tools import only_keys_for, remove_keys
from .config import PlatypusConfig
from .core import (POSITIVE_INFINITY, AdaptiveGridArchive, Archive,
AttributeDominance, Direction, EpsilonBoxArchive,
EpsilonDominance, FitnessArchive,
HypervolumeFitnessEvaluator, MaxEvaluations,
ParetoDominance, Selector, Solution, TerminationCondition,
crowding_distance, crowding_distance_key, fitness_key,
nondominated_prune, nondominated_sort,
nondominated_sort_cmp, nondominated_split,
nondominated_truncate)
from .core import (AdaptiveGridArchive, Algorithm, Archive, AttributeDominance,
Direction, EpsilonBoxArchive, EpsilonDominance,
FitnessArchive, HypervolumeFitnessEvaluator,
ParetoDominance, Selector, Solution, crowding_distance,
crowding_distance_key, fitness_key, nondominated_prune,
nondominated_sort, nondominated_sort_cmp,
nondominated_split, nondominated_truncate)
from .distance import DistanceMatrix
from .errors import PlatypusError
from .evaluator import Job
from .extensions import AdaptiveTimeContinuationExtension, LoggingExtension
from .extensions import AdaptiveTimeContinuationExtension
from .operators import (DifferentialEvolution, NonUniformMutation,
RandomGenerator, TournamentSelector, UniformMutation,
clip)
RandomGenerator, TournamentSelector, UniformMutation)
from .weights import chebyshev, normal_boundary_weights, random_weights


class EvaluateSolution(Job):

def __init__(self, solution):
super().__init__()
self.solution = solution

def run(self):
self.solution.evaluate()

class Algorithm(metaclass=ABCMeta):
"""Base class for all optimization algorithms.
For most use cases, use the :meth:`run` method to execute an algorithm
until the termination conditions are satisfied. Internally, this invokes
the :meth:`step` method to perform each iteration of the algorithm. An
termination conditions and callbacks are evaluated after each step.
Parameters
----------
problem : Problem
The problem being optimized.
evaluator : Evaluator
The evalutor used to evaluate solutions. If `None`, the default
evaluator defined in :attr:`PlatypusConfig` is selected.
log_frequency : int
The frequency to log evaluation progress. If `None`, the default
log frequency defined in :attr:`PlatypusConfig` is selected.
Attributes
----------
nfe : int
The current number of function evaluations (NFE)
result: list or Archive
The current result, which is updated after each iteration.
"""

def __init__(self,
problem,
evaluator=None,
log_frequency=None,
**kwargs):
super().__init__()
self.problem = problem
self.evaluator = evaluator
self.nfe = 0
self._extensions = []

if self.evaluator is None:
self.evaluator = PlatypusConfig.default_evaluator

self.add_extension(LoggingExtension(coalesce(log_frequency, PlatypusConfig.default_log_frequency, 0)))

def add_extension(self, extension):
"""Adds an extension to this algorithm.
Extensions add functionality to an algorithm at specific points during
a run. If multiple extensions are added, they are run in reverse
order. That is, the last extension added is the first to run.
Parameters
----------
extension : Extension
The extension.
"""
if inspect.isclass(extension):
extension = extension()
self._extensions.insert(0, extension)

@abstractmethod
def step(self):
"""Performs one logical step of the algorithm."""
pass

def evaluate_all(self, solutions):
"""Evaluates all of the given solutions.
Subclasses should prefer using this method to evaluate solutions,
ideally providing an entire population to leverage parallelization,
as it tracks NFE.
Parameters
----------
solutions : list of Solution
The solutions to evaluate.
"""
unevaluated = [s for s in solutions if not s.evaluated]

jobs = [EvaluateSolution(s) for s in unevaluated]
results = self.evaluator.evaluate_all(jobs)

# if needed, update the original solution with the results
for i, result in enumerate(results):
if unevaluated[i] != result.solution:
unevaluated[i].variables[:] = result.solution.variables[:]
unevaluated[i].objectives[:] = result.solution.objectives[:]
unevaluated[i].constraints[:] = result.solution.constraints[:]
unevaluated[i].constraint_violation = result.solution.constraint_violation
unevaluated[i].feasible = result.solution.feasible
unevaluated[i].evaluated = result.solution.evaluated

self.nfe += len(solutions)

def run(self, condition, callback=None):
"""Runs this algorithm until the termination condition is reached.
Parameters
----------
condition : int or TerminationCondition
The termination condition. Providing an integer value is converted
into the :class:`MaxEvaluations` condition.
callback : Callable, optional
Callback function that is invoked after every iteration. The
callback is passed this algorithm instance.
"""
if isinstance(condition, int):
condition = MaxEvaluations(condition)

if isinstance(condition, TerminationCondition):
condition.initialize(self)

for extension in self._extensions:
extension.start_run(self)

while not condition(self):
for extension in self._extensions:
extension.pre_step(self)

self.step()

for extension in self._extensions:
extension.post_step(self)

if callback is not None:
callback(self)

for extension in self._extensions:
extension.end_run(self)

class AbstractGeneticAlgorithm(Algorithm, metaclass=ABCMeta):
"""Abstract class for genetic algorithms.
Expand Down
13 changes: 13 additions & 0 deletions platypus/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import inspect

from ._tools import coalesce
from .errors import PlatypusError
from .types import Type

Expand All @@ -31,6 +32,7 @@ def __init__(self):
self._version = None
self._default_variator = {}
self._default_mutator = {}
self._default_logger = None
self.default_evaluator = None
self.default_log_frequency = None

Expand Down Expand Up @@ -124,6 +126,17 @@ def default_mutator(self, problem):

raise PlatypusError(f"no default mutator for {base_type}")

def get_logging_extension(self, log_frequency=None):
"""Returns the default logging extension.
Parameters
----------
log_frequency : int
The frequency that run progress log messages are written. If
:code:`None`, uses :code:`default_log_frequency`.
"""
return self._default_logger(coalesce(log_frequency, self.default_log_frequency, 0))


# Defaults are configured in __init__.py.
PlatypusConfig = _PlatypusConfig()
Loading

0 comments on commit 34b0f93

Please sign in to comment.