Example #1
0
def findm66(accelerator, closed_orbit = None):
    """Calculate accumulatep_in = _trackcpp.DoublePos()
        p_in.rx,p_in.px,p_in.ry,p_in.py,p_in.dl,p_in.de = pos[:,i]
    matrices -- array of matrices along accelerator elements

    Raises TrackingException
    """
    if closed_orbit is None:
        closed_orbit = _trackcpp.CppDoublePosVector()
        r = _trackcpp.track_findorbit6(accelerator._accelerator, closed_orbit)
        if r > 0:
            raise TrackingException(_trackcpp.string_error_messages[r])
    else:
        if closed_orbit.shape[1] != len(accelerator):
            closed_orbit = _trackcpp.CppDoublePosVector()

    m66 = _trackcpp.CppDoubleMatrixVector()
    r = _trackcpp.track_findm66(accelerator._accelerator, closed_orbit, m66)
    if r > 0:
        raise TrackingException(_trackcpp.string_error_messages[r])

    m66_out = []
    for i in range(len(m66)):
        m = _numpy.zeros((6,6))
        for r in range(6):
            for c in range(6):
                m[r,c] = m66[i][r][c]
        m66_out.append(m)

    return m66_out
Example #2
0
def _Numpy2CppDoublePosVector(orbit):
    orbit_out = _trackcpp.CppDoublePosVector()
    for i in range(orbit.shape[1]):
        orbit_out.push_back(_trackcpp.CppDoublePos(
            orbit[0,i], orbit[1,i],
            orbit[2,i], orbit[3,i],
            orbit[4,i], orbit[5,i]))
    return orbit_out
Example #3
0
def _4Numpy2CppDoublePosVector(orbit, de=0.0):
    if isinstance(orbit, _trackcpp.CppDoublePosVector):
        return orbit
    if isinstance(orbit, _numpy.ndarray):
        orbit_out = _trackcpp.CppDoublePosVector()
        for i in range(orbit.shape[1]):
            orbit_out.push_back(
                _trackcpp.CppDoublePos(orbit[0, i], orbit[1, i], orbit[2, i],
                                       orbit[3, i], de, 0.0))
    elif isinstance(orbit, (list, tuple)):
        orbit_out = _trackcpp.CppDoublePosVector()
        orbit_out.push_back(
            _trackcpp.CppDoublePos(orbit[0], orbit[1], orbit[2], orbit[3], de,
                                   0.0))
    else:
        raise TrackingException('invalid orbit argument')
    return orbit_out
Example #4
0
def find_orbit6(accelerator, indices=None, fixed_point_guess=None):
    """Calculate 6D closed orbit of accelerator and return it.

    Accepts an optional list of indices of ring elements where closed orbit
    coordinates are to be returned. If this argument is not passed, closed orbit
    positions are returned at the start of the first element. In addition a guess
    fixed point at the entrance of the ring may be provided.

    Keyword arguments:
    accelerator : Accelerator object
    indices : may be a (list,tuple, numpy.ndarray) of element indices where
              closed orbit data is to be returned or a string:
               'open'  : return the closed orbit at the entrance of all elements.
               'closed': besides all the points of 'open' also return the orbit
                         at the exit of the last element.
             If indices is None the closed orbit is returned only at the
             entrance of the first element.
    fixed_point_guess -- A 6D position where to start the search of the closed
                         orbit at the entrance of the first element. If not
                         provided the algorithm will start with zero orbit.

    Returns:
     orbit : 6D closed orbit at the entrance of the selected elements as a 2D
        numpy array with the 6 phase space variables in the first dimension and
        the indices of the elements in the second dimension.

    Raises TrackingException
    """
    if fixed_point_guess is None:
        fixed_point_guess = _trackcpp.CppDoublePos()
    else:
        fixed_point_guess = _Numpy2CppDoublePos(fixed_point_guess)

    _closed_orbit = _trackcpp.CppDoublePosVector()
    r = _trackcpp.track_findorbit6(accelerator._accelerator, _closed_orbit,
                                   fixed_point_guess)
    if r > 0:
        raise TrackingException(_trackcpp.string_error_messages[r])

    if indices is None:
        closed_orbit = _CppDoublePos2Numpy(_closed_orbit[0])[None, :]
    elif indices == 'open':
        closed_orbit = _numpy.zeros((len(accelerator), 6))
        for i in range(len(accelerator)):
            closed_orbit[i] = _CppDoublePos2Numpy(_closed_orbit[i])
    elif indices == 'closed':
        closed_orbit = _numpy.zeros((len(accelerator) + 1, 6))
        for i in range(len(accelerator)):
            closed_orbit[i] = _CppDoublePos2Numpy(_closed_orbit[i])
        closed_orbit[-1] = closed_orbit[0]
    elif isinstance(indices, (list, tuple, _numpy.ndarray)):
        closed_orbit = _numpy.zeros((len(indices), 6))
        for i, ind in enumerate(indices):
            closed_orbit[i] = _CppDoublePos2Numpy(_closed_orbit[ind])
    else:
        raise TrackingException("invalid value for 'indices' in findorbit6")

    return closed_orbit.T
