Dans_Diffraction.functions_crystallography (version 4.0.1)
index
c:\users\grp66007\onedrive - diamond light source ltd\pythonprojects\dans_diffraction\dans_diffraction\functions_crystallography.py

Module: functions_crystallography.py
 
By Dan Porter, PhD
Diamond
2018
 
Usage: 
    - Run this file in an interactive console
    OR
    - from Dans_Diffraction import functions_crystallography as fc
 
Version 4.0.1
 
Version History:
09/07/15 0.1    Version History started.
30/10/17 1.0    Many updates
06/01/18 2.0    Renamed functions_crystallography.py
02/03/18 2.1    Removed call to tkinter
05/04/18 2.2    Added invert_sym
02/05/18 2.3    Added comparison of lower case elements and symmetry values
04/06/18 2.4    Added new checks to readcif, corrected atomic_properties for python3/numpy1.14
08/06/18 2.5    Corrected a few errors and added some more comments
06/03/19 2.6    Added print_atom_properties
14/08/19 2.7    Added new Dans Element Properties file with extra comment line, new functions added
03/04/20 2.8    Updated attenuation to work with arrays of elements
19/04/20 2.9    Added write_cif, made alterations to readcif for speed and readability, added spacegroup
01/05/20 3.0    Updated atom_properties, now have atomic properties above 92 with warning. Some changes to readcif.
05/05/20 3.0.1  Further changes to readcif. Changed method of symmetry_ops2magnetic. Added str2element
12/05/20 3.1.0  More readcif changes, added atomic_scattering_factor, element_charge_string, split_compound
26/05/20 3.1.1  Updated magnetic space groups, added magnetic positions (was only generators), added write_mcif
09/06/20 3.2    Updated gen_sym_mat, symmetry_ops2magnetic, added sym_op_det
03/09/20 3.2.1  Updated cif_symmetry to allow for missing magnetic centring
21/01/21 3.3    Added xray_scattering_factor_resonant and xray_dispersion_corrections functions added
26/01/21 3.4    Added xray attenuation, transmission and refractive index
31/03/21 3.5    Added point groups, gen_sym_unique
10/06/21 3.6    Corrected mistake in DebyeWaller function. Added x-ray scattering factors from Waasmaier and Kirfel
15/11/21 3.7    Added diffractometer orientation commands from Busing & Levy, H. You
12/01/22 3.7.1  Added gen_sym_axial_vector
23/04/22 3.7.2  Corrected magnitude of Q in magnetic_structure_factors
07/05/23 3.8.0  Added electron_scattering_factors and electron wavelength formula
22/05/23 3.8.1  Added wyckoff_label, find_spacegroup
02/07/23 3.8.2  Added wavelength options to several functions, plut DeBroglie wavelength function
26/09/24 3.9.0  Added complex neutron scattering lengths for isotopes from package periodictable
06/11/24 4.0.0  Fixed error with triclinic bases, added function_lattice.
20/11/24 4.0.1  Added alternative neutron scattering length table
 
Acknoledgements:
    April 2020  Thanks to ChunHai Wang for helpful suggestions in readcif!
    May 2023    Thanks to Carmelo Prestipino for adding electron scattering factors
    Sep 2024    Thanks to thamnos for suggestion to add complex neutron scattering lengths
    Oct 2024    Thanks to Lee Richter for pointing out the error in triclinic basis definition
 
@author: DGPorter

 
Modules
       
Dans_Diffraction.functions_general
Dans_Diffraction.functions_lattice
json
numpy
os
re
sys

 
Functions
       
Bmatrix(basis_vectors)
Calculate the Busing and Levy B matrix from real space basis vectors, with units of 2pi
"choose the x-axis parallel to a*, the y-axis in the plane of a* and b*, and the z-axis perpendicular to that plane"
From: W. R. Busing and H. A. Levy, Acta Cryst. (1967). 22, 457-464
"Angle calculations for 3- and 4-circle X-ray and neutron diffractometers"
See also: https://docs.mantidproject.org/nightly/concepts/Lattice.html
 
B = [[b1, b2 * cos(beta3), b3 * cos(beta2)],
    [0, b2 * sin(beta3), -b3 * sin(beta2) * cos(alpha1)],
    [0, 0, 1 / a3]]
return 2pi * B  # equivalent to transpose([a*, b*, c*])
 
:param basis_vectors: [3*3] array of basis vectors [a[3], b[3], c[3]]
:return: [3*3] B matrix * 2 pi
Q2hkl(qvec, UVstar)
Index vectors in an orthonal basis with a reciprocal basis
:param qvec: [nx3] array of coordinates in an orthogonal basis in A-1
:param UV: [3x3] array of unit cell vectors [[a1,a2,a3],[b1,b2,b3],[c1,c2,c3]]
:return: [nx3] array of vectors in reciprocal lattice units
RcSp(UV)
Generate reciprocal cell from real space unit vecors
Usage:
UVs = RcSp(UV)
  UV = [[3x3]] matrix of vectors [a,b,c]
UV2latpar(UV)
Convert UV=[a,b,c] to a,b,c,alpha,beta,gamma
 a,b,c,alpha,beta,gamma = UV2latpar(UV)
arrange_atom_order(list_of_elements)
Arrange a list of elements in the correct chemical order
  ['Li','Co','O'] = arrange_atom_order(['Co', 'O', 'Li'])
:param list_of_elements: [list of str]
:return: [list of str]
atom_properties(elements=None, fields=None)
Loads the atomic properties of a particular atom from a database
Atomic properties, scattering lengths, form factors and absorption edges for elements upto and including Uranium.
Values are taken from various online sources, see data/README.md for more details.
 
Usage:
        A = atom_properties() >> returns structured array of all properties for all atoms A[0]['Element']='H'
        A = atom_properties('Co') >> returns structured array of all properties for 1 element
        B = atom_properties('Co','Weight') >> returns regular 1x1 array
        B = atom_properties(['Co','O'],'Weight') >> retruns regular 2x1 array
        A = atom_properties('Co',['a1','b1','a2','b2','a3','b3','a4','b4','c']) >> returns structured array of requested properties
        A = atom_properties(['Co','O'],['Z','Weight']) >> returns 2x2 structured array
 
Available properties:
    A = atom_properties()
    print(A.dtype.names)
    print(a[25]['Element'])
    >> 'Fe'
 
Available information includes:
      Z             = Element number
      Element       = Element symbol
      Name          = Element name
      Group         = Element Group on periodic table
      Period        = Element period on periodic table
      Block         = Element electronic block (s,p,d,f)
      ValenceE      = Number of valence electrons
      Config        = Element electronic orbital configuration
      Radii         = Atomic Radii (pm)
      Weight        = Standard atomic weight (g)
      Coh_b         = Bound coherent neutron scattering length
      Inc_b         = Bound incoherent neutron scattering length
      Nabs          = Neutron absorption coefficient
      Nscat         = Neutron scattering coefficient
      a1            = Electron form factor
      b1            = Electron form factor
      a2            = Electron form factor
      b2            = Electron form factor
      a3            = Electron form factor
      b3            = Electron form factor
      a4            = Electron form factor
      b4            = Electron form factor
      c             = Electron form factor
      j0_A          = Magnetic Form factor
      j0_a          = Magnetic Form factor
      j0_B          = Magnetic Form factor
      j0_b          = Magnetic Form factor
      j0_C          = Magnetic Form factor
      j0_c          = Magnetic Form factor
      j0_D          = Magnetic Form factor
      K             = x-ray absorption edge
      L1            = x-ray absorption edge
      L2            = x-ray absorption edge
      L3            = x-ray absorption edge
      M1...         = x-ray absorption edge
atomic_scattering_factor(element, energy_kev=None)
Read atomic scattering factor table, giving f1+f2 for different energies
From: http://henke.lbl.gov/optical_constants/asf.html
:param element: str or list of str, name of element. If element string includes a number, this will multiply values
:param energy_kev: float or list energy in keV (None to return original, uninterpolated list)
:return: f1, f2, shape dependent on shapes of element and energy_kev:  float, or [ene] or [ele, ene]
attenuation(element_z, energy_kev)
Returns the x-ray mass attenuation, u/p, in cm^2/g
  e.g. A = attenuation(23,np.arange(7,8,0.01)) # Cu
       A = attenuation([23,24,25], 5.6)
       a = attenuation(19,4.5) # K
