Пример #1
0
    def __init__(
            self,
            model,
            angle,
            L12=2859,
            footprint=60,
            L2S=120,
            dtheta=3.3,  # angular resolution
            lo_wavelength=2.8,
            hi_wavelength=18,
            dlambda=3.3,
            rebin=2):
        self.model = model

        # turn off resolution smearing
        self.model.dq = 0
        self.bkg = model.bkg.value
        self.angle = angle

        # the fractional width of a square wavelength resolution
        self.dlambda = dlambda / 100.
        self.rebin = rebin / 100.
        self.wavelength_bins = calculate_wavelength_bins(
            lo_wavelength, hi_wavelength, rebin)
        # nominal Q values
        bin_centre = 0.5 * (self.wavelength_bins[1:] +
                            self.wavelength_bins[:-1])
        self.q = general.q(angle, bin_centre)

        # keep a tally of the direct and reflected beam
        self.direct_beam = np.zeros((self.wavelength_bins.size - 1))
        self.reflected_beam = np.zeros((self.wavelength_bins.size - 1))

        # wavelength generator
        a = PN('PLP0000711.nx.hdf')
        q, i, di = a.process(normalise=False,
                             normalise_bins=False,
                             rebin_percent=0,
                             lo_wavelength=lo_wavelength,
                             hi_wavelength=hi_wavelength)
        q = q.squeeze()
        i = i.squeeze()
        self.spectrum_dist = SpectrumDist(q, i)

        # angular resolution generator, based on a trapezoidal distribution
        # The slit settings are the optimised set typically used in an
        # experiment
        self.dtheta = dtheta / 100.
        self.footprint = footprint
        s1, s2 = general.slit_optimiser(footprint,
                                        self.dtheta,
                                        angle=angle,
                                        L2S=L2S,
                                        L12=L12,
                                        verbose=False)
        div, alpha, beta = general.div(s1, s2, L12=L12)
        self.angular_dist = trapz(c=(alpha - beta) / 2. / alpha,
                                  d=(alpha + beta) / 2. / alpha,
                                  loc=-alpha,
                                  scale=2 * alpha)
Пример #2
0
    def run(self, samples):
        """
        Sample the beam.

        2400000 samples roughly corresponds to 1200 sec of *PLATYPUS* using
        dlambda=3.3 and dtheta=3.3 at angle=0.65.
        150000000 samples roughly corresponds to 3600 sec of *PLATYPUS* using
        dlambda=3.3 and dtheta=3.3 at angle=3.0.

        (The sample number <--> actual acquisition time correspondence has
         not been checked fully)

        Parameters
        ----------
        samples: int
            How many samples to run.
        """
        # generate neutrons of different angular divergence
        angles = self.angular_dist.rvs(samples) + self.angle

        # generate neutrons of various wavelengths
        wavelengths = self.spectrum_dist.rvs(size=samples)

        # calculate Q
        q = general.q(angles, wavelengths)

        # calculate reflectivities for a neutron of a given Q.
        # resolution smearing is taken care of elsewhere.
        r = self.model(q, x_err=0.)

        # accept or reject neutrons based on the reflectivity of
        # sample at a given Q.
        criterion = np.random.random(size=samples)
        accepted = criterion < r

        # implement wavelength smearing from choppers
        # factor of 0.68 is used to convert from FWHM-Gaussian to
        # full-width-Uniform
        noise = np.random.random(size=samples) - 0.5
        jittered_wavelengths = wavelengths * (1 + self.dlambda / 0.68 * noise)

        # update direct and reflected beam counts. Rebin smearing
        # is taken into account due to the finite size of the wavelength
        # bins.
        hist = np.histogram(jittered_wavelengths, self.wavelength_bins)

        self.direct_beam += hist[0]

        hist = np.histogram(jittered_wavelengths[accepted],
                            self.wavelength_bins)
        self.reflected_beam += hist[0]
