gusucode.com > symbolic工具箱matlab源码程序 > symbolic/@sym/matlabFunction.m

    function g = matlabFunction(f,varargin)
%matlabFunction Generate a MATLAB file or anonymous function from a sym
%   G = matlabFunction(F) generates a MATLAB anonymous function from sym object
%   F. The free variables of F become the inputs for the resulting function
%   handle G. For example, if x is the free variable of F then G(z) computes in
%   MATLAB what subs(F,x,z) computes in the symbolic engine. The function handle
%   G can be used by functions in MATLAB like FZERO or by functions in other
%   toolboxes. The order of the inputs of G matches the order returned by
%   symvar(F).
%
%   G = matlabFunction(F1,F2,...,FN) generates a MATLAB function
%   with N outputs F1 through FN.
%
%   G = matlabFunction(...,PARAM1,VALUE1,...) uses the specified parameter/value
%   pairs to customize the generated function. The parameters modify the
%   declaration that appears in the generated code. The template for the
%   generated code is
%      function [OUT1, OUT2,..., OUTN] = NAME(IN1, IN2, ... INM)
%   The parameter names can be any of the following
%
%     'File': The value, FILE, must be a string with a valid MATLAB function
%             name. If FILE does not end in '.m' it will be appended. If the
%             file already exists it will be overwritten.  A function handle to
%             the function will be returned in G. If the file name parameter is
%             empty an anonymous function is generated. The NAME in the function
%             template is the file name in FILE.
%
%  'Outputs': The value must be a cell array of N strings specifying the output
%             variable names OUT1, OUT2,... OUTN in the function template.  The
%             default output variable name for OUTk is the variable name of Fk,
%             or 'outk' if Fk is not a simple variable. If the 'File' parameter
%             is not given then the 'Outputs' parameter is ignored.
%
%     'Vars': The value, IN, must be either
%                1) a cell array of M strings or sym arrays, or
%                2) a vector of M symbolic variables.
%             IN specifies the input variable names and their order IN1,
%             IN2,... INM in the function template. If INj is a sym array then
%             the name used in the function template is 'inj'.  The variables
%             listed in IN must be a superset of the free variables in all the
%             Fk. The default value for IN is the union of the free variables in
%             all the Fk.
%
% 'Optimize': The value must be true or false. The internal default value is true.
%             If 'Optimize' is true and used in combination with 'File',
%             then a file with optimized code is generated. Otherwise,
%             if 'Optimize' is set to false the code is not optimized.
%             If 'Optimize' is explictly set to true and a function handle 
%             should be generated then an error is thrown.
%             So explictly setting 'Optimize' to true is only allowed in
%             combination with a non empty 'File'.
%
% 'Sparse'  : The value must be true or false. The internal default value is false.
%             If 'Sparse' is true then matlabFunction() generates a function which
%             convert symbolic matrices to numeric sparse matrices. 
%             Otherwise symbolic matrices are converted to numeric dense matrices
%             which is the default.
%
%   Note: not all MuPAD expressions can be converted to a MATLAB function.
%   For example sets will not be converted.
%
%   Examples:
%     syms x y
%     r = x^2+y^2;
%     f = log(r)+1/r;
%     matlabFunction(f,'File','sample')
%     type sample.m
%       function f = sample(x,y)
%       %SAMPLE
%       %    F = SAMPLE(X,Y)
%       t7 = x.^2;
%       t8 = y.^2;
%       t9 = t7 + t8;
%       f = log(t9)+1./t9;
%
%     % create a sym expression for the Van der Pol ODE and use ode45
%     % to solve and then plot the solution for particular initial conditions.
%     syms t x y
%     mu = 1;
%     vdp = [y; mu*(1-x^2)*y-x];
%     % the generated function will have two inputs and the rows of the second
%     % input will be mapped to the x and y variables. The ode45 function
%     % expects the input function to have this form.
%     vdpf = matlabFunction(vdp,'Vars',{t,[x;y]});
%     % vdpf is @(t,in2)[in2(2,:);-in2(1,:)-in2(2,:).*(in2(1,:).^2-1)]
%     % now pass the function handle to ode45 with initial conditions [2 0]
%     % and plot the result
%     [ts,ys] = ode45(vdpf,[0 20],[2 0]);
%     plot(ts,ys(:,1))
%
%     % create a sym expression for the Van der Pol ODE using symbolic mu and
%     % generate a MATLAB file named 'vdp2' from it
%     syms mu x y
%     vdp = [y; mu*(1-x^2)*y-x];
%     % generate the file vdp2.m with inputs x, y and mu and output
%     % variable named dvdt.
%     matlabFunction(vdp,'File','vdp2','Vars',[x y mu],'Outputs',{'dvdt'});
%     type vdp2
%       function dvdt = vdp2(x,y,mu)
%       %VDP2
%       %    DVDT = VDP2(X,Y,MU)
%       dvdt = [y;- x - mu.*y.*(x.^2 - 1)];
%
%     % create a file with optimized code.
%     syms x 
%     r = x^2 * (x^2+1);
%     matlabFunction(r,'File','sample');
%     % Note: The call is synonymous with 
%     % matlabFunction(r,'File','sample','Optimize',true);
%     type sample.m
%       function r = sample(x)
%       %SAMPLE
%       %    R = SAMPLE(X)
%       t2 = x.^2;
%       r = t2.*(t2+1.0);
%
%     % Create a file with non-optimized code.
%     syms x 
%     r = x^2 * (x^2+1);
%     matlabFunction(r,'File','sample','Optimize',false);
%     type sample.m
%       function r = sample(x)
%       %SAMPLE
%       %    R = SAMPLE(X)
%       r = x.^2.*(x.^2+1.0);
%
%     % Create a function which will return numeric sparse matrices
%     syms x
%     A = diag(x*ones(1,5))
%     matlabFunction(A^2,'File','sample','Sparse',true);
%     type sample.m
%       function out1 = sample(x)
%       %SAMPLE
%       %    OUT1 = SAMPLE(X)
%       t2 = x.^2;
%       out1 = sparse([1,2,3,4,5],[1,2,3,4,5],[t2,t2,t2,t2,t2],5,5);
%      
%
%   See also: function_handle, subs, fzero, ode45

