Пример #1
0
    def set_orientation(self, z_axis=(0, 0, 1), x_axis=(1, 0, 0)):
        """
        Sets the orientation of the antenna.

        Sets up the z-axis and the x-axis of the antenna according to the given
        parameters. Fails if the z-axis and x-axis aren't perpendicular.

        Parameters
        ----------
        z_axis : array_like, optional
            Vector direction of the z-axis of the antenna.
        x_axis : array_like, optional
            Vector direction of the x-axis of the antenna.

        Raises
        ------
        ValueError
            If the z-axis and x-axis aren't perpendicular.

        """
        self.z_axis = normalize(z_axis)
        self.x_axis = normalize(x_axis)
        if not np.isclose(np.dot(self.z_axis, self.x_axis), 0, rtol=0):
            raise ValueError("Antenna's x_axis must be perpendicular to its " +
                             "z_axis")
Пример #2
0
    def receive(self,
                signal,
                direction=None,
                polarization=None,
                force_real=True):
        """Process incoming signal according to the filter function and
        store it to the signals list. Subclasses may extend this fuction,
        but should end with super().receive(signal)."""
        copy = Signal(signal.times,
                      signal.values,
                      value_type=Signal.ValueTypes.voltage)
        copy.filter_frequencies(self.response, force_real=force_real)

        if direction is not None:
            # Calculate theta and phi relative to the orientation
            origin = self.position - normalize(direction)
            r, theta, phi = self._convert_to_antenna_coordinates(origin)
            freq_data, gain_data, phase_data = self.generate_directionality_gains(
                theta, phi)

            def interpolate_directionality(frequencies):
                interp_gains = np.interp(frequencies,
                                         freq_data,
                                         gain_data,
                                         left=0,
                                         right=0)
                interp_phases = np.interp(frequencies,
                                          freq_data,
                                          phase_data,
                                          left=0,
                                          right=0)
                return interp_gains * np.exp(1j * interp_phases)

            copy.filter_frequencies(interpolate_directionality,
                                    force_real=force_real)

        if polarization is None:
            p_gain = 1
        else:
            p_gain = self.polarization_gain(normalize(polarization))

        signal_factor = p_gain * self.efficiency

        if signal.value_type == Signal.ValueTypes.voltage:
            pass
        elif signal.value_type == Signal.ValueTypes.field:
            signal_factor /= self.antenna_factor
        else:
            raise ValueError("Signal's value type must be either " +
                             "voltage or field. Given " +
                             str(signal.value_type))

        copy.values *= signal_factor
        self.signals.append(copy)
Пример #3
0
    def receive(self, signal, origin=None, polarization=None):
        """Process incoming signal according to the filter function and
        store it to the signals list. Subclasses may extend this fuction,
        but should end with super().receive(signal)."""
        copy = Signal(signal.times, signal.values,
                      value_type=Signal.ValueTypes.voltage)
        copy.filter_frequencies(self.response)

        if origin is None:
            d_gain = 1
        else:
            # Calculate theta and phi relative to the orientation
            r, theta, phi = self._convert_to_antenna_coordinates(origin)
            d_gain = self.directional_gain(theta=theta, phi=phi)

        if polarization is None:
            p_gain = 1
        else:
            p_gain = self.polarization_gain(normalize(polarization))

        signal_factor = d_gain * p_gain * self.efficiency

        if signal.value_type==Signal.ValueTypes.voltage:
            pass
        elif signal.value_type==Signal.ValueTypes.field:
            signal_factor /= self.antenna_factor
        else:
            raise ValueError("Signal's value type must be either "
                             +"voltage or field. Given "+str(signal.value_type))

        copy.values *= signal_factor
        self.signals.append(copy)