Пример #3
0
    def _reduce_single_angle(self, scale=1):
        """
        Reduce a single angle.
        """
        n_spectra = self.reflected_beam.n_spectra
        n_tpixels = np.size(self.reflected_beam.m_topandtail, 1)
        n_ypixels = np.size(self.reflected_beam.m_topandtail, 2)

        # calculate omega and two_theta depending on the mode.
        mode = self.reflected_beam.mode

        # we'll need the wavelengths to calculate Q.
        wavelengths = self.reflected_beam.m_lambda
        m_twotheta = np.zeros((n_spectra, n_tpixels, n_ypixels))

        detector_z_difference = (self.reflected_beam.detector_z -
                                 self.direct_beam.detector_z)

        beampos_z_difference = (self.reflected_beam.m_beampos -
                                self.direct_beam.m_beampos)

        Y_PIXEL_SPACING = self.reflected_beam.cat.y_pixels_per_mm[0]

        total_z_deflection = (detector_z_difference +
                              beampos_z_difference * Y_PIXEL_SPACING)

        if mode in ['FOC', 'POL', 'POLANAL', 'MT']:
            # omega_nom.shape = (N, )
            omega_nom = np.degrees(
                np.arctan(total_z_deflection / self.reflected_beam.detector_y)
                / 2.)
            '''
            Wavelength specific angle of incidence correction
            This involves:
            1) working out the trajectory of the neutrons through the
            collimation system.
            2) where those neutrons intersect the sample.
            3) working out the elevation of the neutrons when they hit the
            sample.
            4) correcting the angle of incidence.
            '''
            speeds = general.wavelength_velocity(wavelengths)
            collimation_distance = self.reflected_beam.cat.collimation_distance
            s2_sample_distance = (self.reflected_beam.cat.sample_distance -
                                  self.reflected_beam.cat.slit2_distance)

            # work out the trajectories of the neutrons for them to pass
            # through the collimation system.
            trajectories = find_trajectory(collimation_distance / 1000., 0,
                                           speeds)

            # work out where the beam hits the sample
            res = parabola_line_intersection_point(s2_sample_distance / 1000,
                                                   0, trajectories, speeds,
                                                   omega_nom[:, np.newaxis])
            intersect_x, intersect_y, x_prime, elevation = res

            # correct the angle of incidence with a wavelength dependent
            # elevation.
            omega_corrected = omega_nom[:, np.newaxis] - elevation

            m_twotheta += np.arange(n_ypixels * 1.)[np.newaxis, np.newaxis, :]
            m_twotheta -= self.direct_beam.m_beampos[:, np.newaxis, np.newaxis]
            m_twotheta *= Y_PIXEL_SPACING
            m_twotheta += detector_z_difference
            m_twotheta /= (self.reflected_beam.detector_y[:, np.newaxis,
                                                          np.newaxis])
            m_twotheta = np.arctan(m_twotheta)
            m_twotheta = np.degrees(m_twotheta)

            # you may be reflecting upside down, reverse the sign.
            upside_down = np.sign(omega_corrected[:, 0])
            m_twotheta *= upside_down[:, np.newaxis, np.newaxis]
            omega_corrected *= upside_down[:, np.newaxis]

        elif mode in ['SB', 'DB']:
            # the angle of incidence is half the two theta of the reflected
            # beam
            omega = np.arctan(
                total_z_deflection / self.reflected_beam.detector_y) / 2.

            # work out two theta for each of the detector pixels
            m_twotheta += np.arange(n_ypixels * 1.)[np.newaxis, np.newaxis, :]
            m_twotheta -= self.direct_beam.m_beampos[:, np.newaxis, np.newaxis]
            m_twotheta += detector_z_difference
            m_twotheta -= (
                self.reflected_beam.detector_y[:, np.newaxis, np.newaxis] *
                np.tan(omega[:, np.newaxis, np.newaxis]))

            m_twotheta /= (self.reflected_beam.detector_y[:, np.newaxis,
                                                          np.newaxis])
            m_twotheta = np.arctan(m_twotheta)
            m_twotheta += omega[:, np.newaxis, np.newaxis]

            # still in radians at this point
            # add an extra dimension, because omega_corrected needs to be the
            # angle of incidence for each wavelength. I.e. should be
            # broadcastable to (N, T)
            omega_corrected = np.degrees(omega)[:, np.newaxis]
            m_twotheta = np.degrees(m_twotheta)
        '''
        --Specular Reflectivity--
        Use the (constant wavelength) spectra that have already been integrated
        over 2theta (in processnexus) to calculate the specular reflectivity.
        Beware: this is because m_topandtail has already been divided through
        by monitor counts and error propagated (at the end of processnexus).
        Thus, the 2theta pixels are correlated to some degree. If we use the 2D
        plot to calculate reflectivity
        (sum {Iref_{2theta, lambda}}/I_direct_{lambda}) then the error bars in
        the reflectivity turn out much larger than they should be.
        '''
        ydata, ydata_sd = EP.EPdiv(self.reflected_beam.m_spec,
                                   self.reflected_beam.m_spec_sd,
                                   self.direct_beam.m_spec,
                                   self.direct_beam.m_spec_sd)

        # calculate the 1D Qz values.
        xdata = general.q(omega_corrected, wavelengths)
        xdata_sd = (self.reflected_beam.m_lambda_fwhm /
                    self.reflected_beam.m_lambda)**2
        xdata_sd += (self.reflected_beam.domega[:, np.newaxis] /
                     omega_corrected)**2
        xdata_sd = np.sqrt(xdata_sd) * xdata
        '''
        ---Offspecular reflectivity---
        normalise the counts in the reflected beam by the direct beam
        spectrum this gives a reflectivity. Also propagate the errors,
        leaving the fractional variance (dr/r)^2.
        --Note-- that adjacent y-pixels (same wavelength) are correlated in
        this treatment, so you can't just sum over them.
        i.e. (c_0 / d) + ... + c_n / d) != (c_0 + ... + c_n) / d
        '''
        m_ref, m_ref_sd = EP.EPdiv(
            self.reflected_beam.m_topandtail,
            self.reflected_beam.m_topandtail_sd,
            self.direct_beam.m_spec[:, :, np.newaxis],
            self.direct_beam.m_spec_sd[:, :, np.newaxis])

        # you may have had divide by zero's.
        m_ref = np.where(np.isinf(m_ref), 0, m_ref)
        m_ref_sd = np.where(np.isinf(m_ref_sd), 0, m_ref_sd)

        # calculate the Q values for the detector pixels.  Each pixel has
        # different 2theta and different wavelength, ASSUME that they have the
        # same angle of incidence
        qx, qy, qz = general.q2(omega_corrected[:, :, np.newaxis], m_twotheta,
                                0, wavelengths[:, :, np.newaxis])

        reduction = {}
        reduction['x'] = self.x = xdata
        reduction['x_err'] = self.x_err = xdata_sd
        reduction['y'] = self.y = ydata / scale
        reduction['y_err'] = self.y_err = ydata_sd / scale
        reduction['omega'] = omega_corrected
        reduction['m_twotheta'] = m_twotheta
        reduction['m_ref'] = self.m_ref = m_ref
        reduction['m_ref_err'] = self.m_ref_err = m_ref_sd
        reduction['qz'] = self.m_qz = qz
        reduction['qx'] = self.m_qx = qx
        reduction['nspectra'] = self.n_spectra = n_spectra
        reduction['start_time'] = self.reflected_beam.start_time
        reduction['datafile_number'] = self.datafile_number = (
            self.reflected_beam.datafile_number)

        fnames = []
        datasets = []
        datafilename = self.reflected_beam.datafilename
        datafilename = os.path.basename(datafilename.split('.nx.hdf')[0])

        for i in range(n_spectra):
            data_tup = self.data(scanpoint=i)
            datasets.append(ReflectDataset(data_tup))

        if self.save:
            for i, dataset in enumerate(datasets):
                fname = '{0}_{1}.dat'.format(datafilename, i)
                fnames.append(fname)
                with open(fname, 'wb') as f:
                    dataset.save(f)

                fname = '{0}_{1}.xml'.format(datafilename, i)
                with open(fname, 'wb') as f:
                    dataset.save_xml(f, start_time=reduction['start_time'][i])

        reduction['fname'] = fnames
        return datasets, deepcopy(reduction)