balance_atom_charge(list_of_elements, occupancy=None)
Determine the default charges and assign remaining charge to
unspecified elements
:param list_of_elements: list of element symbols ['Co', 'Fe', ...]
:param occupancy: None or list of occupancies, same length as above
:return: [list of charges]
biso2uiso(biso)
Convert B isotropic thermal parameters to U isotropic thermal parameters
:param biso: Biso value or array
:return: Uiso value or array
cal2theta(qmag, energy_kev=17.794, wavelength_a=None)
Calculate scattering angle at particular energy in keV from magnitude of the wavevector, |Q|
 twotheta = cal2theta(Qmag, energy_kev=17.794)
 - equivalent to -
 twotheta = 2 * arcsin( qmag * wl / 4pi )
 
:param qmag: float or array of wavevector magnitudes, in inverse-Angstroms
:param energy_kev: float photon energy in keV
:param wavelength_a: float wavelength in Anstrom
:return two-theta angle in degrees (or array if qmag is array)
calc_vol(UV)
Calculate volume in Angstrom^3 from unit vectors
caldspace(twotheta, energy_kev=17.794, wavelength_a=None)
Calculate d-spacing from two-theta
  dspace = caldspace(tth, energy_kev)
   - equivalent to -
  dspace = wl / (2 * sin(theta))
 
