Skip to content

Commit

Permalink
Version 3.0.0
Browse files Browse the repository at this point in the history
Large feature update. New GUI features:
 - New diffractometer mode in GUI
 - New ability to simulate diffractometer detectors
 - updated Scattering GUI including electron and neutron wavelengths
 - Unit converter, space group checker etc.
Updates
 - Correction to powder: Thanks Sergio I. Carvajal!
 - Correction to x-ray dispersion sign, thanks Anuradha Vibhakar!
 - Included new wavelength calculations in functions_crystallography.py
 - other minor improvements
  • Loading branch information
DanPorter committed Jul 2, 2023
1 parent 3554f61 commit 52476b0
Show file tree
Hide file tree
Showing 55 changed files with 7,264 additions and 1,005 deletions.
942 changes: 872 additions & 70 deletions Dans_Diffraction.ipynb

Large diffs are not rendered by default.

9 changes: 5 additions & 4 deletions Dans_Diffraction/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,12 @@ Contains symmetry information about the crystal, including the symmetry operatio
Tabulated spacegroup symmetries are found in [data](./data/README.md).

Parameters:

```python
xtl.Symmetry.spacegroup # spacegroup name
xtl.Symmetry.spacegroup_number # spacegroup number
xtl.Symmetry.symmetry_operations # list of symmetry operations (list of strings e.g. ['-x,-y,-z'])
xtl.Symmetry.symmetry_operations_magnetic # list of magnetic symmetry operations (list of strings e.g. ['-x,-y,-z'])
xtl.Symmetry.spacegroup_symbol # spacegroup name
xtl.Symmetry.spacegroup_number # spacegroup number
xtl.Symmetry.symmetry_operations # list of symmetry operations (list of strings e.g. ['-x,-y,-z'])
xtl.Symmetry.symmetry_operations_magnetic # list of magnetic symmetry operations (list of strings e.g. ['-x,-y,-z'])
```

Selected functions (see internal documentation for more):
Expand Down
24 changes: 20 additions & 4 deletions Dans_Diffraction/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@
Diamond
2017
Version 2.3.0
Last updated: 08/05/23
Version 3.0.0
Last updated: 02/07/23
Version History:
02/03/18 1.0 Version History started.
Expand Down Expand Up @@ -67,6 +67,22 @@
06/01/23 2.2.2 Removed redundent references to np.float
14/01/23 2.2.3 Corrected background error in xtl.Scatter.powder
08/05/23 2.3.0 Merged pull request for non-integer hkl option on SF and electron form factors. Thanks Prestipino!
25/06/23 3.0.0 Added new GUI elements including new Scattering UI and diffractomter simulator, plus other updates
Acknoledgements:
2018 Thanks to Hepesu for help with Python3 support and ideas about breaking up calculations
Dec 2019 Thanks to Gareth Nisbet for allowing me to inlude his multiple scattering siumulation
April 2020 Thanks to ChunHai Wang for helpful suggestions in readcif!
May 2020 Thanks to AndreEbel for helpful suggestions on citations
Dec 2020 Thanks to Chris Drozdowski for suggestions about reflection families
Jan 2021 Thanks to aslarsen for suggestions about outputting the structure factor
April 2021 Thanks to Trygve Ræder for suggestions about x-ray scattering factors
Feb 2022 Thanks to Mirko for pointing out the error in two-theta values in Scatter.powder
March 2022 Thanks to yevgenyr for suggesting new peak profiles in Scatter.powder
Jan 2023 Thanks to Anuradha Vibhakar for pointing out the error in f0 + if'-if''
Jan 2023 Thanks to Andreas Rosnes for testing the installation in jupyterlab
May 2023 Thanks to Carmelo Prestipino for adding electron scattering factors
June 2023 Thanks to Sergio I. Rincon for pointing out the rounding error in Scatter.powder
-----------------------------------------------------------------------------
Copyright 2023 Diamond Light Source Ltd.
Expand Down Expand Up @@ -120,8 +136,8 @@
from .classes_fdmnes import Fdmnes, FdmnesAnalysis