Пример #4
0
    def slant_depth(self, endpoint, direction, step=500):
        """
        Calculates the column density of a chord cutting through Earth.

        Integrates the Earth's density along the chord, resulting in a column
        density (or material thickness) with units of mass per area.

        Parameters
        ----------
        endpoint : array_like
            Vector position (m) of the chord endpoint, in a coordinate system
            centered on the surface of the Earth (e.g. a negative third
            coordinate represents the depth below the surface).
        direction : array_like
            Vector direction of the chord, in a coordinate system
            centered on the surface of the Earth (e.g. a negative third
            coordinate represents the chord pointing into the Earth).
        step : float, optional
            Step size (m) for the density integration.

        Returns
        -------
        float
            Column density (g/cm^2) along the chord starting at `endpoint` and
            passing through the Earth at the given `direction`.

        See Also
        --------
        PREM.density : Calculates the Earth's density at a given radius.

        """
        # Convert to Earth-centric coordinate system (e.g. center of the Earth
        # is at (0, 0, 0))
        endpoint = np.array(
            [endpoint[0], endpoint[1], endpoint[2] + self.earth_radius])
        direction = normalize(direction)
        dot_prod = np.dot(endpoint, direction)
        # Check for intersection of line and sphere
        discriminant = dot_prod**2 - np.sum(endpoint**2) + self.earth_radius**2
        if discriminant <= 0:
            return 0
        # Calculate the distance at which the line intersects the sphere
        distance = -dot_prod + np.sqrt(discriminant)
        if distance <= 0:
            return 0
        # Parameterize line integral with ts from 0 to 1, with steps just under
        # the given step size (in meters)
        n_steps = int(distance / step)
        if distance % step:
            n_steps += 1
        ts = np.linspace(0, 1, n_steps)
        xs = endpoint[0] + ts * distance * direction[0]
        ys = endpoint[1] + ts * distance * direction[1]
        zs = endpoint[2] + ts * distance * direction[2]
        rs = np.sqrt(xs**2 + ys**2 + zs**2)
        rhos = self.density(rs)
        # Integrate the density times the distance along the chord
        return 100 * np.trapz(rhos * distance, ts)
Пример #5
0
def _convert_local_to_global(position, local_coords):
    """
    Convert local "station" coordinates into global "array" coordinates.

    Parameters
    ----------
    position : array_like
        Cartesian position in local "station" coordinates to be transformed.
    local_coords : array_like
        Matrix of local coordinate system axes in global coordinates.

    Returns
    -------
    global_position : ndarray
        Cartesian position transformed to the global "array" coordinates.

    """
    local_x = normalize(local_coords[0])
    global_x = (1, 0, 0)
    # Find angle between x-axes and rotation axis perpendicular to both x-axes
    angle = np.arccos(np.dot(local_x, global_x))
    axis = normalize(np.cross(global_x, local_x))
    # Form rotation matrix
    cos = np.cos(angle)
    sin = np.sin(angle)
    ux, uy, uz = axis
    rot = np.array([
        [
            cos + ux**2 * (1 - cos), ux * uy * (1 - cos) - uz * sin,
            ux * uz * (1 - cos) + uy * sin
        ],
        [
            uy * ux * (1 - cos) + uz * sin, cos + uy**2 * (1 - cos),
            uy * uz * (1 - cos) - ux * sin
        ],
        [
            uz * ux * (1 - cos) - uy * sin, uz * uy * (1 - cos) + ux * sin,
            cos + uz**2 * (1 - cos)
        ],
    ])
    # Rotate position to new axes
    return np.dot(rot, position)
Пример #6
0
 def test_normalization(self):
     """Test that vectors are successfully normalized"""
     np.random.seed(SEED)
     for _ in range(1000):
         vector = np.random.normal(size=3)
         if np.array_equal(vector, [0, 0, 0]):
             continue
         unit = normalize(vector)
         quotient = vector / unit
         assert np.linalg.norm(unit) == pytest.approx(1)
         assert quotient[0] == pytest.approx(quotient[1])
         assert quotient[0] == pytest.approx(quotient[2])
Пример #7
0
 def get_bounce_point(self):
     """Calculation of point at which signal is reflected by the ice surface
     (z=0)."""
     z0 = self.from_point[2]
     z1 = self.to_point[2]
     u = self.to_point - self.from_point
     # x-y distance between points
     rho = np.sqrt(u[0]**2 + u[1]**2)
     # x-y distance to bounce point based on geometric arguments
     distance = z0 * rho / (z0 + z1)
     # x-y direction vector
     u_xy = np.array([u[0], u[1], 0])
     direction = normalize(u_xy)
     bounce_point = self.from_point + distance * direction
     bounce_point[2] = 0
     return bounce_point
Пример #8
0
 def __init__(self,
              particle_id,
              vertex,
              direction,
              energy,
              interaction_model=NeutrinoInteraction,
              interaction_type=None,
              weight=None):
     self.id = particle_id
     self.vertex = np.array(vertex)
     self.direction = normalize(direction)
     self.energy = energy
     if inspect.isclass(interaction_model):
         self.interaction = interaction_model(self, kind=interaction_type)
     else:
         raise ValueError(
             "Particle class interaction_model must be a class")
     self.survival_weight = None
     self.interaction_weight = None
     self._forced_weight = weight
