Skip to content

Commit

Permalink
(#71) Consolidate problem settings in base class and integrate pymoo …
Browse files Browse the repository at this point in the history
…compatibility
  • Loading branch information
SevgiAkten committed Nov 12, 2024
1 parent 0c4d77e commit 8ae1f55
Show file tree
Hide file tree
Showing 80 changed files with 1,982 additions and 1,146 deletions.
79 changes: 62 additions & 17 deletions pycellga/problems/abstract_problem.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,75 @@
class AbstractProblem:
"""
An abstract base class for optimization problems.
from abc import ABC, abstractmethod
from typing import List, Tuple, Union, Any
from pymoo.core.problem import Problem

Methods
-------
f(x)
Evaluates the fitness of a given solution x.
class AbstractProblem(Problem, ABC):
"""
Abstract base class for optimization problems.
"""

def f(self, x):
def __init__(self,
design_variables: Union[int, List[str]],
bounds: List[Tuple[float, float]],
objectives: Union[str, int, List[str]],
constraints: Union[str, int, List[str]] = []):
"""
Initialize the problem with variables, bounds, objectives, and constraints.
Parameters
----------
design_variables : int or List[str]
If an integer, it specifies the number of design variables.
If a list of strings, it specifies the names of design variables.
bounds : List[Tuple[float, float]]
Bounds for each design variable as (min, max).
objectives : str, int, or List[str]
Objectives for optimization, e.g., "minimize" or "maximize".
constraints : str, int, or List[str], optional
Constraints for the problem (default is an empty list).
"""
Evaluate the fitness of a given solution x.
# Ensure objectives and constraints are always lists
objectives = [str(objectives)] if isinstance(objectives, (str, int)) else list(objectives)
constraints = [str(constraints)] if isinstance(constraints, (str, int)) else list(constraints)

# Pymoo-specific attributes
n_var = design_variables if isinstance(design_variables, int) else len(design_variables)
xl = [bound[0] for bound in bounds]
xu = [bound[1] for bound in bounds]

super().__init__(n_var=n_var, n_obj=len(objectives), n_constr=len(constraints), xl=xl, xu=xu)

# Custom attributes
self.design_variables = [f"x{i+1}" for i in range(n_var)] if isinstance(design_variables, int) else design_variables
self.bounds = bounds
self.objectives = objectives
self.constraints = constraints

@abstractmethod
def f(self, x: List[Any]) -> float:
"""
Abstract method for evaluating the fitness of a solution.
Parameters
----------
x : list
A list representing a candidate solution.
List of design variable values.
Returns
-------
float
The fitness value of the candidate solution.
Fitness value.
"""
raise NotImplementedError("Subclasses should implement this method.")

Raises
------
NotImplementedError
If the method is not implemented by a subclass.
def evaluate(self, x, out, *args, **kwargs):
"""
Evaluate function for compatibility with pymoo's optimizer.
Parameters
----------
x : numpy.ndarray
Array of input variables.
out : dict
Dictionary to store the output fitness values.
"""
raise NotImplementedError()
out["F"] = self.f(x)
59 changes: 38 additions & 21 deletions pycellga/problems/single_objective/continuous/ackley.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from numpy import pi, e, cos, sqrt, exp
from problems.abstract_problem import AbstractProblem

class Ackley(AbstractProblem):
"""
Ackley function implementation for optimization problems.
Expand All @@ -10,42 +11,58 @@ class Ackley(AbstractProblem):
Attributes
----------
None
design_variables : List[str]
List of variable names.
bounds : List[Tuple[float, float]]
Bounds for each variable.
objectives : List[str]
Objectives for optimization.
constraints : List[str]
Any constraints for the problem.
Methods
-------
f(x: list) -> float
Calculates the Ackley function value for a given list of variables.
evaluate(x, out, *args, **kwargs)
Calculates the Ackley function value for given variables.
f(x)
Alias for evaluate to maintain compatibility with the rest of the codebase.
Notes
-----
-32.768 ≤ xi ≤ 32.768
Global minimum at f(0, 0) = 0
"""

def f(self, x: list) -> float:
def __init__(self, dimension: int):
design_variables = [f"x{i+1}" for i in range(dimension)]
bounds = [(-32.768, 32.768) for _ in range(dimension)]
objectives = ["minimize"]
constraints = []

super().__init__(design_variables, bounds, objectives, constraints)
self.dimension = dimension

def evaluate(self, x, out, *args, **kwargs):
"""
Calculate the Ackley function value for a given list of variables.
Parameters
----------
x : list
A list of float variables.
Returns
-------
float
The Ackley function value.
x : numpy.ndarray
Array of input variables.
out : dict
Dictionary to store the output fitness values.
"""
sum1 = 0.0
sum2 = 0.0
fitness = 0.0

for i in range(len(x)):
gene = x[i]
sum1 += gene * gene
sum2 += cos(2 * pi * gene)
sum1 = sum(gene ** 2 for gene in x)
sum2 = sum(cos(2 * pi * gene) for gene in x)

fitness += -20.0 * exp(-0.2 * sqrt(sum1 / len(x))) - exp(sum2 / len(x)) + 20.0 + e
fitness = -20.0 * exp(-0.2 * sqrt(sum1 / self.dimension)) - exp(sum2 / self.dimension) + 20.0 + e
out["F"] = round(fitness, 3)

return round(fitness, 3)
def f(self, x):
"""
Alias for the evaluate method to maintain compatibility with the rest of the codebase.
"""
result = {}
self.evaluate(x, result)
return result["F"]
57 changes: 40 additions & 17 deletions pycellga/problems/single_objective/continuous/bentcigar.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from problems.abstract_problem import AbstractProblem
from mpmath import power as pw
from typing import List
import numpy as np

class Bentcigar(AbstractProblem):
"""
Expand All @@ -10,38 +12,59 @@ class Bentcigar(AbstractProblem):
Attributes
----------
None
design_variables : List[str]
List of variable names.
bounds : List[Tuple[float, float]]
Bounds for each variable.
objectives : List[str]
Objectives for optimization.
constraints : List[str]
Any constraints for the problem.
Methods
-------
f(X: list) -> float
Calculates the Bentcigar function value for a given list of variables.
evaluate(x, out, *args, **kwargs)
Calculates the Bentcigar function value for given variables.
f(x)
Alias for evaluate to maintain compatibility with the rest of the codebase.
Notes
-----
-100 ≤ xi ≤ 100 for i = 1,…,n
Global minimum at f(0,...,0) = 0
"""

def f(self, X: list) -> float:
def __init__(self, dimension: int):
design_variables = [f"x{i+1}" for i in range(dimension)]
bounds = [(-100, 100) for _ in range(dimension)]
objectives = ["minimize"]
constraints = []

super().__init__(design_variables, bounds, objectives, constraints)
self.dimension = dimension

def evaluate(self, x, out, *args, **kwargs):
"""
Calculate the Bentcigar function value for a given list of variables.
Parameters
----------
X : list
A list of float variables.
Returns
-------
float
The Bentcigar function value.
x : numpy.ndarray
Array of input variables.
out : dict
Dictionary to store the output fitness values.
"""
a = pw(X[0], 2)
a = pw(x[0], 2)
b = pw(10, 6)
sum = 0.0
for i in range(1, len(X)):
sum += pw(X[i], 2)
sum_val = sum(pw(xi, 2) for xi in x[1:])

fitness = a + (b * sum)
return round(fitness, 3)
fitness = a + (b * sum_val)
out["F"] = round(fitness, 3)

def f(self, x):
"""
Alias for the evaluate method to maintain compatibility with the rest of the codebase.
"""
result = {}
self.evaluate(x, result)
return result["F"]
51 changes: 40 additions & 11 deletions pycellga/problems/single_objective/continuous/bohachevsky.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from numpy import cos
from numpy import pi
from numpy import cos, pi
from mpmath import power as pw
from typing import List, Dict, Any
from problems.abstract_problem import AbstractProblem

class Bohachevsky(AbstractProblem):
Expand All @@ -12,31 +12,60 @@ class Bohachevsky(AbstractProblem):
Attributes
----------
None
design_variables : List[str]
List of variable names.
bounds : List[Tuple[float, float]]
Bounds for each variable.
objectives : List[str]
Objectives for optimization.
constraints : List[str]
Any constraints for the problem.
Methods
-------
f(x: list) -> float
Calculates the Bohachevsky function value for a given list of variables.
evaluate(x, out, *args, **kwargs)
Calculates the Bohachevsky function value for given variables.
f(x)
Alias for evaluate to maintain compatibility with the rest of the codebase.
Notes
-----
-15 ≤ xi ≤ 15 for i = 1,…,n
Global minimum at f(0,...,0) = 0
"""

def f(self, x: list) -> float:
def __init__(self, dimension: int):
design_variables = [f"x{i+1}" for i in range(dimension)]
bounds = [(-15, 15) for _ in range(dimension)]
objectives = ["minimize"]
constraints = []

super().__init__(design_variables, bounds, objectives, constraints)
self.dimension = dimension

def evaluate(self, x: List[float], out: Dict[str, Any], *args, **kwargs):
"""
Calculate the Bohachevsky function value for a given list of variables.
Parameters
----------
x : list
A list of float variables.
out : dict
Dictionary to store the output fitness values.
"""
fitness = sum([
pw(x[i], 2) + (2 * pw(x[i + 1], 2))
- (0.3 * cos(3 * pi * x[i]))
- (0.4 * cos(4 * pi * x[i + 1])) + 0.7
for i in range(len(x) - 1)
])
out["F"] = round(fitness, 3)

Returns
-------
float
The Bohachevsky function value.
def f(self, x: List[float]) -> float:
"""
Alias for the evaluate method to maintain compatibility with the rest of the codebase.
"""
return round(sum([(pw(x[i], 2) + (2 * pw(x[i + 1], 2)) - (0.3 * cos(3 * pi * x[i])) - (0.4 * cos(4 * pi * x[i + 1])) + 0.7) for i in range(len(x) - 1)]), 3)
result = {}
self.evaluate(x, result)
return result["F"]
Loading

0 comments on commit 8ae1f55

Please sign in to comment.