Skip to content

Genetic algorithm

Modules under eso.ga implement the genetic algorithm. The representation is split across Gene, Chromosome, and Population. The variation machinery lives in SelectionOperator and GeneticOperator. For the algorithmic explanation, see Genes and chromosomes and Evolution.

Symbol File Role
Gene eso/ga/gene.py One horizontal band, encoded as (band_position, band_height).
Chromosome eso/ga/chromosome.py Set of genes plus a trained CNN. Knows its own fitness.
Population eso/ga/population.py A generation of chromosomes. Trains, scores, replaces.
SelectionOperator eso/ga/selection.py Tournament selection of parents.
GeneticOperator eso/ga/operator.py Reproduction, mutation, crossover.

eso.ga.gene

The atom of the algorithm. A Gene carries no weights — it is a pointer to a strip of the mel-spectrogram. Position and height are expressed as integer indices on the frequency axis (0 to spec_height). The constructor supports four modes: fully random, fixed position and free height, fixed height and free position, or fully fixed.

gene_1 module-attribute

gene_1 = Gene(
    spec_height=200, min_position=0, max_position=128, min_height=1, max_height=10
)

gene_2 module-attribute

gene_2 = Gene(
    spec_height=200,
    min_position=0,
    max_position=128,
    min_height=1,
    max_height=10,
    band_position=80,
    band_height=7,
)

gene_3 module-attribute

gene_3 = Gene(
    spec_height=200,
    min_position=0,
    max_position=128,
    min_height=1,
    max_height=10,
    band_position=100,
)

gene_4 module-attribute

gene_4 = Gene(
    spec_height=200,
    min_position=0,
    max_position=128,
    min_height=1,
    max_height=10,
    band_height=9,
)

Gene

Gene(
    spec_height: int,
    min_position: int,
    max_position: int,
    min_height: int,
    max_height: int,
    minimum_gene_height: int,
    band_position: int = None,
    band_height: int = None,
)

Parameters:

Name Type Description Default
spec_height int

The height of the spectogram

required
min_position int

The minimal position of a band in the spectogram

required
max_position int

The maximal position of a band in the spectogram.

required
min_height int

The minimal height of a band in the spectogram.

required
max_height int

The maximal height of a band in the spectogram.

required
band_position int

The position of the band in the spectogram. The default is None.

None
band_height int

The height of the band in the spectogram. The default is None.

None

Returns:

Type Description
None
Source code in eso/ga/gene.py
def __init__(self, spec_height: int, min_position: int,
             max_position: int, min_height: int,
             max_height: int,  minimum_gene_height: int,
             band_position: int = None,
             band_height: int = None, ) -> None:
    """
    Parameters
    ----------
    spec_height : int
        The height of the spectogram
    min_position : int
        The minimal position of a band in the spectogram
    max_position : int
        The maximal position of a band in the spectogram.
    min_height : int
        The minimal height of a band in the spectogram.
    max_height : int
        The maximal height of a band in the spectogram.
    band_position : int, optional
        The position of the band in the spectogram. 
        The default is None.
    band_height : int, optional
        The height of the band in the spectogram. 
        The default is None.

    Returns
    -------
    None

    """

    # Assumption:
    #    0 <= min_position < max_height < max_position < spec_height

    if max_height == -1:
        raise ValueError("max_height cannot be -1")

    if min_position == -1:
        min_position = 0
    if max_position == -1:
        max_position = spec_height

    if min_height == -1:
        min_height = minimum_gene_height  # 0 is not a valid height

    if min_height < minimum_gene_height:
        min_height=minimum_gene_height

    self.spec_height = spec_height
    self.min_position = min_position
    self.max_position = max_position
    self.min_height = min_height
    self.max_height = max_height

    # Generate a completely random gene
    if (band_position is None) and (band_height is None):
        self._init_random_gene()

    # Generate a partially random gene (fixed band height)
    elif (band_position is None) and (band_height is not None):
        self._init_random_pos_gene(band_height)

    # Generate a partially random gene ()
    elif (band_position is not None) and (band_height is None):
        self._init_random_height_gene(band_position)

    # Generate a gene with given band_position and band_height
    else:
        self._init_set_gene(band_position, band_height)

spec_height instance-attribute

spec_height = spec_height

min_position instance-attribute

min_position = min_position

max_position instance-attribute

max_position = max_position

min_height instance-attribute

min_height = min_height

