Exemplo n.º 1
0
class EventPositions(strax.Plugin):
    """
    Computes the observed and corrected position for the main S1/S2
    pairs in an event. For XENONnT data, it returns the FDC corrected
    positions of the default_reconstruction_algorithm. In case the fdc_map
    is given as a file (not through CMT), then the coordinate system
    should be given as (x, y, z), not (x, y, drift_time).
    """

    depends_on = ('event_basics', )

    __version__ = '0.1.5'

    default_reconstruction_algorithm = straxen.URLConfig(
        default=DEFAULT_POSREC_ALGO,
        help="default reconstruction algorithm that provides (x,y)")

    electron_drift_velocity = straxen.URLConfig(
        default='cmt://'
        'electron_drift_velocity'
        '?version=ONLINE&run_id=plugin.run_id',
        cache=True,
        help='Vertical electron drift velocity in cm/ns (1e4 m/ms)')

    electron_drift_time_gate = straxen.URLConfig(
        default='cmt://'
        'electron_drift_time_gate'
        '?version=ONLINE&run_id=plugin.run_id',
        help='Electron drift time from the gate in ns',
        cache=True)

    def infer_dtype(self):
        dtype = []
        for j in 'x y r'.split():
            comment = f'Main interaction {j}-position, field-distortion corrected (cm)'
            dtype += [(j, np.float32, comment)]
            for s_i in [1, 2]:
                comment = f'Alternative S{s_i} interaction (rel. main S{int(2*(1.5-s_i)+s_i)}) {j}-position, field-distortion corrected (cm)'
                field = f'alt_s{s_i}_{j}_fdc'
                dtype += [(field, np.float32, comment)]

        for j in ['z']:
            comment = 'Interaction z-position, using mean drift velocity only (cm)'
            dtype += [(j, np.float32, comment)]
            for s_i in [1, 2]:
                comment = f'Alternative S{s_i} z-position (rel. main S{int(2*(1.5-s_i)+s_i)}), using mean drift velocity only (cm)'
                field = f'alt_s{s_i}_z'
                dtype += [(field, np.float32, comment)]

        naive_pos = []
        fdc_pos = []
        for j in 'r z'.split():
            naive_pos += [
                (f'{j}_naive', np.float32,
                 f'Main interaction {j}-position with observed position (cm)')
            ]
            fdc_pos += [
                (f'{j}_field_distortion_correction', np.float32,
                 f'Correction added to {j}_naive for field distortion (cm)')
            ]
            for s_i in [1, 2]:
                naive_pos += [(
                    f'alt_s{s_i}_{j}_naive', np.float32,
                    f'Alternative S{s_i} interaction (rel. main S{int(2*(1.5-s_i)+s_i)}) {j}-position with observed position (cm)'
                )]
                fdc_pos += [(
                    f'alt_s{s_i}_{j}_field_distortion_correction', np.float32,
                    f'Correction added to alt_s{s_i}_{j}_naive for field distortion (cm)'
                )]
        dtype += naive_pos + fdc_pos
        for s_i in [1, 2]:
            dtype += [(
                f'alt_s{s_i}_theta', np.float32,
                f'Alternative S{s_i} (rel. main S{int(2*(1.5-s_i)+s_i)}) interaction angular position (radians)'
            )]

        dtype += [('theta', np.float32,
                   f'Main interaction angular position (radians)')]
        return dtype + strax.time_fields

    def setup(self):
        if isinstance(self.config['fdc_map'], str):
            self.map = InterpolatingMap(
                get_resource(self.config['fdc_map'], fmt='binary'))

        elif is_cmt_option(self.config['fdc_map']):
            self.map = InterpolatingMap(
                get_cmt_resource(
                    self.run_id,
                    tuple([
                        'suffix',
                        self.config['default_reconstruction_algorithm'],
                        *self.config['fdc_map']
                    ]),
                    fmt='binary'))
            self.map.scale_coordinates([1., 1., -self.electron_drift_velocity])

        else:
            raise NotImplementedError('FDC map format not understood.')

    def compute(self, events):

        result = {'time': events['time'], 'endtime': strax.endtime(events)}

        # s_i == 0 indicates the main event, while s_i != 0 means alternative S1 or S2 is used based on s_i value
        # while the other peak is the main one (e.g., s_i == 1 means that the event is defined using altS1 and main S2)
        for s_i in [0, 1, 2]:

            # alt_sx_interaction_drift_time is calculated between main Sy and alternative Sx
            drift_time = events['drift_time'] if not s_i else events[
                f'alt_s{s_i}_interaction_drift_time']

            z_obs = -self.electron_drift_velocity * drift_time
            xy_pos = 's2_' if s_i != 2 else 'alt_s2_'
            orig_pos = np.vstack(
                [events[f'{xy_pos}x'], events[f'{xy_pos}y'], z_obs]).T
            r_obs = np.linalg.norm(orig_pos[:, :2], axis=1)
            delta_r = self.map(orig_pos)
            z_obs = z_obs + self.electron_drift_velocity * self.electron_drift_time_gate

            # apply radial correction
            with np.errstate(invalid='ignore', divide='ignore'):
                r_cor = r_obs + delta_r
                scale = np.divide(r_cor,
                                  r_obs,
                                  out=np.zeros_like(r_cor),
                                  where=r_obs != 0)

            # z correction due to longer drift time for distortion
            # calculated based on the Pythagorean theorem where
            # the electron track is assumed to be a straight line
            # (geometrical reasoning not valid if |delta_r| > |z_obs|,
            #  as cathetus cannot be longer than hypothenuse)
            with np.errstate(invalid='ignore'):
                z_cor = -(z_obs**2 - delta_r**2)**0.5
                invalid = np.abs(z_obs) < np.abs(delta_r)
                # do not apply z correction above gate
                invalid |= z_obs >= 0
            z_cor[invalid] = z_obs[invalid]
            delta_z = z_cor - z_obs

            pre_field = '' if s_i == 0 else f'alt_s{s_i}_'
            post_field = '' if s_i == 0 else '_fdc'
            result.update({
                f'{pre_field}x{post_field}':
                orig_pos[:, 0] * scale,
                f'{pre_field}y{post_field}':
                orig_pos[:, 1] * scale,
                f'{pre_field}r{post_field}':
                r_cor,
                f'{pre_field}r_naive':
                r_obs,
                f'{pre_field}r_field_distortion_correction':
                delta_r,
                f'{pre_field}theta':
                np.arctan2(orig_pos[:, 1], orig_pos[:, 0]),
                f'{pre_field}z_naive':
                z_obs,
                # using z_obs in agreement with the dtype description
                # the FDC for z (z_cor) is found to be not reliable (see #527)
                f'{pre_field}z':
                z_obs,
                f'{pre_field}z_field_distortion_correction':
                delta_z
            })

        return result