:param twotheta: float or array of scattering angles, in degrees
:param energy_kev: float photon energy in keV
:param wavelength_a: float wavelength in Anstrom
:return lattice d-spacing in Anstrom
callattice(twotheta, energy_kev=17.794, hkl=(1, 0, 0))
Calculate cubic lattice parameter, a from reflection two-theta
:param twotheta: Bragg angle, deg
:param energy_kev: energy in keV
:param hkl: reflection (cubic only
:return: float, lattice contant
calqmag(twotheta, energy_kev=17.794, wavelength_a=None)
Calculate |Q| at a particular 2-theta (deg) for energy in keV
  magQ = calqmag(twotheta, energy_kev=17.794)
   - equivalent to -
  qmag = 4 * pi * sin(theta) / wl
 
:param twotheta: float or array of scattering angles, in degrees
:param energy_kev: float photon energy in keV
:param wavelength_a: float wavelength in Anstrom
:return wavevector magnitude in inverse-Angstrom
cif2dict(cifvals)
From a dict of key:value pairs generated from a *.cif file (using readcif),
read standard crystallographic infromation into a crystal dictionary, with keys:
    'filename' - cif filename
    'name' - name of structure
    'unit vector'   - [3x3] array of basis vectors [a,b,c]
    'parent cell'   - [3x3] array of parent basis vectors [a,b,c]
    'symmetry'      - list of string symmetry operations
    'space group'   - Space Group string
    'space group number' - Space group number
    'cif'           - cif dict
    The following are specific to atomis within the unit cell:
    'atom type'     - list of elements e.g. ['Co','O', 'O']
    'atom label'    - list of atom site names e.g. ['Co1, 'O1', 'O2']
    'atom position' - [nx3] array of atomic positions
    'atom occupancy'- [nx1] array of atom site occupancies
    'atom uiso'     - [nx1] array of atom site isotropic thermal parameters
    'atom uaniso'   - [nx6] array of atom site anisotropic thermal parameters
    'mag moment'    - [nx3] array of atom site magnetic moment vectors
    'mag time'      - [nx3] array of atom site magnetic time symmetry
    'misc'] = [element, label, cif_pos, occ, Uiso]
:param cifvals: dict from readcif
:return: dict
cif2table(cif)
Generate Latex table of atomic positions from cif dictionary
:param cif: cif dict from readcif
:return: str
cif_check(cifvals, required_keys=None, bad_value='?')
Returns True if all basic required cif parameter are available and real.
E.G.:
    cifvals = readcif(file.cif)
    if cif_check(cifvals):
        print('File OK')
:param cifvals: dict of cif keys form readcif
:param required_keys: list of key strings, or None for default
:param bad_value: if this value is in the cif key item, return False
:return: bool
cif_symmetry(cifvals)
Read symmetries from a cif dict
:param cifvals:
:return: list(symmetry_operations), list(magnetic_operations), list(time operations)
count_atoms(list_of_elements, occupancy=None, divideby=1, latex=False)
Count atoms in a list of elements, returning condenced list of elements
:param list_of_elements: list of element symbols
:param occupancy: list of occupancies of each element (default=None)
:param divideby: divide each count by this (default=1)
:param latex: False*/True
:return: [list of str]
count_charges(list_of_elements, occupancy=None, divideby=1, latex=False)
Count atoms in a list of elements, returning condenced list of elements with charges
:param list_of_elements: list of element symbols
:param occupancy: list of occupancies of each element (default=None)
:param divideby: divide each count by this (default=1)
:param latex: False*/True
:return: [list of str]
cut2powder(qx, qy, qz, cut)
Convert 2D reciprocal space cut to powder pattern
:param qx: [n,m] array of Q coordinates in x
:param qy: [n,m] array of Q coordinates in y
:param qz: [n,m] array or float of Q coordinates in z
:param cut: [n,m] array of intensities
:return: qm[o], pow[o]
debroglie_energy(wavelength_a, mass_kg)
Calcualte the energy in electronvolts using DeBroglie's formula
  E [keV] = h^2 / (2 * e * mass [kg] * A^2 * lambda^2 [A] * 1e3)
:param wavelength_a: wavelength in Angstroms
:param mass_kg: mass in kg
:return: energy in keV
debroglie_wavelength(energy_kev, mass_kg)
Calcualte the wavelength in Angstroms using DeBroglie's formula
  lambda [A] = h  / sqrt( 2 * mass [kg] * E [keV] * 1e3 * e )
:param energy_kev: energy in keV
:param mass_kg: mass in kg
:return: wavelength in Anstroms
debyewaller(uiso, Qmag=0)
Calculate the debye waller factor for a particular Q
 T = debyewaller(uiso,Qmag=[0])
 
    T = exp( -2*pi^2*Uiso/d^2 )
    T = exp( -Uiso/2 * Q^2 )
default_atom_charge(element)
Returns the default charge value for the element
:param element: str: 'Fe'
:return: int or nan
detector_angle_size(detector_distance_mm=565, height_mm=67, width_mm=34, pixel_size_um=172)
Return the angular spread of the detector
  delta_width, gamma_width, pixel_width = detector_angle_size(distance, height, widht, pixel)
:param detector_distance_mm: Detector distance from sample in mm
:param height_mm: Detector size in the vertical direction, in mm
:param width_mm: Detector size in the horizontal direction, in mm
:param pixel_size_um: Pixel size in microns
:return x_angle: total angular spread in the diffractometer x-axis, in degrees
:return z_angle: total angular spread in the diffractometer z-axis, in degrees
:return pixel_angle: angular spread per pixel, in degrees
detector_coverage(wavelength_a=1.5, resolution=0.1, delta=0, gamma=0, detector_distance_mm=565, height_mm=67, width_mm=34)
Determine volume of reciprocal space covered by the detector based on angular spread
:param wavelength_a: Incicent beam wavelength, in Angstroms
:param resolution: Incident beam resolution, in inverse-Angstroms
:param delta: float or array, detector vertical rotation
:param gamma: float or array, detector horizontal rotation
:param detector_distance_mm: Detector distance from sample in mm
:param height_mm: Detector size in the vertical direction, in mm
:param width_mm: Detector size in the horizontal direction, in mm
:return: volume in inverse-Angstroms
detector_rotate(detector_position_mm=(0, 1000.0, 0), delta=0.0, gamma=0.0, labmatrix=array([[1., 0., 0.], [0., 1., 0.], [0., 0., 1.]]))
Return a position array for a vector rotated by delta and gamma, like a detector along [0,1,0]
:param detector_position_mm: detector position from sample in mm
:param delta: vertical rotation about z-axis in Deg
:param gamma: horizontal rotation about x-axis in Deg
:param labmatrix: [3*3] orientation matrix to convert to alternative basis
:return: array
detector_volume(wavelength_a=1.5, resolution=0.1, phi=0, chi=0, eta=0, mu=0, delta=0, gamma=0, detector_distance_mm=565, height_mm=67, width_mm=34, lab=array([[1., 0., 0.], [0., 1., 0.], [0., 0., 1.]]))
Determine volume of reciprocal space covered by the detector, return list of voxels covered by detector
  total_volume, hkl = detector_volume(1.5, 0.1, chi=90, eta=30, delta=60)
The returned list of coordinates (hkl) is defined in the sample frame, where each coordinate specifies a voxel
in reciprocal space with size resolution(inverse-Anstroms) ** 3
As such, the total volume of reciprocal space measured is:
    total_volume = len(hkl) * fc.wavevector(energy_kev=energy_range_ev / 1000.) ** 3
 
:param wavelength_a: Incicent beam wavelength, in Angstroms
:param resolution: Incident beam resolution, in inverse-Angstroms (determines segmentation of calculation)
:param phi: float, phi-axis rotation about sample-z axis
:param chi: float, chi-axis rotation about sample-y' axis
:param eta: float, eta-axis rotation about sample-z'' axis
:param mu: float, mu-axis rotation about sample-x''' axis
:param delta: float, detector rotation vertical rotation
:param gamma: float, detector rotation horizontal rotation
:param detector_distance_mm: Detector distance from sample in mm
:param height_mm: Detector size in the vertical direction, in mm
:param width_mm: Detector size in the horizontal direction, in mm
:param lab: [3x3] Transformation matrix to change the lab coordinate system
:return total_volume: float, total volume of reciprocal space in inverse Angstroms
:return hkl: [nx3] array of voxels covered by detector, integer coordinates in the sample frame
detector_volume_scan(wavelength_a=1.5, resolution=0.1, phi=0, chi=0, eta=0, mu=0, delta=0, gamma=0, detector_distance_mm=565, height_mm=67, width_mm=34, lab=array([[1., 0., 0.], [0., 1., 0.], [0., 0., 1.]]))
Return total reciprocal space volume covered by scanning an axis with a detector
  total, hkl, overlaps = detector_volume_scan(1.5, 0.1, phi=np.arange(-180,180))
Rotation axes can be specified as either floats or arrays, where all arrays must have the same length.
 
:param wavelength_a: Incicent beam wavelength, in Angstroms
:param resolution: Incident beam resolution, in inverse-Angstroms (determines segmentation of calculation)
:param phi: float or array, phi-axis about sample-z axis
:param chi: float or array, chi-axis about sample-y' axis
:param eta: float or array, eta-axis about sample-z'' axis
:param mu: float or array, mu-axis about sample-x''' axis
:param delta: float or array, detector vertical rotation
:param gamma: float or array, detector horizontal rotation
:param detector_distance_mm: Detector distance from sample in mm
:param height_mm: Detector size in the vertical direction, in mm
:param width_mm: Detector size in the horizontal direction, in mm
:param lab: [3x3] Transformation matrix to change the lab coordinate system
:return total: float, total volume covered, in cubic inverse Angstroms
:return hkl: [nx3] array of voxels covered by detector, integer coordinates in the sample frame
:return overlaps: [n] array of the number of times each voxel has been repeatedly measured (0 = only measurred once)
diff2lab(vec, lab=None)
Convert between diffractometer frame and lab frame
Lab frame according to Diamond I16 beamline
Diffractometer frame according to Fig. 1, H. You, J. Appl. Cryst 32 (1999), 614-623
  z-axis : axis parallel to the phi rotation axis when all angles 0 (towards wall (+x) in lab frame)
  x-axis : vector normal to phi axis where phi=0 (toward ceiling (+y) in lab frame)
  y-axis : vector normal to x,z axes (parallel to beam (+z) in lab frame)
:param vec: [3*n] array of vectors
:param lab: [3*3] transformation matrix, None=((0,1,0),(0,0,1),(1,0,0))
:return: [3*n] array of vectors
diff6circle2hkl(ub, phi=0, chi=0, eta=0, mu=0, delta=0, gamma=0, energy_kev=None, wavelength=1.0, lab=None)
Return [h,k,l] position of diffractometer axes with given UB and energy
:param ub: [3*3] array UB orientation matrix following Busing & Levy
:param phi: float sample angle in degrees
:param chi: float sample angle in degrees
:param eta: float sample angle in degrees
:param mu: float sample angle in degrees
:param delta: float detector angle in degrees
:param gamma: float detector angle in degrees
:param energy_kev: float energy in KeV
:param wavelength: float wavelength in A
:param lab: [3*3] lab transformation matrix
:return: [h,k,l]
diff6circlek(delta, gamma, energy_kev=None, wavelength=1.0, lab=None)
Calcualte incident and final wavevectors in diffractometer axis using detector angles
:param delta: float angle in degrees in vertical direction (about diff-z)
:param gamma: float angle in degrees in horizontal direction (about diff-x)
:param energy_kev: float energy in KeV
:param wavelength: float wavelength in A
:param lab: [3*3] lab transformation matrix
:return: [1*3], [1*3] : ki, kf
diff6circleq(delta, gamma, energy_kev=None, wavelength=1.0, lab=None)
Calcualte wavevector in diffractometer axis using detector angles
:param delta: float angle in degrees in vertical direction (about diff-z)
:param gamma: float angle in degrees in horizontal direction (about diff-x)
:param energy_kev: float energy in KeV
:param wavelength: float wavelength in A
:param lab: [3*3] lab transformation matrix
:return: [1*3]
diffractometer_Q(eta, delta, energy_kev=8.0)
Calculate wavevector transfer, Q for diffractometer within the scattering plane.
   Qx,Qy = diffractometer_Q(eta,delta,energy_kev)
   eta = angle (deg) of sample
   delta = angle (deg) of detector
   energy_kev = energy in keV
 
   Coordinate system (right handed):
    x - direction along beam, +ve is from source to sample
    y - direction verticle
    z - direction horizontal
 
   Note: Currently only in the verticle geometry!
diffractometer_rotation(phi=0, chi=0, eta=0, mu=0)
Generate the 6-axis diffracometer rotation matrix
  R = M * E * X * P
Also called Z in H. You, J. Appl. Cryst 32 (1999), 614-623
The diffractometer coordinate system has the convention (all angles zero):
    x-axis points vertically, perpendicular to beam (mu is about x)
    y-axis points along the direction of the beam
    z-axis points along the phi axis, perpendicular to x and y
The vertical scattering plane is in the y-x axis
The horizontal scattering plane is in the y-z axis
   vec' = np.dot(diffractometer_rotation(phi, chi, eta, mu), vec)
   vec must be 1D or column vector (3*n)
:param phi: float angle in degrees, left handed roation about z'''
:param chi: float angle in degrees, right handed rotation about y''
:param eta: float angle in degrees, left handed rotation about z'
:param mu: float angle in degrees, right handed rotation about x
:return:  [3*3] array
diffractometer_step(wavelength_a=1.5, resolution=0.1, phi=0, chi=0, eta=0, mu=0, delta=0, gamma=0, lab=array([[1., 0., 0.], [0., 1., 0.], [0., 0., 1.]]))
Determine the euler angle steps required to scan through reciprocal space at the required resolutuion
    phi_step, chi_step, eta_step, mu_step = diffractometer_step(1.5, 0.1, chi=90, eta=20, delta=40)
The returned step size will provide a rotation that moves the wavevector transfer by approximately the resolutuion.
Any steps returning 0 are unsensitive to the rotation in this position
:param wavelength_a: Incicent beam wavelength, in Angstroms
:param resolution: float, combined resolution required, in inverse Angstroms
:param phi: float, phi-axis about sample-z axis
:param chi: float, chi-axis about sample-y' axis
:param eta: float, eta-axis about sample-z'' axis
:param mu: float, mu-axis about sample-x''' axis
:param delta: float, detector vertical rotation
:param gamma: float, detector horizontal rotation
:param lab: [3x3] Transformation matrix to change the lab coordinate system
:returns: phi_step, chi_step, eta_step, mu_step
dspace2q(dspace)
Calculate d-spacing from |Q|
     Qmag = q2dspace(dspace)
electron_energy(wavelength_a)
Calcualte the electron energy in electronvolts using DeBroglie's formula
  E [eV] ~ 1.5 / lambda^2 [nm]
:param wavelength_a: electron wavelength in Angstroms
:return: energy in eV
electron_scattering_factor(element, Qmag=0)
Read X-ray scattering factor table, calculate f(|Q|)
Uses the coefficients for analytical approximation to the scattering factors 
  Peng, L. M.; Acta Crystallogr A  1996, 52 (2), 257–276. 
  Peng, L.-M.  Acta Cryst A 1998, 54 (4), 481–485. 
Qff = xray_scattering_factor(element, Qmag=[0])
:param element: [n*str] list or array of elements
:param Qmag: [m] array of wavevector distance, in A^-1
:return: [m*n] array of scattering factors
electron_wavelength(energy_ev)
Calcualte the electron wavelength in Angstroms using DeBroglie's formula
  lambda [nm] ~ sqrt( 1.5 / E [eV] )
:param energy_ev: electron energy in eV
:return: wavelength in Anstroms
element_charge_string(symbol, occupancy=1.0, charge=0.0, latex=False)
Return formatted string of element with occupancy and charge
:param symbol: str - element string
:param occupancy: float - element occupancy or number of elements
:param charge: float - element charge
:param latex: if True, returns string formatted with latex tags
:return: str
element_name(element=None)
Returns the element name
:param element: str
:return: int
element_symbol(element_Z=None)
Returns the element sympol for element_Z
:param element_z: int or array or None for all elements
:return: str
element_z(element)
Returns the element number Z
:param element: str
:return: int
energy2wave(energy_kev)
Converts energy in keV to wavelength in A
 wavelength_a = energy2wave(energy_kev)
 lambda [A] = h*c/E = 12.3984 / E [keV]
euler_moment(mxmymz, uv)
Convert moment mxmymz coordinates from cif into eulerian basis
:param mxmymz: [nx3] array, units of Bohr magneton, directed along a,b,c
:param uv: [3x3] array, basis vectors [a,b,c]
:return: moments [nx3] array, units of Bohr magneton, directed along x,y,z
euler_unit_vector(uvw, uv)
Convert vector in a specific basis to a cartesian basis and normalise to a unit vector
:param uvw: [nx3] array as [[u,v,w]]
:param uv: [3x3], basis vectors [a,b,c]
:return: [nx3] array xyz/|xyz|, where x,y,z = u*a+v*b+w*c
filter_transmission(chemical_formula, energy_kev, density, thickness_um=100)
Calculate transmission of x-ray through a slab of material
Equivalent to https://henke.lbl.gov/optical_constants/filter2.html
Based on formulas from: Henke, Gullikson, and Davis, Atomic Data and Nuclear Data Tables 54 no.2, 181-342 (July 1993)
:param chemical_formula: str molecular formula
:param energy_kev: float or array, x-ray energy in keV
:param density: float density in g/cm^3
:param thickness_um: slab thickness in microns
:return: float or array
find_spacegroup(sg_symbol)
Find a spacegroup based on the identifying symbol
:param sg_symbol: str, e.g. 'Fd-3m'
:return: spacegroup dict or None if not found
fitincell(uvw)
Set all fractional coodinates between 0 and 1
Usage:
  uvw = fitincell(uvw)
  uvw = [[nx3]] array of fractional vectors [u,v,w]
genHKL(H, K=None, L=None)
Generate HKL array with all combinations within range specified
Usage:
  HKL = genHKL(max)
   int max = generates each h,k,l from -max to max
  HKL = genHKL([min,max])
   int min, int max = generates h,l,l from min to max
  HKL = genHKL([hmin,hmax],[kmin,kmax],[lmin,lmax])
   hmin,hmax = min and max values for h, k and l respectivley
 
  array HKL = [[nx3]] array of [h,k,l] vectors
 
E.G.
  HKL = genHKL(-3)
E.G.
  HKL = genHKL([-1,1])
E.G.
  HKL = genHKL([-2,2],[0,3],[1,1])
gen_sym_axial_vector(sym_ops, x, y, z)
 Transform axial vector by symmetry operations
Usage:
  uvw = gen_symcen_pos(sym_ops,cen_ops,x,y,z)
  sym_ops = [n*'x,y,z'] array of string symmetry operations
  cen_ops = [m*'x,y,z'] array of string centring operations
  x,y,z = fractional coordinates of atomic posiiton to be modified by symmetry
  uvw = [[nx3]] array of symmetry defined factional coordinates [u,v,w]
 
E.G.
  uvw = gen_symcen_pos(['x,y,z','y,-x,z+1/2'],['x,y,z','x+1/3,y+1/3,z'],0.1,0.2,0.3)
  uvw >> [[0.1,0.2,0.3] , [0.433,0.533,0.3] , [0.2,-0.1,0.8] , [0.533,-0.233,0.8]]
:param sym_ops:
:param x:
:param y:
:param z:
:return:
gen_sym_mat(sym_ops)
Generate transformation matrix from symmetry operation
Currently very ugly but it seems to work
Tested in Test/test_gen_sym_mat - found to be fast and reliable.
sym_mat = gen_sym_mat(['x,y,z','y,-x,z+1/2'])
sym_mat[0] = [[ 1.,  0.,  0.,  0.],
              [ 0.,  1.,  0.,  0.],
              [ 0.,  0.,  1.,  0.],
              [ 0.,  0.,  0.,  1.]])
