%% Optimization
% In this section, we'll cover one of the most important components of dynamic programming - optimization.
% Optimization is the process of finding the set of points in a domain that maximize or minimize the value of the objective function.
%% Numerical Optimization
% Analytical optimization (optimization by hand) involves setting up a problem in a particular way, determining its properties, and using a variety of methods to determine critical points.
% In numerical optimization (optimization by computer), we generally require that the problem have a unique global solution, and that the global solution is also the only local solution over the function's domain.
% By long-standing tradition, numerical optimization routines are either maximization routines, or, as in Matlab's case, minimization routines.
% Any maximization problem can be converted into a minimization problem by multiplying the objective function by -1.
% We will solve our first optimization problem:
%             \max_\mbox{c,n} \ c^{\gamma} (1-n)^{1-\gamma} \\
% s.t. \ \ c = wn + rk
%             where c is consumption, n is labor supply (so 1-n is leisure), w
%  is the wage, r is the interest rate, and k is the amount of capital (assets).
% Let's solve this numerically for some given parameters:

% Assigning parameters
consumption_share = 0.6  ;
wage              = 1.0  ;
interest_rate     = 1.04 ;
capital           = 0.0  ;

% Creating a structure of parameters
parameters.consumption_share = consumption_share ;
parameters.wage              = wage              ;
parameters.interest_rate     = interest_rate     ;
parameters.capital           = capital           ;


% Maximizing utility.
initial_guess                    = 0.5                                                                        ;
[optimal_labor, optimal_utility] = fminsearch(@(labor) calculate_utility( labor, parameters), initial_guess ) ;
optimal_utility                  = -1*optimal_utility                                                         ;

fprintf('Optimal labor supply is %0.3f.\n', optimal_labor   )
fprintf('Optimal utility is %0.3f.\n'     , optimal_utility )

% Creating objective function

%

function utility = calculate_utility( labor, parameters )
    
    consumption = parameters.wage * labor + parameters.interest_rate * parameters.capital                            ;
    
    utility     = ( consumption^parameters.consumption_share )*( ( 1 - labor )^( 1- parameters.consumption_share ) ) ;
    
    utility     = -1 * utility                                                                                       ;    % Converting into minimization problem.
    
end

% Several things to notice here.  First, we introduced the concept of a structure.  A structure is nothing more than a container that organizes data according to something that all of the elements of data have in common.  In this case, everything in the "parameters" structure is...well... a parameter.
% Second, we implicitly substituted the constraint of the optimization problem for consumption, so we only had to solve over optimal labor.
% Third, we converted the maximization problem into a minimization by multiplying the corresponding objective function by -1.  When we found our minimal value, we had to convert the optimal value back to our original terms by multiplying the value by -1 again.
% Fourth, we created a local function (that is, a function that is only seen by the script it's in).  That allowed us to write the objection function in greater detail.
% Fifth, we had to tell fminsearch which dimension of the calculate_utility function to optimize over.  In this case, it was labor, denoted by the @(labor) in the function inputs.
% One final note - the optimization routine assumes that the solution is a global optimum.  Make sure that the optimization problem satisfies the conditions for a global optimum (i.e., concave utility function, convex and compact constraint set).