Repository: numpy/numpy

Issue:
BUG: np.polynomial.*.fromroots: unexpected result with domain
I need to construct monic polynomials with prescribed roots in an interval `[a,b]!=[-1,1]`. According to the [documentation](http://docs.scipy.org/doc/numpy/reference/generated/numpy.polynomial.chebyshev.Chebyshev.fromroots.html#numpy.polynomial.chebyshev.Chebyshev.fromroots) I can provide the roots and the domain in the `fromroots()` function in order to obtain a numerically stable representation (e.g. in the Chebyshev basis) of the monic polynomial in the domain `[a,b]` (by shifting and scaling the domain to `[-1,1]`). However, the resulting polynomials are not monic anymore when a domain is specified. Here is a minimal example with the polynomial `(x-2)*(x-4)` on the domain `[1,5]`:

``` python
from numpy.polynomial import Chebyshev
roots = [2, 4]
domain = [1, 5]
x = 3
p = Chebyshev.fromroots(roots)
pdom = Chebyshev.fromroots(roots, domain=domain)
print('p(%g) = %g' % (x, p(x)))
print('pdom(%g) = %g' % (x, pdom(x)))
```

This results in

```
p(3) = -1
pdom(3) = -0.25
```

I expect that also `pdom(3)=-1`.

`fromroots()` does not seem to handle the domain correctly but it can be fixed by scaling the polynomial:

``` python
pdom *= ((domain[-1] - domain[0])/2.)**len(roots)
print('pdom(%g) = %g' % (x, pdom(x)))
```

I'm not sure whether this is actually a bug or it is intended to behave this way but the documentation is ambiguous. One way or the other, I think it should be addressed. ;)



Discussion:
You are quite right, the documentation is ambiguous. What happens when the domain isn't the default is that the roots are scaled to the window, [-1, 1] in this case, and in that window the polynomial is monic. When the function is evaluated at a point the point is also scaled to the window and then evaluated, so what is effectively happening is the x <- (x - 3)/2 before evaluation and the result is not monic in the original x after the substitution. In this case to recover a monic looking polynomial the result needs to be scaled by 2^deg == 4. Hmm, not sure what the best thing to do here is, the scaling factor could become rather enormous for high degree polynomials, say degrees 50 and no doubt would look pretty weird when plotted.
I'm open to suggestions here, but perhaps a best thing to do is clarify the situation in the documents. Or a new keyword "monic" could be added to choose between the two options for monic.

It's true that the scaling factor may become very large but that's the actual monic polynomial. Is there any application where the scaling of the polynomial is irrelevant?


Resolving patch:
diff --git a/numpy/polynomial/polytemplate.py b/numpy/polynomial/polytemplate.py
--- a/numpy/polynomial/polytemplate.py
+++ b/numpy/polynomial/polytemplate.py
@@ -806,6 +806,13 @@ def fromroots(roots, domain=$domain, window=$domain) :
         ----------
         roots : array_like
             List of roots.
+        domain : {array_like, None}, optional
+            Domain for the resulting instance of $name. If none the domain
+            is the interval from the smallest root to the largest. The
+            default is $domain.
+        window : array_like, optional
+            Window for the resulting instance of $name. The default value
+            is $domain.
 
         Returns
         -------
@@ -817,10 +824,13 @@ def fromroots(roots, domain=$domain, window=$domain) :
         ${nick}fromroots : equivalent function
 
         """
+        [roots] = pu.as_series([roots], trim=False)
         if domain is None :
             domain = pu.getdomain(roots)
-        rnew = pu.mapdomain(roots, domain, window)
-        coef = ${nick}fromroots(rnew)
+        deg = len(roots)
+        off, scl = pu.mapparms(domain, window)
+        rnew = off + scl*roots
+        coef = ${nick}fromroots(rnew) / scl**deg
         return $name(coef, domain=domain, window=window)
 
     @staticmethod


Test patch:
diff --git a/numpy/polynomial/tests/test_classes.py b/numpy/polynomial/tests/test_classes.py
--- a/numpy/polynomial/tests/test_classes.py
+++ b/numpy/polynomial/tests/test_classes.py
@@ -145,7 +145,9 @@ def check_fromroots(Poly):
     assert_almost_equal(p1(r), 0)
 
     # check that polynomial is monic
-    p2 = Polynomial.cast(p1, domain=d, window=w)
+    pdom = Polynomial.domain
+    pwin = Polynomial.window
+    p2 = Polynomial.cast(p1, domain=pdom, window=pwin)
     assert_almost_equal(p2.coef[-1], 1)