max_height instance-attribute

max_height = max_height

get_band_position

get_band_position() -> int

Returns:

Type Description
int

The position of the band in the spectogram.

Source code in eso/ga/gene.py
def get_band_position(self) -> int:
    """

    Returns
    -------
    int
        The position of the band in the spectogram.

    """

    return self.band_position

get_band_height

get_band_height() -> int

Returns:

Type Description
int

The height of the band in the spectogram.

Source code in eso/ga/gene.py
def get_band_height(self) -> int:
    """

    Returns
    -------
    int
        The height of the band in the spectogram.

    """

    return self.band_height

set_band_position

set_band_position(new_band_position: int) -> None

Parameters:

Name Type Description Default
new_band_position int

The new position at which the band should be set.

required

Returns:

Type Description
None
Source code in eso/ga/gene.py
def set_band_position(self, new_band_position: int) -> None:
    """


    Parameters
    ----------
    new_band_position : int
        The new position at which the band should be set.

    Returns
    -------
    None

    """
    assert self.min_position <= new_band_position, \
        "new_band_position should be greater than or equal to min_position"
    assert new_band_position <= (self.max_position - self.band_height), \
        "new_band_position should be less than or equal to (max_position - band_height)"

    self.band_position = new_band_position

set_band_height

set_band_height(new_band_height: int) -> None

Parameters:

Name Type Description Default
new_band_height int

The new height of the band.

required

Returns:

Type Description
None
Source code in eso/ga/gene.py
def set_band_height(self, new_band_height: int) -> None:
    """


    Parameters
    ----------
    new_band_height : int
        The new height of the band.

    Returns
    -------
    None

    """
    upper_height = min(self.max_position -
                       self.band_position, self.max_height)

    assert self.min_height <= new_band_height, \
        "new_band_height should be greater than or equal to min_height"
    assert new_band_height <= upper_height, \
        "new_band_height should be less than or equal to min(max_position-band_position, max_height)"

    self.band_height = new_band_height

eso.ga.chromosome

The candidate solution. A Chromosome is an ordered list of genes plus the CNN trained on the bands those genes describe. It exposes train(), get_fitness(), get_metric(), and get_genes(). Fitness is computed relative to the baseline F1 and parameter count, weighted by lambda_1 and lambda_2 from ChromosomeConfig.

Created on Wed Sep 20 12:17:35 2023

@author: ljeantet, ufuk-cakir

Chromosome

Chromosome(
    results_path,
    num_genes: int,
    min_num_genes: int,
    max_num_genes: int,
    baseline_metric: float,
    baseline_parameters: int,
    gene_args: dict,
    model_args: dict,
    architecture_args: dict,
    lambda_1: float = 0.5,
    lambda_2: float = 0.5,
    stack: bool = False,
    logger=None,
)

Chromosome class

This class represents a chromosome in the genetic algorithm and is a collection of genes.

Parameters:

Name Type Description Default
num_genes int

The number of genes in the chromosome.

required
min_num_genes int

The minimum number of genes in the chromosome.

required
max_num_genes int

The maximum number of genes in the chromosome.

required
baseline_metric float

The baseline metric to compare the accuracy to.

required
baseline_parameters int

The baseline number of parameters to compare the number of trainable parameters to.

required
gene_args dict

The arguments to pass to the gene class.

required
model_args dict

The arguments to pass to the model class.

required
lambda_1 float

The lambda_1 parameter for the fitness function.

0.5
lambda_2 float

The lambda_2 parameter for the fitness function.

0.5
logger Logger

The logger to use.

None
Source code in eso/ga/chromosome.py
def __init__(
    self,
    results_path,
    num_genes: int,
    min_num_genes: int,
    max_num_genes: int,
    baseline_metric: float,
    baseline_parameters: int,
    gene_args: dict,
    model_args: dict,
    architecture_args: dict,
    lambda_1: float = 0.5,
    lambda_2: float = 0.5,
    stack: bool = False,
    logger=None,
):
    self.results_path=results_path
    if num_genes== -1 : 
        self.num_genes = None 
    else :
        self.num_genes = num_genes
    self._min_num_genes = min_num_genes
    self._max_num_genes = max_num_genes
    self.logger = logger
    self._fitness = -np.inf  # Default to -inf
    self._metric = None
    self._metric_name = None
    self._trainable_parameters = None
    self._baseline_metric = baseline_metric
    self._baseline_parameters = baseline_parameters
    self.trained = False
    self._model_args = model_args
    self._architecture_args = architecture_args
    self._lambda_1 = lambda_1
    self._lambda_2 = lambda_2
    self._gene_args = gene_args
    self.stack = stack
    self._init_chromosome()