sym_mat[1] = [[ 0. ,  1. ,  0. ,  0. ],
              [-1. ,  0. ,  0. ,  0. ],
              [ 0. ,  0. ,  1. ,  0.5],
              [ 0.,   0.,   0.,   1.]]
gen_sym_pos(sym_ops, x, y, z)
Generate positions from symmetry operations
Usage:
  uvw = gen_sym_pos(sym_ops,x,y,z)
  sym_ops = [n*'x,y,z'] array of string symmetry operations
  x,y,z = fractional coordinates of atomic posiiton to be modified by symmetry
  uvw = [[nx3]] array of symmetry defined factional coordinates [u,v,w]
 
E.G.
  uvw = gen_sym_pos(['x,y,z','y,-x,z+1/2'],0.1,0.2,0.3)
  uvw >> [[0.1,0.2,0.3] , [0.2,-0.1,0.8]]
gen_sym_ref(sym_ops, hkl)
Generate symmetric reflections given symmetry operations
    symhkl = gen_sym_ref(sym_ops,h,k,l)
gen_sym_unique(sym_ops, x, y, z, cen_ops=None)
Generate positions from symmetry operations with idential positions removed
Usage:
  uvw = gen_sym_unique(sym_ops,x,y,z)
E.G.
  uvw = gen_sym_unique(['x,y,z','y,-x,z+1/2','x,y,z'],0.1,0.2,0.3)
  uvw >> [[0.1,0.2,0.3] , [0.2,-0.1,0.8]]
:param sym_ops: list of str - symmetry operations
:param x: float
:param y: float
:param z: float
:param cen_ops: Optional - list of str for centring operations
:return: array of positions
gen_symcen_ops(sym_ops, cen_ops)
Build complete list of symmetry operations from symmetry and centring vectors
Usage:
  ops = gen_symcen_ops(sym_ops,cen_ops)
  sym_ops = [n*'x,y,z'] array of string symmetry operations
  cen_ops = [m*'x,y,z'] array of string centring operations
  ops = [n*m*'x,y,z'] array of string symmetry operations
 
E.G.
  ops = gen_symcen_pos(['x,y,z','y,-x,z+1/2'],['x,y,z','x+1/3,y+1/3,z'])
  >> ops = ['x,y,z','x+1/3,y+1/3,z','y,-x,z+1/2','y+1/3,-x+1/3,z+1/2']
gen_symcen_pos(sym_ops, cen_ops, x, y, z)
Generate positions from symmetry and centring operations
Usage:
  uvw = gen_symcen_pos(sym_ops,cen_ops,x,y,z)
  sym_ops = [n*'x,y,z'] array of string symmetry operations
  cen_ops = [m*'x,y,z'] array of string centring operations
  x,y,z = fractional coordinates of atomic posiiton to be modified by symmetry
  uvw = [[nx3]] array of symmetry defined factional coordinates [u,v,w]
 
E.G.
  uvw = gen_symcen_pos(['x,y,z','y,-x,z+1/2'],['x,y,z','x+1/3,y+1/3,z'],0.1,0.2,0.3)
  uvw >> [[0.1,0.2,0.3] , [0.433,0.533,0.3] , [0.2,-0.1,0.8] , [0.533,-0.233,0.8]]
