Skip to content

Latest commit

 

History

History
408 lines (304 loc) · 13.3 KB

spectra.rst

File metadata and controls

408 lines (304 loc) · 13.3 KB

Spectra

These are generic objects for storing 1D/2D spectra e.g. density of states or S(Q,w) maps.

Metadata

All spectra objects have a metadata attribute. By default it is an empty dictionary, but it can contain keys/values to help describe the contained spectra. The keys should be strings and values should be only strings or integers. Note there are some special 'functional' keys, see the metadata docstring below for each specific spectrum class for details.

Two Spectrum1D objects can be added together (provided they have the same x_data axes) with the + operator. This will add their y_data and return a single Spectrum1D. Note that any metadata key/value pairs that aren't common to both spectra will be omitted from the new object. For example:

.. testsetup:: si_o_pdos

   fnames = ['si_pdos.json', 'o_pdos.json']
   for fname in fnames:
       shutil.copyfile(
           get_data_path('spectrum1d', 'toy_band.json'), fname)

.. testcode:: si_o_pdos

  from euphonic import Spectrum1D

  pdos_si = Spectrum1D.from_json_file('si_pdos.json')
  pdos_o = Spectrum1D.from_json_file('o_pdos.json')
  total_dos = pdos_si + pdos_o

A 1D spectrum can be broadened using :py:meth:`Spectrum1D.broaden <euphonic.spectra.Spectrum1D.broaden>`, which broadens along the x-axis and returns a new :ref:`Spectrum1D` object. It can broaden with either a Gaussian or Lorentzian and requires a broadening FWHM in the same type of units as x_data. For example:

.. testsetup:: dos

   fnames = 'dos.json'
   shutil.copyfile(
       get_data_path('spectrum1d', 'toy_quartz_dos.json'), fnames)

.. testcode:: dos

  from euphonic import ureg, Spectrum1D

  dos = Spectrum1D.from_json_file('dos.json')
  fwhm = 1.5*ureg('meV')
  dos_broaden = dos.broaden(fwhm, shape='lorentz')

Variable-width broadening can be achieved by passing a Python "Callable" (i.e. function) that accepts a position value as input and returns a broadening width. For example, the energy-dependent resolution of the TOSCA spectrometer is typically treated as a Gaussian following the quadratic function \sigma = 2.5 + 0.005 \omega + 0.0000001 \omega where sigma and omega are the standard deviation and energy transfer in cm:math:^{-1}. To apply this resolution function to a Spectrum1D we package the polynomial into a suitable Callable:

.. testcode:: dos

  from numpy.polynomial import Polynomial

  def tosca_resolution(energy):
      poly_in_wavenumber = Polynomial([2.5, 0.005, 1e-7])
      return poly_in_wavenumber(energy.to('1/cm').magnitude) * ureg('1/cm')
  dos_tosca = dos.broaden(tosca_resolution, shape='gauss', width_convention='std')

See :ref:`Plotting <plotting>`

.. autoclass:: euphonic.spectra.Spectrum1D
   :inherited-members:
   :members:

This is an object for storing multiple 1D spectra which share the same x-axis, e.g. bands in a dispersion plot.

If you have multiple Spectrum1D objects with the same x_data, they can be grouped together into a Spectrum1DCollection object. For example:

.. testcode:: si_o_pdos

  from euphonic import Spectrum1D, Spectrum1DCollection

  pdos_si = Spectrum1D.from_json_file('si_pdos.json')
  pdos_o = Spectrum1D.from_json_file('o_pdos.json')

  pdos_collection = Spectrum1DCollection.from_spectra([pdos_si, pdos_o])

Two Spectrum1DCollection objects can be added together (provided they have the same x_data axes) with the + operator. This will concatenate their y_data, returning a single Spectrum1DCollection that contains all the y_data of both objects. For example:

.. testsetup:: coh_incoh_pdos

   fnames = ['coherent_pdos.json', 'incoherent_pdos.json']
   for fname in fnames:
       shutil.copyfile(
           get_data_path('spectrum1dcollection', 'quartz_666_coh_pdos.json'), fname)

.. testcode:: coh_incoh_pdos

  from euphonic import Spectrum1DCollection

  coh_pdos = Spectrum1DCollection.from_json_file('coherent_pdos.json')
  incoh_pdos = Spectrum1DCollection.from_json_file('incoherent_pdos.json')
  all_pdos = coh_pdos + incoh_pdos