results_path instance-attribute

results_path = results_path

num_genes instance-attribute

num_genes = None

logger instance-attribute

logger = logger

trained instance-attribute

trained = False

stack instance-attribute

stack = stack

sort

sort()
Source code in eso/ga/chromosome.py
def sort(self):
    self._genes = sorted(self._genes, key=lambda x: x.get_band_position())
    return self

get_genes

get_genes()

Get the genes in the chromosome

Returns a list of the genes in the chromosome.

Returns:

Type Description
list

The genes in the chromosome.

Source code in eso/ga/chromosome.py
def get_genes(self):
    """Get the genes in the chromosome

    Returns a list of the genes in the chromosome.

    Returns
    -------
    list
        The genes in the chromosome.
    """
    return self._genes

set_gene

set_gene(position, band_position=None, band_height=None)

Set the gene at a specific position

Set the gene at a specific position in the chromosome. Either the band position or the band height must be specified. This is used for crossover.

Parameters:

Name Type Description Default
position int

The position of the gene to set.

required
band_position int

The position of the band to set.

None
band_height int

The height of the band to set.

None

Raises:

Type Description
ValueError

If the position is greater than the number of genes.

ValueError

If the position is less than 0.

ValueError

If neither the band position or the band height is specified.

Source code in eso/ga/chromosome.py
def set_gene(self, position, band_position=None, band_height=None):
    """Set the gene at a specific position

    Set the gene at a specific position in the chromosome. Either the band position or the band height must be specified.
    This is used for crossover.

    Parameters
    ----------
    position : int
        The position of the gene to set.
    band_position : int, optional
        The position of the band to set.
    band_height : int, optional
        The height of the band to set.

    Raises
    ------
    ValueError
        If the position is greater than the number of genes.
    ValueError
        If the position is less than 0.
    ValueError
        If neither the band position or the band height is specified.
    """
    # do some checks
    # If we specificy both band_positions and band_height
    if position > self.num_genes:
        raise ValueError(
            f"Position {position} is greater than the number of genes {self.num_genes}"
        )
    if position < 0:
        raise ValueError(f"Position {position} is less than 0")
    if band_position is None and band_height is None:
        raise ValueError(
            "You need to specify either the band position or the band height"
        )

    if band_position is None and band_height is not None:
        band_position = self._genes[position].get_band_position()

    elif band_position is not None and band_height is None:
        band_height = self._genes[position].get_band_height()
    # Set the gene
    self._genes[position]._init_set_gene(band_position, band_height)
    self.sort()

get_info

get_info()

Get the information about the chromosome

Returns a string with the information about the chromosome.

Source code in eso/ga/chromosome.py
def get_info(self):
    """Get the information about the chromosome

    Returns a string with the information about the chromosome.
    """
    info = "Chromosome Info:\n"
    info += f"Number of Genes: {self.num_genes}\n"
    info += f"{self._metric_name.capitalize()}: {self._metric}\n"
    info += f"Trainable parameters: {self._trainable_parameters}\n"
    info += f"Fitness: {self._fitness}\n"
    info += "Genes: \n"
    for gene in self._genes:
        info += "\t" + str(gene) + "\n"
    return info

get_metric

get_metric()

Get the accuracy/f1-score of the chromosome

Source code in eso/ga/chromosome.py
def get_metric(self):
    """Get the accuracy/f1-score of the chromosome"""
    return self._metric

get_trainable_parameters

get_trainable_parameters()

Get the number of trainable parameters of the chromosome

Source code in eso/ga/chromosome.py
def get_trainable_parameters(self):
    """Get the number of trainable parameters of the chromosome"""
    return self._trainable_parameters

train

train(X_train, Y_train, X_val, Y_val, save=False, model_name='eso_chromosome')

Train the chromosome

Create the sliced dataset from the encoded bands from the genes and train the model on this dataset.

Parameters:

Name Type Description Default
X_train array

The training dataset.

required
Y_train array

The training labels.

required
X_val array

The validation dataset.

required
Y_val array

The validation labels.

