Shift Up

class ShiftUp(filter=<function ShiftUp.<lambda>>)[source]

Bases: FitnessNormalizer

Shifts negative fitness values to be positive.

Assume you have a vector-valued fitness value, where each number represents a different property of the molecule, for example, [1, -10, 1]

One way to convert the vector-valued fitness value into a scalar fitness value is by summing the elements, and the result in this case would be -8. Clearly this doesn’t work, because the resulting fitness value is not a positive number. To fix this, the -10 should be shifted to a positive value.

ShiftUp finds the minimum value of each element in the vector-valued fitness value across the entire population, and for elements where this minimum value is less than 0, shifts up the element value for every molecule in the population, so that the minimum value in the entire population is 1.

For example, take a population with the vector-valued fitness values

[1, -5, 5]
[3, -10, 2]
[2, 20, 1]

After normalization the fitness values will be.

[1, 6, 5]
[3, 1, 2]
[2, 31, 1]

ShiftUp also works when the fitness value is a single value.

Examples

Ensuring Positive Fitness Values

Here you final fitness value is calculated by taking a Sum of the different components of the fitness value. To ensure that the final sum is positive, each component must also be positive.

import stk
import numpy as np

building_block = stk.BuildingBlock(
    smiles='BrCCBr',
    functional_groups=[stk.BromoFactory()],
)

population = (
    stk.MoleculeRecord(
        topology_graph=stk.polymer.Linear(
            building_blocks=(building_block, ),
            repeating_unit='A',
            num_repeating_units=2,
        ),
    ).with_fitness_value(
        fitness_value=(1, -2, 3),
        normalized=False,
    ),
    stk.MoleculeRecord(
        topology_graph=stk.polymer.Linear(
            building_blocks=(building_block, ),
            repeating_unit='A',
            num_repeating_units=2,
        ),
    ).with_fitness_value(
        fitness_value=(4, 5, -6),
        normalized=False,
    ),
)

# Create the normalizer.
shifter = stk.ShiftUp()

normalized_population = tuple(shifter.normalize(population))
normalized_record1, normalized_record2 = normalized_population
assert np.all(np.equal(
    normalized_record1.get_fitness_value(),
    (1, 1, 10),
))
assert np.all(np.equal(
    normalized_record2.get_fitness_value(),
    (4, 8, 1),
))

Selectively Normalizing Fitness Values

Sometimes, you only want to normalize some members of a population, for example if some do not have an assigned fitness value, because the fitness calculation failed for whatever reason. You can use the filter parameter to exclude records from the normalization

import stk
import numpy as np

building_block = stk.BuildingBlock(
    smiles='BrCCBr',
    functional_groups=[stk.BromoFactory()],
)

population = (
    stk.MoleculeRecord(
        topology_graph=stk.polymer.Linear(
            building_blocks=(building_block, ),
            repeating_unit='A',
            num_repeating_units=2,
        ),
    ).with_fitness_value(
        fitness_value=(1, -2, 3),
        normalized=False,
    ),
    # This will have a fitness value of None.
    stk.MoleculeRecord(
        topology_graph=stk.polymer.Linear(
            building_blocks=(building_block, ),
            repeating_unit='A',
            num_repeating_units=2,
        ),
    ),
)

normalizer = stk.ShiftUp(
    # Only normalize values which are not None.
    filter=lambda population, record:
        record.get_fitness_value() is not None,
)
normalized_population = tuple(normalizer.normalize(population))
normalized_record1, normalized_record2 = normalized_population
assert np.all(np.equal(
    normalized_record1.get_fitness_value(),
    (1, 1, 3),
))
assert normalized_record2.get_fitness_value() is None

Methods

normalize(population)

Normalize the fitness values in population.

__init__(filter=<function ShiftUp.<lambda>>)[source]

Initialize a ShiftUp instance.

Parameters:

filter (callable, optional) – Takes two parameters, first is a tuple of MoleculeRecord instances, and the second is a MoleculeRecord. The callable returns True or False. Only molecules which return True will have fitness values normalized. By default, all molecules will have fitness values normalized. The instance passed to the population argument of normalize() is passed as the first argument, while the second argument will be passed every MoleculeRecord in it, one at a time.

normalize(population)[source]

Normalize the fitness values in population.

Parameters:

population (tuple of MoleculeRecord) – The molecules which need to have their fitness values normalized.

Yields:

MoleculeRecord – A record with a normalized fitness value.