Example #5
0
def find_m44(accelerator, indices=None, energy_offset=0.0, closed_orbit=None):
    """Calculate 4D transfer matrices of elements in an accelerator.

    Keyword arguments:
    accelerator
    indices
    energy_offset
    closed_orbit

    Return values:
    m44
    cumul_trans_matrices -- values at the start of each lattice element
    """
    if indices is None:
        indices = list(range(len(accelerator)))

    if closed_orbit is None:
        # calcs closed orbit if it was not passed.
        fixed_point_guess = _trackcpp.CppDoublePos()
        fixed_point_guess.de = energy_offset
        _closed_orbit = _trackcpp.CppDoublePosVector()
        r = _trackcpp.track_findorbit4(accelerator._accelerator, _closed_orbit,
                                       fixed_point_guess)
        if r > 0:
            raise TrackingException(_trackcpp.string_error_messages[r])
    else:
        _closed_orbit = _4Numpy2CppDoublePosVector(closed_orbit,
                                                   de=energy_offset)

    _cumul_trans_matrices = _trackcpp.CppMatrixVector()
    _m44 = _trackcpp.Matrix()
    _v0 = _trackcpp.CppDoublePos()
    r = _trackcpp.track_findm66(accelerator._accelerator, _closed_orbit,
                                _cumul_trans_matrices, _m44, _v0)
    if r > 0:
        raise TrackingException(_trackcpp.string_error_messages[r])

    m44 = _CppMatrix24Numpy(_m44)
    if indices == 'm44':
        return m44

    cumul_trans_matrices = []
    for i in range(len(_cumul_trans_matrices)):
        cumul_trans_matrices.append(_CppMatrix24Numpy(
            _cumul_trans_matrices[i]))

    return m44, cumul_trans_matrices
Example #6
0
def findorbit6(accelerator, indices=None):
    """Calculate 6D orbit closed-orbit.

    Accepts an optional list of indices of ring elements where closed-orbit
    coordinates are to be returned. If this argument is not passed closed-orbit
    positions are returned at the start of every element.

    Keyword arguments:
    accelerator -- Accelerator object

    Returns:
    orbit -- 6D position at elements

    Raises TrackingException
    """

    closed_orbit = _trackcpp.CppDoublePosVector()
    r = _trackcpp.track_findorbit6(accelerator._accelerator, closed_orbit)
    if r > 0:
        raise TrackingException(_trackcpp.string_error_messages[r])

    closed_orbit = _CppDoublePosVector2Numpy(closed_orbit, indices)
    return closed_orbit