Пример #4
0
    def _reduce_single_angle(self, scale=1):
        """
        Reduce a single angle.
        """
        n_spectra = self.reflected_beam.n_spectra
        n_tpixels = np.size(self.reflected_beam.m_topandtail, 1)
        n_ypixels = np.size(self.reflected_beam.m_topandtail, 2)

        # calculate omega and two_theta depending on the mode.
        mode = self.reflected_beam.mode

        # we'll need the wavelengths to calculate Q.
        wavelengths = self.reflected_beam.m_lambda
        m_twotheta = np.zeros((n_spectra, n_tpixels, n_ypixels))

        if mode in ['FOC', 'POL', 'POLANAL', 'MT']:
            detector_z_difference = (self.reflected_beam.detector_z -
                                     self.direct_beam.detector_z)
            beampos_z_difference = (self.reflected_beam.m_beampos
                                    - self.direct_beam.m_beampos)

            total_z_deflection = (detector_z_difference
                                  + beampos_z_difference * Y_PIXEL_SPACING)

            # omega_nom.shape = (N, )
            omega_nom = np.degrees(np.arctan(total_z_deflection
                                   / self.reflected_beam.detector_y) / 2.)

            '''
            Wavelength specific angle of incidence correction
            This involves:
            1) working out the trajectory of the neutrons through the
            collimation system.
            2) where those neutrons intersect the sample.
            3) working out the elevation of the neutrons when they hit the
            sample.
            4) correcting the angle of incidence.
            '''
            speeds = general.wavelength_velocity(wavelengths)
            collimation_distance = self.reflected_beam.cat.collimation_distance
            s2_sample_distance = (self.reflected_beam.cat.sample_distance
                                  - self.reflected_beam.cat.slit2_distance)

            # work out the trajectories of the neutrons for them to pass
            # through the collimation system.
            trajectories = pm.find_trajectory(collimation_distance / 1000.,
                                              0, speeds)
            
            # work out where the beam hits the sample
            res = pm.parabola_line_intersection_point(s2_sample_distance / 1000,
                                                      0,
                                                      trajectories,
                                                      speeds,
                                                      omega_nom[:, np.newaxis])
            intersect_x, intersect_y, x_prime, elevation = res

            # correct the angle of incidence with a wavelength dependent
            # elevation.
            omega_corrected = omega_nom[:, np.newaxis] - elevation

        elif mode == 'SB' or mode == 'DB':
            omega = self.reflected_beam.M_beampos + self.reflected_beam.detectorZ[:, np.newaxis]
            omega -= self.direct_beam.M_beampos + self.direct_beam.detectorZ
            omega /= 2 * self.reflected_beam.detectorY[:, np.newaxis, np.newaxis]
            omega = np.arctan(omega)

            m_twotheta += np.arange(n_ypixels * 1.)[np.newaxis, np.newaxis, :] * Y_PIXEL_SPACING
            m_twotheta += self.reflected_beam.detectorZ[:, np.newaxis, np.newaxis]
            m_twotheta -= self.direct_beam.M_beampos[:, :, np.newaxis] + self.direct_beam.detectorZ
            m_twotheta -= self.reflected_beam.detectorY[:, np.newaxis, np.newaxis] * np.tan(omega[:, :, np.newaxis])

            m_twotheta /= self.reflected_beam.detectorY[:, np.newaxis, np.newaxis]
            m_twotheta = np.arctan(m_twotheta)
            m_twotheta += omega[:, :, np.newaxis]

        '''
        --Specular Reflectivity--
        Use the (constant wavelength) spectra that have already been integrated
        over 2theta (in processnexus) to calculate the specular reflectivity.
        Beware: this is because m_topandtail has already been divided through
        by monitor counts and error propagated (at the end of processnexus).
        Thus, the 2theta pixels are correlated to some degree. If we use the 2D
        plot to calculate reflectivity
        (sum {Iref_{2theta, lambda}}/I_direct_{lambda}) then the error bars in
        the reflectivity turn out much larger than they should be.
        '''
        ydata, ydata_sd = EP.EPdiv(self.reflected_beam.m_spec,
                                   self.reflected_beam.m_spec_sd,
                                   self.direct_beam.m_spec,
                                   self.direct_beam.m_spec_sd)

        # calculate the 1D Qz values.
        xdata = general.q(omega_corrected, wavelengths)
        xdata_sd = (self.reflected_beam.m_lambda_fwhm
                    / self.reflected_beam.m_lambda) ** 2
        xdata_sd += (self.reflected_beam.domega[:, np.newaxis]
                     / omega_corrected) ** 2
        xdata_sd = np.sqrt(xdata_sd) * xdata

        '''
        ---Offspecular reflectivity---
        normalise the counts in the reflected beam by the direct beam
        spectrum this gives a reflectivity. Also propagate the errors,
        leaving the fractional variance (dr/r)^2.
        --Note-- that adjacent y-pixels (same wavelength) are correlated in this
        treatment, so you can't just sum over them.
        i.e. (c_0 / d) + ... + c_n / d) != (c_0 + ... + c_n) / d
        '''
        m_ref, m_ref_sd = EP.EPdiv(self.reflected_beam.m_topandtail,
                                   self.reflected_beam.m_topandtail_sd,
                                   self.direct_beam.m_spec[:, :, np.newaxis],
                                   self.direct_beam.m_spec_sd[:, :, np.newaxis])

        # you may have had divide by zero's.
        m_ref = np.where(np.isinf(m_ref), 0, m_ref)
        m_ref_sd = np.where(np.isinf(m_ref_sd), 0, m_ref_sd)

        # calculate the Q values for the detector pixels.  Each pixel has
        # different 2theta and different wavelength, ASSUME that they have the
        # same angle of incidence
        qx, qy, qz = general.q2(omega_corrected[:, :, np.newaxis],
                                m_twotheta,
                                0,
                                wavelengths[:, :, np.newaxis])

        reduction = {}
        reduction['xdata'] = self.xdata = xdata
        reduction['xdata_sd'] = self.xdata_sd = xdata_sd
        reduction['ydata'] = self.ydata = ydata
        reduction['ydata_sd'] = self.ydata_sd = ydata_sd
        reduction['m_ref'] = self.m_ref = m_ref
        reduction['m_ref_sd'] = self.m_ref_sd = m_ref_sd
        reduction['qz'] = self.m_qz = qz
        reduction['qy'] = self.m_qy = qy
        reduction['nspectra'] = self.n_spectra = n_spectra
        reduction['datafile_number'] = self.datafile_number = (
            self.reflected_beam.datafile_number)

        fnames = []
        if self.save:
            for i in range(n_spectra):
                data_tup = self.data(scanpoint=i)
                dataset = ReflectDataset(data_tup)
                fname = 'PLP{0:07d}_{1}.dat'.format(self.datafile_number, i)
                fnames.append(fname)
                with open(fname, 'wb') as f:
                    dataset.save(f)
                fname = 'PLP{0:07d}_{1}.xml'.format(self.datafile_number, i)
                with open(fname, 'wb') as f:
                    dataset.save_xml(f)

        reduction['fname'] = fnames
        return deepcopy(reduction)
