## Copyright (C) 2003 Willem J. Atsma <watsma@users.sf.net>
## 
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
## 
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
## 
## You should have received a copy of the GNU General Public License
## along with this program; If not, see <http://www.gnu.org/licenses/>.

## -*- texinfo -*-
## @deftypefn {Function File} {[@var{x}, @var{inf}, @var{msg}] =} symfsolve (@dots{})
## Solve a set of symbolic equations using @command{fsolve}. There are a number of
## ways in which this function can be called.
##
## This solves for all free variables, initial values set to 0:
##
## @example
## symbols
## x=sym("x");   y=sym("y");
## f=x^2+3*x-1;  g=x*y-y^2+3;
## a = symfsolve(f,g);
## @end example
##
## This solves for x and y and sets the initial values to 1 and 5 respectively:
##
## @example
## a = symfsolve(f,g,x,1,y,5);
## a = symfsolve(f,g,@{x==1,y==5@});
## a = symfsolve(f,g,[1 5]);
## @end example
##
## In all the previous examples vector a holds the results: x=a(1), y=a(2).
## If initial conditions are specified with variables, the latter determine
## output order:
##
## @example
## a = symfsolve(f,g,@{y==1,x==2@});  # here y=a(1), x=a(2)
## @end example
##
## The system of equations to solve for can be given as separate arguments or
## as a single cell-array:
##
## @example
## a = symfsolve(@{f,g@},@{y==1,x==2@});  # here y=a(1), x=a(2)
## @end example
##
## If the variables are not specified explicitly with the initial conditions,
## they are placed in alphabetic order. The system of equations can be comma-
## separated or given in a cell-array. The return-values are those of
## fsolve; @var{x} holds the found roots.
## @end deftypefn
## @seealso{fsolve}

function [ x,inf,msg ] = symfsolve (varargin)

  ## separate variables and equations
  eqns = cell();
  vars = cell();

  if iscell(varargin{1})
    if !strcmp(typeinfo(varargin{1}{1}),"ex")
      error("First argument must be (a cell-array of) symbolic expressions.")
    endif
    eqns = varargin{1};
    arg_count = 1;
  else
    arg_count = 0;
    for i=1:nargin
      tmp = disp(varargin{i});
      if( iscell(varargin{i}) || ...
          all(isalnum(tmp) || tmp=="_" || tmp==",") || ...
          !strcmp(typeinfo(varargin{i}),"ex") )
        break;
      endif
      eqns{end+1} = varargin{i};
      arg_count = arg_count+1;
    endfor
  endif
  neqns = length(eqns);
  if neqns==0
    error("No equations specified.")
  endif

  ## make a list with all variables from equations
  tmp=eqns{1};
  for i=2:neqns
    tmp = tmp+eqns{i};
  endfor
  evars = findsymbols(tmp);
  nevars=length(evars);

  ## After the equations may follow initial values. The formats are:
  ##   [0 0.3 -3 ...]
  ##   x,0,y,0.3,z,-3,...
  ##   {x==0, y==0.3, z==-3 ...}
  ##   none - default of al zero initial values

  if arg_count==nargin
    vars = evars;
    nvars = nevars;
    X0 = zeros(nvars,1);
  elseif (nargin-arg_count)>1
    if mod(nargin-arg_count,2)
      error("Initial value symbol-value pairs don't match up.")
    endif
    for i=(arg_count+1):2:nargin
      tmp = disp(varargin{i});
      if all(isalnum(tmp) | tmp=="_" | tmp==",")
        vars{end+1} = varargin{i};
        X0((i-arg_count+1)/2)=varargin{i+1};
      else
        error("Error in symbol-value pair arguments.")
      endif
    endfor
    nvars = length(vars);
  else
    nvars = length(varargin{arg_count+1});
    if nvars!=nevars
      error("The number of initial conditions does not match the number of free variables.")
    endif
    if iscell(varargin{arg_count+1})
      ## cell-array of relations - this should work for a list of strings ("x==3") too.
      for i=1:nvars
        tmp = disp(varargin{arg_count+1}{i});
        vars{end+1} = {sym(strtok(tmp,"=="))};
        X0(i) = str2num(tmp((findstr(tmp,"==")+2):length(tmp)));
      endfor
    else
      ## straight numbers, match up with symbols in alphabetic order
      vars = evars;
      X0 = varargin{arg_count+1};
    endif
  endif

  ## X0 is now a vector, vars a list of variables.
  ## create temporary function:
  symfn = sprintf("function Y=symfn(X) ");
  for i=1:nvars
    symfn = [symfn sprintf("%s=X(%d); ",disp(vars{i}),i)];
  endfor
  for i=1:neqns
    symfn = [symfn sprintf("Y(%d)=%s; ",i,disp(eqns{i}))];
  endfor
  symfn = [symfn sprintf("endfunction")];

  eval(symfn);
  [x,inf,msg] = fsolve("symfn",X0);

endfunction

%!shared
% x = sym ("x");
% y = sym ("y");
% f = x ^ 2 + 3 * x - 1;
% g = x * y - y ^ 2 + 3;
%!test
% assert (symfsolve (f, g), [0.30278; -1.58727]', 1e-5);
%!test
% assert (symfsolve (f, g, x, 1, y, 5), [0.30278; 1.89004]', 1e-5);
%!test
% assert (symfsolve (f, g, {x==1,y==5}), [0.30278; 1.89004]', 1e-5);
%!test
% assert (symfsolve (f, g, [1 5]), [0.30278; 1.89004]', 1e-5);
%!test
% assert (symfsolve ({f, g}, {y==1,x==2}), [1.89004; 0.30278]', 1e-5);