required
Source code in eso/ga/chromosome.py
def train(self, X_train, Y_train, X_val, Y_val, save=False, model_name="eso_chromosome"):
    """Train the chromosome

    Create the sliced dataset from the encoded bands from the genes and train the model on this dataset.

    Parameters
    ----------
    X_train : np.array
        The training dataset.
    Y_train : np.array
        The training labels.
    X_val : np.array
        The validation dataset.
    Y_val : np.array
        The validation labels.
    """
    sliced_X_train = self._create_dataset(X_train)
    sliced_X_val = self._create_dataset(X_val)

    # And then train the model on this dataset
    image_shape = sliced_X_val.shape[1:]
    if len(image_shape) == 3:
        # this means the images have been stacked
        # TODO CHANGE This
        image_shape = image_shape[1:]
        # the first dimension is the number of channels, which is stored in
        # self._n_channels
    else:
        pass

    # Initialize Model
    model = Model(self.results_path,
        input_shape=(self._n_channels, image_shape[0], image_shape[1]),
        logger=self.logger, architecture_args=self._architecture_args,
        **self._model_args,
        use_chromosome=True,
    )

    # Train model
    self._loss = model.train(X_train=sliced_X_train, Y_train=Y_train, X_val=sliced_X_val, Y_val=Y_val, save=save, model_name=model_name)
    # Evaluate model validation accuracy/f1-score

    self._metric, self._metric_name = model.evaluate(
        X_val=sliced_X_val, Y_val=Y_val
    )
    self._trainable_parameters = model.get_number_of_parameters()
    self._fitness = self._calculate_fitness(
        self._metric, self._trainable_parameters
    )
    self.trained = True
    self._model_state_dict = deepcopy(model.get_model_dict())

get_fitness

get_fitness()

Get the fitness of the chromosome

Source code in eso/ga/chromosome.py
def get_fitness(self):
    """Get the fitness of the chromosome"""
    return self._fitness

save

save(path, name)

Save the chromosome to a file.

Parameters:

Name Type Description Default
path str

The path to the directory where the chromosome should be saved.

required
name str

The name of the file.

required
Source code in eso/ga/chromosome.py
def save(self, path, name):
    """
    Save the chromosome to a file.

    Parameters
    ----------
    path : str
        The path to the directory where the chromosome should be saved.
    name : str
        The name of the file.

    """
    # Create path name and add extension
    save_path = os.path.join(path, name + ".pkl")
    # Save as pickle
    with open(save_path, "wb") as f:
        pickle.dump(self, f)

save_model

save_model(path, name='chromosome_cnn_state')
Source code in eso/ga/chromosome.py
def save_model(self, path, name="chromosome_cnn_state"):
    save_path = os.path.join(path, name + ".pth")
    torch.save(self._model_state_dict, save_path)
    self.logger.info(f"CNN model state dict saved to {save_path}!")

predict

predict(X, device, batch_size=128, threshold=0.5)
Source code in eso/ga/chromosome.py
def predict(self, X, device, batch_size=128, threshold=0.5):
    bands = self._create_dataset(X)
    model = Model.load_cnn(self._model_state_dict, device)
    model.batch_size = batch_size  # TODO change this
    model.eval()
    prediction = model(bands)
    # Predict true label if prob([0,1]) > threshold
    prediction = (prediction[:, 1] > threshold).float()
    return prediction

evaluate

evaluate(X, Y, device, batch_size=128, threshold=None)
Source code in eso/ga/chromosome.py
def evaluate(self, X, Y, device, batch_size=128, threshold=None):
    bands = self._create_dataset(X)

    model = Model(self.results_path,input_shape=(self._n_channels, bands.shape[1], bands.shape[2]), architecture_args=self._architecture_args, **self._model_args, use_chromosome=True)
    cnn = Model.load_cnn(self._model_state_dict, device)
    model._cnn = cnn
    model.batch_size = batch_size  # TODO change this
    metric, _ = model.evaluate(bands, Y, threshold)
    return metric

eso.ga.population

A Population is a fixed-size set of chromosomes. It exposes methods to evaluate all chromosomes in a generation, locate the best, replace the weakest with offspring, and serialise itself to disk between generations.

Population

Population(
    results_path,
    pop_size: int,
    chromosome_args: dict,
    model_args: dict,
    gene_args: dict,
    architecture_args: dict,
    logger=None,
    chromsomes: list = None,
    data: Data = None,
)

Initialize a population.