Example #7
0
def find_m66(accelerator, indices=None, closed_orbit=None):
    """Calculate 6D transfer matrices of elements in an accelerator.

    Keyword arguments:
    accelerator
    indices
    closed_orbit

    Return values:
    m66
    cumul_trans_matrices -- values at the start of each lattice element
    """
    if indices is None:
        indices = list(range(len(accelerator)))

    if closed_orbit is None:
        # Closed orbit is calculated by trackcpp
        _closed_orbit = _trackcpp.CppDoublePosVector()
    else:
        _closed_orbit = _Numpy2CppDoublePosVector(closed_orbit)

    _cumul_trans_matrices = _trackcpp.CppMatrixVector()
    _m66 = _trackcpp.Matrix()
    _v0 = _trackcpp.CppDoublePos()
    r = _trackcpp.track_findm66(accelerator._accelerator, _closed_orbit,
                                _cumul_trans_matrices, _m66, _v0)
    if r > 0:
        raise TrackingException(_trackcpp.string_error_messages[r])

    m66 = _CppMatrix2Numpy(_m66)
    if indices == 'm66':
        return m66

    cumul_trans_matrices = MatrixList(_cumul_trans_matrices)

    return m66, cumul_trans_matrices
Example #8
0
def linepass(accelerator, particles, indices=None, element_offset=0):
    """Track particle(s) along a line.

    Accepts one or multiple particles initial positions. In the latter case,
    a list of particles or a numpy 2D array (with particle as second index)
    should be given as input; tracked particles positions along at the exits of
    elements are output variables, as well as information on whether particles
    have been lost along the tracking and where they were lost.

    Keyword arguments:
    accelerator -- Accelerator object
    particles   -- initial 6D particle(s) position(s).
                   Few examples
                        ex.1: pos = [rx,px,ry,py,de,dl]
                        ex.2: pos = [[0.001,0,0,0,0,0],[0.002,0,0,0,0,0]]
                        ex.3: pos = numpy.zeros((6,Np))
    indices     -- list of indices corresponding to accelerator elements at
                   whose exits the tracked particles positions are to be
                   stored; string 'all' corresponds to selecting all elements.
    element_offset -- element offset (default 0) for tracking. tracking will
                      start at the element with index 'element_offset'

    Returns: (pos_out, lost_flag, lost_element, lost_plane)
    particles_out --
         6D position for each particle at entrance of each element . The
         structure of 'pos_out' depends on inputs 'pos' and 'indices'.
         If 'indices' is 'None' then only tracked positions at the end
         of the line are returned. There are still two possibilities
         for the structure of pos_out, depending on 'pos':
         (1) if 'pos' is a single particle defined as a python list of
         coordinates, 'pos_out' will also be a simple list:
         ex.: pos = [rx1,px1,ry1,py1,dl1,de1]
              indices = 'None'
              particles_out = numpy.array([rx2,px2,ry2,py2,dl2,de2])
         (2) if 'pos' is either a python list of particles or a numpy
         matrix then 'pos_out' will be a matrix (numpy array of
         arrays) whose first index selects the coordinate rx, px,
         ry, py, dl, de in this order and the second index selects
         a particular particle.
         ex.: pos = [[rx1,px1,ry1,py1,dl1,de1],
                     [rx2,px2,ry2,py2,dl2,de2]]
              indices = None
              particles_out = array([[rx3, rx4],
                                     [px3, px4],
                                     [ry3, ry4],
                                     [py3, py4],
                                     [de3, de4],
                                     [dl3, dl4]])
         Now, if 'indices' is not 'None' then 'pos_out' can be either
         (3) a numpy matrix, when 'pos' is a single particle defined as
         a python list. The first index of 'pos_out' runs through the
         particle coordinate and the second through the element index
         (4) a numpy rank-3 tensor, when 'pos' is the initial positions
         of many particles. The firs index now is the element index at
         whose exits particles coordinates are returned, the second
         index runs through coordinates in phase space and the third
         index runs through particles.
    lost_element -- list of element index where each particle was lost
                    If the particle survived the tracking through the line its
                    corresponding element in this list is set to 'None'. When
                    there is only one particle defined as a python list (not as
                    a numpy matrix with one column) 'lost_element' returns a
                    single number.
    lost_plane  -- list of integers representing on what plane each particle
                   was lost while being tracked. If the particle is not lost
                   then its corresponding element in the list is set to 'None'.
                   If it is lost in the horizontal or vertical plane it is set
                   to 1 or 2, correspondingly. If tracking is performed with a
                   single particle described as a python list then 'lost_plane'
                   returns a single number

    """

    # store only final position?
    args = _trackcpp.LinePassArgs()
    args.trajectory = False if indices is None else True

    # checks whether single or multiple particles
    particles, return_ndarray, indices = _process_args(accelerator, particles, indices)

    if indices is None:
        particles_out = _numpy.ones((6,particles.shape[1]))
    else:
        particles_out = _numpy.zeros((len(indices),6,particles.shape[1]))
    particles_out.fill(float('nan'))

    lost_flag = False
    lost_element, lost_plane = [], []
    for i in range(particles.shape[1]):

        args.element_offset = element_offset
        p_in = _Numpy2CppDoublePos(particles[:,i])
        p_out = _trackcpp.CppDoublePosVector()

        if _trackcpp.track_linepass_wrapper(accelerator._accelerator, p_in, p_out, args):
            lost_flag = True

        for k in range(20):
            print(str(k+1), p_out[k].rx)

        if indices is None:
            particles_out[:,i] = _CppDoublePos2Numpy(p_out[0])
        else:
            for j in range(len(indices)):
                #pp = p_out[1+indices[j]]
                #print(pp.rx)
                particles_out[j,:,i] = _CppDoublePos2Numpy(p_out[indices[j]])

        if args.element_offset:
            lost_element.append(args.element_offset)
        else:
            lost_element.append(None)

        lost_plane.append(lost_planes[args.lost_plane])

    if len(lost_element) == 1 and not return_ndarray:
        if len(particles_out.shape) == 3:
            particles_out = particles_out[:,:,0]
        else:
            particles_out = particles_out[:,0]
        lost_element = lost_element[0]
        lost_plane = lost_plane[0]

    return particles_out, lost_flag, lost_element, lost_plane
