% genetic_minimizer.m
%
% [x,F,iflag,x_pop,F_pop,F_traj] = ...
%    genetic_minimizer(func_name,x0,OptParam,ModelParam);
%
% This MATLAB routine employs a simple genetic algorithm
% to minimize a cost function for a non-constrained
% problem with continuously-varying parameters.
%
% Input parameters:
% func_name: the routine name that computes the
%           cost function
% x0 : the initial guess
% OptParam: a structure containing optimization parameters
%     .num_gen = total number of generations
%     .num_pop = the population size
%     .num_survivors = the total number of survivors
%     .prob_mutate = prob. of selecting mutation
%     .std_displace = std. displacement during mutation
%     .verbose = if non-zero, print out cost function of
%                best candidate periodically
%     .metric = positive-definite metric matric
%     .dist_sq_min = min. allowable sq. distance between
%               survivors to ensure diversity
% ModelParam: structure of model parameters that may be
%       passed to the cost function vector
% K.J. Beers. MIT ChE. 8/5/03

function [x,F,iflag,x_pop,F_pop,F_traj] = ...
    genetic_minimizer(func_name,x0,OptParam,ModelParam);

iflag = 0;

% First, set OptParam to default values if necessary.
try val = OptParam.num_gen;
catch OptParam.num_gen = 100*length(x0);
end
try
    val = OptParam.num_pop;
catch
    OptParam.num_pop = 100*length(x0);
end
try
    val = OptParam.num_survivors;
catch
    OptParam.num_survivors = ...
        ceil(0.1*OptParam.num_pop);
end
try
    val = OptParam.prob_mutate;
catch
    OptParam.prob_mutate = 0.75;
end
try
    val = OptParam.std_displace;
catch
    OptParam.std_displace = 0.1*max(abs(x0));
    if(OptParam.std_displace < 1e-6)
        OptParam.std_displace = 1e-6;
    end
end
try
    val = OptParam.verbose;
catch
    OptParam.verbose = 0;
end
try
    val = OptParam.metric_matrix;
catch
    OptParam.metric = speye(length(x0));
end
try
    val = OptParam.dist_sq_min;
catch
    OptParam.dist_sq_min = 0.1*OptParam.std_displace;
end

% make sure x0 is column vector
if(size(x0,2)==1)
    x_guess = x0;
else
    x_guess = x0';
end
num_param = length(x0);

% Set as the zeroth population the
% initial guess
x_pop = zeros(num_param,OptParam.num_pop);
F_pop = zeros(1,OptParam.num_pop);
x_pop(:,1) = x0;
F_pop(1) = feval(func_name,x_pop(:,1),ModelParam);
old_survivors = 1;

% Now, iterate over the generations
F_traj = zeros(OptParam.num_gen,1);
for igen=1:OptParam.num_gen

    if(OptParam.verbose)
        if(mod(igen,OptParam.verbose)==0)
            disp(['gen # ', int2str(igen),': ', ...
                    num2str(F_pop(1))]);
        end
    end
    F_traj(igen) = F_pop(1);

    % Fill out the current generation
    for k=old_survivors+1:OptParam.num_pop
        
        % select a move
        if(k==2)  % must choose mutation
            choose_move = 0;
        else
            rand_choose = rand;
            if(rand_choose <= OptParam.prob_mutate)
                choose_move = 0;  % mutate
            else
                choose_move = 1; % cross
            end
        end
        
        % perform mutation if necessary
        if(choose_move==0)
            rand_choose = rand;
            j = 1 + round(rand_choose*(k-2));
            x_pop(:,k) = x_pop(:,j) + ...
                OptParam.std_displace*randn(num_param,1);
            
        % perform crossing of two parents
        else
            rand_choose = rand;
            j = 1 + round(rand_choose*(k-2));
            rand_choose = rand;
            l = 1 + round(rand_choose*(k-2));
            rand_vector = rand(num_param,1);
            x_pop(:,k) = x_pop(:,j);
            find_parent = find(rand_vector < 0.5);
            x_pop(find_parent,k) = x_pop(find_parent,l);
        end
        
        % compute cost function of new member
        F_pop(k) = feval(func_name,x_pop(:,k),ModelParam);
        
    end
    
    % sort by increasing cost function
    [F_sort,i_sort] = sort(F_pop);
    
    % now, go through and identify the survivors,
    % enforcing diversity constraints
    new_survivors = 0;
    x_survivors = zeros(num_param,OptParam.num_survivors);
    for k=1:OptParam.num_pop
        
        % find value in x_pop of kth best member
        k_sorted = i_sort(k);

        % check for diversity
        is_diverse = 1;
        for j=1:new_survivors
            j_sorted = i_sort(j);           
            delta_x = x_pop(:,k_sorted) - x_pop(:,j_sorted); 
            dist_sq = dot(delta_x,OptParam.metric*delta_x);
            if(dist_sq < OptParam.dist_sq_min)
                is_diverse = 0;
                break;
            end
        end
        
        % if diverse, add to survivors
        if(is_diverse)
            new_survivors = new_survivors + 1;
            x_survivors(:,new_survivors) = x_pop(:,k_sorted);
            F_survivors(new_survivors) = F_pop(k_sorted);
        end
        
        % check to see if enough survivors have been added
        if(new_survivors >= OptParam.num_survivors)
            break;
        end
                       
    end
    
    % move survivors to the population
    x_pop = zeros(size(x_pop));
    F_pop = zeros(size(F_pop));
    x_pop(:,1:new_survivors) = x_survivors;
    F_pop(1:new_survivors) = F_survivors;
end

% return best candidate
x = x_pop(:,1);
F = F_pop(1);

iflag = 1;
return;
