diff --git a/NEWS b/NEWS
index 157eb0aa1..2edaccdf2 100644
--- a/NEWS
+++ b/NEWS
@@ -26,6 +26,12 @@ octsympy 2.5.1-dev
using `sym(0.1, 'f')`. Passing `'r'` gives the current heuristic
behaviour. Neither raises a warning.
+ * The default floating-point heuristics now check for square roots of
+ integers, so `sym(sqrt(2))` should work.
+
+ * `sym('i')` and `sym('I')` no longer create the imaginary unit: use
+ `sym(i)` or `sym(1i)`.
+
* Bug fix: isequal(n) for symfun's correctly tests the argnames.
diff --git a/inst/@sym/private/const_to_python_str.m b/inst/@sym/private/const_to_python_str.m
deleted file mode 100644
index adf337c2b..000000000
--- a/inst/@sym/private/const_to_python_str.m
+++ /dev/null
@@ -1,96 +0,0 @@
-%% Copyright (C) 2015-2017 Colin B. Macdonald
-%% Copyright (C) 2016 Lagu
-%%
-%% This file is part of OctSymPy.
-%%
-%% OctSymPy 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 3 of the License,
-%% or (at your option) any later version.
-%%
-%% This software 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 software; see the file COPYING.
-%% If not, see .
-
-%% -*- texinfo -*-
-%% @deftypefun {[@var{s}, @var{flag}] =} const_to_python_str (@var{x})
-%% Recognize constant and return their python string equivalent.
-%%
-%% Private helper function.
-%%
-%% Caution: there are two copies of this file for technical
-%% reasons: make sure you modify both of them!
-%%
-%% @seealso{sym, vpa}
-%% @end deftypefun
-
-function [s, flag] = const_to_python_str (x)
-
- % Table of special doubles and special strings. Format for each row is:
- % double value, {list of strings to recognize}, resulting python expr
- % Note: case sensitive
- % Note: python expr should be in list for identity "sym(sympy(x)) == x"
- list = {pi {'pi'} 'S.Pi'; ...
- inf {'inf' 'Inf' 'oo'} 'S.Infinity'; ...
- nan {'NaN' 'nan'} 'S.NaN'; ...
- i {'i' 'I'} 'S.ImaginaryUnit'};
- % Special Sympy constants to recognize
- const = {'zoo'};
-
- flag = 1;
-
- if (isa (x, 'double')) % Number comparison
- for j = 1:length (list)
- if (isequaln (x, list{j, 1}))
- s = list{j, 3};
- return
- elseif (isequaln (x, -list{j, 1}))
- s = ['-' list{j, 3}];
- return
- end
- end
- elseif (isa (x, 'char')) % Char comparison
- for j = 1:length (list)
- for n = 1:length (list{j, 2})
- if (strcmp (x, list{j, 2}{n}) || strcmp (x, ['+' list{j, 2}{n}]))
- s = list{j, 3};
- return
- elseif (strcmp (x, ['-' list{j, 2}{n}]))
- s = ['-' list{j, 3}];
- return
- end
- end
- end
- for j = 1:length (const) % Check if is a python constant
- if (strcmp (x, const{j}) || strcmp (x, ['+' const{j}]))
- s = const{j};
- return
- elseif (strcmp (x, ['-' const{j}]))
- s = ['-' const{j}];
- return
- end
- end
- else
- error ('Format not supported.')
- end
-
- if (isa (x, 'char'))
- flag = 0;
- s = x;
- return
- elseif (isa (x, 'double'))
- if ((abs (x) < 1e15) && (mod (x,1) == 0))
- % special treatment for "small" integers
- s = num2str (x); % better than sprintf('%d', large)
- else
- s = '';
- flag = 0;
- end
- end
-
-end
diff --git a/inst/@sym/private/detect_special_str.m b/inst/@sym/private/detect_special_str.m
new file mode 100644
index 000000000..189516fdb
--- /dev/null
+++ b/inst/@sym/private/detect_special_str.m
@@ -0,0 +1,60 @@
+%% Copyright (C) 2015-2017 Colin B. Macdonald
+%% Copyright (C) 2016 Lagu
+%%
+%% This file is part of OctSymPy.
+%%
+%% OctSymPy 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 3 of the License,
+%% or (at your option) any later version.
+%%
+%% This software 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 software; see the file COPYING.
+%% If not, see .
+
+%% -*- texinfo -*-
+%% @deftypefun {@var{s} =} detect_special_str (@var{x})
+%% Recognize special constants, return their sympy string equivalent.
+%%
+%% Private helper function.
+%%
+%% This function should return a string @var{s} which can be instantiated
+%% directly in Python such as @code{['return' @var{s}]}. If no special
+%% value is found, it returns the empty string.
+%%
+%% @seealso{sym, vpa}
+%% @end deftypefun
+
+function s = detect_special_str (x)
+
+ % Table of special strings. Format for each row is:
+ % {list of strings to recognize}, resulting python expr
+ % Note: case sensitive
+ % Note: python expr should be in list for identity "sym(sympy(x)) == x"
+ table = {{'pi'} 'S.Pi'; ...
+ {'inf' 'Inf' 'oo'} 'S.Infinity'; ...
+ {'NaN' 'nan'} 'S.NaN'; ...
+ {'zoo'} 'S.ComplexInfinity'};
+
+ s = '';
+
+ assert (ischar (x))
+
+ for j = 1:length (table)
+ for n = 1:length (table{j, 1})
+ if (strcmp (x, table{j, 1}{n}) || strcmp (x, ['+' table{j, 1}{n}]))
+ s = table{j, 2};
+ return
+ elseif (strcmp (x, ['-' table{j, 1}{n}]))
+ s = ['-' table{j, 2}];
+ return
+ end
+ end
+ end
+
+end
diff --git a/inst/private/magic_str_str.m b/inst/@sym/private/double_to_sym_exact.m
similarity index 60%
rename from inst/private/magic_str_str.m
rename to inst/@sym/private/double_to_sym_exact.m
index 82fd0ce1b..674b13527 100644
--- a/inst/private/magic_str_str.m
+++ b/inst/@sym/private/double_to_sym_exact.m
@@ -1,4 +1,4 @@
-%% Copyright (C) 2015 Colin B. Macdonald
+%% Copyright (C) 2017 Colin B. Macdonald
%%
%% This file is part of OctSymPy.
%%
@@ -17,29 +17,22 @@
%% If not, see .
%% -*- texinfo -*-
-%% @deftypefun {@var{s} =} magic_str_str (@var{x})
-%% Recognize special string values and substitute others.
+%% @deftypefun {@var{y} =} double_to_sym_exact (@var{x})
+%% Convert a double value to the equivalent rational sym
%%
-%% Private function.
+%% Private helper function.
%%
-%% @seealso{sym, vpa}
%% @end deftypefun
-function s = magic_str_str(x, varargin)
-
- if (~ischar(x))
- error('OctSymPy:magic_str_str:notstring', ...
- 'Expected a string');
- end
-
- if (strcmpi(x, 'inf')) || (strcmpi(x, '+inf'))
- s = 'oo';
- elseif (strcmpi(x, '-inf'))
- s = '-oo';
- elseif (strcmpi(x, 'i'))
- s = 'I';
+function y = double_to_sym_exact (x)
+ if (isnan (x))
+ y = python_cmd ('return S.NaN');
+ elseif (isinf (x) && x < 0)
+ y = python_cmd ('return -S.Infinity');
+ elseif (isinf (x))
+ y = python_cmd ('return S.Infinity');
else
- s = x;
+ %% Rational will exactly convert from a float
+ y = python_cmd ('return Rational(_ins[0])', x);
end
-
end
diff --git a/inst/@sym/private/double_to_sym_heuristic.m b/inst/@sym/private/double_to_sym_heuristic.m
new file mode 100644
index 000000000..63168bda5
--- /dev/null
+++ b/inst/@sym/private/double_to_sym_heuristic.m
@@ -0,0 +1,64 @@
+%% Copyright (C) 2017 Colin B. Macdonald
+%%
+%% This file is part of OctSymPy.
+%%
+%% OctSymPy 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 3 of the License,
+%% or (at your option) any later version.
+%%
+%% This software 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 software; see the file COPYING.
+%% If not, see .
+
+%% -*- texinfo -*-
+%% @deftypefun {@var{y} =} double_to_sym_heuristic (@var{x}, @var{ratwarn}, @var{argnstr})
+%% Convert a double value to a nearby "nice" sym
+%%
+%% Private helper function.
+%%
+%% @end deftypefun
+
+function y = double_to_sym_heuristic (x, ratwarn, argnstr)
+ assert (isempty (argnstr))
+ if (isnan (x))
+ y = python_cmd ('return S.NaN');
+ elseif (isinf (x) && x < 0)
+ y = python_cmd ('return -S.Infinity');
+ elseif (isinf (x))
+ y = python_cmd ('return S.Infinity');
+ elseif (isequal (x, pi))
+ %% Special case for pi
+ y = python_cmd ('return S.Pi');
+ elseif (isequal (x, -pi))
+ y = python_cmd ('return -S.Pi');
+ elseif ((abs (x) < flintmax) && (mod (x, 1) == 0))
+ y = python_cmd ('return Integer(_ins[0])', int64 (x));
+ else
+ %% Find the nearest rational, rational*pi or sqrt(integer)
+ if (ratwarn)
+ warning('OctSymPy:sym:rationalapprox', ...
+ 'passing floating-point values to sym is dangerous, see "help sym"');
+ end
+ [N1, D1] = rat (x);
+ [N2, D2] = rat (x / pi);
+ N3 = round (x^2);
+ err1 = abs (N1 / D1 - x);
+ err2 = abs ((N2*pi) / D2 - x);
+ err3 = abs (sqrt (N3) - x);
+ if (err1 <= err3)
+ if (err1 <= err2)
+ y = python_cmd ('return Rational(*_ins)', int64 (N1), int64 (D1));
+ else
+ y = python_cmd ('return Rational(*_ins)*S.Pi', int64 (N2), int64 (D2));
+ end
+ else
+ y = python_cmd ('return sqrt(Integer(*_ins))', int64 (N3));
+ end
+ end
+end
diff --git a/inst/@sym/sym.m b/inst/@sym/sym.m
index f8d0214c9..99862c1b1 100644
--- a/inst/@sym/sym.m
+++ b/inst/@sym/sym.m
@@ -107,9 +107,9 @@
%% @code{sym(1)/10} (and this is true for most decimal expressions).
%% It is generally impossible to determine which exact symbolic value the
%% user intended.
-%% The warning indicates that some heuristics have been applied,
-%% namely a preference for ``small'' fractions (and small fractions
-%% of π).
+%% The warning indicates that some heuristics have been applied
+%% (namely a preference for ``small'' fractions, small fractions
+%% of π and square roots of integers).
%% Further examples include:
%% @example
%% @group
@@ -277,7 +277,6 @@
end
asm = {};
- check = true;
isnumber = isnumeric (x) || islogical (x);
ratwarn = true;
ratflag = 'r';
@@ -320,80 +319,52 @@
s = numeric_array_to_sym (x);
return
- elseif (isa (x, 'double')) % Handle doubles
- check = false;
-
- switch ratflag
- case 'f'
- % TODO: need something like const_to_python_str (x) for inf, nan, not pi
- if (isreal (x))
- s = python_cmd ('return Rational(_ins[0])', x);
- else
- s = python_cmd ('return Rational(_ins[0]) + I*Rational(_ins[1])', ...
- real (x), imag (x));
- end
- return
-
- case 'r'
- iscmplx = ~isreal (x);
- if (iscmplx)
- xx = {real(x); imag(x)};
- else
- xx = {x};
- end
- ss = cell(2, 1);
-
- for n = 1:numel (xx)
- tmpx = xx{n};
- [ss{n}, flag] = const_to_python_str (tmpx);
- if (~flag)
- % Allow 1/3 and other "small" fractions.
- % Personally, I like a warning here so I can catch bugs.
- % Matlab SMT does this (w/o warning).
- if (ratwarn)
- warning('OctSymPy:sym:rationalapprox', ...
- 'passing floating-point values to sym is dangerous, see "help sym"');
- end
- [N1, D1] = rat (tmpx);
- [N2, D2] = rat (tmpx / pi);
- if (10*abs (D2) < abs (D1))
- % use frac*pi if demoninator significantly shorter
- ss{n} = sprintf ('Rational(%s, %s)*pi', num2str (N2), num2str (D2));
- else
- ss{n} = sprintf ('Rational(%s, %s)', num2str (N1), num2str (D1));
- end
- else
- ss{n} = sprintf ('S(%s)', ss{n});
- end
- end
-
- if (iscmplx)
- x = sprintf ('%s + I*(%s)', ss{1}, ss{2});
- else
- x = ss{1};
- end
-
- otherwise
- error ('sym: this case should not be possible')
+ elseif (isa (x, 'double')) % Handle double/complex
+ iscmplx = ~isreal (x);
+ if (iscmplx && isequal (x, 1i))
+ s = python_cmd ('return S.ImaginaryUnit');
+ return
+ elseif (iscmplx)
+ xx = {real(x); imag(x)};
+ else
+ xx = {x};
+ end
+ yy = cell(2, 1);
+ for n = 1:numel (xx)
+ x = xx{n};
+ switch ratflag
+ case 'f'
+ y = double_to_sym_exact (x);
+ case 'r'
+ y = double_to_sym_heuristic (x, ratwarn, []);
+ otherwise
+ error ('sym: this case should not be possible')
+ end
+ yy{n} = y;
end
+ if (iscmplx)
+ s = yy{1} + yy{2}*1i;
+ else
+ s = yy{1};
+ end
+ return
+
+ elseif (isinteger (x)) % Handle integer vealues
+ s = python_cmd ('return Integer(*_ins)', x);
+ return
elseif (islogical (x)) % Handle logical values
- check = false;
if (x)
- x = 'S.true';
+ s = python_cmd ('return S.true');
else
- x = 'S.false';
+ s = python_cmd ('return S.false');
end
-
- elseif (isinteger (x)) % Handle integer vealues
- check = false;
- x = num2str (x, '%ld');
+ return
end
if (isa (x, 'char'))
- % We now have a char; need to decide whether to use S() or Symbol() on it.
+ %% Need to decide whether to use S() or Symbol()
- if (check)
% TODO: Warning if you try make a sym with the same name of a system function.
%symsnotfunc (x);
@@ -415,16 +386,17 @@
% end
%end
- [x, flag] = const_to_python_str (x);
- if (flag)
- check = false;
- end
-
- isnum = ~isempty (regexp (x, '^[-+]*?\d*\.?\d*(e-?\d+)?$')); % Is Number
+ y = detect_special_str (x);
+ if (~ isempty (y))
+ assert (isempty (asm), 'Only symbols can have assumptions.')
+ s = python_cmd (['return ' y]);
+ return
end
+ isnum = ~isempty (regexp (x, '^[-+]*?\d*\.?\d*(e-?\d+)?$'));
+
%% Use Symbol() for words, not numbers, not "f(x)".
- if (check && (~ isnum) && (~ isempty (regexp (x, '^\w+$'))))
+ if ((~ isnum) && (~ isempty (regexp (x, '^\w+$'))))
cmd = { 'd = dict()'
'x = _ins[0]'
@@ -464,12 +436,10 @@
assert (isempty (asm), 'Only symbols can have assumptions.')
% TODO: figure version might warn on expression strings
- %if (check)
% Check if the user try to execute operations from sym
%if (~isempty (regexp (xc, '\!|\&|\^|\:|\*|\/|\\|\+|\-|\>|\<|\=|\~')))
% warning ('Please avoid execute operations from sym function.');
%end
- %end
% Usually want rational output here (i.e., if input was "1.2").
% But if input has words and parentheses it might be raw Sympy code.
@@ -804,6 +774,13 @@
%! assert (isequal (sym(inf, 'f'), sym(inf)))
%! assert (isequal (sym(-inf, 'f'), sym(-inf)))
%! assert (isequaln (sym(nan, 'f'), sym(nan)))
+%! assert (isequal (sym(complex(inf, -inf), 'f'), sym(complex(inf, -inf))))
+%! assert (isequaln (sym(complex(nan, inf), 'f'), sym(complex(nan, inf))))
+%! assert (isequaln (sym(complex(-inf, nan), 'f'), sym(complex(-inf, nan))))
+
+%!test
+%! assert (isequal (sym (sqrt(2), 'r'), sqrt (sym (2))))
+%! assert (isequal (sym (sqrt(12345), 'r'), sqrt (sym (12345))))
%!test
%! % symbols with special sympy names
@@ -827,6 +804,7 @@
%!warning sym (-1e16);
%!warning sym (10.33);
%!warning sym (-5.23);
+%!warning sym (sqrt (1.4142135623731));
%!error
%! x = sym ('x', 'positive2');
diff --git a/inst/private/const_to_python_str.m b/inst/private/const_to_python_str.m
deleted file mode 100644
index adf337c2b..000000000
--- a/inst/private/const_to_python_str.m
+++ /dev/null
@@ -1,96 +0,0 @@
-%% Copyright (C) 2015-2017 Colin B. Macdonald
-%% Copyright (C) 2016 Lagu
-%%
-%% This file is part of OctSymPy.
-%%
-%% OctSymPy 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 3 of the License,
-%% or (at your option) any later version.
-%%
-%% This software 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 software; see the file COPYING.
-%% If not, see .
-
-%% -*- texinfo -*-
-%% @deftypefun {[@var{s}, @var{flag}] =} const_to_python_str (@var{x})
-%% Recognize constant and return their python string equivalent.
-%%
-%% Private helper function.
-%%
-%% Caution: there are two copies of this file for technical
-%% reasons: make sure you modify both of them!
-%%
-%% @seealso{sym, vpa}
-%% @end deftypefun
-
-function [s, flag] = const_to_python_str (x)
-
- % Table of special doubles and special strings. Format for each row is:
- % double value, {list of strings to recognize}, resulting python expr
- % Note: case sensitive
- % Note: python expr should be in list for identity "sym(sympy(x)) == x"
- list = {pi {'pi'} 'S.Pi'; ...
- inf {'inf' 'Inf' 'oo'} 'S.Infinity'; ...
- nan {'NaN' 'nan'} 'S.NaN'; ...
- i {'i' 'I'} 'S.ImaginaryUnit'};
- % Special Sympy constants to recognize
- const = {'zoo'};
-
- flag = 1;
-
- if (isa (x, 'double')) % Number comparison
- for j = 1:length (list)
- if (isequaln (x, list{j, 1}))
- s = list{j, 3};
- return
- elseif (isequaln (x, -list{j, 1}))
- s = ['-' list{j, 3}];
- return
- end
- end
- elseif (isa (x, 'char')) % Char comparison
- for j = 1:length (list)
- for n = 1:length (list{j, 2})
- if (strcmp (x, list{j, 2}{n}) || strcmp (x, ['+' list{j, 2}{n}]))
- s = list{j, 3};
- return
- elseif (strcmp (x, ['-' list{j, 2}{n}]))
- s = ['-' list{j, 3}];
- return
- end
- end
- end
- for j = 1:length (const) % Check if is a python constant
- if (strcmp (x, const{j}) || strcmp (x, ['+' const{j}]))
- s = const{j};
- return
- elseif (strcmp (x, ['-' const{j}]))
- s = ['-' const{j}];
- return
- end
- end
- else
- error ('Format not supported.')
- end
-
- if (isa (x, 'char'))
- flag = 0;
- s = x;
- return
- elseif (isa (x, 'double'))
- if ((abs (x) < 1e15) && (mod (x,1) == 0))
- % special treatment for "small" integers
- s = num2str (x); % better than sprintf('%d', large)
- else
- s = '';
- flag = 0;
- end
- end
-
-end
diff --git a/inst/test_bugs.tst b/inst/test_bugs.tst
index 2407ae154..26b4926ad 100644
--- a/inst/test_bugs.tst
+++ b/inst/test_bugs.tst
@@ -242,8 +242,3 @@
%! % FIXME: not same as an freshly created 5 x 6.
%! C = sym('B', [5 6]);
%! assert (isequal(B, C))
-
-%!test
-%! f1 = fileread ('private/const_to_python_str.m');
-%! f2 = fileread ('@sym/private/const_to_python_str.m');
-%! assert (strcmp (f1, f2))
diff --git a/inst/vpa.m b/inst/vpa.m
index 35b31e412..ac2048b6d 100644
--- a/inst/vpa.m
+++ b/inst/vpa.m
@@ -62,8 +62,15 @@
'x, n = _ins'
'return sympy.N(x, n),' };
r = python_cmd (cmd, x, n);
- elseif (ischar(x))
- x = magic_str_str(x);
+ elseif (ischar (x))
+ if (strcmp (x, 'inf') || strcmp (x, 'Inf') || strcmp (x, '+inf') || ...
+ strcmp (x, '+Inf'))
+ x = 'S.Infinity';
+ elseif (strcmp (x, '-inf') || strcmp (x, '-Inf'))
+ x = '-S.Infinity';
+ elseif (strcmp (x, 'I'))
+ x = 'Symbol("I")';
+ end
% Want Float if its '2.3' but N if its 'pi'
cmd = {
'x, n = _ins'
@@ -72,18 +79,24 @@
'except ValueError:'
' return sympy.N(x, n),' };
r = python_cmd (cmd, x, n);
- elseif (isfloat(x) && ~isreal (x))
- r = vpa(real(x), n) + sym('I')*vpa(imag(x), n);
+ elseif (isfloat (x) && ~isreal (x))
+ r = vpa (real (x), n) + sym (1i)*vpa (imag (x), n);
elseif (isfloat(x) && isscalar(x) == 1)
- [s, flag] = const_to_python_str(x);
- if (flag)
- r = vpa(s, n);
- else
- cmd = {
- 'x, n = _ins'
- 'return sympy.Float(x, n),' };
- r = python_cmd (cmd, x, n);
+ if (isnan (x))
+ x = 'S.NaN';
+ elseif (isinf (x) && x < 0)
+ x = '-S.Infinity';
+ elseif (isinf (x))
+ x = 'S.Infinity';
+ elseif (isequal (x, pi))
+ x = 'S.Pi';
+ elseif (isequal (x, -pi))
+ x = '-S.Pi';
end
+ cmd = {
+ 'x, n = _ins'
+ 'return sympy.N(x, n)' };
+ r = python_cmd (cmd, x, n);
elseif (isinteger(x) && isscalar(x) == 1)
cmd = {
'x, n = _ins'
@@ -195,11 +208,14 @@
%! % can pass i directly to vpa
%! a = vpa(sym(i));
%! b = vpa(i);
-%! c = vpa('i');
-%! d = vpa('I');
-%! assert (isequal (a, b))
-%! assert (isequal (a, c))
-%! assert (isequal (a, d))
+
+%!test
+%! % 'i' and 'I' just make vars
+%! a = vpa(sym(1i));
+%! b = vpa('i');
+%! c = vpa('I');
+%! assert (~isequal (a, b))
+%! assert (~isequal (a, c))
%!test
%! % inf/-inf do not become symbol('inf')