Exemplo n.º 2
0
class EventPositions(strax.Plugin):
    """
    Computes the observed and corrected position for the main S1/S2
    pairs in an event. For XENONnT data, it returns the FDC corrected
    positions of the default_reconstruction_algorithm. In case the fdc_map
    is given as a file (not through CMT), then the coordinate system
    should be given as (x, y, z), not (x, y, drift_time).
    """

    depends_on = ('event_basics', )

    __version__ = '0.1.3'

    dtype = [
        ('x', np.float32,
         'Interaction x-position, field-distortion corrected (cm)'),
        ('y', np.float32,
         'Interaction y-position, field-distortion corrected (cm)'),
        ('z', np.float32,
         'Interaction z-position, field-distortion corrected (cm)'),
        ('r', np.float32,
         'Interaction radial position, field-distortion corrected (cm)'),
        ('z_naive', np.float32,
         'Interaction z-position using mean drift velocity only (cm)'),
        ('r_naive', np.float32,
         'Interaction r-position using observed S2 positions directly (cm)'),
        ('r_field_distortion_correction', np.float32,
         'Correction added to r_naive for field distortion (cm)'),
        ('theta', np.float32, 'Interaction angular position (radians)')
    ] + strax.time_fields

    def setup(self):

        is_CMT = isinstance(self.config['fdc_map'], tuple)

        if is_CMT:

            cmt, cmt_conf, is_nt = self.config['fdc_map']
            cmt_conf = (
                f'{cmt_conf[0]}_{self.config["default_reconstruction_algorithm"]}',
                cmt_conf[1])
            map_algo = cmt, cmt_conf, is_nt

            self.map = InterpolatingMap(
                get_resource(get_config_from_cmt(self.run_id, map_algo),
                             fmt='binary'))
            self.map.scale_coordinates(
                [1., 1., -self.config['electron_drift_velocity']])

        elif isinstance(self.config['fdc_map'], str):
            self.map = InterpolatingMap(
                get_resource(self.config['fdc_map'], fmt='binary'))

        else:
            raise NotImplementedError('FDC map format not understood.')

    def compute(self, events):

        result = {'time': events['time'], 'endtime': strax.endtime(events)}

        z_obs = -self.config['electron_drift_velocity'] * events['drift_time']
        orig_pos = np.vstack([events[f's2_x'], events[f's2_y'], z_obs]).T
        r_obs = np.linalg.norm(orig_pos[:, :2], axis=1)
        delta_r = self.map(orig_pos)

        # apply radial correction
        with np.errstate(invalid='ignore', divide='ignore'):
            r_cor = r_obs + delta_r
            scale = r_cor / r_obs

        # z correction due to longer drift time for distortion
        # (geometrical reasoning not valid if |delta_r| > |z_obs|,
        #  as cathetus cannot be longer than hypothenuse)
        with np.errstate(invalid='ignore'):
            z_cor = -(z_obs**2 - delta_r**2)**0.5
            invalid = np.abs(z_obs) < np.abs(delta_r)
        z_cor[invalid] = z_obs[invalid]

        result.update({
            'x': orig_pos[:, 0] * scale,
            'y': orig_pos[:, 1] * scale,
            'r': r_cor,
            'r_naive': r_obs,
            'r_field_distortion_correction': delta_r,
            'theta': np.arctan2(orig_pos[:, 1], orig_pos[:, 0]),
            'z_naive': z_obs,
            'z': z_cor
        })

        return result