Пример #5
0
def resolution_kernel(p_theta,
                      p_wavelength,
                      theta0,
                      wavelength0,
                      npnts=1001,
                      spectrum=None):
    """
    Creates a full resolution kernel based on angular and wavelength components

    Parameters
    ----------
    p_theta: P_Theta
        Angular component
    p_wavelength: P_Wavelength
        Wavelength components
    theta0: array-like
        Nominal angle of incidence, degrees
    wavelength0: array-like
        Nominal wavelength, Angstrom
    npnts: float
        number of points in the resolution kernel
    spectrum: None or callable
        Function, spectrum(wavelength) that specifies the intensity of
        the neutron spectrum at a given wavelength

    Returns
    -------
    kernel: np.ndarray
        Full resolution kernel. Has shape `(N, 2, npnts)` where `N` is the
        number of points in theta0/wavelength0.
        kernel[:, 0, :] and kernel[:, 1, :] correspond to `Q` and `PDF(Q)` for
        each of the data points in the first dimension.
    """
    theta0_arr = np.asfarray(theta0).ravel()
    wavelength0_arr = np.asfarray(wavelength0).ravel()
    qpnts = max(theta0_arr.size, wavelength0_arr.size)
    arr = [
        np.array(a) for a in np.broadcast_arrays(theta0_arr, wavelength0_arr)
    ]
    theta0_arr = arr[0]
    wavelength0_arr = arr[1]

    mean_q = general.q(theta0_arr, wavelength0_arr)
    max_q = general.q(
        theta0_arr + p_theta.width,
        wavelength0_arr - p_wavelength.width(wavelength0_arr),
    )
    width = max_q - mean_q

    kernel = np.zeros((qpnts, 2, npnts))

    for i in range(qpnts):
        Q = np.linspace(mean_q[i] - width[i], mean_q[i] + width[i], npnts)
        kernel[i, 0, :] = Q

        # angular component
        pqt = pq_theta(p_theta, theta0_arr[i], wavelength0_arr[i], Q)

        # burst time component
        pqb = pq_wavelength(p_wavelength.burst, theta0_arr[i],
                            wavelength0_arr[i], Q, spectrum)

        # crossing time component
        pqc = pq_wavelength(
            p_wavelength.crossing,
            theta0_arr[i],
            wavelength0_arr[i],
            Q,
            spectrum,
        )

        # rebinning component
        pqda = pq_wavelength(p_wavelength.da, theta0_arr[i],
                             wavelength0_arr[i], Q, spectrum)

        spacing = np.diff(Q)[0]

        p = np.convolve(pqt, pqb, "same")
        p = np.convolve(p, pqc, "same")
        p = np.convolve(p, pqda, "same")
        p *= spacing**3.0

        kernel[i, 1, :] = p / integrate.simps(p, Q)

    return kernel