A Spectrum1DCollection can be indexed just like a list to obtain a specific spectrum as a Spectrum1D, or a subset of spectra as another Spectrum1DCollection, for example, to plot only specific spectra:

.. testcode:: coh_incoh_pdos

  from euphonic import Spectrum1DCollection
  from euphonic.plot import plot_1d

  spec1d_col = Spectrum1DCollection.from_json_file('coherent_pdos.json')
  # Plot the 1st spectrum
  spec1d_0 = spec1d_col[0]
  fig1 = plot_1d(spec1d_0)

  # Plot the 2nd - 5th spectra
  spec1d_col_1_5 = spec1d_col[1:5]
  fig2 = plot_1d(spec1d_col_1_5)

A collection of 1D spectra can also be broadened using :py:meth:`Spectrum1DCollection.broaden <euphonic.spectra.Spectrum1DCollection.broaden>`, which broadens each spectrum individually, giving the same result as using :py:meth:`Spectrum1D.broaden <euphonic.spectra.Spectrum1D.broaden>` on each contained spectrum.

You can group and sum specific spectra from a Spectrum1DCollection based on their metadata using :py:meth:`Spectrum1DCollection.group_by <euphonic.spectra.Spectrum1DCollection.group_by>`. For example, if you have a collection spec1d_col containing 8 spectra with the following metadata:

.. testsetup:: metadata_spec

   import numpy
   from euphonic import Spectrum1DCollection, ureg
   fake_metadata =  {'line_data': [
       {'index': 1, 'species': 'Si', 'weighting': 'coherent'},
       {'index': 2, 'species': 'Si', 'weighting': 'coherent'},
       {'index': 3, 'species': 'O', 'weighting': 'coherent'},
       {'index': 4, 'species': 'O', 'weighting': 'coherent'},
       {'index': 1, 'species': 'Si', 'weighting': 'incoherent'},
       {'index': 2, 'species': 'Si', 'weighting': 'incoherent'},
       {'index': 3, 'species': 'O', 'weighting': 'incoherent'},
       {'index': 4, 'species': 'O', 'weighting': 'incoherent'}]}
   spec1d_col = Spectrum1DCollection(
       numpy.arange(5)*ureg('meV'),
       numpy.ones((len(fake_metadata['line_data']), 5))*ureg('1/meV'),
       metadata=fake_metadata)

>>> spec1d_col.metadata
{'line_data': [{'index': 1, 'species': 'Si', 'weighting': 'coherent'}, {'index': 2, 'species': 'Si', 'weighting': 'coherent'}, {'index': 3, 'species': 'O', 'weighting': 'coherent'}, {'index': 4, 'species': 'O', 'weighting': 'coherent'}, {'index': 1, 'species': 'Si', 'weighting': 'incoherent'}, {'index': 2, 'species': 'Si', 'weighting': 'incoherent'}, {'index': 3, 'species': 'O', 'weighting': 'incoherent'}, {'index': 4, 'species': 'O', 'weighting': 'incoherent'}]}

If you want to group and sum spectra that have the same weighting, pass 'weighting' to the group_by method. This would produce a collection containing 2 spectra with the following metadata (the species and index metadata are not common across all the grouped spectra, so have been discarded):

>>> weighting_pdos = spec1d_col.group_by('weighting')
>>> weighting_pdos.metadata
{'line_data': [{'weighting': 'coherent'}, {'weighting': 'incoherent'}]}

You can also group by multiple keys, for example you can group and sum spectra that have both the same weighting and species, producing the following metadata:

>>> weighting_species_pdos = spec1d_col.group_by('weighting', 'species')
>>> weighting_species_pdos.metadata
{'line_data': [{'weighting': 'coherent', 'species': 'Si'}, {'species': 'O', 'weighting': 'coherent'}, {'weighting': 'incoherent', 'species': 'Si'}, {'weighting': 'incoherent', 'species': 'O'}]}

You can select specific spectra from a Spectrum1DCollection based on their metadata using :py:meth:`Spectrum1DCollection.select <euphonic.spectra.Spectrum1DCollection.select>`. For example, if you have a collection spec1d_col containing 8 spectra with the following metadata:

>>> spec1d_col.metadata
{'line_data': [{'index': 1, 'species': 'Si', 'weighting': 'coherent'}, {'index': 2, 'species': 'Si', 'weighting': 'coherent'}, {'index': 3, 'species': 'O', 'weighting': 'coherent'}, {'index': 4, 'species': 'O', 'weighting': 'coherent'}, {'index': 1, 'species': 'Si', 'weighting': 'incoherent'}, {'index': 2, 'species': 'Si', 'weighting': 'incoherent'}, {'index': 3, 'species': 'O', 'weighting': 'incoherent'}, {'index': 4, 'species': 'O', 'weighting': 'incoherent'}]}