getenergy()
group_intensities(q_values, intensity, min_overlap=0.01)
Group reflections within the overlap, returning the index max intensity of each group
:param q_values: [1*n] array of floats, position parameter of each reflection
:param intensity: [1*n] array of floats, intensity parameter of each reflection
:param min_overlap: float, how close the reflections are to be grouped
:return: group_idx: [1*m] array of int index
hkl2Q(hkl, UVstar)
Convert reflection indices (hkl) to orthogonal basis in A-1
:param hkl: [nx3] array of reflections
:param UV: [3x3] array of unit cell vectors [[a1,a2,a3],[b1,b2,b3],[c1,c2,c3]]
:return: [nx3] array of wavevectors in an orthogonal basis, in A-1
hkl2Qmag(hkl, UVstar)
Calcualte magnitude of Q from hkl reflection
:param hkl: [nx3] array of reflections
:param UV: [3x3] array of unit cell vectors
:return: [nx1] array of wavevector magnitudes, in A-1
hkl2dspace(hkl, UVstar)
Calcualte d-spacing from hkl reflection
:param hkl: [nx3] array of reflections
:param UV: [3x3] array of reciprocal unit cell vectors
:return: [nx1] array of d-spacing in A
hkl2str(hkl)
Convert hkl to string (h,k,l)
:param hkl:
:return: str '(h,k,l)'
hkl2twotheta(hkl, UVstar, energy_kev=17.794, wavelength_a=None)
Calcualte d-spacing from hkl reflection
:param hkl: [nx3] array of reflections
:param UV: [3x3] array of unit cell vectors
:param energy_kev: float energy in keV
:param wavelength_a: float wavelength in Angstroms
:return: [nx1] array of d-spacing in A
indx(coords, basis_vectors)
Index cartesian coordinates on a lattice defined by basis vectors
Usage (reciprocal space):
    [[h, k, l], ...] = index_lattice([[qx, qy, qz], ...], [a*, b*, c*])
Usage (direct space):
    [u, v, w] = index_lattice([x, y, z], [a, b, c])
 
:param coords: [nx3] array of coordinates
:param basis_vectors: [3*3] array of basis vectors [a[3], b[3], c[3]]
:return: [nx3] array of vectors in units of reciprocal lattice vectors
invert_sym(sym_op)
Invert the sign of the given symmetry operation
Usage:
  new_op = invert_sym(sym_op)
  sym_op = str symmetry operation e.g. 'x,y,z'
  new_op = inverted symmetry
E.G.
  new_op = invert_sym('x,y,z')
  >> new_op = '-x,-y,-z'
labvector(vec, U=None, R=None, LAB=None)
Transform any vector through the orientation, rotation and lab transformations
:param vec: [n*3] array of vectors in the diffractometer frame
:param U: [3*3] oritenation matrix (see umatrix)
:param R: [3x3] rotation matrix (see diffractometer_rotation)
:param LAB: [3x3] transformation matrix between diffractometer frame and lab frame
:return: [n*3] array of Q vectors in the lab coordinate system
labwavevector(hkl, UV, U=None, R=None, LAB=None)
Calculate the lab wavevector using the unit-vector, oritenation matrix and rotation matrix
Returns vectors in the lab coordinate system, by default defined like Diamond Light Source:
  x-axis : away from synchrotron ring, towards wall
  y-axis : towards ceiling
  z-axis : along beam direction
:param hkl: [3xn] array of (h, k, l) reciprocal lattice vectors
:param UV: [3*3] Unit-vector matrix (see latpar2ub_rot)
:param U: [3*3] oritenation matrix (see umatrix)
:param R: [3x3] rotation matrix (see diffractometer_rotation)
:param LAB: [3x3] transformation matrix between diffractometer frame and lab frame
:return: [3xn] array of Q vectors in the lab coordinate system
latpar2uv(*lattice_parameters, **kwargs)
Convert a,b,c,alpha,beta,gamma to UV=[A,B,C]
 UV = latpar2uv(a,b,c,alpha=90.,beta=90.,gamma=120.)
 Vector c is defined along [0,0,1]
 Vector a and b are defined by the angles
latpar2uv_rot(*lattice_parameters, **kwargs)
Convert a,b,c,alpha,beta,gamma to UV=[A,B,C]
 UV = latpar2uv_rot(a,b,c,alpha=90.,beta=90.,gamma=120.)
 Vector b is defined along [0,1,0]
 Vector a and c are defined by the angles
lattice_hkl2dspace(hkl, *lattice_parameters, **kwargs)
Calcualte dspace from lattice parameters
:param hkl: [nx3] array of reflections
:param lattice_parameters: a,b,c,alpha,beta,gamma
:return: float, d-spacing in A
lattice_hkl2twotheta(hkl, *lattice_parameters, energy_kev=17.794, wavelength_a=None, **kwargs)
Calcualte dspace from lattice parameters
:param hkl: [nx3] array of reflections
:param energy_kev: float, radiation energy in keV
:param lattice_parameters: a,b,c,alpha,beta,gamma
:return: float, d-spacing in A
load_pointgroup(pg_number)
Load point group using number
Point Groups:
Triclinic
  1 C1  (    1) GenPos:   1
  2 Ci  (   -1) GenPos:   2
Monoclinic
  3 C2  (    2) GenPos:   2
  4 Cs  (    m) GenPos:   2
  5 C2h (  2/m) GenPos:   4
Orthorhombic
  6 D2  (  222) GenPos:   4
  7 C2v (  mm2) GenPos:   4
  8 D2h (  mmm) GenPos:   8
Tetragonal
  9 C4  (    4) GenPos:   4
 10 S4  (   -4) GenPos:   4
 11 C4h (  4/m) GenPos:   8
 12 D4  (  422) GenPos:   8
 13 C4v (  4mm) GenPos:   8
 14 D2d ( -42m) GenPos:   8
 15 D4h (4/mmm) GenPos:  16
Trigonal
 16 C3  (    3) GenPos:   3
 17 C3i (   -3) GenPos:   6
 18 D3  (  312) GenPos:   6
 19 C3v (  3m1) GenPos:   6
 20 D3d ( -31m) GenPos:  12
Hexagonal
 21 C6  (    6) GenPos:   6
 22 C3h (   -6) GenPos:   6
 23 C6h (  6/m) GenPos:  12
 24 D6  (  622) GenPos:  12
 25 C6v (  6mm) GenPos:  12
 26 D3h ( -6m2) GenPos:  12
 27 D6h (6/mmm) GenPos:  24
Cubic
 28 T   (   23) GenPos:  12
 29 Th  (  m-3) GenPos:  24
 30 O   (  432) GenPos:  24
 31 Td  ( -43m) GenPos:  24
 32 Oh  ( m-3m) GenPos:  48
:param pg_number: int or str, e.g. 'cubic'
:return: dict
magnetic_form_factor(element, Qmag=0.0)
Read Magnetic form factor table, calculate <j0(|Q|)>
Analytical approximation of the magnetic form factor:
    <j0(|Q|)> = A*exp(-a*s^2) + B*exp(-b*s^2) + C*exp(-c*s^2) + D, where s = sin(theta)/lambda in A-1
See more about the approximatio here: https://www.ill.eu/sites/ccsl/ffacts/ffactnode3.html
Note: Currently only J0 terms are used and 5d atoms are not currently included
Usage:
     Qff = read_mff(element,Qmag)
     element = str element name, e.g. 'Co'
     Qmag = magnitude of Q=sin(theta)/lambda in A-1 at which to calcualte, can be a list or array to return multiple values
     Qff = Magnetic form factor for element at Qmag
E.G.
    Qff = read_mff('Co',np.arange(0,4,0.1))
maxHKL(Qmax, UV)
Returns the maximum indexes for given max radius
e.g.
    UVstar = RcSp([[3,0,0],[0,3,0],[0,0,10]]) # A^-1
    Qmax = 4.5 # A^-1
    max_hkl = maxHKL(Qmax,UVstar)
    max_hkl =
    >>> [3,3,8]
molecular_attenuation_length(chemical_formula, energy_kev, density, grazing_angle=90)
Calcualte X-Ray Attenuation Length
Equivalent to: https://henke.lbl.gov/optical_constants/atten2.html
Based on formulas from: Henke, Gullikson, and Davis, Atomic Data and Nuclear Data Tables 54 no.2, 181-342 (July 1993)
:param chemical_formula: str molecular formula
:param energy_kev: float or array, x-ray energy in keV
:param density: float density in g/cm^3
:param grazing_angle: incidence angle relative to the surface, in degrees
:return: float or array, in microns
molecular_reflectivity(chemical_formula, energy_kev, density, grazing_angle)
Calculate the specular reflectivity of a material
From: https://xdb.lbl.gov/Section4/Sec_4-2.html
:param chemical_formula: str molecular formula
:param energy_kev: float or array, x-ray energy in keV
:param density: float, density in g/cm^3
:param grazing_angle: float, incidence angle relative to the surface, in degrees
:return: float or array
molecular_refractive_index(chemical_formula, energy_kev, density)
Calculate Complex Index of Refraction
    n = 1 - (1/2pi)N*r0*lambda^2*(f1+if2) = 1 - Delta - iBeta