Пример #6
0
    def _reduce_single_angle(self, scale=1):
        """
        Reduce a single angle.
        """
        n_spectra = self.reflected_beam.n_spectra
        n_tpixels = np.size(self.reflected_beam.m_topandtail, 1)
        n_xpixels = np.size(self.reflected_beam.m_topandtail, 2)

        # we'll need the wavelengths to calculate Q.
        wavelengths = self.reflected_beam.m_lambda
        m_twotheta = np.zeros((n_spectra, n_tpixels, n_xpixels))

        detrot_difference = (self.reflected_beam.detector_z -
                             self.direct_beam.detector_z)

        # difference in pixels between reflected position and direct beam
        # at the two different detrots.
        QZ_PIXEL_SPACING = self.reflected_beam.cat.qz_pixel_size[0]
        dy = self.reflected_beam.detector_y

        # convert that pixel difference to angle (in small angle approximation)
        # higher `som` leads to lower m_beampos. i.e. higher two theta
        # is at lower pixel values
        beampos_2theta_diff = -(self.reflected_beam.m_beampos -
                                self.direct_beam.m_beampos)
        beampos_2theta_diff *= QZ_PIXEL_SPACING / dy[0]
        beampos_2theta_diff = np.degrees(beampos_2theta_diff)

        total_2theta_deflection = detrot_difference + beampos_2theta_diff

        # omega_nom.shape = (N, )
        omega_nom = total_2theta_deflection / 2.0
        omega_corrected = omega_nom[:, np.newaxis]

        m_twotheta += np.arange(n_xpixels * 1.0)[np.newaxis, np.newaxis, :]
        m_twotheta -= self.direct_beam.m_beampos[:, np.newaxis, np.newaxis]
        # minus sign in following line because higher two theta is at lower
        # pixel values
        m_twotheta *= -QZ_PIXEL_SPACING / dy[:, np.newaxis, np.newaxis]
        m_twotheta = np.degrees(m_twotheta)
        m_twotheta += detrot_difference

        # you may be reflecting upside down, reverse the sign.
        upside_down = np.sign(omega_corrected[:, 0])
        m_twotheta *= upside_down[:, np.newaxis, np.newaxis]
        omega_corrected *= upside_down[:, np.newaxis]
        """
        --Specular Reflectivity--
        Use the (constant wavelength) spectra that have already been integrated
        over 2theta (in processnexus) to calculate the specular reflectivity.
        Beware: this is because m_topandtail has already been divided through
        by monitor counts and error propagated (at the end of processnexus).
        Thus, the 2theta pixels are correlated to some degree. If we use the 2D
        plot to calculate reflectivity
        (sum {Iref_{2theta, lambda}}/I_direct_{lambda}) then the error bars in
        the reflectivity turn out much larger than they should be.
        """
        ydata, ydata_sd = EP.EPdiv(
            self.reflected_beam.m_spec,
            self.reflected_beam.m_spec_sd,
            self.direct_beam.m_spec,
            self.direct_beam.m_spec_sd,
        )

        # calculate the 1D Qz values.
        xdata = general.q(omega_corrected, wavelengths)
        xdata_sd = (self.reflected_beam.m_lambda_fwhm /
                    self.reflected_beam.m_lambda)**2
        xdata_sd += (self.reflected_beam.domega[:, np.newaxis] /
                     omega_corrected)**2
        xdata_sd = np.sqrt(xdata_sd) * xdata
        """
        ---Offspecular reflectivity---
        normalise the counts in the reflected beam by the direct beam
        spectrum this gives a reflectivity. Also propagate the errors,
        leaving the fractional variance (dr/r)^2.
        --Note-- that adjacent y-pixels (same wavelength) are correlated in
        this treatment, so you can't just sum over them.
        i.e. (c_0 / d) + ... + c_n / d) != (c_0 + ... + c_n) / d
        """
        m_ref, m_ref_sd = EP.EPdiv(
            self.reflected_beam.m_topandtail,
            self.reflected_beam.m_topandtail_sd,
            self.direct_beam.m_spec[:, :, np.newaxis],
            self.direct_beam.m_spec_sd[:, :, np.newaxis],
        )

        # you may have had divide by zero's.
        m_ref = np.where(np.isinf(m_ref), 0, m_ref)
        m_ref_sd = np.where(np.isinf(m_ref_sd), 0, m_ref_sd)

        # calculate the Q values for the detector pixels.  Each pixel has
        # different 2theta and different wavelength, ASSUME that they have the
        # same angle of incidence
        qx, qy, qz = general.q2(
            omega_corrected[:, :, np.newaxis],
            m_twotheta,
            0,
            wavelengths[:, :, np.newaxis],
        )

        reduction = {}
        reduction["x"] = self.x = xdata
        reduction["x_err"] = self.x_err = xdata_sd
        reduction["y"] = self.y = ydata / scale
        reduction["y_err"] = self.y_err = ydata_sd / scale
        reduction["omega"] = omega_corrected
        reduction["m_twotheta"] = m_twotheta
        reduction["m_ref"] = self.m_ref = m_ref
        reduction["m_ref_err"] = self.m_ref_err = m_ref_sd
        reduction["qz"] = self.m_qz = qz
        reduction["qx"] = self.m_qx = qx
        reduction["nspectra"] = self.n_spectra = n_spectra
        reduction["start_time"] = self.reflected_beam.start_time
        reduction[
            "datafile_number"] = self.datafile_number = self.reflected_beam.datafile_number

        fnames = []
        datasets = []
        datafilename = self.reflected_beam.datafilename
        datafilename = os.path.basename(datafilename.split(".nx.hdf")[0])

        for i in range(n_spectra):
            data_tup = self.data(scanpoint=i)
            datasets.append(ReflectDataset(data_tup))

        if self.save:
            for i, dataset in enumerate(datasets):
                fname = "{0}_{1}.dat".format(datafilename, i)
                fnames.append(fname)
                with open(fname, "wb") as f:
                    dataset.save(f)

                fname = "{0}_{1}.xml".format(datafilename, i)
                with open(fname, "wb") as f:
                    dataset.save_xml(f, start_time=reduction["start_time"][i])

        reduction["fname"] = fnames
        return datasets, deepcopy(reduction)