If you want to select only the spectra where weighting='coherent', use the select method, which would create a collection containing 4 spectra, with the following metadata:

>>> coh_pdos = spec1d_col.select(weighting='coherent')
>>> coh_pdos.metadata
{'weighting': 'coherent', 'line_data': [{'index': 1, 'species': 'Si'}, {'index': 2, 'species': 'Si'}, {'index': 3, 'species': 'O'}, {'index': 4, 'species': 'O'}]}

You can also select multiple values for a specific key. For example, to select spectra where index=1 or index=2:

>>> coh_or_incoh_pdos = spec1d_col.select(index=[1, 2])
>>> coh_or_incoh_pdos.metadata
{'species': 'Si', 'line_data': [{'index': 1, 'weighting': 'coherent'}, {'index': 1, 'weighting': 'incoherent'}, {'index': 2, 'weighting': 'coherent'}, {'index': 2, 'weighting': 'incoherent'}]}

You can also select by multiple key/values. To select only the spectra with weighting='coherent' and species='Si':

>>> coh_si_pdos = spec1d_col.select(weighting='coherent', species='Si')
>>> coh_si_pdos.metadata
{'weighting': 'coherent', 'species': 'Si', 'line_data': [{'index': 1}, {'index': 2}]}

All spectra in a Spectrum1DCollection can be summed with :py:meth:`Spectrum1DCollection.sum <euphonic.spectra.Spectrum1DCollection.sum>`. This produces a single Spectrum1D object.

See :ref:`Plotting <plotting>`

.. autoclass:: euphonic.spectra.Spectrum1DCollection
   :inherited-members:
   :members:

A 2D spectrum can be broadened using :py:meth:`Spectrum2D.broaden <euphonic.spectra.Spectrum2D.broaden>`, which broadens along either or both of the x/y-axes and returns a new :ref:`Spectrum2D` object. It can broaden with either a Gaussian or Lorentzian and requires a broadening FWHM in the same type of units as x_data/y_data for broadening along the x/y-axis respectively. For example:

.. testsetup:: sqw

   fnames = 'sqw.json'
   shutil.copyfile(
       get_data_path('spectrum2d', 'lzo_57L_bragg_sqw.json'), fnames)

.. testcode:: sqw

  from euphonic import ureg, Spectrum2D

  sqw = Spectrum2D.from_json_file('sqw.json')
  x_fwhm = 0.05*ureg('1/angstrom')
  y_fwhm = 1.5*ureg('meV')
  sqw_broaden = sqw.broaden(x_width=x_fwhm, y_width=y_fwhm, shape='lorentz')

See :ref:`Plotting <plotting>`

Inelastic neutron-scattering (INS) experiments are often performed on time-of-flight (ToF) spectrometers where a wide ToF range yields a wide range of energy transfers which are measured simultaneously. In "direct geometry" the incident energy is fixed (e.g. by a Fermi chopper) while in "indirect geometry" the scattered energy is fixed (e.g. by scattering from an "analyser" crystal). Conservation laws allow the overall energy and momentum transfer to be determined for a given scattering angle and crystal orientation. In powder measurements, the crystal orientation is not needed so the kinematic limits --- the accessible (q, \omega) range --- determined by the conservation laws are given solely by these instrument parameters.

The function :py:func:`euphonic.spectra.apply_kinematic_constraints <euphonic.spectra.apply_kinematic_constraints>` applies these limits to a powder-averaged :ref:`Spectrum2D` object with appropriate dimensions (i.e. the x- and y-axes represent |q| and \omega respectively). Inaccessible data values are set to NaN; in Matplotlib colour maps this will leave them unset.

Sample values for INS spectrometers
Facility Instrument E_i / meV E_f / meV 2\theta / {}^\circ
ILL LAGRANGE   4.5 10--90
ISIS LET 1--25   5--140
ISIS MAPS 15--2000   3--60
ISIS MARI 7--1000   3--135
ISIS MERLIN 7--2000   3--135
ILL PANTHER 76, 112, 150   5--136
ISIS TOSCA   3.97 45, 135
.. autofunction:: euphonic.spectra.apply_kinematic_constraints

.. autoclass:: euphonic.spectra.Spectrum2D
   :members:
   :inherited-members: