Repository: atcollab/at (language: python)

Issue:
Documentation improvement
This updates 2 items of the PyAT documentation:

1. Since PyAT is now distributed on [conda-forge](https://conda-forge.org), the installation instructions are updated mention it,
2. @simoneliuzzo reported in #728 that the documentation of get_optics was incomplete. This is now improved.


Discussion / hints:

Fix patch:
diff --git a/pyat/at.c b/pyat/at.c
index 735d82f54..af18959cc 100644
--- a/pyat/at.c
+++ b/pyat/at.c
@@ -525,9 +525,11 @@ static PyObject *at_atpass(PyObject *self, PyObject *args, PyObject *kwargs) {
             Py_DECREF(PyPassMethod);
             if (!LibraryListPtr) return print_error(elem_index, rout);  /* No trackFunction for the given PassMethod: RuntimeError */
             pylength = PyObject_GetAttrString(el, "Length");
-            length = PyFloat_AsDouble(pylength);
-            Py_XDECREF(pylength);
-            if (PyErr_Occurred()) {
+            if (pylength) {
+                length = PyFloat_AsDouble(pylength);
+                Py_XDECREF(pylength);
+            }
+            else {
                 length = 0.0;
                 PyErr_Clear();
             }
diff --git a/pyat/at/tracking/atpass.pyi b/pyat/at/tracking/atpass.pyi
index 94b9e9ea0..f51cc2f92 100644
--- a/pyat/at/tracking/atpass.pyi
+++ b/pyat/at/tracking/atpass.pyi
@@ -2,10 +2,10 @@
 
 import numpy as np
 from typing import List, Optional
-from at.lattice import Element, Particle
+from at.lattice import Element, Particle, Refpts
 
 def atpass(line: List[Element], r_in: np.ndarray, nturns: int,
-           refpts: np.ndarray,
+           refpts: Optional[Refpts] = [],
            turn: Optional[int] = None,
            energy: Optional[float] = None,
            particle: Optional[Particle] = None,


Test patch:
diff --git a/pyat/test/test_atpass.py b/pyat/test/test_atpass.py
index 337b14ed3..719924dbc 100644
--- a/pyat/test/test_atpass.py
+++ b/pyat/test/test_atpass.py
@@ -1,7 +1,8 @@
-import pytest
 import numpy
-from at.tracking.atpass import atpass
+import pytest
+
 from at import elements, uint32_refpts
+from at.tracking.atpass import atpass  # Direct access to the C function
 
 
 def test_incorrect_types_raises_value_error(rin):
@@ -10,7 +11,7 @@ def test_incorrect_types_raises_value_error(rin):
     with pytest.raises(TypeError):
         atpass([], 1, 1)
     with pytest.raises(TypeError):
-        atpass([], rin, 'a')
+        atpass([], rin, "a")
 
 
 def test_incorrect_dimensions_raises_value_error():
@@ -46,11 +47,11 @@ def test_transposed_c_array_gives_same_result_as_fortran_array():
     behaviour we see is not what we'd expect if we used numpy as intended.
     """
     # Standard numpy array (6, 2) as used in pyAT.
-    rin_fortran = numpy.array(numpy.arange(12).reshape(6, 2), order='F') * 1e-5
-    # Taking an copy of the transpose and making sure it is C-aligned should
+    rin_fortran = numpy.array(numpy.arange(12).reshape(6, 2), order="F") * 1e-5
+    # Taking a copy of the transpose and making sure it is C-aligned should
     # give us the same layout of data in memory, but with a (2, 6) array.
-    rin_c = numpy.copy(rin_fortran.T, order='C')
-    lat = [elements.Drift('drift', 1.0)]
+    rin_c = numpy.copy(rin_fortran.T, order="C")
+    lat = [elements.Drift("drift", 1.0)]
     rout_fortran = atpass(lat, rin_fortran, 1)
     # at.c does not accept (x, 6) arrays.  This transpose allows rin_c
     # to pass the dimension check, but does NOT change the layout in memory
@@ -63,14 +64,20 @@ def test_transposed_c_array_gives_same_result_as_fortran_array():
 
 
 def test_missing_pass_method_raises_attribute_error(rin):
-    lat = [elements.Marker('marker')]
+    lat = [elements.Marker("marker")]
     del lat[0].PassMethod
     with pytest.raises(AttributeError):
         atpass(lat, rin, 1)
 
 
-def test_missing_length_raises_attribute_error(rin):
-    lat = [elements.Drift('drift', 1.0)]
+def test_missing_integrator_raises_runtime_error(rin):
+    lat = [elements.Drift("drift", 1.0, PassMethod="UnknownPass")]
+    with pytest.raises(RuntimeError):
+        atpass(lat, rin, 1)
+
+
+def test_integrator_error(rin):
+    lat = [elements.Drift("drift", 1.0)]
     del lat[0].Length
     with pytest.raises(AttributeError):
         atpass(lat, rin, 1)
@@ -78,7 +85,7 @@ def test_missing_length_raises_attribute_error(rin):
 
 @pytest.mark.parametrize("reuse", (True, False))
 def test_reuse_attributes(rin, reuse):
-    lat = [elements.Drift('drift', 1.0)]
+    lat = [elements.Drift("drift", 1.0)]
     rin[0, 0] = 1e-6
     rin[1, 0] = 1e-6
     rin_copy = numpy.copy(rin)
@@ -99,15 +106,17 @@ def test_reuse_attributes(rin, reuse):
 
 def test_two_particles_for_two_turns():
     rin = numpy.asfortranarray(numpy.zeros((6, 2)))
-    lat = [elements.Drift('drift', 1.0)]
+    lat = [elements.Drift("drift", 1.0)]
     rin[1][0] = 1e-6
     rin[3][0] = -2e-6
     rout = atpass(lat, rin, 2, refpts=uint32_refpts([1], 1))
     # results from Matlab
-    rout_particle1_turn1 = numpy.array([1e-6, 1e-6, -2e-6, -2e-6, 0,
-                                        2.5e-12]).reshape(6, 1)
-    rout_particle1_turn2 = numpy.array([2e-6, 1e-6, -4e-6, -2e-6, 0,
-                                        5e-12]).reshape(6, 1)
+    rout_particle1_turn1 = numpy.array([1e-6, 1e-6, -2e-6, -2e-6, 0, 2.5e-12]).reshape(
+        6, 1
+    )
+    rout_particle1_turn2 = numpy.array([2e-6, 1e-6, -4e-6, -2e-6, 0, 5e-12]).reshape(
+        6, 1
+    )
     # the second particle doesn't change
     rout_particle2 = numpy.zeros((6, 1))
     # the second index is particle number
@@ -119,12 +128,11 @@ def test_two_particles_for_two_turns():
 
 
 def test_one_particle_for_two_turns_with_no_refpts(rin):
-    lat = [elements.Drift('drift', 1.0)]
+    lat = [elements.Drift("drift", 1.0)]
     rin[1][0] = 1e-6
     rin[3][0] = -2e-6
     atpass(lat, rin, 2)
-    rout_expected = numpy.array([2e-6, 1e-6, -4e-6, -2e-6, 0,
-                                 5e-12]).reshape(6, 1)
+    rout_expected = numpy.array([2e-6, 1e-6, -4e-6, -2e-6, 0, 5e-12]).reshape(6, 1)
     # rin is changed in place
     numpy.testing.assert_equal(rin, rout_expected)
 
@@ -133,8 +141,8 @@ def test_1d_particle():
     # This is just a demonstration that if you pass no refpts you get back
     # a (6, *, 0, *) array. You may do this if you want only to operate
     # on rin in-place.
-    lat = [elements.Drift('drift', 1.0)]
-    rin = numpy.zeros(6,)
+    lat = [elements.Drift("drift", 1.0)]
+    rin = numpy.zeros(6)
     rin[1] = 1e-6
     # an empty refpts returns only the value at the end of the last turn
     rout = atpass(lat, rin, 1)