Пример #7
0
    def sample(self, samples, random_state=None):
        """
        Sample the beam for reflected signal.

        2400000 samples roughly corresponds to 1200 sec of *PLATYPUS* using
        dlambda=3.3 and dtheta=3.3 at angle=0.65.
        150000000 samples roughly corresponds to 3600 sec of *PLATYPUS* using
        dlambda=3.3 and dtheta=3.3 at angle=3.0.

        (The sample number <--> actual acquisition time correspondence has
         not been checked fully)

        Parameters
        ----------
        samples: int
            How many samples to run.
        random_state: {int, `~np.random.RandomState`, `~np.random.Generator`}, optional
        If `random_state` is not specified the
        `~np.random.RandomState` singleton is used.
        If `random_state` is an int, a new ``RandomState`` instance is used,
        seeded with seed.
        If `random_state` is already a ``RandomState`` or a ``Generator``
        instance, then that object is used.
        Specify `random_state` for repeatable minimizations.
        """
        # grab a random number generator
        rng = check_random_state(random_state)

        # generate neutrons of various wavelengths
        wavelengths = self.spectrum_dist.rvs(size=samples, random_state=rng)

        # generate neutrons of different angular divergence
        angles = self.angular_dist.rvs(samples, random_state=rng) + self.angle

        # angular deviation due to gravity
        # --> no correction for gravity affecting width of angular resolution
        if self.gravity:
            speeds = general.wavelength_velocity(wavelengths)
            # trajectories through slits for different wavelengths
            trajectories = pm.find_trajectory(self.L12 / 1000.0, 0, speeds)
            # elevation at sample
            elevations = pm.elevation(
                trajectories, speeds, (self.L12 + self.L2S) / 1000.0
            )
            angles -= elevations

        # calculate Q
        q = general.q(angles, wavelengths)

        # calculate reflectivities for a neutron of a given Q.
        # the angular resolution smearing has already been done. The wavelength
        # resolution smearing follows.
        r = self.model(q, x_err=0.0)

        # accept or reject neutrons based on the reflectivity of
        # sample at a given Q.
        criterion = rng.uniform(size=samples)
        accepted = criterion < r

        # implement wavelength smearing from choppers. Jitter the wavelengths
        # by a uniform distribution whose full width is dlambda / 0.68.
        if self.force_gaussian:
            noise = rng.standard_normal(size=samples)
            jittered_wavelengths = wavelengths * (
                1 + self.dlambda / 2.3548 * noise
            )
        else:
            noise = rng.uniform(-0.5, 0.5, size=samples)
            jittered_wavelengths = wavelengths * (
                1 + self.dlambda / 0.68 * noise
            )

        # update reflected beam counts. Rebin smearing
        # is taken into account due to the finite size of the wavelength
        # bins.
        hist = np.histogram(
            jittered_wavelengths[accepted], self.wavelength_bins
        )
        self.reflected_beam += hist[0]
        self.bmon_reflect += float(samples)

        # update resolution kernel. If we have more than 100000 in all
        # bins skip
        if (
            len(self._res_kernel)
            and np.min([len(v) for v in self._res_kernel.values()]) > 500000
        ):
            return

        bin_loc = np.digitize(jittered_wavelengths, self.wavelength_bins)
        for i in range(1, len(self.wavelength_bins)):
            # extract q values that fall in each wavelength bin
            q_for_bin = np.copy(q[bin_loc == i])
            q_samples_so_far = self._res_kernel.get(i - 1, np.array([]))
            updated_samples = np.concatenate((q_samples_so_far, q_for_bin))

            # no need to keep double precision for these sample arrays
            self._res_kernel[i - 1] = updated_samples.astype(np.float32)