Source code in eso/ga/population.py
def __init__(
    self,
    results_path,
    pop_size: int,
    chromosome_args: dict,
    model_args: dict,
    gene_args: dict,
    architecture_args: dict,
    logger=None,
    chromsomes: list = None,
    data: Data = None,
):
    """
    Initialize a population.
    """
    self.results_path=results_path
    self.pop_size = pop_size
    self.logger = logger
    self._data = data
    self._chromosome_args = chromosome_args
    self._gene_args = gene_args
    self._model_args = model_args
    self._architecture_args = architecture_args
    # self._set(config, logger, data)
    # Initialize Data
    # data_config = config["preprocessing"]
    # data_config["preprocess"] = False
    # data_config["force_recreate_dataset"] = False
    # This will create the dataset if it doesn't exist, and load it if it does
    # Pass this to the chromosome class so that it can use it to train and evaluate
    # self.data = Data(data_config, self.logger, verbose = False)

    if chromsomes is None:
        self._initial_population()
    else:
        self.chromosomes = chromsomes

results_path instance-attribute

results_path = results_path

pop_size instance-attribute

pop_size = pop_size

logger instance-attribute

logger = logger

chromosomes instance-attribute

chromosomes = chromsomes

reset_trained_flags

reset_trained_flags()
Source code in eso/ga/population.py
def reset_trained_flags(self):
    for chromosome in self.chromosomes:
        chromosome.trained = False

save

save(file_path, save_as_pickle=True)

Save the population to a file.

:param path: The path to the file. :param save_as_pickle: Whether to save as a pickle file or not.

Source code in eso/ga/population.py
def save(self, file_path, save_as_pickle=True):
    """
    Save the population to a file.

    :param path: The path to the file.
    :param save_as_pickle: Whether to save as a pickle file or not.
    """
    if save_as_pickle:
        self._save_population_to_pickle(file_path)
    else:
        raise NotImplementedError(
            "Only saving as pickle is supported at the moment."
        )

load staticmethod

load(file_path, logger, data)
Source code in eso/ga/population.py
@staticmethod
def load(file_path, logger, data):
    # Open the file for reading in binary mode ('rb')
    with open(file_path, "rb") as file:
        loaded_population = pickle.load(file)
    # loaded_population._set(config=config, logger=logger, data=data)
    loaded_population._data = data
    loaded_population.logger = logger
    return loaded_population

train_population

train_population(progress_handler=None, stop_event=None)

Train the population of chromosomes.

:param epochs: The number of epochs to train each chromosome.

Source code in eso/ga/population.py
def train_population(self, progress_handler=None, stop_event=None):
    """
    Train the population of chromosomes.

    :param epochs: The number of epochs to train each chromosome.
    """

    train_X, train_Y = self._data.get_data(type="train")
    val_X, val_Y = self._data.get_data(type="validation")
    # validation_loader = self._data.get_validation_data()
    if progress_handler:
        progress_handler.set_training_max(self.pop_size)
        progress_handler.set_training_value(0)

    for i, chromosome in enumerate(
        tqdm(
            self.chromosomes,
            desc="Training Chromosomes",
            total=len(self.chromosomes),
            ascii=" >=",
        )
    ):
        if stop_event is not None:
            if stop_event.is_set():
                self.logger.info("Stopping training...")
                return True
        if progress_handler:
            progress_handler.set_training_value(i + 1)

        if chromosome.trained:
            self.logger.info(f"Skipping chromosome {i}.")
            continue
        else:
            # self.logger.info(f"Training chromosome {i} out of {self.pop_size}...")

            # chromosome._fitness = np.random.randint(0,1)
            chromosome.train(train_X, train_Y, val_X, val_Y)

get_chromosomes

get_chromosomes()

Get the chromosomes in the population.

:return: The chromosomes in the population.

Source code in eso/ga/population.py
def get_chromosomes(self):
    """
    Get the chromosomes in the population.

    :return: The chromosomes in the population.
    """
    return self.chromosomes

replace_chromosomes

replace_chromosomes(new_chromosomes)

Replace the chromosomes in the population with new ones.

:param new_chromosomes: The new chromosomes to replace the old ones with.

Source code in eso/ga/population.py
def replace_chromosomes(self, new_chromosomes):
    """
    Replace the chromosomes in the population with new ones.

    :param new_chromosomes: The new chromosomes to replace the old ones with.
    """

    self.chromosomes = new_chromosomes