Example #9
0
def ringpass(accelerator, pos, nr_turns=1, trajectory=False, offset=0):
    """Track particle(s) along a ring.

    Accepts one or multiple particles. In the latter case, a list of particles
    or numpy 2D array (with particle as first index) should be given as input;
    also, outputs get an additional dimension, with particle as first index.

    Keyword arguments:
    accelerator -- Accelerator object
    pos         -- initial 6D position or list of positions
    num_turns   -- number of turns (default 1)
    trajectory  -- True if trajectory should be calculated at each element
                   (default False)
    offset      -- element offset (default 0)

    Returns: (pos, turn, offset, plane)
    pos    -- 6D position at each element
    turn   -- last turn number
    offset -- last element offset
    plane  -- plane where particle was lost

    Raises TrackingException
    """

    # checks whether single or multiple particles
    pos, return_ndarray, _ = _process_args(accelerator, pos, indices=None)

    if trajectory:
        pos_out = _numpy.zeros((nr_turns,6,pos.shape[1]))
    else:
        pos_out = _numpy.zeros((6,pos.shape[1]))
    pos_out.fill(float('nan'))

    args = _trackcpp.LinePassArgs()
    args.trajectory = trajectory
    args.nr_turns = nr_turns

    lost_flag = False
    lost_turn, lost_element, lost_plane = [], [], []
    for i in range(pos.shape[1]):

        args.element_offset = offset
        p_in = _Numpy2CppDoublePos(pos[:,i])
        p_out = _trackcpp.CppDoublePosVector()

        if _trackcpp.track_ringpass_wrapper(accelerator._accelerator, p_in, p_out, args):
            lost_flag = True

        if trajectory:
            for n in range(nr_turns):
                pos_out[n,:,i] = _CppDoublePos2Numpy(p_out[i])
        else:
            pos_out[:,i] = _CppDoublePos2Numpy(p_out[0])

        if args.lost_turn < nr_turns:
            lost_turn.append(args.lost_turn)
        else:
            lost_turn.append(None)

        if args.element_offset < len(accelerator):
            lost_element.append(args.element_offset)
        else:
            lost_element.append(None)

        lost_plane.append(lost_planes[args.lost_plane])

    if len(lost_element) == 1 and not return_ndarray:
        pos_out = pos_out[:,0]
        lost_turn = lost_turn[0]
        lost_element = lost_element[0]
        lost_plane = lost_plane[0]

    return pos_out, lost_flag, lost_turn, lost_element, lost_plane