__version__ = '2.3.0'
__date__ = '16/05/23'
__version__ = '3.0.0'
__date__ = '02/07/23'


# Build
Expand Down
154 changes: 109 additions & 45 deletions Dans_Diffraction/classes_crystal.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@
Diamond
2017
Version 3.2.3
Last updated: 12/01/21
Version 3.2.4
Last updated: 22/05/23
Version History:
27/07/17 1.0 Version History started.
Expand All @@ -48,6 +48,7 @@
22/10/20 3.2.1 Added Cell.moment, updated Cell.latt()
15/11/21 3.2.2 Added Cell.orientation, updated Cell.UV()
12/01/21 3.2.3 Added Symmetry.axial_vector
22/05/23 3.2.4 Added Symmetry.wyckoff_label(), Symmetry.spacegroup_dict
@author: DGPorter
"""
Expand All @@ -65,7 +66,7 @@
from .classes_multicrystal import MultiCrystal
from .classes_plotting import Plotting, PlottingSuperstructure

__version__ = '3.2.3'
__version__ = '3.2.4'


class Crystal:
Expand Down Expand Up @@ -614,15 +615,16 @@ def Qmag(self, HKL):
Q = self.calculateQ(HKL)
return fg.mag(Q)

def tth(self, HKL, energy_kev=8.048):
def tth(self, HKL, energy_kev=8.048, wavelength_a=None):
"""
Returns the two-theta angle, in deg, of [h,k,l] at specified energy in keV
:param HKL: list of hkl reflections
:param energy_kev: energy in keV
:param energy_kev: photon energy in keV
:param wavelength_a: wavelength in Angstroms
:return: two-theta angles
"""
Qmag = self.Qmag(HKL)
return fc.cal2theta(Qmag, energy_kev)
return fc.cal2theta(Qmag, energy_kev, wavelength_a)

def angle(self, hkl1, hkl2):
"""
Expand Down Expand Up @@ -669,43 +671,46 @@ def dspace(self, hkl):
Qmag = self.Qmag(hkl)
return fc.q2dspace(Qmag)

def max_hkl(self, energy_kev=8.048, max_angle=180.0):
def max_hkl(self, energy_kev=8.048, max_angle=180.0, wavelength_a=None, maxq=None):
"""
Returns the maximum index of h, k and l for a given energy
:param energy_kev: energy in keV
:param max_angle: maximum two-theta at this energy
:param wavelength_a: wavelength in A
:param maxq: maximum wavevetor transfere in A-1 (suplants all above)
:return: maxh, maxk, maxl
"""

Qmax = fc.calqmag(max_angle, energy_kev)
Qpos = [[Qmax, Qmax, Qmax],
[-Qmax, Qmax, Qmax],
[Qmax, -Qmax, Qmax],
[-Qmax, -Qmax, Qmax],
[Qmax, Qmax, -Qmax],
[-Qmax, Qmax, -Qmax],
[Qmax, -Qmax, -Qmax],
[-Qmax, -Qmax, -Qmax]]
if maxq is None:
maxq = fc.calqmag(max_angle, energy_kev, wavelength_a)
Qpos = [[maxq, maxq, maxq],
[-maxq, maxq, maxq],
[maxq, -maxq, maxq],
[-maxq, -maxq, maxq],
[maxq, maxq, -maxq],
[-maxq, maxq, -maxq],
[maxq, -maxq, -maxq],
[-maxq, -maxq, -maxq]]
hkl = self.indexQ(Qpos)
return np.ceil(np.abs(hkl).max(axis=0)).astype(int)

def all_hkl(self, energy_kev=8.048, max_angle=180.0):
def all_hkl(self, energy_kev=8.048, max_angle=180.0, wavelength_a=None, maxq=None):
"""
Returns an array of all (h,k,l) reflections at this energy
:param energy_kev: energy in keV
:param max_angle: max two-theta angle
:param wavelength_a: wavelength in A
:param maxq: maximum wavevetor transfere in A-1 (suplants all above)
:return: array hkl[:,3]
"""

