Note

SimpleGADriver is based on a simple genetic algorithm implementation sourced from the lecture notes for the class 2009 AAE550 taught by Prof. William A. Crossley at Purdue University.

This genetic algorithm optimizer supports integer and continuous variables. It uses a binary encoding scheme to encode any continuous variables into a user-definable number of bits. The number of bits you choose should be equal to the base-2 logarithm of the number of discrete values you want between the min and max value. A higher value means more accuracy for this variable, but it also increases the number of generations (and hence total evaluations) that will be required to find the minimum. If you do not specify a value for bits for a continuous variable, then the variable is assumed to be integer, and encoded as such.

The examples below show a mixed-integer problem to illustrate usage of this driver with both integer and discrete design variables.

from openmdao.api import Problem, Group, IndepVarComp, SimpleGADriver
from openmdao.test_suite.components.branin import Branin

prob = Problem()
model = prob.model = Group()

model.connect('p2.xI', 'comp.x0')
model.connect('p1.xC', 'comp.x1')

prob.driver.options['bits'] = {'p1.xC': 8}

prob.setup()
prob.run_driver()

# Optimal solution
print('comp.f', prob['comp.f'])

comp.f [ 0.49454471]
print('p2.xI', prob['p2.xI'])

p2.xI [ 3.]
print('p1.xC', prob['p1.xC'])

p1.xC [ 2.41176471]

Option Default Acceptable Values Acceptable Types Description
bits {} N/A [‘dict’] Number of bits of resolution. Default is an empty dict, where every unspecified variable is assumed to be integer, and the number of bits is calculated automatically. If you have a continuous var, you should set a bits value as a key in this dictionary.
debug_print [] N/A [‘list’] List of what type of Driver variables to print at each iteration. Valid items in list are ‘desvars’, ‘ln_cons’, ‘nl_cons’, ‘objs’, ‘totals’
elitism True N/A N/A If True, replace worst performing point with best from previous generation each iteration.
max_gen 100 N/A N/A Number of generations before termination.
pop_size 0 N/A N/A Number of points in the GA. Set to 0 and it will be computed as four times the number of bits.
procs_per_model 1 N/A N/A Number of processors to give each model under MPI.
run_parallel False N/A N/A Set to True to execute the points in a generation in parallel.

Note: Options can be passed as keyword arguments at initialization.

You can change the number of generations to run the genetic algorithm by setting the “max_gen” option.

from openmdao.api import Problem, Group, IndepVarComp, SimpleGADriver
from openmdao.test_suite.components.branin import Branin

prob = Problem()
model = prob.model = Group()

model.connect('p2.xI', 'comp.x0')
model.connect('p1.xC', 'comp.x1')

prob.driver.options['bits'] = {'p1.xC': 8}
prob.driver.options['max_gen'] = 5

prob.setup()
prob.run_driver()

# Optimal solution
print('comp.f', prob['comp.f'])

comp.f [ 0.49454471]
print('p2.xI', prob['p2.xI'])

p2.xI [ 3.]
print('p1.xC', prob['p1.xC'])

p1.xC [ 2.41176471]

You can change the population size by setting the “pop_size” option. The default value for pop_size is 0, which means that the driver automatically computes a population size that is 4 times the total number of bits for all variables encoded.

from openmdao.api import Problem, Group, IndepVarComp, SimpleGADriver
from openmdao.test_suite.components.branin import Branin

prob = Problem()
model = prob.model = Group()

model.connect('p2.xI', 'comp.x0')
model.connect('p1.xC', 'comp.x1')

prob.driver.options['bits'] = {'p1.xC': 8}
prob.driver.options['pop_size'] = 10

prob.setup()
prob.run_driver()

# Optimal solution
print('comp.f', prob['comp.f'])

comp.f [ 0.49399549]
print('p2.xI', prob['p2.xI'])

p2.xI [-3.]
print('p1.xC', prob['p1.xC'])

p1.xC [ 11.94117647]

## Running a GA in Parallel¶

If you have a model that doesn’t contain any distributed components or parallel groups, then the model evaluations for a new generation can be performed in parallel by turning on the “parallel” option:

from openmdao.api import Problem, Group, IndepVarComp, SimpleGADriver
from openmdao.test_suite.components.branin import Branin

prob = Problem()
model = prob.model = Group()

model.connect('p2.xI', 'comp.x0')
model.connect('p1.xC', 'comp.x1')

prob.driver.options['bits'] = {'p1.xC': 8}
prob.driver.options['max_gen'] = 10
prob.driver.options['run_parallel'] = True

prob.setup(check=False)
prob.run_driver()

# Optimal solution
print('comp.f', prob['comp.f'])

(rank 0) comp.f [ 0.50279674]
(rank 1) comp.f [ 0.50279674]
print('p2.xI', prob['p2.xI'])

(rank 0) p2.xI [ 3.]
(rank 1) p2.xI [ 3.]
print('p1.xC', prob['p1.xC'])

(rank 0) p1.xC [ 2.29411765]
(rank 1) p1.xC [ 2.29411765]

## Running a GA on a Parallel Model in Parallel¶

If you have a model that does contain distributed components or parallel groups, you can also use SimpleGADriver to optimize it. If you have enough processors, you can also simultaneously evaluate multiple points in your population by turning on the “parallel” option and setting the “procs_per_model” to the number of processors that your model requires. Take care that you submit your parallel run with enough processors such that the number of processors the model requires divides evenly into it, as in this example, where the model requires 2 and we give it 4.

from openmdao.api import Problem, Group, IndepVarComp, SimpleGADriver, ParallelGroup
from openmdao.test_suite.components.branin import Branin

prob = Problem()
model = prob.model = Group()

model.connect('p2.xI', 'par.comp1.x0')
model.connect('p1.xC', 'par.comp1.x1')
model.connect('p2.xI', 'par.comp2.x0')
model.connect('p1.xC', 'par.comp2.x1')

model.add_subsystem('comp', ExecComp('f = f1 + f2'))
model.connect('par.comp1.f', 'comp.f1')
model.connect('par.comp2.f', 'comp.f2')

prob.driver.options['bits'] = {'p1.xC': 8}
prob.driver.options['max_gen'] = 10
prob.driver.options['pop_size'] = 25
prob.driver.options['run_parallel'] = True
prob.driver.options['procs_per_model'] = 2

prob.driver._randomstate = 1

prob.setup(check=False)
prob.run_driver()

# Optimal solution
print('comp.f', prob['comp.f'])

(rank 0) comp.f [ 0.98799098]
(rank 1) comp.f [ 0.98799098]
(rank 2) comp.f [ 0.98799098]
(rank 3) comp.f [ 0.98799098]
print('p2.xI', prob['p2.xI'])

(rank 0) p2.xI [-3.]
(rank 1) p2.xI [-3.]
(rank 2) p2.xI [-3.]
(rank 3) p2.xI [-3.]
print('p1.xC', prob['p1.xC'])

(rank 0) p1.xC [ 11.94117647]
(rank 1) p1.xC [ 11.94117647]
(rank 2) p1.xC [ 11.94117647]
(rank 3) p1.xC [ 11.94117647]

Tags