Example #10
0
def calc_twiss(accelerator=None,
               init_twiss=None,
               fixed_point=None,
               indices='open',
               energy_offset=None):
    """Return Twiss parameters of uncoupled dynamics.

    Keyword arguments:
    accelerator   -- Accelerator object
    init_twiss    -- Twiss parameters at the start of first element
    fixed_point   -- 6D position at the start of first element
    indices       -- Open or closed
    energy_offset -- float denoting the energy deviation (used only for periodic
                     solutions).

    Returns:
    tw -- list of Twiss objects (closed orbit data is in the objects vector)
    m66 -- one-turn transfer matrix

    """
    if indices == 'open':
        closed_flag = False
    elif indices == 'closed':
        closed_flag = True
    else:
        raise OpticsException("invalid value for 'indices' in calc_twiss")

    _m66 = _trackcpp.Matrix()
    _twiss = _trackcpp.CppTwissVector()

    if init_twiss is not None:
        ''' as a transport line: uses init_twiss '''
        _init_twiss = init_twiss._t
        if fixed_point is None:
            _fixed_point = _init_twiss.co
        else:
            raise OpticsException(
                'arguments init_twiss and fixed_point are mutually exclusive')
        r = _trackcpp.calc_twiss(accelerator._accelerator, _fixed_point, _m66,
                                 _twiss, _init_twiss, closed_flag)

    else:
        ''' as a periodic system: try to find periodic solution '''
        if accelerator.harmonic_number == 0:
            raise OpticsException(
                'Either harmonic number was not set or calc_twiss was'
                'invoked for transport line without initial twiss')

        if fixed_point is None:
            _closed_orbit = _trackcpp.CppDoublePosVector()
            _fixed_point_guess = _trackcpp.CppDoublePos()
            if energy_offset is not None: _fixed_point_guess.de = energy_offset

            if not accelerator.cavity_on and not accelerator.radiation_on:
                r = _trackcpp.track_findorbit4(accelerator._accelerator,
                                               _closed_orbit,
                                               _fixed_point_guess)
            elif not accelerator.cavity_on and accelerator.radiation_on:
                raise OpticsException(
                    'The radiation is on but the cavity is off')
            else:
                r = _trackcpp.track_findorbit6(accelerator._accelerator,
                                               _closed_orbit,
                                               _fixed_point_guess)

            if r > 0:
                raise _tracking.TrackingException(
                    _trackcpp.string_error_messages[r])
            _fixed_point = _closed_orbit[0]

        else:
            _fixed_point = _tracking._Numpy2CppDoublePos(fixed_point)
            if energy_offset is not None: _fixed_point.de = energy_offset

        r = _trackcpp.calc_twiss(accelerator._accelerator, _fixed_point, _m66,
                                 _twiss)

    if r > 0:
        raise OpticsException(_trackcpp.string_error_messages[r])

    twiss = TwissList(_twiss)
    m66 = _tracking._CppMatrix2Numpy(_m66)

    return twiss, m66