get_best_chromosome

get_best_chromosome() -> Chromosome

Get the chromosome with the highest fitness value.

Parameters:

Name Type Description Default
None
required

Returns:

Type Description
Chromosome

The chromosome with the highest fitness value

Source code in eso/ga/population.py
def get_best_chromosome(self) -> Chromosome:
    """
    Get the chromosome with the highest fitness value.

    Parameters
    ----------
    None

    Returns
    -------
    Chromosome
        The chromosome with the highest fitness value
    """
    # Check if the chromosomes have been evaluated
    if max(self.chromosomes, key=lambda x: x.get_fitness()) is None:
        raise ValueError("The chromosomes have not been evaluated yet.")
    return max(self.chromosomes, key=lambda x: x.get_fitness())

eso.ga.selection

Tournament selection. The only parameter is the tournament size t. Each call samples t chromosomes with replacement and returns the one with the highest fitness. Crossover requires two parents, so the operator is invoked twice for each crossover event.

SelectionOperator

SelectionOperator(tournament_size)

Initialize the SelectionOperator with a tournament size.

Source code in eso/ga/selection.py
def __init__(self, tournament_size):
    """
    Initialize the SelectionOperator with a tournament size.
    """


    self.tournament_size = tournament_size

tournament_size instance-attribute

tournament_size = tournament_size

select_parents

select_parents(population: Population) -> list
Source code in eso/ga/selection.py
def select_parents(self, population: Population) -> list:
    parent1 = self.select_one_parent(population)
    parent2 = self.select_one_parent(population)

    # Ensure that parent1 and parent2 are distinct individuals.
    while parent1 == parent2:
        parent2 = self.select_one_parent(population)

    return parent1, parent2

select_one_parent

select_one_parent(population: Population)

Select a parent individual from the population using tournament selection.

:param population: The population from which to select a parent. :return: The best individual selected through tournament selection.

Source code in eso/ga/selection.py
def select_one_parent(self, population: Population):
    """
    Select a parent individual from the population using tournament selection.

    :param population: The population from which to select a parent.
    :return: The best individual selected through tournament selection.
    """
    if not isinstance(population, Population):
        raise ValueError(
            "The 'population' argument must be a Population object")

    if not population:
        raise ValueError("Population is empty")

    if self.tournament_size > population.pop_size :
        raise ValueError(
            f"The tournament size has to be smaller than the population size {population.pop_size}"
        )


    chromosomes = population.chromosomes

    # Randomly select `self.tournament_size` individuals from the population.

    ids = np.random.choice(
        chromosomes, size=self.tournament_size, replace=False)

    # Find the individual with the highest fitness.
    best = max(ids, key=lambda x: x.get_fitness())

    return best

eso.ga.operator

The genetic operators. mutate adds a bounded random delta to a gene's position, height, or both. crossover swaps a randomly chosen range of genes between two parents. reproduce copies a parent unchanged into the next generation. Rates are configured in GeneticOperatorConfig and must sum to one.

Created on Fri Oct 6 10:38:46 2023

@author: aaron-joel

GeneticOperator

GeneticOperator(
    band_height_fixed,
    band_position_fixed,
    spec_height,
    crossover_rate,
    mutation_rate,
    reproduction_rate,
    mutation_height_range,
    mutation_position_range,
)

Initialize the GeneticOperator class.

Parameters:

Name Type Description Default
config dict

Configuration file for input setting.

required

Returns:

Type Description
None

DESCRIPTION.

Source code in eso/ga/operator.py
def __init__(
    self,
    band_height_fixed,
    band_position_fixed,
    spec_height,
    crossover_rate,
    mutation_rate,
    reproduction_rate,
    mutation_height_range,
    mutation_position_range,
) -> None:
    """
    Initialize the GeneticOperator class.

    Parameters
    ----------
    config : dict
        Configuration file for input setting.

    Returns
    -------
    None
        DESCRIPTION.

    """
    self._crossover_rate = crossover_rate
    self._mutation_rate = mutation_rate
    self._reproduction_rate = reproduction_rate
    self._mutation_height_range = mutation_height_range
    self._mutation_position_range = mutation_position_range
    self.spec_height=spec_height
    self._check_rates()
    self.band_height_fixed = band_height_fixed
    self.band_position_fixed = band_position_fixed

spec_height instance-attribute

spec_height = spec_height

band_height_fixed instance-attribute