%   Copyright 2008-2015 The MathWorks, Inc.

narginchk(1,inf);

% process inputs
N = getSyms(varargin);
funs = {f, varargin{1:N}};
funs = cellfun(@(f)sym(f),funs,'UniformOutput',false);
args = varargin(N+1:end);
opts = getOptions(args);

% compute non-trivial defaults and verify inputs
funvars = getFunVars(funs);
vars = checkVars(funvars,opts);
inputs = getInputs(vars);
if length(unique(sort(inputs))) ~= length(inputs)
    error(message('symbolic:sym:matlabFunction:RepeatedVarName', format(inputs)));
end
funnames = cell(1,N+1);
for k = 1:N+1
    funnames{k} = inputname(k);
end
outputs = getOutputs(funnames,opts);
varnames = format(inputs);

% generate anonymous function or file
if ~isempty(opts.File)
    if ~isempty(opts.Outputs) 
        if length(unique(sort(outputs))) ~= N+1
            error(message('symbolic:sym:matlabFunction:IncorrectNumberOfOutputVars', N+1));
        end
        if any(ismember(outputs,inputs))
            error(message('symbolic:sym:matlabFunction:InvalidOutputNames'));
        end
    end
    file = normalize(opts.File,'.m');
    body = renameFileInputs(vars,inputs,funvars);
    clear(char(file)); % necessary to avoid problems in for-loops
    g = writeMATLAB(funs,file,varnames,outputs,body, opts.Optimize, opts.Sparse);
    tmp = exist(char(file),'file'); %#ok
                                    % necessary to make the function available
                                    % on the PATH right away
else
    body = mup2matcell(funs, opts.Sparse);
    body = renameInputs(body,vars,inputs);
    g = symengine('makeFhandle',varnames,body);
end

