Repository: scipy/scipy

Issue:
optimize.newton : overwrites x0 argument when it is a numpy array
<!-- 
Thank you for taking the time to report a SciPy issue.
Please describe the issue in detail, and for bug reports
fill in the fields below. You can delete the sections that 
don't apply to your issue. You can view the final output
by clicking the preview button above.
-->

My issue is about the `x0` argument of the `optimize.newton` function. It seems that when this argument is a numpy array, it is overwritten during evaluation.

I believe the origin of the bug (if this behavior is considered a bug) is inside the `_array_newton` helper function (c. line 356) where an internal variable `p` is defined in terms of `x0` using the `np.asarray` function. This does not result in a copy and so `p` references the same array as `x0`. 

I think the fix would be to modify the call to `np.asarray` by appending the `.copy()` method.

### Reproducing code example:
<!-- 
If you place your code between the triple backticks below, 
it will be marked as a code block automatically 
-->


```
import numpy as np
import scipy.optimize as so

x0 = np.array([0.1, 3.0])
print(x0) # x0 is [0.1 3. ]
so.newton(np.sin, x0, np.cos, tol=1e-6)
print(x0) # x0 is now [0.         3.14159265]
```

### Error message:
<!-- If any, paste the *full* error message inside a code block
as above (starting from line Traceback)
-->


### Scipy/Numpy/Python version information:
<!-- You can simply run the following and paste the result in a code block

-->
```
1.2.1 1.16.2 sys.version_info(major=3, minor=6, micro=5, releaselevel='final', serial=0)
```


Discussion:
@pfalloon Yes, this should be considered a bug. We're having a similar issue in #9793. I don't think appending ``.copy()`` is the best solution. ``np.asarray(...)`` copies an array iff the array is not a numpy array. Having a ``.copy()`` will *always* cause another copy, even if the array is already copied.

One possible solution would be to first test if the passed array is already a numpy array, if it is, explicitly make a copy before calling ``np.asarray()``.

Resolving patch:
diff --git a/scipy/optimize/zeros.py b/scipy/optimize/zeros.py
--- a/scipy/optimize/zeros.py
+++ b/scipy/optimize/zeros.py
@@ -359,11 +359,13 @@ def _array_newton(func, x0, fprime, args, tol, maxiter, fprime2, full_output):
     Do not use this method directly. This method is called from `newton`
     when ``np.size(x0) > 1`` is ``True``. For docstring, see `newton`.
     """
+    # Explicitly copy `x0` as `p` will be modified inplace, but, the
+    # user's array should not be altered.
     try:
-        p = np.asarray(x0, dtype=float)
+        p = np.array(x0, copy=True, dtype=float)
     except TypeError:
         # can't convert complex to float
-        p = np.asarray(x0)
+        p = np.array(x0, copy=True)
 
     failures = np.ones_like(p, dtype=bool)
     nz_der = np.ones_like(failures)


Test patch:
diff --git a/scipy/optimize/tests/test_zeros.py b/scipy/optimize/tests/test_zeros.py
--- a/scipy/optimize/tests/test_zeros.py
+++ b/scipy/optimize/tests/test_zeros.py
@@ -5,7 +5,8 @@
 
 from numpy.testing import (assert_warns, assert_,
                            assert_allclose,
-                           assert_equal)
+                           assert_equal,
+                           assert_array_equal)
 import numpy as np
 from numpy import finfo, power, nan, isclose
 
@@ -406,6 +407,13 @@ def test_deriv_zero_warning(self):
         dfunc = lambda x: 2*x
         assert_warns(RuntimeWarning, zeros.newton, func, 0.0, dfunc)
 
+    def test_newton_does_not_modify_x0(self):
+        # https://github.com/scipy/scipy/issues/9964
+        x0 = np.array([0.1, 3])
+        x0_copy = x0.copy()  # Copy to test for equality.
+        newton(np.sin, x0, np.cos)
+        assert_array_equal(x0, x0_copy)
+
 
 def test_gh_5555():
     root = 0.1