# @Author: Thomas Firmin <ThomasFirmin>
# @Date: 2022-05-03T15:41:48+02:00
# @Email: thomas.firmin@univ-lille.fr
# @Project: Zellij
# @Last modified by: tfirmin
# @Last modified time: 2022-10-03T22:38:06+02:00
# @License: CeCILL-C (http://www.cecill.info/index.fr.html)
from zellij.core.search_space import ContinuousSearchspace
from zellij.core.metaheuristic import Metaheuristic
from zellij.strategies.tools.chaos_map import Chaos_map, Henon
import zellij.utils.progress_bar as pb
import numpy as np
import logging
logger = logging.getLogger("zellij.CO")
[docs]class CGS(Metaheuristic):
"""Chaotic Global search
CGS is an exploration :ref:`meta` using chaos to violently move in the :ref:`sp`.
It is continuous optimization, so the :ref:`sp` is converted to continuous.
To do so, it uses a :ref:`cmap`, such as Henon or Kent map.
Attributes
----------
level : int
Chaotic level corresponds to the number of iteration of the chaotic map
map : Chaos_map
Chaotic map used to sample points. See Chaos_map object.
up_bounds : list
List of float containing the upper bounds of the search space converted to continuous
lo_bounds : list
List of float containing the lower bounds of the search space converted to continuous
center : float
List of floats containing the coordinates of the search space center converted to continuous
radius : float
List of floats containing the radius for each dimensions of the search space converted to continuous
See Also
--------
:ref:`meta` : Parent class defining what a Metaheuristic is
Chaotic_optimization : CGS is used here to perform an exploration
CLS : Chaotic Local Search
CFS : Chaotic Fine Search
Examples
--------
>>> from zellij.core import Loss
>>> from zellij.core import ContinuousSearchspace
>>> from zellij.core import FloatVar, ArrayVar
>>> from zellij.strategies import CGS
>>> from zellij.strategies.tools import Henon
>>> from zellij.utils.benchmark import himmelblau
...
>>> lf = Loss()(himmelblau)
>>> sp = ContinuousSearchspace(ArrayVar(FloatVar("a",-5,5), FloatVar("b",-5,5)),lf)
...
... # Henon(map size, dimensions)
>>> chaosmap = Henon(250,sp.size)
... # 4 points/iterations: 4x250=1000
>>> cgs = CGS(sp, 1000, 250, chaosmap)
>>> cgs.run()
"""
def __init__(
self,
search_space,
f_calls,
level,
map,
verbose=True,
):
"""__init__(search_space, f_calls, level, map, verbose=True)
Initialize CGS class
Parameters
----------
search_space : Searchspace
Search space object containing bounds of the search space
f_calls : int
Maximum number of :ref:`lf` calls
level : int
Chaotic level corresponds to the number of iteration of the chaotic map
map : Chaos_map
Chaotic map used to sample points. See :ref:`cmap` object.
verbose : boolean, default=True
Algorithm verbosity
"""
##############
# PARAMETERS #
##############
super().__init__(search_space, f_calls, verbose)
assert hasattr(search_space, "to_continuous") or isinstance(
search_space, ContinuousSearchspace
), logger.error(
f"""If the `search_space` is not a `ContinuousSearchspace`,
the user must give a `Converter` to the :ref:`sp` object
with the kwarg `to_continuous`"""
)
self.map = map.map
self.level = level
#############
# VARIABLES #
#############
if isinstance(self.search_space, ContinuousSearchspace):
self.bounds = np.array(
[
[v.low_bound for v in self.search_space.values],
[v.up_bound for v in self.search_space.values],
],
dtype=float,
)
else:
self.bounds = np.array(
[
[0.0] * self.search_space.size,
[1.0] * self.search_space.size,
],
dtype=float,
)
# Working attributes, saved to avoid useless computations.
self.up_plus_lo = self.bounds[1] + self.bounds[0]
self.up_m_lo = self.bounds[1] - self.bounds[0]
self.center = np.multiply(0.5, self.up_plus_lo)
self.radius = np.multiply(0.5, self.up_m_lo)
self.center_m_lo_bounds = self.center - self.bounds[0]
[docs] def run(self, shift=1, H=None, n_process=1):
"""run(shift=1, H=None, n_process=1)
Parameters
----------
shift : int, default=1
Determines the starting point of the chaotic map.
H : Fractal, default=None
When used by :ref:`dba`, a fractal corresponding to the current subspace is given
n_process : int, default=1
Determines the number of best solution found to return.
Returns
-------
best_sol : list[float]
Returns a list of the :code:`n_process` best found points to the continuous format
best_scores : list[float]
Returns a list of the :code:`n_process` best found scores associated to best_sol
"""
logger.info("CGS starting")
self.build_bar(self.level)
self.k = shift
# For each level of chaos
shift_map = (self.k - 1) * self.level
points = np.empty((0, self.search_space.size), dtype=float)
n_points = self.search_space.loss.calls
l = 0
logger.info("CGS computing chaotic points")
while l < self.level and n_points < self.f_calls:
# Randomly select a parameter index of a solution
d = np.random.randint(self.search_space.size)
# Apply 3 transformations on the selected chaotic variables
r_mul_y = np.multiply(self.up_m_lo, self.map[l + shift_map])
# xx = [np.add(self.center,r_mul_y), np.add(self.center,np.multiply(self.radius,np.multiply(2,y)-1)), np.subtract(self.bounds[1],r_mul_y)]
# for each transformation of the chaotic variable
# for x in xx:
#
# x_ = np.subtract(self.up_plus_lo,x)
# sym = np.matrix([x,x,x_,x_])
# sym[1,d] = x_[d]
# sym[3,d] = x[d]
# points = np.append(points,sym,axis=0)
# n_points += 4
xx = [self.bounds[0] + r_mul_y, self.bounds[1] - r_mul_y]
# for each transformation of the chaotic variable
sym = np.array([xx[0], xx[1], xx[0], xx[1]])
sym[2, d] = xx[1][d]
sym[3, d] = xx[0][d]
points = np.append(points, sym, axis=0)
n_points += 4
l += 1
self.meta_pb.update()
# Update progress bar
self.pending_pb(len(points))
logger.info("CGS evaluating chaotic points")
if isinstance(self.search_space, ContinuousSearchspace):
ys = self.search_space.loss(points, algorithm="CGS")
else:
ys = self.search_space.loss(
self.search_space.to_continuous.reverse(points), algorithm="CGS"
)
# Update progress bar
self.update_main_pb(
len(points), explor=True, best=self.search_space.loss.new_best
)
self.close_bar()
logger.info("CGS ending")
return self.search_space.loss.get_best(n_process)
[docs]class CLS(Metaheuristic):
"""Chaotic Local Search
CLS is an exploitation :ref:`meta` using chaos to wiggle points arround an initial solution.\
It uses a rotating polygon to distribute those points, a progressive and mooving zoom on the best solution found, to refine it.
It is continuous optimization, so the :ref:`sp` is converted to continuous.
To do so, it uses a :ref:`cmap`, such as Henon or Kent map.
Attributes
----------
level : int
Chaotic level: the number of iteration of the chaotic map
map : Chaos_map
Chaotic map used to sample points. See Chaos_map object.
polygon : int
Vertex number of the rotating polygon (has an influence on the number of evaluated points)
red_rate : float
Reduction rate of the progressive zoom on the best solution found
up_bounds : list
List of float containing the upper bounds of the search space converted to continuous
lo_bounds : list
List of float containing the lower bounds of the search space converted to continuous
center : float
List of floats containing the coordinates of the search space center converted to continuous
radius : float
List of floats containing the radius for each dimensions of the search space converted to continuous
See Also
--------
:ref:`meta` : Parent class defining what a Metaheuristic is
Chaotic_optimization : CLS is used here to perform an exploitation
CGS : Chaotic Global Search
CFS : Chaotic Fine Search
Examples
--------
>>> from zellij.core import Loss
>>> from zellij.core import ContinuousSearchspace
>>> from zellij.core import FloatVar, ArrayVar
>>> from zellij.strategies import CLS
>>> from zellij.strategies.tools import Henon
>>> from zellij.utils.benchmark import himmelblau
...
>>> lf = Loss()(himmelblau)
>>> sp = ContinuousSearchspace(ArrayVar(FloatVar("a",-5,5), FloatVar("b",-5,5)),lf)
...
... # Henon(map size, dimensions)
>>> chaosmap = Henon(50,sp.size)
... # 2xpolygon points/iterations: 2x10x50=1000
>>> cls = CLS(sp, 1000, 50, 10,chaosmap)
>>> point = sp.random_point()
>>> cls.run(point, lf([point])[0])
"""
def __init__(
self,
search_space,
f_calls,
level,
polygon,
map,
verbose=True,
):
"""__init__(self,search_space,f_calls,level,polygon,map,verbose=True)
Initialize CLS class
Parameters
----------
search_space : Searchspace
Search space object containing bounds of the search space
f_calls : int
Maximum number of :ref:`lf` calls
level : int
Chaotic level corresponds to the number of iteration of the chaotic map
polygon : int
Vertex number of the rotating polygon (has an influence on the number of evaluated points)
map : Chaos_map
Chaotic map used to sample points. See Chaos_map object.
verbose : boolean, default=True
Algorithm verbosity
"""
##############
# PARAMETERS #
##############
super().__init__(search_space, f_calls, verbose)
assert hasattr(search_space, "to_continuous") or isinstance(
search_space, ContinuousSearchspace
), logger.error(
f"""If the `search_space` is not a `ContinuousSearchspace`,
the user must give a `Converter` to the :ref:`sp` object
with the kwarg `to_continuous`"""
)
self.level = level
self.polygon = polygon
self.map = map.map
self.red_rate = np.random.random()
#############
# VARIABLES #
#############
if isinstance(self.search_space, ContinuousSearchspace):
self.bounds = np.array(
[
[v.low_bound for v in self.search_space.values],
[v.up_bound for v in self.search_space.values],
],
dtype=float,
)
else:
self.bounds = np.array(
[
[0.0] * self.search_space.size,
[1.0] * self.search_space.size,
],
dtype=float,
)
self.up_plus_lo = self.bounds[1] + self.bounds[0]
self.up_m_lo = self.bounds[1] - self.bounds[0]
self.center = np.multiply(0.5, self.up_plus_lo)
self.radius = np.multiply(0.5, self.up_m_lo)
self.center_m_lo_bounds = self.center - self.bounds[0]
trigo_val = 2 * np.pi / self.polygon
self.H = [np.zeros(self.polygon), np.zeros(self.polygon)]
for i in range(1, self.polygon + 1):
# Initialize trigonometric part of symetric variables (CLS & CFS)
self.H[0][i - 1] = np.cos(trigo_val * i)
self.H[1][i - 1] = np.sin(trigo_val * i)
[docs] def run(
self, X0=None, Y0=None, chaos_level=0, shift=1, H=None, n_process=1
):
"""run(X0=None, Y0=None, chaos_level=0, shift=1, H=None, n_process=1)
Parameters
----------
X0 : list[float], optional
Initial solution. If None, a Fractal must be given (H!=None)
Y0 : {int, float}, optional
Score of the initial solution
chaos_level : int, default=0
Determines at which level of the chaos map, the algorithm starts
shift : int, default=1
Determines the starting point of the chaotic map.
H : Fractal, optional
When used by :ref:`dba`, a fractal corresponding to the current subspace is given
n_process : int, default=1
Determines the number of best solution found to return.
Returns
-------
best_sol : list[float]
Returns a list of the :code:`n_process` best found points to the continuous format
best_scores : list[float]
Returns a list of the :code:`n_process` best found scores associated to best_sol
"""
logger.info("CLS starting")
self.build_bar(self.level)
if X0:
if isinstance(self.search_space, ContinuousSearchspace):
self.X0 = np.array(X0)
else:
self.X0 = np.array(
self.search_space.to_continuous.convert([X0])[0]
)
elif H:
self.X0 = H.center
else:
raise ValueError("No starting point given to CLS")
if Y0:
self.Y0 = Y0
else:
logger.info("CLS evaluating initial solution")
if isinstance(self.search_space, ContinuousSearchspace):
self.Y0 = self.search_space.loss([self.X0], algorithm="CLS")[0]
else:
self.Y0 = self.search_space.loss(
self.search_space.to_continuous.reverse([self.X0]),
algorithm="CLS",
)[0]
self.k = shift
self.chaos_level = chaos_level
# Initialization
shift = self.chaos_level * (self.k - 1) * self.level
# Limits of the search space, if parameter greater than center, then = 1 else = -1, used to avoid overflow
db = np.minimum(self.bounds[1] - self.X0, self.X0 - self.bounds[0])
center_m_solution = self.center - self.X0
points = np.empty((0, self.search_space.size), dtype=float)
n_points = self.search_space.loss.calls
l = 0
logger.info("CLS computing chaotic points")
# for each level of chaos
while l < self.level and n_points < self.f_calls:
self.red_rate = np.random.random()
# Local search area radius
Rl = self.radius * self.red_rate
# Decomposition vector
d = np.random.randint(self.search_space.size)
# zoom speed
gamma = 10 ** (-2 * self.red_rate * l) / (l + 1)
# for each parameter of a solution, determine the improved radius
xx = np.minimum(gamma * Rl, db)
# Compute both chaotic variable of the polygonal model thanks to a chaotic map
xv = [
np.multiply(self.map[shift + l], xx),
np.multiply(1 - self.map[shift + l], xx),
]
# For both chaotic variable
for x in xv:
xi = np.outer(self.H[1], x)
xi[:, d] = x[d] * self.H[0]
xt = self.X0 + xi
points = np.append(points, xt, axis=0)
n_points += self.polygon
l += 1
self.meta_pb.update()
# Update progress bar
self.pending_pb(len(points))
logger.info("CLS evaluating chaotic points")
if isinstance(self.search_space, ContinuousSearchspace):
ys = self.search_space.loss(points, algorithm="CLS")
else:
ys = self.search_space.loss(
self.search_space.to_continuous.reverse(points), algorithm="CLS"
)
# Update progress bar
self.update_main_pb(
len(points), explor=True, best=self.search_space.loss.new_best
)
ys = np.array(ys)
idx = np.array(np.argsort(ys))[:n_process]
# best solution found
best_sol = points[idx]
best_scores = ys[idx]
self.close_bar()
logger.info("CLS ending")
return self.search_space.loss.get_best(n_process)
[docs]class CFS(Metaheuristic):
"""Chaotic Fine Search
CFS is an exploitation :ref:`meta` using chaos to wiggle points arround an initial solution.\
Contrary to CLS, CFS uses an exponential zoom on the best solution found, it works at a much smaller scale than the CLS.
It is continuous optimization, so the :ref:`sp` is converted to continuous.
To do so, it uses a :ref:`cmap`, such as Henon or Kent map.
Attributes
----------
level : int
Chaotic level corresponds to the number of iteration of the chaotic map
map : Chaos_map
Chaotic map used to sample points. See Chaos_map object.
polygon : int
Vertex number of the rotating polygon (has an influence on the number of evaluated points)
red_rate : float
Reduction rate of the progressive zoom on the best solution found
up_bounds : list
List of float containing the upper bounds of the search space converted to continuous
lo_bounds : list
List of float containing the lower bounds of the search space converted to continuous
center : float
List of floats containing the coordinates of the search space center converted to continuous
radius : float
List of floats containing the radius for each dimensions of the search space converted to continuous
See Also
--------
:ref:`meta` : Parent class defining what a Metaheuristic is
Chaotic_optimization : CLS is used here to perform an exploitation
CGS : Chaotic Global Search
CLS : Chaotic Local Search
Examples
--------
>>> from zellij.core import Loss
>>> from zellij.core import ContinuousSearchspace
>>> from zellij.core.variables import FloatVar, ArrayVar
>>> from zellij.strategies import CFS
>>> from zellij.strategies.tools import Henon
>>> from zellij.utils.benchmark import himmelblau
...
>>> lf = Loss()(himmelblau)
>>> sp = ContinuousSearchspace(ArrayVar(FloatVar("a",-5,5), FloatVar("b",-5,5)),lf)
...
... # Henon(map size, dimensions)
>>> chaosmap = Henon(50,sp.size)
... # 2xpolygon points/iterations: 2x10x50=1000
>>> cfs = CFS(sp, 1000, 50, 10, chaosmap)
>>> point = sp.random_point()
>>> cfs.run(point, lf([point])[0])
"""
def __init__(
self,
search_space,
f_calls,
level,
polygon,
map,
verbose=True,
):
"""__init__(self,search_space,f_calls,level,polygon,map,verbose=True,converter=None)
Initialize CLS class
Parameters
----------
search_space : Searchspace
Search space object containing bounds of the search space
f_calls : int
Maximum number of :ref:`lf` calls
level : int
Chaotic level corresponds to the number of iteration of the chaotic map
polygon : int
Vertex number of the rotating polygon (has an influence on the number of evaluated points)
map : Chaos_map
Chaotic map used to sample points. See Chaos_Simulated Annealingmap object.
verbose : boolean, default=True
Algorithm verbosity
"""
##############
# PARAMETERS #
##############
super().__init__(search_space, f_calls, verbose)
assert hasattr(search_space, "to_continuous") or isinstance(
search_space, ContinuousSearchspace
), logger.error(
f"""If the `search_space` is not a `ContinuousSearchspace`,
the user must give a `Converter` to the :ref:`sp` object
with the kwarg `to_continuous`"""
)
self.level = level
self.polygon = polygon
self.map = map.map
self.red_rate = np.random.random()
#############
# VARIABLES #
#############
if isinstance(self.search_space, ContinuousSearchspace):
self.bounds = np.array(
[
[v.low_bound for v in self.search_space.values],
[v.up_bound for v in self.search_space.values],
],
dtype=float,
)
else:
self.bounds = np.array(
[
[0.0] * self.search_space.size,
[1.0] * self.search_space.size,
],
dtype=float,
)
self.up_plus_lo = self.bounds[1] + self.bounds[0]
self.up_m_lo = self.bounds[1] - self.bounds[0]
self.center = np.multiply(0.5, self.up_plus_lo)
self.radius = np.multiply(0.5, self.up_m_lo)
self.center_m_lo_bounds = self.center - self.bounds[0]
trigo_val = 2 * np.pi / self.polygon
self.H = [np.zeros(self.polygon), np.zeros(self.polygon)]
for i in range(1, self.polygon + 1):
# Initialize trigonometric part of symetric variables (CLS & CFS)
self.H[0][i - 1] = np.cos(trigo_val * i)
self.H[1][i - 1] = np.sin(trigo_val * i)
[docs] def stochastic_round(self, solution, k):
s = np.array(solution)
r = np.random.uniform(-1, 1, len(s))
# perturbation on CFS zoom
z = np.round(s.astype(float)) + (k % 2) * r
return z
[docs] def run(
self, X0=None, Y0=None, chaos_level=0, shift=1, H=None, n_process=1
):
"""run(X0=None, Y0=None, chaos_level=0, shift=1, H=None, n_process=1)
Parameters
----------
X0 : list[float], optional
Initial solution. If None, a Fractal must be given (H!=None)
Y0 : {int, float}, optional
Score of the initial solution
chaos_level : int, default=0
Determines at which level of the chaos map, the algorithm starts
shift : int, default=1
Determines the starting point of the chaotic map.
H : Fractal, optional
When used by :ref:`dba`, a fractal corresponding to the current subspace is given
n_process : int, default=1
Determines the number of best solution found to return.
Returns
-------
best_sol : list[float]
Returns a list of the :code:`n_process` best found points to the continuous format
best_scores : list[float]
Returns a list of the :code:`n_process` best found scores associated to best_sol
"""
logger.info("CFS starting")
self.build_bar(self.level)
if X0:
if isinstance(self.search_space, ContinuousSearchspace):
self.X0 = np.array(X0)
else:
self.X0 = np.array(
self.search_space.to_continuous.convert([X0])[0]
)
elif H:
self.X0 = H.center
else:
raise ValueError("No starting point given to CFS")
if Y0:
self.Y0 = Y0
else:
logger.info("CLS evaluating initial solution")
if isinstance(self.search_space, ContinuousSearchspace):
self.Y0 = self.search_space.loss([self.X0], algorithm="CFS")[0]
else:
self.Y0 = self.search_space.loss(
self.search_space.to_continuous.reverse([self.X0]),
algorithm="CFS",
)[0]
self.k = shift
self.chaos_level = chaos_level
shift = self.chaos_level * (self.k - 1) * self.level
y = self.map[shift]
# Limits of the search area, if parameter greater than center, then = 1 else = -1, used to avoid overflow
db = np.minimum(self.bounds[1] - self.X0, self.X0 - self.bounds[0])
r_g = np.zeros(self.search_space.size)
# Randomly select the reduction rate
# red_rate = random.random()*0.5
xc = self.X0
zc = self.Y0
center_m_solution = self.center - self.X0
points = np.empty((0, self.search_space.size), dtype=float)
n_points = self.search_space.loss.calls
l = 0
logger.info("CFS computing chaotic points")
# for each level of chaos
while l < self.level and n_points < self.f_calls:
# Local search area radius
self.red_rate = np.random.random()
Rl = self.radius * self.red_rate
# Decomposition vector
d = np.random.randint(self.search_space.size)
# Exponential Zoom factor on the search window
pc = 10 ** (l + 1)
# Compute the error/the perturbation applied to the solution
error_g = np.absolute(
self.X0 - (self.stochastic_round(pc * self.X0, shift + l) / pc)
)
r = np.random.random()
# for each parameter of a solution determines the improved radius
r_g = np.minimum((Rl * error_g) / (l**2 + 1), db)
# Compute both chaotic variable of the polygonal model thanks to a chaotic map
xv = [np.multiply(r_g, y), np.multiply(r_g, y)]
# For both chaotic variable
for x in xv:
xi = np.outer(self.H[1], x)
xi[:, d] = x[d] * self.H[0]
xt = self.X0 + xi
points = np.append(points, xt, axis=0)
n_points += self.polygon
l += 1
self.meta_pb.update()
# Update progress bar
self.pending_pb(len(points))
logger.info("CFS evaluating chaotic points")
if isinstance(self.search_space, ContinuousSearchspace):
ys = self.search_space.loss(points, algorithm="CFS")
else:
ys = self.search_space.loss(
self.search_space.to_continuous.reverse(points), algorithm="CFS"
)
# Update progress bar
self.update_main_pb(
len(points), explor=True, best=self.search_space.loss.new_best
)
ys = np.array(ys)
idx = np.array(np.argsort(ys))[:n_process]
# best solution found
best_sol = points[idx]
best_scores = ys[idx]
self.close_bar()
logger.info("CFS ending")
return self.search_space.loss.get_best(n_process)
[docs]class Chaotic_optimization(Metaheuristic):
"""Chaotic_optimization
Chaotic optimization combines CGS, CLS and CFS.
Attributes
----------
chaos_map : {'henon', 'kent', 'tent', 'logistic', 'random', Chaos_map}
If a string is given, the algorithm will select the corresponding map. The chaotic map is used to sample points.\
If it is a map, it will directly use it. Be carefull, the map size must be sufficient according to the parametrization.
exploration_ratio : float
It will determine the number of calls to the loss function dedicated to exploration and exploitation, according to chaotic levels associated to CGS, CLS and CFS.
polygon : int
Vertex number of the rotating polygon (has an influence on the number of evaluated points) for CLS and CFS
red_rate : float
Reduction rate of the progressive zoom on the best solution found for CLS and CFS
CGS_level : int
Number of chaotic level associated to CGS
CLS_level : int
Number of chaotic level associated to CLS
CFS_level : int
Number of chaotic level associated to CFS
verbose : boolean, default=True
Algorithm verbosity
Methods
-------
run(self, n_process=1)
Runs Chaotic_optimization
See Also
--------
:ref:`meta` : Parent class defining what a Metaheuristic is
CGS : Chaotic Global Search
CLS : Chaotic Local Search
CFS : Chaotic Fine Search
Examples
--------
>>> from zellij.core import Loss
>>> from zellij.core import ContinuousSearchspace
>>> from zellij.core import FloatVar, ArrayVar
>>> from zellij.strategies import Chaotic_optimization
>>> from zellij.utils.benchmark import himmelblau
...
>>> lf = Loss()(himmelblau)
>>> sp = ContinuousSearchspace(ArrayVar(FloatVar("a",-5,5), FloatVar("b",-5,5)),lf)
>>> co = Chaotic_optimization(sp, 1000)
>>> co.run()
"""
def __init__(
self,
search_space,
f_calls,
chaos_map=Henon,
exploration_ratio=0.30,
levels=(32, 6, 2),
polygon=4,
red_rate=0.5,
verbose=True,
):
"""__init__(search_space, f_calls,chaos_map="henon", exploration_ratio = 0.70, levels = (32,6,2), polygon=4, red_rate=0.5, verbose=True)
Initialize CGS class
Parameters
----------
search_space : Searchspace
Search space object containing bounds of the search space
f_calls : int
Maximum number of :ref:`lf` calls
chaos_map : {'henon', 'kent', 'tent', 'logistic', 'random', Chaos_map}
If a string is given, the algorithm will select the corresponding map. The chaotic map is used to sample points.\
If it is a map, it will directly use it. Be carefull, the map size must be sufficient according to the parametrization.
exploration_ratio : float, default=0.80
Must be between 0 and 1.\
It will determine the number of calls to the loss function dedicated to exploration and exploitation, according to chaotic levels associated to CGS, CLS and CFS.
levels : (int, int, int)
Used to determine the number of chaotic levels for respectively, CGS, CLS and CFS.
polygon : int, default=4
Vertex number of the rotating polygon (has an influence on the number of evaluated points) for CLS and CFS
red_rate : float, default=0.5
Reduction rate of the progressive zoom on the best solution found
verbose : boolean, default=True
Algorithm verbosity
"""
##############
# PARAMETERS #
##############
super().__init__(search_space, f_calls, verbose)
assert hasattr(search_space, "to_continuous") or isinstance(
search_space, ContinuousSearchspace
), logger.error(
f"""If the `search_space` is not a `ContinuousSearchspace`,
the user must give a `Converter` to the :ref:`sp` object
with the kwarg `to_continuous`"""
)
self.chaos_map = chaos_map
self.exploration_ratio = exploration_ratio
self.polygon = polygon
self.red_rate = red_rate
self.CGS_level = levels[0]
self.CLS_level = levels[1]
self.CFS_level = levels[2]
#############
# VARIABLES #
#############
if self.CGS_level > 0:
if self.CLS_level != 0 or self.CFS_level != 0:
self.iterations = np.ceil(
(self.f_calls * self.exploration_ratio)
/ (4 * self.CGS_level)
)
self.inner_iterations = np.ceil(
(self.f_calls * (1 - self.exploration_ratio))
/ (
(self.CLS_level + self.CFS_level)
* self.polygon
* self.iterations
)
)
else:
self.iterations = np.ceil(self.f_calls / (4 * self.CGS_level))
self.inner_iterations = 0
else:
raise ValueError("CGS level must be > 0")
if type(chaos_map) == str:
self.map_size = int(
np.max(
[
self.iterations * self.CGS_level,
self.iterations
* self.inner_iterations
* self.CLS_level,
self.iterations
* self.inner_iterations
* self.CFS_level,
]
)
)
else:
self.map_size = int(
np.ceil(
np.max(
[
self.iterations * self.CGS_level,
self.iterations
* self.inner_iterations
* self.CLS_level,
self.iterations
* self.inner_iterations
* self.CFS_level,
]
)
/ len(self.chaos_map)
)
)
self.map = self.chaos_map(self.map_size, self.search_space.size)
logging.info(str(self))
[docs] def run(self, H=None, n_process=1):
"""run(H=None, n_process=1)
Runs the Chaotic_optimization
Parameters
----------
H : Fractal, default=None
When used by :ref:`dba`, a fractal corresponding to the current subspace is given
n_process : int, default=1
Determine the number of best solution found to return.
Returns
-------
best_sol : list[float]
Returns a list of the :code:`n_process` best found points to the continuous format
best_scores : list[float]
Returns a list of the :code:`n_process` best found scores associated to best_sol
"""
logger.info("Chaotic optimization starting")
# Progress bar
self.build_bar(self.iterations * self.inner_iterations)
# Initialize CGS/CLS/CFS
cgs = CGS(
self.search_space,
self.f_calls,
self.CGS_level,
self.map,
verbose=self.verbose,
)
cls = CLS(
self.search_space,
self.f_calls,
self.CLS_level,
self.polygon,
self.map,
verbose=self.verbose,
)
cfs = CFS(
self.search_space,
self.f_calls,
self.CFS_level,
self.polygon,
self.map,
verbose=self.verbose,
)
cgs.manager, cls.manager, cfs.manager = (
self.manager,
self.manager,
self.manager,
)
# Initialize historic vector
best_sol = np.array([])
best_scores = np.array([])
k = 1
# Outer loop (exploration)
while (
k <= self.iterations and self.search_space.loss.calls < self.f_calls
):
logger.info("Chaotic optimization: Exploration phase")
# If there is CGS
if self.CGS_level > 0:
prec_calls = self.search_space.loss.calls
self.pending_pb(self.CGS_level * 4)
x_inter, loss_value = cgs.run(k)
self.update_main_pb(
self.search_space.loss.calls - prec_calls,
explor=True,
best=self.search_space.loss.new_best,
)
# Store to return best solution found
best_sol = np.append(best_sol, x_inter)
best_scores = np.append(best_scores, loss_value)
# Else select random point for the exploitation
else:
logger.warning(
"Chaotic optimization: using random instead of CGS"
)
x_inter = [np.random.random(self.search_space.size)]
self.pending_pb(1)
loss_value = self.search_space.loss(x_inter, algorithm="CO")
self.update_main_pb(
1, explor=True, best=self.search_space.loss.new_best
)
# Store to return best solution found
best_sol = np.append(x_inter)
best_scores = np.append(loss_value)
logger.debug(
f"Iterations | Loss function calls | Best value from CGS"
)
logger.debug(
f"{k} < {self.iterations} | {self.search_space.loss.calls} < {self.f_calls} | {loss_value}"
)
logger.debug(
f"New best solution found {self.search_space.loss.new_best}"
)
inner = 0
# Inner loop (exploitation)
while (
inner < self.inner_iterations
and self.search_space.loss.calls < self.f_calls
):
logger.info("Chaotic optimization: Exploitation phase")
if self.CLS_level > 0:
prec_calls = self.search_space.loss.calls
self.pending_pb(self.CLS_level * self.polygon * 2)
x_inter, loss_value = cls.run(
x_inter[0], loss_value[0], inner, k
)
self.update_main_pb(
self.search_space.loss.calls - prec_calls,
explor=False,
best=self.search_space.loss.new_best,
)
# Store to return best solution found
best_sol = np.append(best_sol, x_inter)
best_scores = np.append(best_scores, loss_value)
if self.CFS_level > 0:
prec_calls = self.search_space.loss.calls
self.pending_pb(self.CFS_level * self.polygon * 2)
x_inter, loss_value = cfs.run(
x_inter[0], loss_value[0], inner, k
)
self.update_main_pb(
self.search_space.loss.calls - prec_calls,
explor=False,
best=self.search_space.loss.new_best,
)
# Store to return best solution found
best_sol = np.append(best_sol, x_inter)
best_scores = np.append(best_scores, loss_value)
logger.debug(
f"Iterations | Loss function calls | Best value from CGS"
)
logger.debug(
f"{k} < {self.iterations} | {self.search_space.loss.calls} < {self.f_calls} | {loss_value}"
)
logger.debug(
f"New best solution found {self.search_space.loss.new_best}"
)
inner += 1
self.meta_pb.update()
ind_min = np.argsort(best_scores)[0:n_process]
best_scores = np.array(best_scores)[ind_min].tolist()
best_sol = np.array(best_sol)[ind_min].tolist()
k += 1
self.close_bar()
logger.info("Chaotic optimization ending")
return self.search_space.loss.get_best(n_process)
def __str__(self):
return f"Max Loss function calls:{self.f_calls}\nDimensions:{self.search_space.size}\nExploration/Exploitation:{self.exploration_ratio}|{1-self.exploration_ratio}\nRegular polygon:{self.polygon}\nZoom:{self.red_rate}\nIterations:\n\tGlobal:{self.iterations}\n\tInner:{self.inner_iterations}\nChaos Levels:\n\tCGS:{self.CGS_level}\n\tCLS:{self.CLS_level}\n\tCFS:{self.CFS_level}\nMap size:{self.map_size}x{self.search_space.size}"