band_height_fixed = band_height_fixed

band_position_fixed instance-attribute

band_position_fixed = band_position_fixed

set_crossover_rate

set_crossover_rate(rate: float) -> None

Set the crossover rate to the specified value of 'rate'

Parameters:

Name Type Description Default
rate float

Propotion of population that should be crossover.

required

Returns:

Type Description
None
Source code in eso/ga/operator.py
def set_crossover_rate(self, rate: float) -> None:
    """
    Set the crossover rate to the specified value of 'rate'

    Parameters
    ----------
    rate : float
        Propotion of population that should be crossover.

    Returns
    -------
    None

    """
    if not isinstance(rate, float):
        raise TypeError("crossover rate should be of type 'float'")

    if rate < 0 or rate >= 1.0:
        raise ValueError("crossover rate should be between 0.0 and 1.0")

    self._crossover_rate = rate

set_mutation_rate

set_mutation_rate(rate: float) -> None

Set the mutation rate to the specified value of 'rate'

Parameters:

Name Type Description Default
rate float

Proportion of population that should be mutated.

required

Returns:

Type Description
None
Source code in eso/ga/operator.py
def set_mutation_rate(self, rate: float) -> None:
    """
    Set the mutation rate to the specified value of 'rate'

    Parameters
    ----------
    rate : float
        Proportion of population that should be mutated.

    Returns
    -------
    None

    """
    if not isinstance(rate, float):
        raise TypeError("mutation rate should be of type 'float'")

    if rate < 0 or rate >= 1.0:
        raise ValueError("mutation rate should be between 0.0 and 1.0")

    self._mutation_rate = rate

get_crossover_rate

get_crossover_rate() -> float

Get the crossover rate

Returns:

Type Description
float

The crossover rate.

Source code in eso/ga/operator.py
def get_crossover_rate(self) -> float:
    """
    Get the crossover rate

    Returns
    -------
    float
        The crossover rate.

    """
    return self._crossover_rate

get_mutation_rate

get_mutation_rate() -> float

Get the mutation rate

Returns:

Type Description
float

The mutation rate.

Source code in eso/ga/operator.py
def get_mutation_rate(self) -> float:
    """
    Get the mutation rate

    Returns
    -------
    float
        The mutation rate.

    """
    return self._mutation_rate

random_crossover

random_crossover(parent1: Chromosome, parent2: Chromosome) -> tuple

Takes two individuals from the population and generates two offsprings by exchanging a random number of genes at randomly selected position in both individual chromosomes.

Parameters:

Name Type Description Default
parent1 Chromosome

The first parent.

required
parent2 Chromosome

The second parent.

required

Returns:

Type Description
tuple

A tuple of offsprings (Chromosome, Chromosome).

Source code in eso/ga/operator.py
def random_crossover(self, parent1: Chromosome, parent2: Chromosome) -> tuple:
    """
    Takes two individuals from the population and generates two offsprings
    by exchanging a random number of genes at randomly selected position
    in both individual chromosomes.

    Parameters
    ----------
    parent1 : Chromosome
        The first parent.
    parent2 : Chromosome
        The second parent.

    Returns
    -------
    tuple
        A tuple of offsprings (Chromosome, Chromosome).

    """

    # Make a copy of the parents.
    offspring1 = copy.deepcopy(parent1)
    offspring2 = copy.deepcopy(parent2)

    # Number of genes (No assumption is made about the chromosome length)
    n1 = len(parent1)
    n2 = len(parent2)
    n = min(n1, n2)

    # Randomly figure out how many genes to exchange
    m = random.randint(1, n)

    # Randomly select positions at which to exchange the genes for each
    # individual chromosome
    parent1_positions = random.sample(range(n1), m)
    parent2_positions = random.sample(range(n2), m)

    parent1_genes = parent1.get_genes()
    parent2_genes = parent2.get_genes()

    # Loop over the position in both chromosomes and exchange the genes
    for i, j in zip(parent1_positions, parent2_positions):
        # Exchange the ith gene of parent1 with the jth gene of parent2
        offspring1.set_gene(
            i,
            parent2_genes[j].get_band_position(),
            parent2_genes[j].get_band_height(),
        )
        offspring2.set_gene(
            j,
            parent1_genes[i].get_band_position(),
            parent1_genes[i].get_band_height(),
        )

    return (offspring1, offspring2)

crossover