Equivalent to: https://henke.lbl.gov/optical_constants/getdb2.html
Based on formulas from: Henke, Gullikson, and Davis, Atomic Data and Nuclear Data Tables 54 no.2, 181-342 (July 1993)
:param chemical_formula: str molecular formula
:param energy_kev: float or array, x-ray energy in keV
:param density: float density in g/cm^3
:return: n(complex), Delta, Beta
molecular_weight(compound_name)
Calculate the molecular weight of given compound
:param compound_name: str elements
:return: float weight in g
neutron_energy(wavelength_a)
Calcualte the neutron energy in milli-electronvolts using DeBroglie's formula
  E [meV] ~ 81.82 / lambda^2 [A]
:param wavelength_a: neutron wavelength in Angstroms
:return: energy in meV
neutron_scattering_length(elements, table='neutron data booklet')
Return neutron scattering length, b, in fm
 
    b = neutron_scattering_length('Co')
 
Now includes complex neutron scattering lengths for isotopes from package periodictable
    b = neutron_scattering_length('7-Li')
 
Lists of elements also allowed
    [bLi, bCo, bO] = neutron_scattering_length(['7-Li', 'Co', 'O'])
 
Default values are extracted from Periodic Table https://github.com/pkienzle/periodictable
 - Values originally from Neutron Data Booklet, by A-J Dianoux, G. Lander (2003), with additions and corrections upto v1.7.0 (2023)
 
Alternative values are also available:
 - ITC Vol. C, Section 4.4.4., By V. F. Sears Table 4.4.4.1 (Jan 1995)
 
:param elements: [n*str] list or array of elements
:param table: name of data table to use, options are 'neutron data booklet' or 'sears'
:return: [n] array of scattering lengths
neutron_wavelength(energy_mev)
Calcualte the neutron wavelength in Angstroms using DeBroglie's formula
  lambda [A] ~ sqrt( 81.82 / E [meV] )
:param energy_mev: neutron energy in meV
:return: wavelength in Anstroms
orbital_configuration(element, charge=None)
Returns the orbital configuration of an element as a list of strings
:param element: str, element name, e.g. 'Fe' or 'Fe3+' or '0.8Fe2+'
:param charge: int, element charge (overwrites charge given in element)
:return: ['1s2', '2s2', '2p6', ...]
orthogonal_axes(x_axis=(1, 0, 0), y_axis=(0, 1, 0))
Returns orthogonal right-handed cartesian axes based on the plane of two non-perpendicular vectors
E.G.
    x,y,z = orthogonal_axes([1,0,0],[0.5,0.5,0])
    >> x = array([1,0,0])
    >> y = array([0,1,0])
    >> z = array([0,0,1])
E.G.
    x,y,z = orthogonal_axes([0,1,0],[0,0,1])
    >> x = array([0,1,0])
    >> y = array([0,0,1])
    >> z = array([1,0,0])
peaks_on_plane(peak_x, peak_y, peak_height, peak_width, max_x, max_y, pixels_width=1001, background=0)
Creates a rectangular grid and adds Gaussian peaks with height "intensity"
:param peak_x: [nx1] array of x coordinates
:param peak_y: [nx1] array of y coordinates
:param peak_height: [nx1] array of peak heights
:param peak_width: [nx1] or float, gaussian width
:param max_x: grid will be created from -max_x : +max_x horizontally
:param max_y: grid will be created from -max_y : +max_y vertically
:param pixels_width: grid will contain pixels horizontally and the number vertically will be scaled
:param background: if >0, add a normaly distributed background with average level = background
:return: x, y, plane
peakwidth_deg(domain_size_a, twotheta, wavelength_a=None, energy_kev=None, instrument_resolution=0)
Return the peak width in degrees for a given two-theta based on the
crystallite domain size and instrument resolution.
  Equivalent to the Scherrer equation with shape factor 1.0
:param domain_size_a: crystallite domain size in Anstroms
:param twotheta: scattering angle in degrees
:param wavelength_a: incident beam wavelength, in Angstroms
:param energy_kev: or, incident beam energy, in keV
:param instrument_resolution: instrument resolution in inverse Anstroms
:return:
photoabsorption_crosssection(elements, energy_kev)
Calculate the photoabsorption cross section from the atomic scattering factors
    u = 2*r0*lambda*f2
See: https://henke.lbl.gov/optical_constants/intro.html
:param elements: str or list of string element symbol, if list, absorption will be summed over elements
:param energy_kev: float or array x-ray energy
:return: float or array [len(energy)] m^2
pointgroups()
Read pointgroup file, return dict
powder_average(tth, energy_kev)
Return the powder average correction for intensities at a given two-theta
    Ip = I0*PA,    PA = 1/|Q|^2
:param tth: two-theta in deg
:param energy_kev: energy in keV
:return: PA
print_atom_properties(elements=None)
Outputs string of stored atomic properties
:param elements: str or list or None
        str: 'Co'
        list: ['Co', 'O']
:return: str
q2dspace(qmag)
Calculate d-spacing from |Q|
     dspace = q2dspace(Qmag)
q2units(qmag, units='tth', energy_kev=None)
Convert |Q| in A^-1 to choice of units
:param qmag: float or array in inverse-Angstrom
:param units: str: 'tth', 'dspace', 'q' (raises ValueError if wrong)
:param energy_kev: float or None
:return: float or array
read_atom_properties_file(filedir)
Reads the text file "Dans Element Properties.txt"
Returns a list of dicts containing atomic properites from multiple sources
  data = read_atom_properties_file(filedir)
  data[22]['Element']
:param filedir: location of "Dans Element Properties.txt"
:return: [dict, ...]
read_neutron_scattering_lengths(table='neutron data booklet')
Read neutron scattering length of element or isotope
Returns table of complex bound coherent neutron scattering length, in fm for elements and isotopes
 
    Natural average for each element given by 'element', e.g. 'Ti'
    Isotope value given by 'weight-element', e.g. '46-Ti'
 
Default values are extracted from Periodic Table https://github.com/pkienzle/periodictable
 - Values originally from Neutron Data Booklet, by A-J Dianoux, G. Lander (2003), with additions and corrections upto v1.7.0 (2023)
 
Alternative values are also available:
 - ITC Vol. C, Section 4.4.4., By V. F. Sears Table 4.4.4.1 (Jan 1995)
 
:param table: name of data table to use, options are 'neutron data booklet' or 'sears'
:return: {'isotope': complex, ...}
readcif(filename=None, debug=False)
Open a Crystallographic Information File (*.cif) file and store all entries in a key:value dictionary
 Looped values are stored as lists under a single key entry
 All values are stored as strings
E.G.
  crys=readcif('somefile.cif')
  crys['_cell_length_a'] = '2.835(2)'
 
crys[key] = value
available keys are give by crys.keys()
 
To debug the file with outputted messages, use:
  cif = readcif(file, debug=True)
 
Some useful standard CIF keywords:
    _cell_length_a
    _cell_length_b
    _cell_length_c
    _cell_angle_alpha
    _cell_angle_beta
    _cell_angle_gamma
    _space_group_symop_operation_xyz
    _atom_site_label
    _atom_site_type_symbol
    _atom_site_occupancy
    _atom_site_U_iso_or_equiv
    _atom_site_fract_x
    _atom_site_fract_y
    _atom_site_fract_z
reciprocal_volume(twotheta, twotheta_min=0, energy_kev=None, wavelength_a=1.5)
Calculate the volume of reciprocal space covered between two scattering angles
:param twotheta: larger scattering angle (Bragg angle, 2theta)
:param twotheta_min: smaller scattering angle
:param energy_kev: photon energy, in keV
:param wavelength_a: wavelength in Angstroms
:return: float, volume, in Angstroms^-3
reflections_on_detector(qxqyqz, energy_kev, det_distance=1.0, det_normal=(0, -1.0, 0), delta=0.0, gamma=0.0, det_width=1.0, det_height=1.0, labmatrix=array([[1., 0., 0.], [0., 1., 0.], [0., 0., 1.]]))
Return relative position of reflection on detector
:param qxqyqz: [nx3] array of reflection coordinates in Q-space [qx, qy, qz]
:param energy_kev: float, Incident beam energy in keV
:param det_distance: float, Detector distance in meters
:param det_normal: (dx,dy,dz) direction of detector normal
:param delta: float, angle in degrees in vertical direction (about diff-z)
:param gamma: float angle in degrees in horizontal direction (about diff-x)
:param det_width: float, detector width along x-axis in meters
:param det_height: float, detector height along z-axis in meters
:param labmatrix: [3x3] orientation array to convert to difference basis
:return uvw: [nx3] array of positions relative to the centre of the detector, or NaN if not incident
:return wavevector_difference: [n] array of wavevector difference in A-1
resolution2energy(res, twotheta=180.0)
Calcualte the energy required to achieve a specific resolution at a given two-theta
:param res: measurement resolution in A (==d-spacing)
:param twotheta: Bragg angle in Degrees
:return: float
rotmatrixx(mu)
Generate diffractometer rotation matrix mu about x-axis (right handed)
Equivalent to ROLL in the Tait–Bryan convention
Equivalent to mu in You et al. diffractometer convention (right handed)
    r = rotmatrix_x(mu)
    vec' = np.dot(r, vec)