Exemplo n.º 3
0
class EventPositions(strax.Plugin):
    """
    Computes the observed and corrected position for the main S1/S2
    pairs in an event. For XENONnT data, it returns the FDC corrected
    positions of the default_reconstruction_algorithm. In case the fdc_map
    is given as a file (not through CMT), then the coordinate system
    should be given as (x, y, z), not (x, y, drift_time).
    """

    depends_on = ('event_basics', )

    __version__ = '0.1.4'

    default_reconstruction_algorithm = straxen.URLConfig(
        default=DEFAULT_POSREC_ALGO,
        help="default reconstruction algorithm that provides (x,y)")

    dtype = [
        ('x', np.float32,
         'Interaction x-position, field-distortion corrected (cm)'),
        ('y', np.float32,
         'Interaction y-position, field-distortion corrected (cm)'),
        ('z', np.float32,
         'Interaction z-position, using mean drift velocity only (cm)'),
        ('r', np.float32,
         'Interaction radial position, field-distortion corrected (cm)'),
        ('z_naive', np.float32,
         'Interaction z-position using mean drift velocity only (cm)'),
        ('r_naive', np.float32,
         'Interaction r-position using observed S2 positions directly (cm)'),
        ('r_field_distortion_correction', np.float32,
         'Correction added to r_naive for field distortion (cm)'),
        ('z_field_distortion_correction', np.float32,
         'Correction added to z_naive for field distortion (cm)'),
        ('theta', np.float32, 'Interaction angular position (radians)')
    ] + strax.time_fields

    def setup(self):

        self.electron_drift_velocity = get_correction_from_cmt(
            self.run_id, self.config['electron_drift_velocity'])
        self.electron_drift_time_gate = get_correction_from_cmt(
            self.run_id, self.config['electron_drift_time_gate'])

        if isinstance(self.config['fdc_map'], str):
            self.map = InterpolatingMap(
                get_resource(self.config['fdc_map'], fmt='binary'))

        elif is_cmt_option(self.config['fdc_map']):
            self.map = InterpolatingMap(
                get_cmt_resource(
                    self.run_id,
                    tuple([
                        'suffix',
                        self.config['default_reconstruction_algorithm'],
                        *self.config['fdc_map']
                    ]),
                    fmt='binary'))
            self.map.scale_coordinates([1., 1., -self.electron_drift_velocity])

        else:
            raise NotImplementedError('FDC map format not understood.')

    def compute(self, events):

        result = {'time': events['time'], 'endtime': strax.endtime(events)}

        z_obs = -self.electron_drift_velocity * (events['drift_time'] -
                                                 self.electron_drift_time_gate)
        orig_pos = np.vstack([events[f's2_x'], events[f's2_y'], z_obs]).T
        r_obs = np.linalg.norm(orig_pos[:, :2], axis=1)
        delta_r = self.map(orig_pos)

        # apply radial correction
        with np.errstate(invalid='ignore', divide='ignore'):
            r_cor = r_obs + delta_r
            scale = r_cor / r_obs

        # z correction due to longer drift time for distortion
        # (geometrical reasoning not valid if |delta_r| > |z_obs|,
        #  as cathetus cannot be longer than hypothenuse)
        with np.errstate(invalid='ignore'):
            z_cor = -(z_obs**2 - delta_r**2)**0.5
            invalid = np.abs(z_obs) < np.abs(delta_r)
            # do not apply z correction above gate
            invalid |= z_obs >= 0
        z_cor[invalid] = z_obs[invalid]
        delta_z = z_cor - z_obs

        result.update({
            'x': orig_pos[:, 0] * scale,
            'y': orig_pos[:, 1] * scale,
            'r': r_cor,
            'r_naive': r_obs,
            'r_field_distortion_correction': delta_r,
            'theta': np.arctan2(orig_pos[:, 1], orig_pos[:, 0]),
            'z_naive': z_obs,
            # using z_obs in agreement with the dtype description
            # the FDC for z (z_cor) is found to be not reliable (see #527)
            'z': z_obs,
            'z_field_distortion_correction': delta_z
        })

        return result