% find the index separating the functions from the option/value pairs
% return the last index of the functions, or 0 if none
function N = getSyms(args)
chars = cellfun(@ischar,args) | cellfun(@isstruct,args);
N = find(chars,1,'first');
if isempty(N)
    N = length(args);
else
    N = N-1;
end

% compute the default value for 'Vars'.
% returns the sorted union of the symvars of the funs
function funvars = getFunVars(funs)
vars = cellfun(@(x)symvar(x),funs,'UniformOutput',false);
vars = unique([vars{:}]);
funparams = cellfun(@(x)argnames(x),funs,'UniformOutput',false);
funvars = unique([funparams{:}, vars], 'stable');

% get the 'Vars' value and check it for errors
function v = checkVars(funvars,opts)
if isempty(opts.Vars)
    v = funvars;
else
    v = opts.Vars;
end

[v,vexpanded] = var2cell(v);
if ~isempty(vexpanded)
    if ~iscellstr(vexpanded)
        error(message('symbolic:sym:matlabFunction:InvalidVars'));
    elseif ~all(cellfun(@(x)isvarname(x),vexpanded))
        error(message('symbolic:sym:matlabFunction:InvalidVarName'));
    end
end
checkVarsSubset(vexpanded,funvars);

% check that the funvars are a subset of the expanded vars.
function checkVarsSubset(vexpanded,funvars)
vars = var2cell(funvars);
missing = cellfun(@(x)~any(strcmp(char(x),vexpanded)),vars);
if any(missing)
    misvars = vars(missing);
    varnames = format(misvars);
    if length(misvars) > 1
        error(message('symbolic:sym:matlabFunction:FreeVariables', varnames));
    end    
    error(message('symbolic:sym:matlabFunction:FreeVariable', varnames));
end