crossover(parent1: Chromosome, parent2: Chromosome)

Takes two individuals from the population and generates two offsprings.

Parameters:

Name Type Description Default
parent1 Chromosome

The first parent.

required
parent2 Chromosome

The second parent.

required

Returns:

Type Description
tuple

A tuple of offsprings (Chromosome, Chromosome).

Source code in eso/ga/operator.py
def crossover(self, parent1: Chromosome, parent2: Chromosome):
    """
    Takes two individuals from the population and generates two offsprings.

    Parameters
    ----------
    parent1 : Chromosome
        The first parent.
    parent2 : Chromosome
        The second parent.

    Returns
    -------
    tuple
        A tuple of offsprings (Chromosome, Chromosome).

    """

    # Make a deepcopy of the parents
    offspring1 = copy.deepcopy(parent1)
    offspring2 = copy.deepcopy(parent2)

    # Number of genes
    # (Assumption: len(parent1) are not always the same len(parent2))
    n1 = len(parent1)
    n2 = len(parent2)
    n = min(n1, n2)

    # Perform the crossover operation

    # Pick a random number n, between 0 and n-1 and swap the genes
    # of parent1 and parent2 from [n:]

    # offspring1 = parent1[:n] + parent2[n:]
    # offspring2 = parent2[:n] + parent1[n:]

    start_idx = random.randint(0, n - 1)

    # modified the selection of end_idx instead of having it fixed
    end_idx = random.randint(start_idx + 1, n)

    # Let's ensure that the "whole" Chromosome is not changed.
    # This situation occurs when start_idx = 0 and end_idx = n
    if start_idx == 0 and end_idx == n:
        end_idx = n - 1

    parent1_genes = parent1.get_genes()
    parent2_genes = parent2.get_genes()

    for k in range(start_idx, end_idx):
        offspring1.set_gene(
            k,
            parent2_genes[k].get_band_position(),
            parent2_genes[k].get_band_height(),
        )
        offspring2.set_gene(
            k,
            parent1_genes[k].get_band_position(),
            parent1_genes[k].get_band_height(),
        )

    return (offspring1, offspring2)

mutate

mutate(chromosome: Chromosome) -> Chromosome

Randomly mutate the chromosome.

Parameters:

Name Type Description Default
chromosome Chromosome

Chromosome to be mutated.

required

Returns:

Type Description
Chromosome

The mutated Chromosome.

Source code in eso/ga/operator.py
def mutate(self, chromosome: Chromosome) -> Chromosome:
    """
    Randomly mutate the chromosome.

    Parameters
    ----------
    chromosome : Chromosome
        Chromosome to be mutated.

    Returns
    -------
    Chromosome
        The mutated Chromosome.

    """

    # First check if Chromosome has Stacked channels
    # Because then we dont want to change the height

    if chromosome._n_channels == 1:
        # Concatenated Chromosome: can mutate position and/or band height

        if not self.band_height_fixed  and not self.band_position_fixed : 
            # can mutate position and band height
            choice = random.randint(1, 3)

            if choice == 1:
                mutated_chromosome = self._mutate_gene_position(chromosome)
            elif choice == 2:
                mutated_chromosome = self._mutate_gene_height(chromosome)
            else:
                mutated_chromosome = self._mutate_gene(chromosome)

        elif self.band_height_fixed  and not self.band_position_fixed : 
            # can mutate only position : 
            mutated_chromosome = self._mutate_gene_position(chromosome)

        elif not self.band_height_fixed  and self.band_position_fixed : 
            #can mutate only height :
            mutated_chromosome = self._mutate_gene_height(chromosome)

    else:
        # Stacked Chromosome: only mutate position
        mutated_chromosome = self._mutate_gene_position(chromosome)
    return mutated_chromosome

reproduce

reproduce(population: Population) -> Chromosome

Randomly selects an individual from the current population and moves it to the next generation.

Parameters:

Name Type Description Default
population Population

The current population to select from.

required

Returns:

Type Description
Chromosome

The randomly selected chromosome.

Source code in eso/ga/operator.py
def reproduce(self, population: Population) -> Chromosome:
    """
    Randomly selects an individual from the current population and
    moves it to the next generation.

    Parameters
    ----------
    population : Population
        The current population to select from.

    Returns
    -------
    Chromosome
        The randomly selected chromosome.

    """

    # Return a random chromosome from the population
    return random.choice(population.get_chromosomes())