Пример #9
0
    def event(self):
        """Generate particle, propagate signal through ice to antennas,
        process signal at antennas, and return the original particle."""
        p = self.gen.create_particle()
        n = self.ice.index(p.vertex[2])
        for ant in self.ant_array:
            pf = PathFinder(self.ice, p.vertex, ant.position)
            rpf = ReflectedPathFinder(self.ice, p.vertex, ant.position)

            for path in [pf, rpf]:
                # If path is invalid, skip it
                if not (path.exists):
                    continue

                # p.direction and k should both be unit vectors
                # epol is (negative) vector rejection of k onto p.direction
                k = path.received_ray
                epol = normalize(np.vdot(k, p.direction) * k - p.direction)
                # In case k and p.direction are equal
                # (antenna directly on shower axis), just let epol be all zeros

                psi = np.arccos(np.vdot(p.direction, path.emitted_ray))
                # TODO: Support angles larger than pi/2
                if psi > np.pi / 2:
                    continue

                times = np.linspace(-20e-9, 80e-9, 2048, endpoint=False)
                pulse = AskaryanSignal(times=times,
                                       energy=p.energy,
                                       theta=psi,
                                       n=n)

                path.propagate(pulse)
                # Dividing by path length scales Askaryan pulse properly
                pulse.values /= path.path_length

                ant.receive(pulse, origin=p.vertex, polarization=epol)

        return p
Пример #10
0
    def propagate(self, signal=None, polarization=None):
        """
        Propagate the signal with optional polarization along the ray path.

        Applies the frequency-dependent signal attenuation along the ray path
        and shifts the times according to the ray time of flight. Additionally
        provides the s and p polarization directions.

        Parameters
        ----------
        signal : Signal, optional
            ``Signal`` object to propagate.
        polarization : array_like, optional
            Vector representing the linear polarization of the `signal`.

        Returns
        -------
        tuple of Signal
            Tuple of ``Signal`` objects representing the s and p polarizations
            of the original `signal` attenuated along the ray path. Only
            returned if `signal` was not ``None``.
        tuple of ndarray
            Tuple of polarization vectors representing the s and p polarization
            directions of the `signal` at the end of the ray path. Only
            returned if `polarization` was not ``None``.

        See Also
        --------
        pyrex.Signal : Base class for time-domain signals.

        """
        if polarization is None:
            if signal is None:
                return

            else:
                copy = Signal(signal.times+self.tof, signal.values,
                              value_type=signal.value_type)
                copy.filter_frequencies(self.attenuation)
                return copy

        else:
            # Unit vectors perpendicular and parallel to plane of incidence
            # at the launching point
            u_s0 = normalize(np.cross(self.emitted_direction, [0, 0, 1]))
            u_p0 = normalize(np.cross(u_s0, self.emitted_direction))
            # Unit vector parallel to plane of incidence at the receiving point
            # (perpendicular vector stays the same)
            u_p1 = normalize(np.cross(u_s0, self.received_direction))

            if signal is None:
                return (u_s0, u_p1)

            else:
                # Amplitudes of s and p components
                pol_s = np.dot(polarization, u_s0)
                pol_p = np.dot(polarization, u_p0)
                # Fresnel reflectances of s and p components
                f_s, f_p = self.fresnel
                # Apply fresnel s and p coefficients in addition to attenuation
                attenuation_s = lambda freqs: self.attenuation(freqs) * f_s
                attenuation_p = lambda freqs: self.attenuation(freqs) * f_p
                signal_s = Signal(signal.times+self.tof, signal.values*pol_s,
                                  value_type=signal.value_type)
                signal_p = Signal(signal.times+self.tof, signal.values*pol_p,
                                  value_type=signal.value_type)
                signal_s.filter_frequencies(attenuation_s, force_real=True)
                signal_p.filter_frequencies(attenuation_p, force_real=True)
                return (signal_s, signal_p), (u_s0, u_p1)
