Repository: sympy/sympy
Pull request: Fixed pretty printing and codegen error messages for empty piecewise
<!-- Your title above should be a short description of what
was changed. Do not include the issue number in the title. -->

New behavior:
- Manually constructing Piecewise() now raises a type error
- Piecewise evaluated with no matching condition gives nan

#### References to other Issues or PRs
<!-- If this pull request fixes an issue, write "Fixes #NNNN" in that exact
format, e.g. "Fixes #1234". See
https://github.com/blog/1506-closing-issues-via-pull-requests .-->
Fixes #15005 based on the discussion there

#### Brief description of what is fixed or changed


#### Other comments


#### Release Notes

<!-- Write the release notes for this release below. See
https://github.com/sympy/sympy/wiki/Writing-Release-Notes for more information
on how to write release notes. If there is no release notes entry for this PR,
write "NO ENTRY". The bot will check your release notes automatically to see
if they are formatted correctly. -->

<!-- BEGIN RELEASE NOTES -->
* functions
  * Piecewise with no arguments not allowed anymore
  * Piecewise evaluated with no matching conditions gives nan
<!-- END RELEASE NOTES -->


Problem statement:
empty Piecewise fails to pretty print
Consider:
```
>>> f = Piecewise()
>>> print(f)
Piecewise()
```
Ok, but:
```
>>> pprint(f)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-5-dd6b50590f87> in <module>()
----> 1 pprint(f)

/home/cbm/src/sympy.git/sympy/printing/pretty/pretty.pyc in pretty_print(expr, wrap_line, num_columns, use_unicode, full_prec, order, use_unicode_sqrt_char)
   2379     print(pretty(expr, wrap_line=wrap_line, num_columns=num_columns,
   2380                  use_unicode=use_unicode, full_prec=full_prec, order=order,
-> 2381                  use_unicode_sqrt_char=use_unicode_sqrt_char))
   2382 
   2383 pprint = pretty_print

/home/cbm/src/sympy.git/sympy/printing/pretty/pretty.pyc in pretty(expr, **settings)
   2339 
   2340     try:
-> 2341         return pp.doprint(expr)
   2342     finally:
   2343         pretty_use_unicode(uflag)

/home/cbm/src/sympy.git/sympy/printing/pretty/pretty.pyc in doprint(self, expr)
     60 
     61     def doprint(self, expr):
---> 62         return self._print(expr).render(**self._settings)
     63 
     64     # empty op so _print(stringPict) returns the same

/home/cbm/src/sympy.git/sympy/printing/printer.pyc in _print(self, expr, **kwargs)
    285                 printmethod = '_print_' + cls.__name__
    286                 if hasattr(self, printmethod):
--> 287                     return getattr(self, printmethod)(expr, **kwargs)
    288             # Unknown object, fall back to the emptyPrinter.
    289             return self.emptyPrinter(expr)

/home/cbm/src/sympy.git/sympy/printing/pretty/pretty.pyc in _print_Piecewise(self, pexpr)
   1038         # max widths
   1039         maxw = [max([P[i, j].width() for i in range(len_args)])
-> 1040                 for j in range(2)]
   1041 
   1042         # FIXME: Refactor this code and matrix into some tabular environment.

ValueError: max() arg is an empty sequence

>>> latex(f)

<similar error>
```

Now maybe you think no one should make empty Piecewise but they can occur naturally:
```
>>> f = Piecewise((42, x > 0)))
>>> pprint(f)
{42  for x > 0
```
`f` is undefined for non-positive numbers:
```
>>> n = Symbol('n', negative=True)
>>> print(f.subs(z, n))
Piecewise()
>>> print(f.subs(z, n))

<error as above>
```

(Another possible resolution is that empty Piecewise raise an exception.)

More background, just FYI: `octave_code(Piecewise())` and the other code generators deal with this in a graceful way by raising exception for all Piecewise that do not have a default block.

Hints from issue discussion:

Commit message: Fixed pretty printing and codegen error messages for empty piecewise

1. pprint called on an empty Piecewise function now prints "undefined"
2. codegen print functions raise the relevant "No otherwise statement error"
when encountering Piecewise functions

Patch to review:
diff --git a/sympy/printing/ccode.py b/sympy/printing/ccode.py
index bb69ea60945a..15649cdce2b6 100644
--- a/sympy/printing/ccode.py
+++ b/sympy/printing/ccode.py
@@ -341,7 +341,7 @@ def _print_NegativeInfinity(self, expr):
         return '-HUGE_VAL'
 
     def _print_Piecewise(self, expr):