vec must be 1D or column vector (3*n)
:param mu: float angle in degrees
:return: [3*3] array
rotmatrixy(chi)
Generate diffractometer rotation matrix chi about y-axis (right handed)
Equivalent to PITCH in the Tait–Bryan convention
Equivalent to -chi in You et al. diffractometer convention (left handed)
    r = rotmatrix_y(chi)
    vec' = np.dot(r, vec)
vec must be 1D or column vector (3*n)
:param chi: float angle in degrees
:return: [3*3] array
rotmatrixz(phi)
Generate diffractometer rotation matrix about z-axis (right handed)
Equivalent to YAW in the Tait-Bryan convention
Equivalent to -phi, -eta, -delta in You et al. diffractometer convention (left handed)
    r = rotmatrix_z(phi)
    vec' = np.dot(r, vec)
vec must be 1D or column vector (3*n)
:param phi: float angle in degrees
:return: [3*3] array
scale_intensity(intensity, wavevector_diff, resolution=0.5)
Scale intensity depending on wavevector difference and resolution
:param intensity: intensity value
:param wavevector_diff: distance from centre in inverse angstroms
:param resolution: resolution in inverse angstroms
:return:
scherrer_fwhm(size, twotheta, wavelength_a=None, energy_kev=None, shape_factor=0.9)
Use the Scherrer equation to calculate the size of a crystallite from a peak width
  L = K * lambda / fwhm * cos(theta)
See: https://en.wikipedia.org/wiki/Scherrer_equation
:param size: crystallite domain size in Angstroms
:param twotheta: 2*Bragg angle, in degrees
:param wavelength_a: incident beam wavelength, in Angstroms
:param energy_kev: or, incident beam energy, in keV
:param shape_factor: dimensionless shape factor, dependent on shape of crystallite
:return: float, peak full-width-at-half-max in degrees
scherrer_size(fwhm, twotheta, wavelength_a=None, energy_kev=None, shape_factor=0.9)
Use the Scherrer equation to calculate the size of a crystallite from a peak width
  L = K * lambda / fwhm * cos(theta)
See: https://en.wikipedia.org/wiki/Scherrer_equation
:param fwhm: full-width-half-maximum of a peak, in degrees
:param twotheta: 2*Bragg angle, in degrees
:param wavelength_a: incident beam wavelength, in Angstroms
:param energy_kev: or, incident beam energy, in keV
:param shape_factor: dimensionless shape factor, dependent on shape of crystallite
:return: float, crystallite domain size in Angstroms
spacegroup(sg_number)
Return a dict of information for a space group
:param sg_number: int space group number, as in the international tables of Crystallography
:return: dict
spacegroup_list(sg_numbers=None)
Return a structured list of space groups with general operations
:param sg_numbers: list of space group numbers (None for whole list)
:return: str
spacegroup_magnetic(msg_number)
Return dict of magnetic spacegroup, given magnetic spacegroup number
:param msg_number: magnetic space group number e.g. 61.433
:return: dict with keys:
    'parent number': sg,
    'space group number': number,
    'space group name': label,
    'setting': setting,
    'type name': type_name,
    'related group': rel_number,
    'related name': rel_name,
    'related setting': rel_setting,
    'operators general': xyz_op,
    'operators magnetic': mxmymz_op,
    'operators time': time,
spacegroup_magnetic_list(sg_number=None, sg_dict=None)
Return str list of magnetic space groups
:param sg_number: space group number (1-230)
:param sg_dict: alternate input, spacegroup dict from spacegroup function
:return: str
spacegroup_subgroups(sg_number)
Return dict of maximal subgroups for spacegroup
:param sg_number: space group number (1-230)
:return: dict
spacegroup_subgroups_list(sg_number=None, sg_dict=None)
Return str of maximal subgroups for spacegroup
:param sg_number: space group number (1-230)
:param sg_dict: alternate input, spacegroup dict from spacegroup function
:return: str
spacegroups()
Return a dict of all space groups
Loaded from json file created from the Bilbao crystallographic server: https://www.cryst.ehu.es/
:return: dict with keys:
    'space group number'
    'space group name html'
    'space group name'
    'web address generator'
    'web address wyckoff'
    'web address site check'     # address % (u, v, w),
    'general positions'
    'magnetic space groups',
    'positions centring'
    'positions multiplicity'
    'positions wyckoff letter'
    'positions symmetry'
    'positions coordinates'
    'subgroup number',
    'subgroup name',
    'subgroup index',
    'subgroup type'
spacegroups_magnetic(sg_number=None, sg_dict=None)
Returns dict of magnetic space groups for required space group
:param sg_number: space group number (1-230) or None to return all magnetic spacegroups
:param sg_dict: alternate input, spacegroup dict from spacegroup function
:return: dict
split_compound(compound_name)
Convert a molecular or compound name into a list of elements and numbers.
Assumes all element multiplications are to the LEFT of the element name.
Values in brackets are multiplied out.
E.g.
    split_compound('Mn0.3(Fe3.6(Co1.2)2)4(Mo0.7Pr44)3')
    >> ['Mn0.3', 'Fe14.4', 'Co9.6', 'Mo2.1', 'Pr132']
:param compound_name: str
:return: list of str
split_element_symbol(element)
From element symbol, split charge and occupancy
  symbol, occupancy, charge = split_element_symbol('Co3+')
Any numbers appended by +/- are taken as charge, otherwise they are counted as occupancy.
e.g.    element     >   symbol  |   occupancy   |   charge
        Co3+            Co          1.              3
        3.2O2-          O           3.2             -2
        fe3             Fe          3               0
:param element: str
:return: symbol: str
:return: occupancy: float
:return: charge: float
str2element(string)
Finds element name in string
sum_sym_ref(symhkl)
Remove equivalent hkl positions and return the number of times each is generated.
sym_mat2str(sym_mat, time=None)
Generate symmetry operation string from matrix
:param sym_mat: array [3x3] or [4x4]
:param time: +/-1 or None
:return: str 'x,y,z(,1)'
sym_op_det(sym_ops)
Return the determinant of a symmetry operation
:param sym_ops: str e.g. 'x,-y,z+1/2' or 'y, x+y, -z, -1' or list of str ['x,y,z',...]
:return: float |det| or list of floats
sym_op_mx(operations)
Convert each operation from x,y,z to mx,my,mz
:param operations: ['x,y,z',]
:return: ['mx,my,mz',]
sym_op_recogniser(sym_ops)
Evaluates symmetry operations and returns str name of operation
 
Doesn't work yet - works on simple systems but not trigonal or hexagonal operations - for example sg167
sym_op_time(operations)
Return the time symmetry of a symmetry operation
:param operations: list(str) e.g. ['x,-y,z+1/2', 'y, x+y, -z, -1']
:return: list, +/-1
symmetry_ops2magnetic(operations)
Convert list of string symmetry operations to magnetic symmetry operations
See Vesta_Manual.pdf Section 9.1.1 "Creation and editing of a vector"
Magnetic symmetry
    µ' = TPMµ
T = Time operators x,y,z,(+1)
P = Parity operator (determinant of M)
M = Symmetry operator without translations
 
:param operations: list of str ['x,y,z',]
:return: list of str ['x,y,z']
ubmatrix(uv, u)
Return UB matrix (units of 2pi)
:param uv: [3*3] unit vector [a,b,c]
:param u: [3*3] orientation matrix in the diffractometer frame
:return: [3*3] array
uiso2biso(uiso)
Convert U isotropic thermal parameters to B isotropic thermal parameters
:param uiso: Uiso value or array
:return: Biso value or array
umatrix(a_axis=None, b_axis=None, c_axis=None)
Define an orientation matrix in the diffractometer frame
Diffractometer frame according to Fig. 1, H. You, J. Appl. Cryst 32 (1999), 614-623
  z-axis : axis parallel to the phi rotation axis when all angles 0 (towards wall (+x) in lab frame)
  x-axis : vector normal to phi axis where phi=0 (toward ceiling (+y) in lab frame)
  y-axis : vector normal to x,z axes (parallel to beam (+z) in lab frame)
