def test_convert_tof_to_wavelength_no_scatter(): # scatter=True and scatter=False only differ in how Ltotal is computed. tof = make_test_data(coords=('tof', 'Ltotal'), dataset=True) no_scatter = scn.convert(tof, origin='tof', target='wavelength', scatter=False) scatter = scn.convert(tof, origin='tof', target='wavelength', scatter=True) assert sc.identical(no_scatter, scatter)
def test_convert_dataset_vs_dataarray(origin, target): inputs = make_dataset_in(origin) expected = scn.convert(inputs, origin=origin, target=target, scatter=True) result = sc.Dataset( data={ name: scn.convert( data.copy(), origin=origin, target=target, scatter=True) for name, data in inputs.items() }) for name, data in result.items(): assert sc.identical(data, expected[name])
def test_convert_binned_convert_slice(target): tof = make_test_data(coords=('tof', 'Ltotal', 'two_theta'))['tof', 0].copy() tof.data = make_tof_binned_events() original = tof.copy() full = scn.convert(tof, origin='tof', target=target, scatter=True) sliced = scn.convert(tof['spectrum', 1:2], origin='tof', target=target, scatter=True) assert sc.identical(sliced, full['spectrum', 1:2]) assert sc.identical(tof, original)
def process_event_data(file, lambda_binning): """ Load and reduce event data (used for Vanadium and Empty instrument) file: Nexus file to be loaded and its data reduced in this script lambda_binning: lambda_min, lamba_max, number_of_bins """ # load nexus file event_data = scn.load(file, advanced_geometry=True, load_pulse_times=False, mantid_args={'LoadMonitors': True}) # ################################ # Monitor correction # extract monitor and convert from tof to wavelength mon4_lambda = scn.convert(event_data.attrs['monitor4'].values, 'tof', 'wavelength', scatter=False) mon4_smooth = smooth_data(mon4_lambda, dim='wavelength', NPoints=40) del mon4_lambda # ################################ # vana and EC # convert to lambda event_lambda = scn.convert(event_data, 'tof', 'wavelength', scatter=True) # normalize to monitor lambda_min, lambda_max, number_bins = lambda_binning edges_lambda = sc.Variable(['wavelength'], unit=sc.units.angstrom, values=np.linspace(lambda_min, lambda_max, num=number_bins)) mon_rebin = sc.rebin(mon4_smooth, 'wavelength', edges_lambda) del mon4_smooth event_lambda_norm = event_lambda.bins / sc.lookup(func=mon_rebin, dim='wavelength') del mon_rebin, event_lambda return event_lambda_norm
def test_convert_coords_vs_attributes(): with_coords = make_test_data(coords=('tof', 'Ltotal'), dataset=True) with_attrs = with_coords.copy() with_attrs['counts'].attrs['Ltotal'] = with_attrs.coords.pop('Ltotal') from_coords = scn.convert(with_coords, origin='tof', target='wavelength', scatter=True) from_attrs = scn.convert(with_attrs, origin='tof', target='wavelength', scatter=True) assert sc.identical(from_coords, from_attrs)
def test_convert_integer_input_elastic(target): da_float_coord = make_test_data(coords=('tof', 'L1', 'L2', 'two_theta')) da_int_coord = da_float_coord.copy() da_int_coord.coords['tof'] = da_float_coord.coords['tof'].astype('int64') res = scn.convert(da_int_coord, origin='tof', target=target, scatter=True) expected = scn.convert(da_float_coord, origin='tof', target=target, scatter=True) assert res.coords[target].dtype == sc.DType.float64 assert sc.allclose(res.coords[target], expected.coords[target], rtol=1e-15 * sc.units.one)
def test_convert_Q_to_wavelength(): tof = make_test_data(coords=('tof', 'Ltotal', 'two_theta')) Q = scn.convert(tof, origin='tof', target='Q', scatter=True) del Q.attrs['wavelength'] wavelength_from_Q = scn.convert(Q, origin='Q', target='wavelength', scatter=True) wavelength_from_tof = scn.convert(tof, origin='tof', target='wavelength', scatter=True) assert sc.allclose(wavelength_from_Q.coords['wavelength'], wavelength_from_tof.coords['wavelength'], rtol=1e-14 * sc.units.one)
def test_convert_beam_length_and_angle(target, make_ref): original = make_test_data(coords=('incident_beam', 'scattered_beam')) converted = scn.convert(original, origin='position', target=target, scatter=True) assert sc.identical(converted.meta[target], make_ref())
def test_unit_conversion(self): import mantid.simpleapi as mantid eventWS = self.base_event_ws ws = mantid.Rebin(eventWS, 10000, PreserveEvents=False) tmp = scn.mantid.convert_Workspace2D_to_data_array(ws) target_tof = tmp.coords['tof'] ws = mantid.ConvertUnits(InputWorkspace=ws, Target="Wavelength", EMode="Elastic") converted_mantid = scn.mantid.convert_Workspace2D_to_data_array(ws) da = scn.mantid.convert_EventWorkspace_to_data_array( eventWS, load_pulse_times=False) da = sc.histogram(da, bins=target_tof) d = sc.Dataset(data={da.name: da}) converted = scn.convert(d, 'tof', 'wavelength', scatter=True) self.assertTrue( np.all(np.isclose(converted_mantid.values, converted[""].values))) self.assertTrue( np.all( np.isclose( converted_mantid.coords['wavelength'].values, converted.coords['wavelength'].values, )))
def test_convert_tof_to_dspacing(): tof = make_test_data(coords=('tof', 'Ltotal', 'two_theta'), dataset=True) dspacing = scn.convert(tof, origin='tof', target='dspacing', scatter=True) check_tof_conversion_metadata(dspacing, 'dspacing', sc.units.angstrom) # Rule of thumb (https://www.psi.ch/niag/neutron-physics): # v [m/s] = 3956 / \lambda [ Angstrom ] tof_in_seconds = tof.coords['tof'] * 1e-6 # Spectrum 0 is 11 m from source # 2d sin(theta) = n \lambda # theta = 45 deg => d = lambda / (2 * 1 / sqrt(2)) for val, t in zip(dspacing.coords['dspacing']['spectrum', 0].values, tof_in_seconds.values): np.testing.assert_almost_equal(val, 3956.0 / (11.0 / t) / math.sqrt(2.0), val * 1e-3) # Spectrum 1 # sin(2 theta) = 0.1/(L-10) L = 10.0 + math.sqrt(1.0 * 1.0 + 0.1 * 0.1) lambda_to_d = 1.0 / (2.0 * math.sin(0.5 * math.asin(0.1 / (L - 10.0)))) for val, t in zip(dspacing.coords['dspacing']['spectrum', 1].values, tof_in_seconds.values): np.testing.assert_almost_equal(val, 3956.0 / (L / t) * lambda_to_d, val * 1e-3)
def test_mantid_convert_tof_to_direct_energy_transfer(): efixed = 1000 * sc.Unit('meV') in_ws = make_workspace('tof', emode='Direct', efixed=efixed) out_mantid = mantid_convert_units(in_ws, 'energy_transfer', emode='Direct', efixed=efixed) in_da = scn.mantid.from_mantid(in_ws) out_scipp = scn.convert(data=in_da, origin='tof', target='energy_transfer', scatter=True) # The conversion consists of multiplications and additions, thus the relative error # changes with the inputs. In this case, small tof yields a large error due to # the 1/tof**2 factor in the conversion. # rtol is chosen to account for linearly changing tof in the input data. assert sc.allclose( out_scipp.coords['energy_transfer'], out_mantid.coords['energy_transfer'], rtol=sc.linspace( 'energy_transfer', 1e-6, 1e-10, out_scipp.coords['energy_transfer'].sizes['energy_transfer'])) assert sc.identical(out_scipp.coords['spectrum'], out_mantid.coords['spectrum'])
def test_convert_non_tof(origin, target, target_unit, keep_tof): tof = make_test_data(coords=('tof', 'Ltotal', 'two_theta'), dataset=True) original = scn.convert(tof, origin='tof', target=origin, scatter=True) if not keep_tof: del original['counts'].attrs['tof'] converted = scn.convert(original, origin=origin, target=target, scatter=True) check_tof_conversion_metadata(converted, target, target_unit) converted_from_tof = scn.convert(tof, origin='tof', target=target, scatter=True) assert sc.allclose(converted.coords[target], converted_from_tof.coords[target], rtol=1e-14 * sc.units.one)
def test_convert_beam_length_no_scatter(): original = make_test_data(coords=('position', 'source_position')) converted = scn.convert(original, origin='position', target='Ltotal', scatter=False) expected = sc.norm(make_position() - make_source_position()) assert sc.identical(converted.coords['Ltotal'], expected)
def test_convert_scattering_conversion_fails_with_noscatter_mode(): tof = make_test_data(coords=('tof', 'Ltotal', 'two_theta'), dataset=True) scn.convert(tof, origin='tof', target='dspacing', scatter=True) # no exception with pytest.raises(RuntimeError): scn.convert(tof, origin='tof', target='dspacing', scatter=False) wavelength = scn.convert(tof, origin='tof', target='wavelength', scatter=True) scn.convert(wavelength, origin='wavelength', target='Q', scatter=True) with pytest.raises(RuntimeError): scn.convert(wavelength, origin='wavelength', target='Q', scatter=False)
def test_convert_beams(target): def check_positions(data): assert 'sample_position' not in data.coords assert ('source_position' in data.coords) == (target == 'scattered_beam') assert ('position' in data.coords) == (target == 'incident_beam') # A single sample position. original = make_test_data(coords=('position', 'sample_position', 'source_position')) converted = scn.convert(original, origin='position', target=target, scatter=True) check_positions(converted) assert sc.identical( converted.coords[target], make_incident_beam() if target == 'incident_beam' else make_scattered_beam()) # Two sample positions. original = make_test_data(coords=('position', 'source_position')) original.coords['sample_position'] = sc.vectors(dims=['spectrum'], values=[[1.0, 0.0, 0.2], [2.1, -0.3, 1.4]], unit='m') converted = scn.convert(original, origin='position', target=target, scatter=True) check_positions(converted) if target == 'incident_beam': assert sc.allclose(converted.coords['incident_beam'], sc.vectors(dims=['spectrum'], values=[[1.0, 0.0, 10.2], [2.1, -0.3, 11.4]], unit='m'), rtol=1e-14 * sc.units.one) if target == 'scattered_beam': assert sc.allclose(converted.coords['scattered_beam'], sc.vectors(dims=['spectrum'], values=[[0.0, 0.0, -0.2], [-2.0, 0.3, -0.4]], unit='m'), rtol=1e-14 * sc.units.one)
def test_convert_input_unchanged(): inputs = make_test_data(coords=('tof', 'Ltotal'), dataset=True) original = inputs.copy(deep=True) result = scn.convert(inputs, origin='tof', target='wavelength', scatter=True) assert not sc.identical(result, original) assert sc.identical(inputs, original)
def test_convert_tof_to_energy_transfer_direct_indirect_are_distinct(): tof_direct = make_test_data(coords=('tof', 'L1', 'L2'), dataset=True) tof_direct.coords['incident_energy'] = 22.0 * sc.units.meV direct = scn.convert(tof_direct, origin='tof', target='energy_transfer', scatter=True) tof_indirect = make_test_data(coords=('tof', 'L1', 'L2'), dataset=True) tof_indirect.coords['final_energy'] = 22.0 * sc.units.meV indirect = scn.convert(tof_indirect, origin='tof', target='energy_transfer', scatter=True) assert not sc.allclose(direct.coords['energy_transfer'], indirect.coords['energy_transfer'], rtol=0.0 * sc.units.one, atol=1e-11 * sc.units.meV)
def test_convert_tof_to_energy_transfer_indirect(): tof = make_test_data(coords=('tof', 'L1', 'L2'), dataset=True) with pytest.raises(RuntimeError): scn.convert(tof, origin='tof', target='energy_transfer', scatter=True) ef = 25.0 * sc.units.meV tof.coords['final_energy'] = ef indirect = scn.convert(tof, origin='tof', target='energy_transfer', scatter=True) check_tof_conversion_metadata(indirect, 'energy_transfer', sc.units.meV) t = tof.coords['tof'] t0 = sc.to_unit(tof.coords['L2'] * sc.sqrt(m_n / 2 / ef), t.unit) assert sc.all(t0 < t).value # only test physical region here ref = sc.to_unit(m_n / 2 * (tof.coords['L1'] / (t - t0))**2, ef.unit).rename_dims({'tof': 'energy_transfer'}) - ef assert sc.allclose(indirect.coords['energy_transfer'], ref, rtol=sc.scalar(1e-13))
def test_convert_with_factor_type_promotion(): tof = make_test_data(coords=('tof', 'L1', 'L2', 'two_theta')) tof.coords['tof'] = sc.array(dims=['tof'], values=[4000, 5000, 6100, 7300], unit='us', dtype='float32') for target in TOF_TARGET_DIMS: res = scn.convert(tof, origin='tof', target=target, scatter=True) assert res.coords[target].dtype == sc.DType.float32 for key in ('incident_energy', 'final_energy'): inelastic = tof.copy() inelastic.coords[key] = sc.scalar(35, dtype=sc.DType.float32, unit=sc.units.meV) res = scn.convert(inelastic, origin='tof', target='energy_transfer', scatter=True) assert res.coords['energy_transfer'].dtype == sc.DType.float32
def test_convert_tof_to_energy_elastic_fails_if_inelastic_params_present(): # Note these conversions fail only because they are not implemented. # It should definitely be possible to support this. tof = make_test_data(coords=('tof', 'L1', 'L2'), dataset=True) scn.convert(tof, origin='tof', target='energy', scatter=True) tof.coords['incident_energy'] = 2.1 * sc.units.meV with pytest.raises(RuntimeError): scn.convert(tof, origin='tof', target='energy', scatter=True) del tof.coords['incident_energy'] scn.convert(tof, origin='tof', target='energy', scatter=True) tof.coords['final_energy'] = 2.1 * sc.units.meV with pytest.raises(RuntimeError): scn.convert(tof, origin='tof', target='energy', scatter=True)
def test_convert_tof_to_Q(): tof = make_test_data(coords=('tof', 'Ltotal', 'two_theta'), dataset=True) wavelength = scn.convert(tof, origin='tof', target='wavelength', scatter=True) Q_from_tof = scn.convert(tof, origin='tof', target='Q', scatter=True) Q_from_wavelength = scn.convert(wavelength, origin='wavelength', target='Q', scatter=True) check_tof_conversion_metadata(Q_from_tof, 'Q', sc.units.one / sc.units.angstrom) check_tof_conversion_metadata(Q_from_wavelength, 'Q', sc.units.one / sc.units.angstrom) # wavelength is intermediate in this case and thus kept but not in the other case. assert sc.identical(Q_from_tof, Q_from_wavelength) # Rule of thumb (c): # v [m/s] = 3956 / \lambda [ Angstrom ] tof_in_seconds = tof.coords['tof'] * 1e-6 # Spectrum 0 is 11 m from source # Q = 4pi sin(theta) / lambda # theta = 45 deg => Q = 2 sqrt(2) pi / lambda for val, t in zip(Q_from_wavelength.coords['Q']['spectrum', 0].values, tof_in_seconds.values): np.testing.assert_almost_equal( val, 2.0 * math.sqrt(2.0) * math.pi / (3956.0 / (11.0 / t)), val * 1e-3) # Spectrum 1 # sin(2 theta) = 0.1/(L-10) L = 10.0 + math.sqrt(1.0 * 1.0 + 0.1 * 0.1) lambda_to_Q = 4.0 * math.pi * math.sin(math.asin(0.1 / (L - 10.0)) / 2.0) for val, t in zip(Q_from_wavelength.coords['Q']['spectrum', 1].values, tof_in_seconds.values): np.testing.assert_almost_equal(val, lambda_to_Q / (3956.0 / (L / t)), val * 1e-3)
def test_convert_binned_events_converted(target): tof = make_test_data(coords=('Ltotal', 'two_theta'), dataset=True) del tof['counts'] tof['events'] = make_tof_binned_events() # Construct events with all coords. binned_tof = tof['events'].copy() for name in ('Ltotal', 'two_theta'): binned_tof.bins.coords[name] = sc.bins_like(binned_tof, tof.coords[name]) dense_tof = binned_tof.bins.constituents['data'] expected = scn.convert(dense_tof, origin='tof', target=target, scatter=True) for intermediate in ('Ltotal', 'two_theta'): expected.attrs.pop(intermediate, None) expected.coords.pop(intermediate, None) expected = sc.bins(**{**binned_tof.bins.constituents, 'data': expected}) res = scn.convert(tof, origin='tof', target=target, scatter=True) assert sc.identical(res['events'].data, expected)
def test_convert_slice(target): tof = make_test_data(coords=('tof', 'position', 'sample_position', 'source_position'), dataset=True) expected = scn.convert(tof['counts'], origin='tof', target=target, scatter=True)['spectrum', 0].copy() assert sc.identical( scn.convert(tof['counts']['spectrum', 0].copy(), origin='tof', target=target, scatter=True), expected) # Converting slice of item is same as item of converted slice assert sc.identical( scn.convert(tof['counts']['spectrum', 0].copy(), origin='tof', target=target, scatter=True).data, scn.convert(tof['spectrum', 0].copy(), origin='tof', target=target, scatter=True)['counts'].data)
def test_convert_integer_input_inelastic(input_energy, input_dtypes): da_float_coord = make_test_data(coords=('tof', 'L1', 'L2', 'two_theta')) da_float_coord.coords[input_energy] = sc.scalar(35, dtype=sc.DType.float64, unit=sc.units.meV) da_int_coord = da_float_coord.copy() for i, name in enumerate(('tof', input_energy)): da_int_coord.coords[name] = da_float_coord.coords[name].astype( input_dtypes[i]) res = scn.convert(da_int_coord, origin='tof', target='energy_transfer', scatter=True) expected = scn.convert(da_float_coord, origin='tof', target='energy_transfer', scatter=True) assert res.coords['energy_transfer'].dtype == sc.DType.float64 assert sc.allclose(res.coords['energy_transfer'], expected.coords['energy_transfer'], rtol=1e-14 * sc.units.one)
def test_convert_tof_to_energy_transfer_indirect_unphysical(): tof = make_test_data(coords=('tof', 'L1', 'L2'), dataset=True) ef = 25.0 * sc.units.meV tof.coords['final_energy'] = ef t0 = sc.to_unit(tof.coords['L2'] * sc.sqrt(m_n / ef), sc.units.s) coord, is_unphysical = make_unphysical_tof(t0, tof) tof.coords['tof'] = coord result = scn.convert(tof, origin='tof', target='energy_transfer', scatter=True) assert sc.identical(sc.isnan(result.coords['energy_transfer']), is_unphysical)
def test_mantid_convert_tof_to_dspacing(): in_ws = make_workspace('tof') out_mantid = mantid_convert_units(in_ws, 'dspacing') in_da = scn.mantid.from_mantid(in_ws) out_scipp = scn.convert(data=in_da, origin='tof', target='dspacing', scatter=True) assert sc.allclose(out_scipp.coords['dspacing'], out_mantid.coords['dspacing'], rtol=1e-8 * sc.units.one) assert sc.identical(out_scipp.coords['spectrum'], out_mantid.coords['spectrum'])
def test_inelastic_unit_conversion(self): import mantid.simpleapi as mantid eventWS = self.base_event_ws ws_deltaE = mantid.ConvertUnits(eventWS, Target='DeltaE', EMode='Direct', EFixed=3) ref = scn.from_mantid(ws_deltaE) da = scn.from_mantid(eventWS) # Boost and Mantid use CODATA 2006. This test passes if we manually # change the implementation to use the old constants. Alternatively # we can correct for this by scaling L1^2 or L2^2, and this was also # confirmed in C++. Unfortunately only positions are accessible to # correct for this here, and due to precision issues with # dot/norm/sqrt this doesn't actually fix the test. We additionally # exclude low TOF region, and bump relative and absolute accepted # errors from 1e-8 to 1e-5. m_n_2006 = 1.674927211 m_n_2018 = 1.67492749804 e_2006 = 1.602176487 e_2018 = 1.602176634 scale = (m_n_2006 / m_n_2018) / (e_2006 / e_2018) da.coords['source_position'] *= np.sqrt(scale) da.coords['position'] *= np.sqrt(scale) low_tof = da.bins.constituents['data'].coords[ 'tof'] < 49000.0 * sc.units.us da.coords['incident_energy'] = 3.0 * sc.units.meV da = scn.convert(da, 'tof', 'energy_transfer', scatter=True) assert sc.all( sc.isnan(da.coords['energy_transfer']) | sc.isclose(da.coords['energy_transfer'], ref.coords['energy_transfer'], atol=1e-8 * sc.units.meV, rtol=1e-8 * sc.units.one)).value assert sc.all( low_tof | sc.isnan(da.bins.constituents['data'].coords['energy_transfer']) | sc.isclose( da.bins.constituents['data'].coords['energy_transfer'], ref.bins.constituents['data'].coords['energy_transfer'], atol=1e-5 * sc.units.meV, rtol=1e-5 * sc.units.one)).value
def test_mantid_convert_tof_to_energy(): in_ws = make_workspace('tof') out_mantid = mantid_convert_units(in_ws, 'energy') in_da = scn.mantid.from_mantid(in_ws) out_scipp = scn.convert(data=in_da, origin='tof', target='energy', scatter=True) # Mantid reverses the order of the energy dim. mantid_energy = sc.empty_like(out_mantid.coords['energy']) assert mantid_energy.dims[1] == 'energy' mantid_energy.values = out_mantid.coords['energy'].values[..., ::-1] assert sc.allclose(out_scipp.coords['energy'], mantid_energy, rtol=1e-7 * sc.units.one) assert sc.identical(out_scipp.coords['spectrum'], out_mantid.coords['spectrum'])
def test_convert_tof_to_wavelength(): tof = make_test_data(coords=('tof', 'Ltotal'), dataset=True) wavelength = scn.convert(tof, origin='tof', target='wavelength', scatter=True) check_tof_conversion_metadata(wavelength, 'wavelength', sc.units.angstrom) # Rule of thumb (https://www.psi.ch/niag/neutron-physics): # v [m/s] = 3956 / \lambda [ Angstrom ] tof_in_seconds = tof.coords['tof'] * 1e-6 # Spectrum 0 is 11 m from source for val, t in zip(wavelength.coords['wavelength']['spectrum', 0].values, tof_in_seconds.values): np.testing.assert_almost_equal(val, 3956.0 / (11.0 / t), val * 1e-3) # Spectrum 1 L = 10.0 + math.sqrt(1.0 * 1.0 + 0.1 * 0.1) for val, t in zip(wavelength.coords['wavelength']['spectrum', 1].values, tof_in_seconds.values): np.testing.assert_almost_equal(val, 3956.0 / (L / t), val * 1e-3)
def test_convert_tof_to_energy_elastic(): tof = make_test_data(coords=('tof', 'Ltotal'), dataset=True) energy = scn.convert(tof, origin='tof', target='energy', scatter=True) check_tof_conversion_metadata(energy, 'energy', sc.units.meV) tof_in_seconds = sc.to_unit(tof.coords['tof'], 's') # e [J] = 1/2 m(n) [kg] (l [m] / tof [s])^2 joule_to_mev = sc.to_unit(1.0 * sc.Unit('J'), sc.units.meV).value neutron_mass = sc.to_unit(m_n, sc.units.kg).value # Spectrum 0 is 11 m from source for val, t in zip(energy.coords['energy']['spectrum', 0].values, tof_in_seconds.values): np.testing.assert_almost_equal( val, joule_to_mev * neutron_mass / 2 * (11 / t)**2, val * 1e-3) # Spectrum 1 L = 10.0 + math.sqrt(1.0 * 1.0 + 0.1 * 0.1) for val, t in zip(energy.coords['energy']['spectrum', 1].values, tof_in_seconds.values): np.testing.assert_almost_equal( val, joule_to_mev * 0.5 * neutron_mass * (L / t)**2, val * 1e-3)