-        if expr.args[-1].cond != True:
+        if len(expr.args) == 0 or expr.args[-1].cond != True:
             # We need the last conditional to be a True, otherwise the resulting
             # function may not return a result.
             raise ValueError("All Piecewise expressions must contain an "
diff --git a/sympy/printing/fcode.py b/sympy/printing/fcode.py
index cb25317736da..ad301f24370c 100644
--- a/sympy/printing/fcode.py
+++ b/sympy/printing/fcode.py
@@ -209,7 +209,7 @@ def _print_sign(self, expr):
 
 
     def _print_Piecewise(self, expr):
-        if expr.args[-1].cond != True:
+        if len(expr.args) == 0 or expr.args[-1].cond != True:
             # We need the last conditional to be a True, otherwise the resulting
             # function may not return a result.
             raise ValueError("All Piecewise expressions must contain an "
diff --git a/sympy/printing/glsl.py b/sympy/printing/glsl.py
index 1853f13b5b72..c427b15108f4 100644
--- a/sympy/printing/glsl.py
+++ b/sympy/printing/glsl.py
@@ -212,7 +212,7 @@ def _print_Function_with_args(self, func, func_args):
             return self._print_not_supported(func)
 
     def _print_Piecewise(self, expr):
-        if expr.args[-1].cond != True:
+        if len(expr.args) == 0 or expr.args[-1].cond != True:
             # We need the last conditional to be a True, otherwise the resulting
             # function may not return a result.
             raise ValueError("All Piecewise expressions must contain an "
diff --git a/sympy/printing/jscode.py b/sympy/printing/jscode.py
index 621784424e0e..ef5b0f507fcb 100644
--- a/sympy/printing/jscode.py
+++ b/sympy/printing/jscode.py
@@ -138,7 +138,7 @@ def _print_NegativeInfinity(self, expr):
         return 'Number.NEGATIVE_INFINITY'
 
     def _print_Piecewise(self, expr):
-        if expr.args[-1].cond != True:
+        if len(expr.args) == 0 or expr.args[-1].cond != True:
             # We need the last conditional to be a True, otherwise the resulting
             # function may not return a result.
             raise ValueError("All Piecewise expressions must contain an "
diff --git a/sympy/printing/julia.py b/sympy/printing/julia.py
index c1e28061dc1b..413548224c3a 100644
--- a/sympy/printing/julia.py
+++ b/sympy/printing/julia.py
@@ -418,7 +418,7 @@ def _print_yn(self, expr):
 
 
     def _print_Piecewise(self, expr):
-        if expr.args[-1].cond != True:
+        if len(expr.args) == 0 or expr.args[-1].cond != True:
             # We need the last conditional to be a True, otherwise the resulting
             # function may not return a result.
             raise ValueError("All Piecewise expressions must contain an "
diff --git a/sympy/printing/octave.py b/sympy/printing/octave.py
index b395d8c98e98..295eebd42401 100644
--- a/sympy/printing/octave.py
+++ b/sympy/printing/octave.py
@@ -458,7 +458,7 @@ def _nested_binary_math_func(self, expr):
 
 
     def _print_Piecewise(self, expr):
-        if expr.args[-1].cond != True:
+        if len(expr.args) == 0 or expr.args[-1].cond != True:
             # We need the last conditional to be a True, otherwise the resulting
             # function may not return a result.
             raise ValueError("All Piecewise expressions must contain an "
diff --git a/sympy/printing/pretty/pretty.py b/sympy/printing/pretty/pretty.py
index e818a8317019..e40563fa481a 100644
--- a/sympy/printing/pretty/pretty.py
+++ b/sympy/printing/pretty/pretty.py
@@ -1023,6 +1023,12 @@ def _print_NDimArray(self, expr):
 
     def _print_Piecewise(self, pexpr):
 
+        if len(pexpr.args) == 0:
+            D = prettyForm('undefined')
+            D.baseline = D.height()//2
+            D.binding = prettyForm.OPEN
+            return D
+
         P = {}
         for n, ec in enumerate(pexpr.args):
             P[n, 0] = self._print(ec.expr)
diff --git a/sympy/printing/rust.py b/sympy/printing/rust.py
index 4e20785d4ce1..516706575e1e 100644
--- a/sympy/printing/rust.py
+++ b/sympy/printing/rust.py
@@ -400,7 +400,7 @@ def _print_NaN(self, expr, _type=False):
         return "NAN"
 
     def _print_Piecewise(self, expr):
-        if expr.args[-1].cond != True:
+        if len(expr.args) == 0 or expr.args[-1].cond != True:
             # We need the last conditional to be a True, otherwise the resulting
             # function may not return a result.
             raise ValueError("All Piecewise expressions must contain an "


Code review comments:
diff_hunk: @@ -418,7 +418,7 @@ def _print_yn(self, expr):
 
 
     def _print_Piecewise(self, expr):
-        if expr.args[-1].cond != True:
+        if len(expr.args) == 0 or expr.args[-1].cond != True:
path: sympy/printing/julia.py
text: @user1:
I thought all of these codegen were working correctly before?

@author:
I changed this because the codegens were raising a tuple index out of range error later on in the function (when given an empty piecewise function) instead of raising the relevant ValueError that there isn't a default block.