:param a_axis: direction of a in the diffractometer frame
:param b_axis: direction of b in the diffractometer frame
:param c_axis: direction of c in the diffractometer frame
:return: [3*3] array
warn(message, category=None, stacklevel=1, source=None)
Issue a warning, or maybe ignore it or raise an exception.
wave2energy(wavelength)
Converts wavelength in A to energy in keV
 energy_kev = wave2energy(wavelength_a)
 Energy [keV] = h*c/L = 12.3984 / lambda [A]
wavevector(energy_kev=None, wavelength=None)
Return wavevector = 2pi/lambda
wavevector_difference(Q, ki)
Returns the difference between reciprocal lattice coordinates and incident wavevector, in A-1
When the difference is zero, the condition for diffraction is met.
  Laue condition: Q = ha* + kb* + lc* == kf - ki
  Elastic scattering:            |kf| = |ki|
  Therefore:          |Q + ki| - |ki| = 0
  Expanding & simplifing: Q.Q + 2Q.ki = 0
:param Q: [[nx3]] array of reciprocal lattice coordinates, in A-1
:param ki: [x,y,z] incident wavevector, in A-1
:return: [nx1] array of difference, in A-1
wavevector_resolution(energy_range_ev=200, wavelength_range_a=None, domain_size_a=1000)
Calculate the combined wavevector resolutuion in inverse Angstroms
Combines instrument incident beam resolution and the mosaic of the crystal
:param energy_range_ev: Incident x-ray beam resolution in eV
:param wavelength_range_a: Incident beam resolution in Angstroms
:param domain_size_a: crysallite size in Angstroms (provides mosic using Scherrer equation)
:return: float, resolutuion in inverse Angstroms
write_cif(cifvals, filename=None, comments=None)
Write .cif file with values from a cif dict,
Only basic items are saved, rather than returning the orginal file
:param cifvals: dict from readcif
:param filename: filename to write (use None to return string)
:param comments: str comments to write to the file top matter
:return: None
write_mcif(cifvals, filename=None, comments=None)
Write magnetic .mcif file with values from a cif dict,
Only basic items are saved, rather than returning the original file
:param cifvals: dict from readcif
:param filename: filename to write (use None to return string)
:param comments: str comments to write to the file top matter
:return: None
wyckoff_labels(spacegroup_dict, UVW)
Return Wyckoff site labels for given positions
:param spacegroup_dict: spacegroup dict from tables, if magnetic spacegroup given, uses the parent spacegroup
:param UVW: n*3 array([u,v,w]) atomic positions in fractional coordinates
:return: list[n] of Wyckoff site letters
xray_attenuation_length(elements, energy_kev, atom_per_volume, grazing_angle=90)
Calcualte the attenuation length in microns
The depth into the material measured along the surface normal where the intensity of
x-rays falls to 1/e of its value at the surface.
  A = sin(th) / n * mu
:param elements: str or list of str, if list - absorption will be summed over elements
:param energy_kev: float array
:param atom_per_volume: float atoms per A^3
:param grazing_angle: incidence angle relative to the surface, in degrees
:return: float or array in microns
xray_dispersion_corrections(elements, energy_kev=None)
Read xray dispersion corrections from atomic scattering factor table, giving f' and f" for different energies
From: http://henke.lbl.gov/optical_constants/asf.html
:param elements: list of str, name of element
:param energy_kev: float or list energy in keV (None to return original, uninterpolated list)
:return: f', f" with shape (len(energy), len(elements))
xray_harmonic_rejection(elements, energy_kev, atom_per_volume, grazing_angle, harmonic=2)
Calculate the specular reflectivity of a material
From: https://xdb.lbl.gov/Section4/Sec_4-2.html
:param elements: str or list of str, if list - absorption will be summed over elements
:param energy_kev: float array
:param atom_per_volume: float atoms per A^3
:param grazing_angle: incidence angle relative to the surface, in degrees
:param harmonic: int harmonic multiple
:return: float or array
xray_reflectivity(elements, energy_kev, atom_per_volume, grazing_angle)
Calculate the specular reflectivity of a material
From: https://xdb.lbl.gov/Section4/Sec_4-2.html
:param elements: str or list of str, if list - absorption will be summed over elements
:param energy_kev: float array
:param atom_per_volume: float atoms per A^3
:param grazing_angle: incidence angle relative to the surface, in degrees
:return: float or array
xray_refractive_index(elements, energy_kev, atom_per_volume)
Calculate the complex index of refraction of a material
  n = 1 - (1/2pi)N*r0*lambda^2*(f1+if2) = 1 - Delta - iBeta
:param elements: str or list of str, if list atomic scattering factors will be summed over elements
:param energy_kev: float array
:param atom_per_volume: float atoms per A^3
:return: complex float or array
xray_scattering_factor(element, Qmag=0)
Read X-ray scattering factor table, calculate f(|Q|)
Uses the coefficients for analytical approximation to the scattering factors - ITC, p578 Table 6.1.1.4
 Qff = xray_scattering_factor(element, Qmag=[0])
:param element: [n*str] list or array of elements
:param Qmag: [m] array of wavevector distance, in A^-1
:return: [m*n] array of scattering factors
xray_scattering_factor_WaasKirf(element, Qmag=0)
Read X-ray scattering factor table, calculate f(|Q|)
Uses the coefficients for analytical approximation to the scattering factors from:
   "Waasmaier and Kirfel, Acta Cryst. (1995) A51, 416-431"
File from https://github.com/diffpy/libdiffpy/blob/master/src/runtime/f0_WaasKirf.dat
 Qff = xray_scattering_factor_WaasKirf(element, Qmag=[0])
:param element: [n*str] list or array of elements
:param Qmag: [m] array of wavevector distance, in A^-1
:return: [m*n] array of scattering factors
xray_scattering_factor_resonant(element, Qmag, energy_kev)
Read X-ray scattering factor table, calculate f(|Q|)
Uses the coefficients for analytical approximation to the scattering factors - ITC, p578
 Qff = xray_scattering_factor_resonant(element, energy_kev, qmag)
 
if resonant_energy = float(energy in keV), the total atomic scattering amplitude will be returned:
    f(|Q|, E) = f0(|Q|) + f'(E) - if''(E)
See:
:param element: [n*str] list or array of elements
:param Qmag: [m] array wavevector distance |Q|, in A^-1
:param energy_kev: [o] array energy in keV
:return: [m*n*o] complex array of scattering factors
xray_transmission(elements, energy_kev, atom_per_volume, distance_um)
Calculate the transimssion of x-rays through a thick slab
  T/T0 = exp(-n*mu*d)
:param elements: str or list of str, if list - absorption will be summed over elements
:param energy_kev: float array
:param atom_per_volume: float atoms per A^3
:param distance_um: float distance in microns
:return: float or array

 
Data
        ATOMFILE = r'C:\Users\grp66007\OneDrive - Diamond Light Sourc...Dans_Diffraction\data\Dans Element Properties.txt'
CIF_REQUIRES = ['_cell_length_a', '_cell_length_b', '_cell_length_c', '_cell_angle_alpha', '_cell_angle_beta', '_cell_angle_gamma', '_atom_site_label', '_atom_site_fract_x', '_atom_site_fract_y', '_atom_site_fract_z']
ELEMENT_LIST = ['Zr', 'Mo', 'Es', 'Eu', 'Fe', 'Fl', 'Fm', 'Fr', 'Ga', 'Gd', 'Ge', 'He', 'Hf', 'Hg', 'Ho', 'Hs', 'In', 'Ir', 'Kr', 'La', ...]
NSLFILE = r'C:\Users\grp66007\OneDrive - Diamond Light Sourc...ction\data\neutron_isotope_scattering_lengths.dat'
NSLFILE_SEARS = r'C:\Users\grp66007\OneDrive - Diamond Light Sourc...data\neutron_isotope_scattering_lengths_sears.dat'
PENGFILE = r'C:\Users\grp66007\OneDrive - Diamond Light Sourc...s\Dans_Diffraction\Dans_Diffraction\data\peng.dat'
datadir = r'C:\Users\grp66007\OneDrive - Diamond Light Sourc...onProjects\Dans_Diffraction\Dans_Diffraction\data'
element_regex = re.compile('Zr|Mo|Es|Eu|Fe|Fl|Fm|Fr|Ga|Gd|Ge|He|...n|Co|Cr|Cs|Cu|Mn|Md|Mt|Rb|Rf|Rh|Rn|Ru|Sb|Sc|Se|S)
regex_sub_element = re.compile('[^a-zA-Z]')