Qmax = fc.calqmag(max_angle, energy_kev)

# Find the largest indices
hmax, kmax, lmax = self.max_hkl(energy_kev, max_angle)
hmax, kmax, lmax = self.max_hkl(energy_kev, max_angle, wavelength_a, maxq)
# Generate the grid
HKL = fc.genHKL([hmax, -hmax], [kmax, -kmax], [lmax, -lmax])
# Some will be above the threshold
Qm = self.Qmag(HKL)
return HKL[Qm <= Qmax, :]
if maxq is None:
maxq = fc.calqmag(max_angle, energy_kev, wavelength_a)
return HKL[Qm <= maxq, :]

def reflection_hkl(self, energy_kev=8.048, max_angle=180.0,
specular=(0, 0, 1), theta_offset=0, min_theta=0, max_theta=180.):
Expand Down Expand Up @@ -945,6 +950,17 @@ def info(self):
out += '%20s : %s\n' % (key, self.properties[key][0])
return out

def uvw(self):
"""
Returns a [1x3] array of current positions
:return: np.array([1x3])
"""
return np.asarray([self.u, self.v, self.w], dtype=float)

def total_moment(self):
"""Return the total moment along a, b, c directions"""
return np.sum(self.mxmymz)


class Atoms:
"""
Expand Down Expand Up @@ -1513,14 +1529,15 @@ class Symmetry:
symmetry_operations_magnetic = ['x,y,z']
symmetry_operations_time = [1]
symmetry_matrices = np.eye(4)
spacegroup_dict = fc.spacegroup(1)

def __init__(self, symmetry_operations=None, symmetry_operations_magnetic=None):
"""Initialises the symmetry group"""

if symmetry_operations is not None:
self.addsym(symmetry_operations, symmetry_operations_magnetic)

self.generate_matrices()
else:
self.generate_matrices()

def fromcif(self, cifvals):
"""
Expand Down Expand Up @@ -1578,6 +1595,19 @@ def fromcif(self, cifvals):
self.spacegroup_number = float(sgn)
except ValueError:
self.spacegroup_number = 0
try:
if '.' in sgn:
self.spacegroup_dict = fc.spacegroup_magnetic(sgn)
else:
self.spacegroup_dict = fc.spacegroup(sgn)
except KeyError:
# Find from spacegroup
check = fc.find_spacegroup(spacegroup)
if check:
self.spacegroup_dict = check
self.spacegroup_number = check['space group number']
else:
self.spacegroup_dict = fc.spacegroup(1)

self.generate_matrices()