Пример #11
0
    def event(self):
        """
        Create a neutrino event and run it through the simulation chain.

        Creates a particle using the ``generator``, produces a signal from that
        event, propagates that signal through the ice according to the
        ``ice_model`` and the ``ray_tracer``, and passes it into the
        ``antennas`` for processing.

        Returns
        -------
        event : Event
            The neutrino event generated which is responsible for the waveforms
            on the antennas.
        triggered : bool, optional
            If the ``triggers`` parameter was specified, contains whether the
            global trigger condition of the detector was met.

        See Also
        --------
        pyrex.Event : Class for storing a tree of `Particle` objects
                      representing an event.
        pyrex.Particle : Class for storing particle attributes.

        """
        event = self.gen.create_event()
        ray_paths = []
        polarizations = []
        for i in range(len(self.antennas)):
            ray_paths.append([])
            polarizations.append([])
        for particle in event:
            logger.info("Processing event for %s", particle)
            if isinstance(self.weight_min, Sequence):
                if ((particle.survival_weight is not None
                     and particle.survival_weight < self.weight_min[0]) or
                    (particle.interaction_weight is not None
                     and particle.interaction_weight < self.weight_min[1])):
                    logger.debug("Skipping particle with weight below %s",
                                 self.weight_min)
                    continue
            elif particle.weight < self.weight_min:
                logger.debug("Skipping particle with weight below %s",
                             self.weight_min)
                continue

            for i, ant in enumerate(self.antennas):
                rt = self.ray_tracer(particle.vertex,
                                     ant.position,
                                     ice_model=self.ice)

                # If no path(s) between the points, skip ahead
                if not rt.exists:
                    logger.debug("Ray paths to %s do not exist", ant)
                    continue

                theta_c = np.arccos(1 / self.ice.index(particle.vertex[2]))

                ray_paths[i].extend(rt.solutions)
                for path in rt.solutions:
                    # nu_pol is the signal polarization at the neutrino vertex
                    # It's calculated as the (negative) vector rejection of
                    # path.emitted_direction onto particle.direction, making
                    # epol orthogonal to path.emitted_direction in the same
                    # plane as particle.direction and path.emitted_direction
                    # This is equivalent to the vector triple product
                    # (particle.direction x path.emitted_direction) x
                    # path.emitted_direction
                    # In the case when path.emitted_direction and
                    # particle.direction are equal, just let nu_pol be zeros
                    nu_pol = normalize(
                        np.vdot(path.emitted_direction, particle.direction) *
                        path.emitted_direction - particle.direction)
                    polarizations[i].append(nu_pol)

                    psi = np.arccos(
                        np.vdot(particle.direction, path.emitted_direction))
                    logger.debug("Angle to %s is %f degrees", ant,
                                 np.degrees(psi))

                    try:
                        if np.abs(psi - theta_c) > self.offcone_max:
                            raise ValueError("Viewing angle is larger than " +
                                             "offcone limit " +
                                             str(np.degrees(self.offcone_max)))
                        pulse = self.signal_model(
                            times=self.signal_times,
                            particle=particle,
                            viewing_angle=psi,
                            viewing_distance=path.path_length,
                            ice_model=self.ice)
                    except ValueError as err:
                        logger.debug("Eliminating invalid Askaryan signal: %s",
                                     err)
                        ant.receive(
                            EmptySignal(self.signal_times + path.tof,
                                        value_type=EmptySignal.Type.field))
                    else:
                        ant_pulses, ant_pols = path.propagate(
                            signal=pulse,
                            polarization=nu_pol,
                            attenuation_interpolation=self.
                            attenuation_interpolation)
                        ant.receive(ant_pulses,
                                    direction=path.received_direction,
                                    polarization=ant_pols)

        if self.triggers is None:
            triggered = None
        elif isinstance(self.triggers, dict):
            triggered = {
                key: trigger_func(self.antennas)
                for key, trigger_func in self.triggers.items()
            }
        else:
            triggered = self.triggers(self.antennas)

        if self.writer is not None:
            self.writer.add(event=event,
                            triggered=triggered,
                            ray_paths=ray_paths,
                            polarizations=polarizations,
                            events_thrown=self.gen.count - self._gen_count)

        self._gen_count = self.gen.count

        if triggered is None:
            return event
        elif isinstance(self.triggers, dict):
            return event, triggered['global']
        else:
            return event, triggered
