1%% Copyright (C) 2014, 2016, 2018-2019 Colin B. Macdonald
2%%
3%% This file is part of OctSymPy.
4%%
5%% OctSymPy is free software; you can redistribute it and/or modify
6%% it under the terms of the GNU General Public License as published
7%% by the Free Software Foundation; either version 3 of the License,
8%% or (at your option) any later version.
9%%
10%% This software is distributed in the hope that it will be useful,
11%% but WITHOUT ANY WARRANTY; without even the implied warranty
12%% of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See
13%% the GNU General Public License for more details.
14%%
15%% You should have received a copy of the GNU General Public
16%% License along with this software; see the file COPYING.
17%% If not, see <http://www.gnu.org/licenses/>.
18
19%% -*- texinfo -*-
20%% @documentencoding UTF-8
21%% @defun findsymbols (@var{x})
22%% Return a list (cell array) of the symbols in an expression.
23%%
24%% The list is sorted alphabetically.  For details, @pxref{@@sym/symvar}.
25%%
26%% If two variables have the same symbol but different assumptions,
27%% they will both appear in the output.  It is not well-defined
28%% in what order they appear.
29%%
30%% @var{x} could be a sym, sym array, cell array, or struct.
31%%
32%% @example
33%% @group
34%% syms x y z
35%% C = @{x, 2*x*y, [1 x; sin(z) pi]@};
36%% S = findsymbols (C)
37%%   @result{} S = @{ ... @}
38%% S@{:@}
39%%   @result{} ans = (sym) x
40%%   @result{} ans = (sym) y
41%%   @result{} ans = (sym) z
42%% @end group
43%% @end example
44%%
45%% Note ℯ, ⅈ, π, etc are not considered as symbols.
46%%
47%% Note only returns symbols actually appearing in the RHS of a
48%% @code{symfun}.
49%%
50%% @seealso{symvar, @@sym/symvar, @@sym/findsym}
51%% @end defun
52
53function L = findsymbols(obj, dosort)
54
55  if (nargin == 1)
56    dosort = true;
57  elseif (nargin ~= 2)
58    print_usage ();
59  end
60
61  if isa(obj, 'sym')
62    cmd = { 's = _ins[0].free_symbols'
63            'l = list(s)'
64            'l = sorted(l, key=str)'
65            'return l,' };
66    L = pycall_sympy__ (cmd, obj);
67
68
69  elseif iscell(obj)
70    %fprintf('Recursing into a cell array of numel=%d\n', numel(obj))
71    L = {};
72    for i=1:numel(obj)
73      temp = findsymbols(obj{i}, false);
74      if ~isempty(temp)
75        L = {L{:} temp{:}};
76      end
77    end
78
79
80  elseif isstruct(obj)
81    %fprintf('Recursing into a struct array of numel=%d\n', numel(obj))
82    L = {};
83    fields = fieldnames(obj);
84    for i=1:numel(obj)
85      for j=1:length(fields)
86        thisobj = getfield(obj, {i}, fields{j});
87        temp = findsymbols(thisobj, false);
88        if ~isempty(temp)
89          L = {L{:} temp{:}};
90        end
91      end
92    end
93
94  else
95    L = {};
96  end
97
98
99  % sort and make unique using internal representation
100  if dosort
101    Ls = {};
102    for i=1:length(L)
103      Ls{i} = sympy (L{i});
104    end
105    [tilde, I] = unique(Ls);
106    L = L(I);
107  end
108end
109
110
111%!test
112%! syms x b y n a arlo
113%! z = a*x + b*pi*sin (n) + exp (y) + exp (sym (1)) + arlo;
114%! s = findsymbols (z);
115%! assert (isequal ([s{:}], [a,arlo,b,n,x,y]))
116%!test
117%! syms x
118%! s = findsymbols (x);
119%! assert (isequal (s{1}, x))
120%!test
121%! syms z x y a
122%! s = findsymbols ([x y; 1 a]);
123%! assert (isequal ([s{:}], [a x y]))
124%!assert (isempty (findsymbols (sym (1))))
125%!assert (isempty (findsymbols (sym ([1 2]))))
126%!assert (isempty (findsymbols (sym (nan))))
127%!assert (isempty (findsymbols (sym (inf))))
128%!assert (isempty (findsymbols (exp (sym (2)))))
129
130%!test
131%! % empty sym for findsymbols, findsym, and symvar
132%! assert (isempty (findsymbols (sym([]))))
133%! assert (isempty (findsym (sym([]))))
134%! assert (isempty (symvar (sym([]))))
135
136%!test
137%! % diff. assumptions make diff. symbols
138%! x1 = sym('x');
139%! x2 = sym('x', 'positive');
140%! f = x1*x2;
141%! assert (length (findsymbols (f)) == 2)
142
143%!test
144%! % symfun or sym
145%! syms x f(y)
146%! a = f*x;
147%! b = f(y)*x;
148%! assert (isequal (findsymbols(a), {x y}))
149%! assert (isequal (findsymbols(b), {x y}))
150
151%!test
152%! % findsymbols on symfun does not find the argnames (unless they
153%! % are on the RHS of course, this matches SMT 2014a).
154%! syms a x y
155%! f(x, y) = a;  % const symfun
156%! assert (isequal (findsymbols(f), {a}))
157%! syms a x y
158%! f(x, y) = a*y;
159%! assert (isequal (findsymbols(f), {a y}))
160
161%!test
162%! % sorts lexigraphically, same as symvar *with single input*
163%! % (note symvar does something different with 2 inputs).
164%! syms A B a b x y X Y
165%! f = A*a*B*b*y*X*Y*x;
166%! assert (isequal (findsymbols(f), {A B X Y a b x y}))
167%! assert (isequal (symvar(f), [A B X Y a b x y]))
168
169%!test
170%! % symbols in matpow
171%! syms x y
172%! syms n
173%! A = [sin(x) 2; y 1];
174%! B = A^n;
175%! L = findsymbols(B);
176%! assert (isequal (L, {n x y}))
177