% convert a string or sym array into a 1-by-N cell array of strings
% also optionally return the cellstr of expanded sym array vars joined together
function [v,vexpand] = var2cell(v)
if isa(v,'sym')
    v = reshape(v,[],1);
    v = num2cell(v.');
    v = cellfun(@(x)char(x),v,'UniformOutput',false);
elseif ischar(v)
    v = {v};
end
if nargout > 1
    vexpand = cellfun(@var2cell,v,'UniformOutput',false);
    vexpand = [vexpand{:}];
end

% get the input names for the function
function inputs = getInputs(v)
inputs = cell(size(v));
for k = 1:length(v)
    if isa(v{k},'sym') && ~isscalar(v{k})
        inputs{k} = sprintf('in%d',k);
    else
        inputs{k} = char(v{k});
    end
end

% get the output names for the function
function outputs = getOutputs(funnames,opts)
if isempty(opts.Outputs)
    outputs = funnames;
    for k = 1:length(outputs)
        if isempty(outputs{k})
            outputs{k} = sprintf('out%d',k);
        end
    end
else
    outputs = opts.Outputs;
end


% Rename the variables (and sym array variables) in 'Vars' to be 'inputs'.
% A sym array variable gets replaced with an indexed variable from inputs.
% Other variables are simply renamed to the corresponding inputs name.
% 'body' is a string with the body of the anonymous function to create
% that gets modified to contain the renamed variables.
% assumes length(inputs) == length(vars)
function body = renameInputs(body,vars,inputs)
for k=1:length(inputs)
    body = replaceOneInput(body,vars{k},inputs{k});
end

% Rename the variables (and sym array variables) in 'Vars' to be 'inputs'.
% A sym array variable gets replaced with an indexed variable from inputs.
% Other variables are simply renamed to the corresponding inputs name.
% 'funvars' is the list of scalar symbolic variables to rename. The output
% 'mapping' is a string of MATLAB code to perform the renaming. It is
% empty if no renaming is needed.
% assumes length(inputs) == length(vars)
function mapping = renameFileInputs(vars,inputs,funvars)
body = format(funvars);
if isempty(body)
    mapping = '';
    return;
end
body(body==',') = '#'; % use # as a separator
for k=1:length(inputs)
    body = replaceOneInput(body,vars{k},inputs{k});
end
% now form 'x = A;\n y = B;\n' ...
rhs = regexp(body,'#','split');
cfunvars = num2cell(funvars);
lhs = cellfun(@(x)char(x),cfunvars,'UniformOutput',false);
same = strcmp(lhs,rhs);
rhs(same) = [];
lhs(same) = [];
eq = repmat({' = '},size(rhs));
eol = repmat({';\n'},size(rhs));
mapping = [lhs;eq;rhs;eol];
mapping = [mapping{:}];

% possibly replace 'var' with 'input' in 'body'. If 'var' is a sym array
% then replace the names in 'var' with indexed expressions using 'input'
function body = replaceOneInput(body,var,input)
if isa(var,'sym') && ~isscalar(var)
    % array sym replacement.
    if isvector(var)
        % vectorize the indexing along orthogonal direction of var
        format = getFormat(var);
        body = replaceArrayInput(body,var,input,format);
    else
        % use scalar indexing
        body = replaceArrayInput(body,var,input,'%s(%d)');
    end
end

% get the format to use for vectorized indexing expression from var.
function format = getFormat(var)
[m,n]=size(var); %#ok
if m == 1
    % row vector so vectorize along columns
    format = '%s(:,%d)';
else
    % column vector so vectorize along rows
    format = '%s(%d,:)';
end

% replace 'var' names with indexed strings into 'input' according to
% the specified sprintf 'format'.
function body = replaceArrayInput(body,var,input,format)
N = numel(var);
v = mupadmex('symobj::flattenSymOrder',var.s);
for k = 1:N
    index = sprintf(format,input,k);
    vk = mupadmex(sprintf('op(%s,%d)',v.s,k),0);
    body = replaceIdentifier(body,vk,index);
end

% replace full identifier instances of id with rep in body.
function body = replaceIdentifier(body,id,rep)
word_id = ['\<' id '\>'];
body = regexprep(body,word_id,rep);

% Removes the last char from an expr if it is the statement
% delimiter ';'
function result = stripLastChar(expr)
if expr(end) == ';' 
    result = expr(1:end-1);
else
    result = expr;
end

% Convert cell array of sym expressions to body of an anon fun
function r = mup2matcell(c,sparseMat)
if isscalar(c)
    r = mup2mat(c{1},true,sparseMat);
else
    anonymousArgs = num2cell(true(1,length(c)));
    if sparseMat
        sparseArgs = num2cell(true(1,length(c)));
    else
        sparseArgs = num2cell(false(1,length(c)));
    end
    c = cellfun(@mup2mat,c,anonymousArgs,sparseArgs,'UniformOutput',false);
    c = cellfun(@stripLastChar,c,'UniformOutput',false);
    r = sprintf('%s,',c{:});
    r = ['deal(' r(1:end-1) ')'];
end

function res = mup2mat(r,anonymous,sparseMat)
% MUP2MAT Mupad to MATLAB string conversion.
%   MUP2MAT(r) converts the Mupad string r containing
%   matrix, vector, or array to a valid MATLAB string.
if anonymous 
    ano='TRUE';
else
    ano='FALSE';
end
if sparseMat 
    spa='TRUE';
else
    spa='FALSE';
end
res = mupadmex('symobj::generateMATLAB',r.s,ano,spa,0); 
res = vectorize(res(2:end-1)); % remove quotes

% file = normalizeFile(file,ext) append extension ext if file doesn't
% have one already.
function file = normalize(file,ext)
[~,~,x] = fileparts(file);
if isempty(x)
    file = [file ext];
end

% Horzcat sym array or cell array v with commas
function varnames = format(v)
if isempty(v)
    varnames = '';
else
    if ~iscell(v)
        v = num2cell(v);
    end
    varnames = cellfun(@(x)[char(x) ','],v,'UniformOutput',false);
    varnames = [varnames{:}];
    varnames(end) = [];
end

% Generate MATLAB. f is the expr to generate. file is file name.
% varnames is the formatted input variables
% outputs is the cell array of output names
% mapping is string with input to variable mapping
% optim is true or false
% sparseMat is true or false
function g = writeMATLAB(f,file,varnames,outputs,mapping,optim,sparseMat)
[fid,msg] = fopen(file,'wt');
if fid == -1
    error(message('symbolic:sym:matlabFunction:FileError', file, msg));
end
tmp = onCleanup(@()fclose(fid));
[f,tvalues,tnames] = optimize(f,optim);
[~,fname] = fileparts(file);
outnames = formatOutputs(outputs);
writeHeader(fid,fname,varnames,outnames);
if ~isempty(mapping)
    fprintf(fid,mapping);
end
if isscalar(outputs)
    writeBody(fid,tvalues,sparseMat);
    writeOutput(fid,outputs,f,1,sparseMat);
else
    writeMultiOutputBody(fid,outputs,f,tvalues,tnames,sparseMat);
end
g = str2func(fname);

function [f,tvalues,tnames] = optimize(f,optim)
if isscalar(f)
    % This will force scalars to use the same indexing logic as nonscalars.
    % It will be ignored when writing the output. see writeOutputs.
    f = [f {'0'}];
end

if optim
    % now ask MuPAD to optimize with intermediate temporary expressions
    [tvalues,f,tnames] = mupadmexnout('symobj::optimizeWithIntermediates',f{:});
else
    % We have to return the format which
    % mupadmexnout('symobj::optimizeWithIntermediates',f{:});
    % would return if it can't optimize f.
    tvalues = sym(zeros(1, 0)); 
    f = feval(symengine, 'DOM_LIST', f{:});
    tnames = sym(zeros(1, 0)); 
end
tnames = tocell(tnames);% list of temp variables in order of assignment

% write out the function declaration and help
function writeHeader(fid,fname,varnames,outnames)
symver = ver('symbolic');
if ~isempty(varnames)
    varnames = ['(' varnames ')'];
end
symver = symver(1);
fprintf(fid,'function %s = %s%s\n',outnames,fname,varnames);
fprintf(fid,'%%%s\n',upper(fname));
fprintf(fid,'%%    %s = %s%s\n\n',upper(outnames),upper(fname),upper(varnames));
fprintf(fid,'%%    This function was generated by the Symbolic Math Toolbox version %s.\n',symver.Version);
fprintf(fid,'%%    %s\n\n',datestr(now));

% write the body of the optimized code
% if indent is supplied prepend that number of spaces to the code
function writeBody(fid,f,sparseMat,indent)
fmt = '';
if nargin == 4
    fmt = repmat(' ',1,indent);
end
for k = 1:length(f)
    eqnk = privsubsref(f,k);
    body = mup2mat(eqnk,false,sparseMat);
    fprintf(fid,[fmt body]);
end

% get the string for the declaration and help from the cell array of names
function outnames = formatOutputs(outputs)
outnames = format(outputs);
if length(outputs)>1
    outnames = ['[' outnames ']'];
end

% write the assignments to the output variables
function writeOutput(fid,outputs,expr,N,sparseMat)
% expr looks something like
% array(1..1, 1..2, (1,1) = array(1..1, 1..2, (1,1) = sin(t5), (1,2) = cos(t)), (1,2) = cos(t5))
% which is an array of arrays (or scalars). each array element is an output.
% We need to deal the elements of expr to outputs
% Note if length(outputs) == 1 then expr has length 2 to keep indexing the
% same as in the array case. This is ok since we never index expr(2).
fmt = '';
if N > 1
    fmt = '    ';
end
y = mupadmex(['symobj::fixupVar("' outputs{N} '") = ' expr.s '[' num2str(N) ']']);
body = mup2mat(y,false,sparseMat);
body = [fmt body];
fprintf(fid,body);

% write the body of the optimized code involving multiple outputs
% Optimize the code so that if nargout is less than all the outputs
% the computation skips any intermediates that will be discarded.
% Well, actually some extra intermediates will be computed if
% those intermediates are interspersed between "earlier" computations.
function writeMultiOutputBody(fid,outputs,f,tvalues,tnames,sparseMat)
for k = 1:length(outputs)
    [inter,tvalues,tnames] = computeIntermediates(tvalues,f,k,tnames);
    writeOneOutputOfMultiOutputBody(fid,f,inter,outputs,k,sparseMat);
end

% write a single output of a multi-output body. The first output is
% written directly but others are wrapped inside nargout checks.
function writeOneOutputOfMultiOutputBody(fid,f,tvalues,outputs,N,sparseMat)
indent = 0;
if N > 1
    fprintf(fid,'if nargout > %d\n',N-1);
    indent = 4;
end
writeBody(fid,tvalues,sparseMat,indent);
writeOutput(fid,outputs,f,N,sparseMat);
if N > 1
    fprintf(fid,'end\n');
end

% extract from tvalues the intermediate computations that are required for
% computing fend(k). Returns the array of intermediates in the
% same order they appeared in f and return the arrays f and temps
% with those entries removed. (will be empty at k=end)
function [inter,tvalues,tnames] = computeIntermediates(tvalues,fend,k,tnames)
% find the largest index of the tNNN identifiers in fend(k)
inter = [];
fk = privsubsref(fend,k);
expr = char(fk);
if ~isempty(tvalues)
    reqs = unique(regexp(expr,'\<t[0-9]+\>','match'));
    if isempty(reqs), return, end
    inds = cellfun(@(x){find(strcmp(x,tnames),1,'last')},reqs);
    last = max([inds{:}]);
    if ~isempty(last)
        inter = privsubsref(tvalues,1:last);
        tvalues = privsubsref(tvalues,last+1:numel(tvalues));
        tnames = tnames(last+1:end);
    end
end

% validator for variable parameter
function t = isVars(x)
if iscell(x)
    if ~isvector(x) && ~isempty(x)
        error(message('symbolic:sym:matlabFunction:InvalidVars')); 
    end
    for k = 1:length(x)
        if ~(isa(x{k},'sym') || (ischar(x{k}) && isvector(x{k}))) || isa(x{k},'symfun')
            error(message('symbolic:sym:matlabFunction:InvalidVars')); 
        end
    end
    t = true;
elseif (ischar(x) && isvector(x))
    t = true;
elseif isa(x,'sym')
    if isa(x,'symfun')
        error(message('symbolic:sym:matlabFunction:InvalidVars'));  
    end
    t = true;
else
    error(message('symbolic:sym:matlabFunction:InvalidVars')); 
end

% validator for output parameter
function t = isOutputVars(x)
if iscellstr(x) && (isvector(x) || isempty(x))
    if all(cellfun(@isvarname,x))
        t = true;
    else
        error(message('symbolic:sym:matlabFunction:InvalidVarName'));
    end
else
    error(message('symbolic:sym:matlabFunction:InvalidOutputVars'));
end

% validator for file parameter
function t = isFunc(x)
[~,file] = fileparts(x);
t = isempty(file) || isvarname(file);

% parse inputs and return option structure output
function opts = getOptions(args)
ip = inputParser;
ip.addParameter('Vars',{},@isVars);
ip.addParameter('File','',@isFunc);
ip.addParameter('Outputs',{},@isOutputVars);
ip.addParameter('Optimize',true,@(x) isequal(x,true) || isequal(x,false));
ip.addParameter('Sparse',false,@(x) isequal(x,true) || isequal(x,false));
ip.parse(args{:});
% 'Optimize' was set explicitly by the user
if isempty(find(strcmp(ip.UsingDefaults, 'Optimize'),1))
    if isempty(ip.Results.File) && ip.Results.Optimize
        error(message('symbolic:sym:matlabFunction:OptimizeNotAllowed'));
    end
end
opts = ip.Results;

function c = tocell(x)
if isempty(x)
    c = {};
elseif isscalar(x)
    str = char(x);
    if strncmp(str,'matrix(',7)
        str = str(10:end-3); % remove matrix([[...]])
    end
    c = {str};
else
    str = char(x);
    if strncmp(str,'matrix(',7)
        str = str(10:end-3); % remove matrix([[...]])
    end
    c = regexp(str,',','split');
    c = cellfun(@strtrim,c,'UniformOutput',false);
end