Expand All @@ -1587,7 +1617,6 @@ def update_cif(self, cifvals):
:param cifvals: cif dict from functions_crystallography.readcif
:return: cifvals
"""
keys = cifvals.keys()

cifvals['_symmetry_equiv_pos_as_xyz'] = self.symmetry_operations
cifvals['_space_group_symop_operation_xyz'] = self.symmetry_operations
Expand Down Expand Up @@ -1617,34 +1646,44 @@ def update_cif(self, cifvals):
cifvals['_space_group_magn.number_BNS'] = self.spacegroup_number
return cifvals

def load_spacegroup(self, sg_number):
def load_spacegroup(self, sg_number=None, sg_dict=None):
"""
Load symmetry operations from a spacegroup from the International Tables of Crystallogrphy
See functions_crystallography.spacegroup for more details
:param sg_number: space group number (1-230)
:param sg_dict: alternative input: spacegroup dict from fc.spacegroup
:return: None
"""
spacegroup = fc.spacegroup(sg_number)
self.spacegroup_number = int(spacegroup['space group number'])
self.spacegroup = spacegroup['space group name']
if sg_dict is None:
sg_dict = fc.spacegroup(sg_number)
if 'positions magnetic' in sg_dict:
self.load_magnetic_spacegroup(sg_dict=sg_dict)
return
self.spacegroup_number = int(sg_dict['space group number'])
self.spacegroup = sg_dict['space group name']
self.spacegroup_dict = sg_dict

symops = spacegroup['general positions']
symops = sg_dict['general positions']
self.symmetry_operations = symops
self.symmetry_operations_magnetic = fc.symmetry_ops2magnetic(symops)
self.symmetry_operations_time = [1] * len(symops)

def load_magnetic_spacegroup(self, msg_number):
def load_magnetic_spacegroup(self, msg_number=None, sg_dict=None):
"""
Load symmetry operations from a magnetic spacegroup from Bilbao crystallographic server
Replaces the current symmetry operators and the magnetic symmetry operators.
See functions_crystallography.spacegroup_magnetic for more details
:param msg_number: magnetic space group number e.g. 61.433
:return:
:param sg_dict: alternative inuput: spacegroup dict from fc.spacegroup_magnetic
:return: None
"""

maggroup = fc.spacegroup_magnetic(msg_number)
if sg_dict is None:
maggroup = fc.spacegroup_magnetic(msg_number)
else:
maggroup = sg_dict
self.spacegroup_number = maggroup['space group number']
self.spacegroup = maggroup['space group name']
self.spacegroup_dict = maggroup
symops = maggroup['positions general']
symmag = maggroup['positions magnetic']
self.symmetry_operations = symops
Expand Down Expand Up @@ -1719,25 +1758,19 @@ def generate_matrices(self):

self.symmetry_matrices = fc.gen_sym_mat(self.symmetry_operations)

def print_subgroups(self, sg_number=None):
def print_subgroups(self):
"""
Return str of subgroups of this spacegroup
:param sg_number: spacegroup number, None to use current one
:return: str
"""
if sg_number is None:
sg_number = self.spacegroup_number
return fc.spacegroup_subgroups_list(sg_number)
return fc.spacegroup_subgroups_list(sg_dict=self.spacegroup_dict)

def print_magnetic_spacegroups(self, sg_number=None):
def print_magnetic_spacegroups(self):
"""
Return str of available magnetic spacegroups for this spacegroup
:param sg_number: spacegroup number, None to use current one
:return: str
"""
if sg_number is None:
sg_number = self.spacegroup_number
return fc.spacegroup_magnetic_list(sg_number)
return fc.spacegroup_magnetic_list(sg_dict=self.spacegroup_dict)

def symmetric_coordinates(self, UVW, MXMYMZ=None, remove_identical=True):
"""
Expand Down Expand Up @@ -2130,6 +2163,37 @@ def parity_time_info(self):
out += "%3d, %19s, %6s, %9s, %8s, %19s\n" % (n, operations[n], t, p, mag_str, magnetic_ops[n])
return out

def wyckoff_labels(self, UVW):
"""
Return Wyckoff site for position
:param UVW: (u,v,w) or None to use xtl.Atoms.uvw()
:return: list of Wyckoff site letters
"""
return fc.wyckoff_labels(self.spacegroup_dict, UVW)

def print_wyckoff_sites(self):
"""Return info str about Wycoff positions for this spacegroup"""

spg = self.spacegroup_dict
out = 'Spacegoup: %s (%s)\n' % (spg['space group name'], spg['space group number'])
if 'parent number' in spg:
spg = fc.spacegroup(self.spacegroup_dict['parent number'])
out += ' Parent: %s (%s)\n' % (spg['space group name'], spg['space group number'])
centring = spg['positions centring']
out += '\nCentring operations: %2d : \n ' % len(centring)
out += '\n '.join(centring)
out += '\n\n'
out += 'Wyckoff Sites\n'
coordinates = spg['positions coordinates']
multiplicity = spg['positions multiplicity']
symmetry = spg['positions symmetry']
wyckoff_letter = spg['positions wyckoff letter']
for n in range(len(coordinates)):
out += '%3s : %10s : %2s\n ' % (wyckoff_letter[n], symmetry[n], multiplicity[n])
out += '\n '.join(coordinates[n])
out += '\n\n'
return out

def info(self):
"""
Prints the symmetry information
Expand Down
Loading

0 comments on commit 52476b0

Please sign in to comment.