//  [python]
def _apply_synthetic_array(self, tx_rot_mat, rx_rot_mat, k_rx,
                               k_tx, a):
        # pylint: disable=line-too-long
        r"""
        Synthetically apply transmitter and receiver arrays to the channel
        coefficients ``a``

        Input
        ------
        tx_rot_mat : [..., 3, 3], tf.float
            Rotation matrix built from the orientation of the transmitters

        rx_rot_mat : [3, 3], tf.float
            Rotation matrix built from the orientation of the receivers

        k_rx : [..., 3], tf.float
            Directions of arrivals of the rays

        k_tx : [..., 3], tf.float
            Directions of departure of the rays

        a : [..., num_rx_patterns, num_tx_patterns], tf.complex
            Channel coefficients

        Output
        -------
        a : [..., num_rx_ant, num_tx_ant], tf.complex
            Channel coefficients with the antenna array applied
        """

        two_pi = tf.cast(2.*PI, self._rdtype)

        # Rotated position of the TX antenna elements
        # [..., tx_array_size, 3]
        tx_rel_ant_pos = expand_to_rank(self._scene.tx_array.positions,
                                        tf.rank(tx_rot_mat), 0)
        # [..., 1, 3, 3]
        tx_rot_mat_ = tf.expand_dims(tx_rot_mat, axis=-3)
        # [..., tx_array_size, 3]
        tx_rel_ant_pos = tf.linalg.matvec(tx_rot_mat_, tx_rel_ant_pos)

        # Rotated position of the RX antenna elements
        # [1, rx_array_size, 3]
        rx_rel_ant_pos = self._scene.rx_array.positions
        # [1, 3, 3]
        rx_rot_mat = tf.expand_dims(rx_rot_mat, axis=0)
        # [rx_array_size, 3]
        rx_rel_ant_pos = tf.linalg.matvec(rx_rot_mat, rx_rel_ant_pos)
        # [..., rx_array_size, 3]
        rx_rel_ant_pos = expand_to_rank(rx_rel_ant_pos, tf.rank(tx_rel_ant_pos),
                                        0)

        # Expand dims for broadcasting with antennas
        # [..., 1, 3]
        k_rx = tf.expand_dims(k_rx, axis=-2)
        k_tx = tf.expand_dims(k_tx, axis=-2)
        # Compute the synthetic phase shifts due to the antenna array
        # Transmitter side
        # [..., tx_array_size]
        tx_phase_shifts = dot(tx_rel_ant_pos, k_tx)
        # Receiver side
        # [..., rx_array_size]
        rx_phase_shifts = dot(rx_rel_ant_pos, k_rx)
        # Total phase shift
        # [..., rx_array_size, 1]
        rx_phase_shifts = tf.expand_dims(rx_phase_shifts, axis=-1)
        # [..., 1, tx_array_size]
        tx_phase_shifts = tf.expand_dims(tx_phase_shifts, axis=-2)
        # [..., rx_array_size, tx_array_size]
        phase_shifts = rx_phase_shifts + tx_phase_shifts
        phase_shifts = two_pi*phase_shifts/self._scene.wavelength
        # Apply the phase shifts
        # [..., 1, rx_array_size, 1, tx_array_size]
        phase_shifts = tf.expand_dims(phase_shifts, axis=-2)
        phase_shifts = tf.expand_dims(phase_shifts, axis=-4)
        # [..., num_rx_patterns, 1, num_tx_patterns, 1]
        a = tf.expand_dims(a, axis=-1)
        a = tf.expand_dims(a, axis=-3)
        # [..., num_rx_patterns, rx_array_size, num_tx_patterns, tx_array_size]
        a = a*tf.exp(tf.complex(tf.zeros_like(phase_shifts), phase_shifts))
        # Reshape to merge antenna patterns and array
        # [...,
        #   num_rx_ant=num_rx_patterns*rx_array_size,
        #   num_tx_ant=num_tx_patterns*tx_array_size ]
        a = flatten_dims(a, 2, len(a.shape)-4)
        a = flatten_dims(a, 2, len(a.shape)-2)

        return a