def test_find_trajectory(self): # the angle needs to be 45 degrees for a projectile launched # at 300 m/s with a range of x=9177, y = 0 traj = pm.find_trajectory(9177.4459168013527, 0, 300.) assert_almost_equal(traj, 45., 5) # Test for theta != 0 # Assume parabolic path passes through known peak height. # peak height = v_0y ** 2 / 2 / g # the angle needs to be 45 degrees for a projectile passing # through x = 9177 / 2, arctan(peak_height / 9177 * 2) peak_height = (300 * np.sin(np.radians(45.)))**2 / 2. / constants.g assert_almost_equal(peak_height, 2294.3614792003382) theta = np.degrees(np.arctan(peak_height / 9177.4459168013527 * 2.)) traj = pm.find_trajectory(9177.4459168013527 / 2., theta, 300.) assert_almost_equal(traj, 45., 5)
def test_find_trajectory(self): # the angle needs to be 45 degrees for a projectile launched # at 300 m/s with a range of x=9177, y = 0 traj = pm.find_trajectory(9177.4459168013527, 0, 300.) assert_almost_equal(traj, 45., 5) # Test for theta != 0 # Assume parabolic path passes through known peak height. # peak height = v_0y ** 2 / 2 / g # the angle needs to be 45 degrees for a projectile passing # through x = 9177 / 2, arctan(peak_height / 9177 * 2) peak_height = (300 * np.sin(np.radians(45.))) ** 2 / 2. / constants.g assert_almost_equal(peak_height, 2294.3614792003382) theta = np.degrees(np.arctan(peak_height / 9177.4459168013527 * 2.)) traj = pm.find_trajectory(9177.4459168013527 / 2., theta, 300.) assert_almost_equal(traj, 45., 5)
def test_parabola_line_intersection_point(self): traj = pm.find_trajectory(3, -0.62, 300.) res = pm.parabola_line_intersection_point(3, -0.62, traj, 300, 0) assert_almost_equal(res[3], -0.6293646114131306) assert_almost_equal(res[2], 0) assert_almost_equal(res[1], pm.y_deflection(traj, 300, 3)) assert_almost_equal(res[0], 3) res = pm.parabola_line_intersection_point(3.1, -0.62, traj, 300, 0.8) assert_(res[0] < 3.1) assert_almost_equal(np.array(res), np.array([3.0988052120901273, -0.033550291159381511, 0.0011947938059390722, -0.6299889176505941]))
def test_parabola_line_intersection_point(self): traj = pm.find_trajectory(3, -0.62, 300.) res = pm.parabola_line_intersection_point(3, -0.62, traj, 300, 0) assert_almost_equal(res[3], -0.6293646114131306) assert_almost_equal(res[2], 0) assert_almost_equal(res[1], pm.y_deflection(traj, 300, 3)) assert_almost_equal(res[0], 3) res = pm.parabola_line_intersection_point(3.1, -0.62, traj, 300, 0.8) assert_(res[0] < 3.1) assert_almost_equal( np.array(res), np.array([ 3.0988052120901273, -0.033550291159381511, 0.0011947938059390722, -0.6299889176505941 ]))
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)
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)
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, )