Пример #12
0
    def apply_response(self,
                       signal,
                       direction=None,
                       polarization=None,
                       force_real=False):
        """
        Process the complete antenna response for an incoming signal.

        Processes the incoming signal according to the frequency response of
        the antenna, the efficiency, and the antenna factor. May also apply the
        directionality and the polarization gain depending on the provided
        parameters. Subclasses may wish to overwrite this function if the
        full antenna response cannot be divided nicely into the described
        pieces.

        Parameters
        ----------
        signal : Signal
            Incoming ``Signal`` object to process.
        direction : array_like, optional
            Vector denoting the direction of travel of the signal as it reaches
            the antenna (in the global coordinate frame). If ``None`` no
            directional response will be applied.
        polarization : array_like, optional
            Vector denoting the signal's polarization direction (in the global
            coordinate frame). If ``None`` no polarization gain will be applied.
        force_real : boolean, optional
            Whether or not the frequency response should be redefined in the
            negative-frequency domain to keep the values of the filtered signal
            real.

        Returns
        -------
        Signal
            Processed ``Signal`` object after the complete antenna response has
            been applied. Should have a ``value_type`` of ``voltage``.

        Raises
        ------
        ValueError
            If the given `signal` does not have a ``value_type`` of ``voltage``
            or ``field``.

        See Also
        --------
        pyrex.Signal : Base class for time-domain signals.

        """
        new_signal = signal.copy()
        new_signal.value_type = Signal.Type.voltage
        new_signal.filter_frequencies(self.frequency_response,
                                      force_real=force_real)

        if direction is None:
            d_gain = 1
        else:
            # Calculate theta and phi relative to the antenna's orientation
            origin = self.position - normalize(direction)
            r, theta, phi = self._convert_to_antenna_coordinates(origin)
            d_gain = self.directional_gain(theta=theta, phi=phi)

        if polarization is None:
            p_gain = 1
        else:
            p_gain = self.polarization_gain(normalize(polarization))

        signal_factor = d_gain * p_gain * self.efficiency

        if signal.value_type == Signal.Type.voltage:
            pass
        elif signal.value_type == Signal.Type.field:
            signal_factor /= self.antenna_factor
        else:
            raise ValueError("Signal's value type must be either " +
                             "voltage or field. Given " +
                             str(signal.value_type))

        new_signal *= signal_factor

        return new_signal
Пример #13
0
 def received_ray(self):
     """Direction from which ray is received."""
     return normalize(self.to_point - self.bounce_point)
Пример #14
0
    def apply_response(self,
                       signal,
                       direction=None,
                       polarization=None,
                       force_real=True):
        """
        Process the complete antenna response for an incoming signal.

        Processes the incoming signal according to the frequency response of
        the antenna, the efficiency, and the antenna factor. May also apply the
        directionality and the polarization gain depending on the provided
        parameters. Subclasses may wish to overwrite this function if the
        full antenna response cannot be divided nicely into the described
        pieces.

        Parameters
        ----------
        signal : Signal
            Incoming ``Signal`` object to process.
        direction : array_like, optional
            Vector denoting the direction of travel of the signal as it reaches
            the antenna (in the global coordinate frame). If ``None`` no
            directional response will be applied.
        polarization : array_like, optional
            Vector denoting the signal's polarization direction (in the global
            coordinate frame). If ``None`` no polarization gain will be applied.
        force_real : boolean, optional
            Whether or not the frequency response should be redefined in the
            negative-frequency domain to keep the values of the filtered signal
            real.

        Returns
        -------
        Signal
            Processed ``Signal`` object after the complete antenna response has
            been applied. Should have a ``value_type`` of ``voltage``.

        Raises
        ------
        ValueError
            If the given `signal` does not have a ``value_type`` of ``voltage``
            or ``field``.
            Or if only one of `direction` and `polarization` is specified.

        See Also
        --------
        pyrex.Signal : Base class for time-domain signals.

        """
        new_signal = signal.copy()
        new_signal.value_type = Signal.Type.voltage
        freq_response = self.frequency_response

        if direction is not None and polarization is not None:
            # Calculate theta and phi relative to the orientation
            origin = self.position - normalize(direction)
            r, theta, phi = self._convert_to_antenna_coordinates(origin)
            # Calculate polarization vector in the antenna coordinates
            y_axis = np.cross(self.z_axis, self.x_axis)
            transformation = np.array([self.x_axis, y_axis, self.z_axis])
            ant_pol = np.dot(transformation, normalize(polarization))
            # Calculate directional response as a function of frequency
            directive_response = self.directional_response(theta, phi, ant_pol)
            freq_response = lambda f: (self.frequency_response(f) *
                                       directive_response(f))

        elif (direction is not None and polarization is None
              or direction is None and polarization is not None):
            raise ValueError(
                "Direction and polarization must be specified together")

        # Apply (combined) frequency response
        new_signal.filter_frequencies(freq_response, force_real=force_real)

        signal_factor = self.efficiency

        if signal.value_type == Signal.Type.voltage:
            pass
        elif signal.value_type == Signal.Type.field:
            signal_factor /= self.antenna_factor
        else:
            raise ValueError("Signal's value type must be either " +
                             "voltage or field. Given " +
                             str(signal.value_type))

        new_signal *= signal_factor

        return new_signal