Example #11
0
def line_pass(accelerator, particles, indices=None, element_offset=0):
    """Track particle(s) along a line.

    Accepts one or multiple particles initial positions. In the latter case,
    a list of particles or a numpy 2D array (with particle as second index)
    should be given as input; tracked particles positions at the entrances of
    elements are output variables, as well as information on whether particles
    have been lost along the tracking and where they were lost.

    Keyword arguments: (accelerator, particles, indices, element_offset)

    accelerator -- Accelerator object
    particles   -- initial 6D particle(s) position(s).
                   Few examples
                        ex.1: particles = [rx,px,ry,py,de,dl]
                        ex.2: particles = [[0.001,0,0,0,0,0],[0.002,0,0,0,0,0]]
                        ex.3: particles = numpy.zeros((Np,6))
    indices     -- list of indices corresponding to accelerator elements at
                   whose entrances, tracked particles positions are to be
                   stored; string 'open' corresponds to selecting all elements.
    element_offset -- element offset (default 0) for tracking. tracking will
                      start at the element with index 'element_offset'

    Returns: (particles_out, lost_flag, lost_element, lost_plane)

    particles_out -- 6D position for each particle at entrance of each element.
                     The structure of 'particles_out' depends on inputs
                     'particles' and 'indices'. If 'indices' is None then only
                     tracked positions at the end of the line are returned.
                     There are still two possibilities for the structure of
                     particles_out, depending on 'particles':

                    (1) if 'particles' is a single particle defined as a python
                        list of coordinates, 'particles_out' will also be a
                        simple list:
                        ex.:particles = [rx1,px1,ry1,py1,de1,dl1]
                            indices = None
                            particles_out=numpy.array([rx2,px2,ry2,py2,de2,dl2])

                    (2) if 'particles' is either a python list of particles or a
                        numpy matrix then 'particles_out' will be a matrix
                        (numpy array of arrays) whose first index selects a
                        particular particle and second index picks a coordinate
                        rx, px, ry, py, de or dl, in this order.
                        ex.:particles = [[rx1,px1,ry1,py1,de1,dl1],
                                         [rx2,px2,ry2,py2,de2,dl2]]
                            indices = None
                            particles_out = numpy.array(
                                [ [rx3,px3,ry3,py3,de3,dl3],
                                  [rx4,px4,ry4,py4,de4,dl4]
                                ])

                    Now, if 'indices' is not None then 'particles_out' can be
                    either

                    (3) a numpy matrix, when 'particles' is a single particle
                        defined as a python list. The first index of
                        'particles_out' runs through the particle coordinate and
                        the second through the element index

                    (4) a numpy rank-3 tensor, when 'particles' is the initial
                        positions of many particles. The first index now is the
                        particle index, the second index is the coordinate index
                        and the third index is the element index at whose
                        entrances particles coordinates are returned.

    lost_flag    -- a general flag indicating whether there has been particle
                    loss.
    lost_element -- list of element index where each particle was lost
                    If the particle survived the tracking through the line its
                    corresponding element in this list is set to None. When
                    there is only one particle defined as a python list (not as
                    a numpy matrix with one column) 'lost_element' returns a
                    single number.
    lost_plane   -- list of strings representing on what plane each particle
                    was lost while being tracked. If the particle is not lost
                    then its corresponding element in the list is set to None.
                    If it is lost in the horizontal or vertical plane it is set
                    to string 'x' or 'y', correspondingly. If tracking is
                    performed with a single particle described as a python list
                    then 'lost_plane' returns a single string

    """

    # store only final position?
    args = _trackcpp.LinePassArgs()
    args.trajectory = False if indices is None else True

    # checks whether single or multiple particles, reformats particles
    particles, return_ndarray, indices = _process_args(accelerator, particles,
                                                       indices)

    # initialize particles_out tensor according to input options
    if indices is None:
        particles_out = _numpy.ones((particles.shape[0], 6))
    else:
        particles_out = _numpy.zeros((particles.shape[0], 6, len(indices)))
    particles_out.fill(float('nan'))

    lost_flag = False
    lost_element, lost_plane = [], []

    # loop over particles
    for i in range(particles.shape[0]):

        # python particle pos -> trackcpp particle pos
        args.element_offset = element_offset
        p_in = _Numpy2CppDoublePos(particles[i, :])
        p_out = _trackcpp.CppDoublePosVector()

        # tracking
        if _trackcpp.track_linepass_wrapper(accelerator._accelerator, p_in,
                                            p_out, args):
            lost_flag = True

        # trackcpp particle pos -> python particle pos
        if indices is None:
            particles_out[i, :] = _CppDoublePos2Numpy(p_out[0])
        else:
            for j in range(len(indices)):
                particles_out[i, :, j] = _CppDoublePos2Numpy(p_out[indices[j]])

        # fills vectors with info about particle loss
        if args.lost_plane:
            lost_element.append(args.element_offset)
            lost_plane.append(lost_planes[args.lost_plane])
        else:
            lost_element.append(None)
            lost_plane.append(None)

    # simplifies output structure in case of single particle and python list
    if len(lost_element) == 1 and not return_ndarray:
        if len(particles_out.shape) == 3:
            particles_out = particles_out[0, :, :]
        else:
            particles_out = particles_out[0, :]
        lost_element = lost_element[0]
        lost_plane = lost_plane[0]

    return particles_out, lost_flag, lost_element, lost_plane