Пример #8
0
    def __init__(
        self,
        model,
        angle,
        L12=2859,
        footprint=60,
        L2S=120,
        dtheta=3.3,
        lo_wavelength=2.8,
        hi_wavelength=18,
        dlambda=3.3,
        rebin=2,
        gravity=False,
        force_gaussian=False,
        force_uniform_wavelength=False,
    ):
        self.model = model

        self.bkg = model.bkg.value
        self.angle = angle

        # dlambda refers to the FWHM of the gaussian approximation to a uniform
        # distribution. The full width of the uniform distribution is
        # dlambda/0.68.
        self.dlambda = dlambda / 100.0
        # the rebin percentage refers to the full width of the bins. You have to
        # multiply this value by 0.68 to get the equivalent contribution to the
        # resolution function.
        self.rebin = rebin / 100.0
        self.wavelength_bins = calculate_wavelength_bins(
            lo_wavelength, hi_wavelength, rebin
        )
        bin_centre = 0.5 * (
            self.wavelength_bins[1:] + self.wavelength_bins[:-1]
        )

        # angular deviation due to gravity
        # --> no correction for gravity affecting width of angular resolution
        elevations = 0
        if gravity:
            speeds = general.wavelength_velocity(bin_centre)
            # trajectories through slits for different wavelengths
            trajectories = pm.find_trajectory(L12 / 1000.0, 0, speeds)
            # elevation at sample
            elevations = pm.elevation(
                trajectories, speeds, (L12 + L2S) / 1000.0
            )

        # nominal Q values
        self.q = general.q(angle - elevations, bin_centre)

        # keep a tally of the direct and reflected beam
        self.direct_beam = np.zeros((self.wavelength_bins.size - 1))
        self.reflected_beam = np.zeros((self.wavelength_bins.size - 1))

        # beam monitor counts for normalisation
        self.bmon_direct = 0
        self.bmon_reflect = 0

        self.gravity = gravity

        # wavelength generator
        self.force_uniform_wavelength = force_uniform_wavelength
        if force_uniform_wavelength:
            self.spectrum_dist = uniform(
                loc=lo_wavelength - 1, scale=hi_wavelength - lo_wavelength + 1
            )
        else:
            a = PN("PLP0000711.nx.hdf")
            q, i, di = a.process(
                normalise=False,
                normalise_bins=False,
                rebin_percent=0.5,
                lo_wavelength=max(0, lo_wavelength - 1),
                hi_wavelength=hi_wavelength + 1,
            )
            q = q.squeeze()
            i = i.squeeze()
            self.spectrum_dist = SpectrumDist(q, i)

        self.force_gaussian = force_gaussian

        # angular resolution generator, based on a trapezoidal distribution
        # The slit settings are the optimised set typically used in an
        # experiment. dtheta/theta refers to the FWHM of a Gaussian
        # approximation to a trapezoid.

        # stores the q vectors contributing towards each datapoint
        self._res_kernel = {}
        self._min_samples = 0

        self.dtheta = dtheta / 100.0
        self.footprint = footprint
        self.angle = angle
        self.L2S = L2S
        self.L12 = L12
        s1, s2 = general.slit_optimiser(
            footprint,
            self.dtheta,
            angle=angle,
            L2S=L2S,
            L12=L12,
            verbose=False,
        )
        div, alpha, beta = general.div(s1, s2, L12=L12)
        self.div, self.s1, self.s2 = s1, s2, div

        if force_gaussian:
            self.angular_dist = norm(scale=div / 2.3548)
        else:
            self.angular_dist = trapz(
                c=(alpha - beta) / 2.0 / alpha,
                d=(alpha + beta) / 2.0 / alpha,
                loc=-alpha,
                scale=2 * alpha,
            )
Пример #9
0
 def test_q(self):
     q = general.q(1., 2.)
     assert_almost_equal(q, 0.1096567037)
Пример #10
0
 def test_q(self):
     q = general.q(1., 2.)
     assert_almost_equal(q, 0.1096567037)