Пример #15
0
 def emitted_ray(self):
     """Direction in which ray is emitted."""
     return normalize(self.to_point - self.from_point)
Пример #16
0
 def test_zero(self):
     """Test that the zero vector doesn't cause problems"""
     unit = normalize([0, 0, 0])
     assert np.array_equal(unit, [0, 0, 0])
Пример #17
0
 def __init__(self, vertex, direction, energy):
     self.vertex = np.array(vertex)
     self.direction = normalize(direction)
     self.energy = energy
Пример #18
0
    def event(self):
        """
        Create a neutrino event and run it through the simulation chain.

        Creates a particle using the ``generator``, produces a signal from that
        event, propagates that signal through the ice according to the
        ``ice_model`` and the ``ray_tracer``, and passes it into the
        ``antennas`` for processing.

        Returns
        -------
        event : Event
            The neutrino event generated which is responsible for the waveforms
            on the antennas.
        triggered : bool, optional
            If the ``triggers`` parameter was specified, contains whether the
            global trigger condition of the detector was met.

        See Also
        --------
        pyrex.Event : Class for storing a tree of `Particle` objects
                      representing an event.
        pyrex.Particle : Class for storing particle attributes.

        """
        event = self.gen.create_event()
        ray_paths = []
        polarizations = []
        for i in range(len(self.antennas)):
            ray_paths.append([])
            polarizations.append([])
        for particle in event:
            logger.info("Processing event for %s", particle)
            for i, ant in enumerate(self.antennas):
                rt = self.ray_tracer(particle.vertex,
                                     ant.position,
                                     ice_model=self.ice)

                # If no path(s) between the points, skip ahead
                if not rt.exists:
                    logger.debug("Ray paths to %s do not exist", ant)
                    continue

                ray_paths[i].extend(rt.solutions)
                for path in rt.solutions:
                    # nu_pol is the signal polarization at the neutrino vertex
                    # It's calculated as the (negative) vector rejection of
                    # path.emitted_direction onto particle.direction, making
                    # epol orthogonal to path.emitted_direction in the same
                    # plane as particle.direction and path.emitted_direction
                    # This is equivalent to the vector triple product
                    # (particle.direction x path.emitted_direction) x
                    # path.emitted_direction
                    # In the case when path.emitted_direction and
                    # particle.direction are equal, just let nu_pol be zeros
                    nu_pol = normalize(
                        np.vdot(path.emitted_direction, particle.direction) *
                        path.emitted_direction - particle.direction)
                    polarizations[i].append(nu_pol)

                    psi = np.arccos(
                        np.vdot(particle.direction, path.emitted_direction))
                    logger.debug("Angle to %s is %f degrees", ant,
                                 np.degrees(psi))
                    # TODO: Support angles larger than pi/2
                    # (low priority since these angles are far from the
                    # cherenkov cone)
                    if psi > np.pi / 2:
                        continue

                    pulse = self.signal_model(
                        times=self.signal_times,
                        particle=particle,
                        viewing_angle=psi,
                        viewing_distance=path.path_length,
                        ice_model=self.ice)

                    ant_pulses, ant_pols = path.propagate(signal=pulse,
                                                          polarization=nu_pol)

                    ant.receive(ant_pulses,
                                direction=path.received_direction,
                                polarization=ant_pols)

        if self.triggers is None:
            triggered = None
        elif isinstance(self.triggers, dict):
            triggered = {
                key: trigger_func(self.antennas)
                for key, trigger_func in self.triggers.items()
            }
        else:
            triggered = self.triggers(self.antennas)

        if self.writer is not None:
            self.writer.add(event=event,
                            triggered=triggered,
                            ray_paths=ray_paths,
                            polarizations=polarizations,
                            events_thrown=self.gen.count - self._gen_count)

        self._gen_count = self.gen.count

        if triggered is None:
            return event
        elif isinstance(self.triggers, dict):
            return event, triggered['global']
        else:
            return event, triggered
Пример #19
0
 def set_orientation(self, z_axis=[0,0,1], x_axis=[1,0,0]):
     self.z_axis = normalize(z_axis)
     self.x_axis = normalize(x_axis)
     if np.dot(self.z_axis, self.x_axis)!=0:
         raise ValueError("Antenna's x_axis must be perpendicular to its "
                          +"z_axis")