Example #12
0
def ring_pass(accelerator,
              particles,
              nr_turns=1,
              turn_by_turn=None,
              element_offset=0):
    """Track particle(s) along a ring.

    Accepts one or multiple particles initial positions. In the latter case,
    a list of particles or a numpy 2D array (with particle as firts index)
    should be given as input; tracked particles positions at the end of
    the ring are output variables, as well as information on whether particles
    have been lost along the tracking and where they were lost.

    Keyword arguments: (accelerator, particles, nr_turns,
                        turn_by_turn, elment_offset)

    accelerator    -- Accelerator object
    particles      -- initial 6D particle(s) position(s).
                      Few examples
                        ex.1: particles = [rx,px,ry,py,de,dl]
                        ex.2: particles = [[0.001,0,0,0,0,0],[0.002,0,0,0,0,0]]
                        ex.3: particles = numpy.zeros((Np,6))
    nr_turns       -- number of turns around ring to track each particle.
    turn_by_turn   -- parameter indicating what turn by turn positions are to
                      be returned. If None ringpass returns particles
                      positions only at the end of the ring, at the last turn.
                      If turn_by_turn is 'closed' ringpass returns positions
                      at the end of the ring for every turn. If it is 'open'
                      than positions are returned at the beginning of every
                      turn.

    element_offset -- element offset (default 0) for tracking. tracking will
                      start at the element with index 'element_offset'

    Returns: (particles_out, lost_flag, lost_turn, lost_element, lost_plane)

    particles_out -- 6D position for each particle at end of ring. The structure
                     of 'particles_out' depends on inputs 'particles' and
                     'turn_by_turn'. If 'turn_by_turn' is None then only
                     tracked positions at the end 'nr_turns' are returned. There
                     are still two possibilities for the structure of
                     particles_out, depending on 'particles':

                    (1) if 'particles' is a single particle defined as a python
                        list of coordinates, 'particles_out' will also be a
                        simple list:
                        ex.:particles = [rx1,px1,ry1,py1,de1,dl1]
                            turn_by_turn = False
                            particles_out=numpy.array([rx2,px2,ry2,py2,de2,dl2])

                    (2) if 'particles' is either a python list of particles or a
                        numpy matrix then 'particles_out' will be a matrix
                        (numpy array of arrays) whose first index selects the
                        coordinate rx, px, ry, py, de or dl, in this order, and
                        the second index selects a particular particle.
                        ex.: particles = [[rx1,px1,ry1,py1,de1,dl1],
                                          [rx2,px2,ry2,py2,de2,dl2]]
                             turn_by_turn = False
                             particles_out = numpy.array(
                                 [ [rx3,px3,ry3,py3,de3,dl3],
                                   [rx4,px4,ry4,py4,de4,dl4]
                                 ])

                     'turn_by_turn' can also be either 'close' or 'open'. In
                     either case 'particles_out' will have tracked positions at
                     the entrances of the elements. The difference is that for
                     'closed' it will have an additional tracked position at the
                     exit of the last element, thus closing the data, in case
                     the line is a ring. The format of 'particles_out' is ...

                    (3) a numpy matrix, when 'particles' is a single particle
                        defined as a python list. The first index of
                        'particles_out' runs through coordinates rx, px, ry, py,
                        de or dl and the second index runs through the turn
                        number

                    (4) a numpy rank-3 tensor, when 'particles' is the initial
                        positions of many particles. The first index now runs
                        through particles, the second through coordinates and
                        the third through turn number.

    lost_flag    -- a general flag indicating whether there has been particle
                    loss.
    lost_turn    -- list of turn index where each particle was lost.
    lost_element -- list of element index where each particle was lost
                    If the particle survived the tracking through the ring its
                    corresponding element in this list is set to None. When
                    there is only one particle defined as a python list (not as
                    a numpy matrix with one column) 'lost_element' returns a
                    single number.
    lost_plane   -- list of strings representing on what plane each particle
                    was lost while being tracked. If the particle is not lost
                    then its corresponding element in the list is set to None.
                    If it is lost in the horizontal or vertical plane it is set
                    to string 'x' or 'y', correspondingly. If tracking is
                    performed with a single particle described as a python list
                    then 'lost_plane' returns a single string

    """

    # checks whether single or multiple particles, reformats particles
    particles, return_ndarray, _ = _process_args(accelerator,
                                                 particles,
                                                 indices=None)

    # initialize particles_out tensor according to input options
    if turn_by_turn:
        particles_out = _numpy.zeros((particles.shape[0], 6, nr_turns))
    else:
        particles_out = _numpy.zeros((particles.shape[0], 6))
    particles_out.fill(float('nan'))
    lost_flag = False
    lost_turn, lost_element, lost_plane = [], [], []

    # static parameters of ringpass
    args = _trackcpp.RingPassArgs()
    args.nr_turns = nr_turns
    args.trajectory = True if turn_by_turn else False

    # loop over particles
    for i in range(particles.shape[0]):

        # python particle pos -> trackcpp particle pos
        args.element_offset = element_offset
        p_in = _Numpy2CppDoublePos(particles[i, :])
        p_out = _trackcpp.CppDoublePosVector()

        # tracking
        if _trackcpp.track_ringpass_wrapper(accelerator._accelerator, p_in,
                                            p_out, args):
            lost_flag = True

        # trackcpp particle pos -> python particle pos
        if turn_by_turn:
            if turn_by_turn == 'closed':
                for n in range(nr_turns):
                    particles_out[i, :, n] = _CppDoublePos2Numpy(p_out[n])
            elif turn_by_turn == 'open':
                particles_out[i, :, 0] = particles[i, :]
                for n in range(1, nr_turns):
                    particles_out[i, :, n] = _CppDoublePos2Numpy(p_out[n - 1])

        else:
            particles_out[i, :] = _CppDoublePos2Numpy(p_out[0])

        # fills vectors with info about particle loss
        if args.lost_plane:
            lost_turn.append(args.lost_turn)
            lost_element.append(args.lost_element)
            lost_plane.append(lost_planes[args.lost_plane])
        else:
            lost_turn.append(None)
            lost_element.append(None)
            lost_plane.append(None)

    # simplifies output structure in case of single particle and python list
    if len(lost_element) == 1 and not return_ndarray:
        if len(particles_out.shape) == 3:
            particles_out = particles_out[0, :, :]
        else:
            particles_out = particles_out[0, :]
        lost_turn = lost_turn[0]
        lost_element = lost_element[0]
        lost_plane = lost_plane[0]

    return particles_out, lost_flag, lost_turn, lost_element, lost_plane