Dans_Diffraction.functions_general (version 2.2.1, 01/June/2023)
index
c:\users\grp66007\onedrive - diamond light source ltd\pythonprojects\dans_diffraction\dans_diffraction\functions_general.py

Module: Generally useful functions "functions_general.py"
 
Contains various useful shortcuts for manipulating strings and arrays,
making use of numpy and re.
 
By Dan Porter, PhD
Diamond
2021
 
Version 2.2.0
Last updated: 11/03/22
 
Version History:
06/01/18 1.0    Program created from DansGeneralProgs.py V2.3
02/05/18 1.1    Added find_vector
24/05/18 1.2    Corrected 'quad' for the case (1,-2,1)=1
31/10/18 1.3    Added complex2str
20/08/19 1.4    Added search_dict_lists
27/03/20 1.5    Corrected error in gauss for 1d case when centre /= 0
05/05/20 1.6    New version of readstfm, allows E powers and handles non-numbers.
12/05/20 1.7    Added sph2cart, replace_bracket_multiple
20/07/20 1.8    Added vector_inersection and plane_intersection, updated findranges, added whererun
01/12/20 1.8.1  Added get_methods function
26/01/21 1.8.2  Added shortstr and squaredata
02/02/21 2.0.0  Merged changes in other versions, added vector_intersection and you_normal_vector
15/11/21 2.1.0  Added normal_basis
11/03/22 2.2.0  Added Lorentzian, pVoight, peak_function functions
 
@author: DGPorter

 
Modules
       
inspect
numpy
os
re
sys

 
Functions
       
ang(a, b, deg=False)
Returns the angle, in Radians between vectors a and b
E.G.
ang([1,0,0],[0,1,0]) >> 1.571
ang([1,0,0],[0,1,0],'deg') >> 90
array_str(A)
Returns a short string with array information
:param A: np array
:return: str
cart2sph(xyz, deg=False)
Convert coordinates in cartesian to coordinates in spherical
https://en.wikipedia.org/wiki/Spherical_coordinate_system
ISO convention used.
    theta = angle from Z-axis to X-axis
      phi = angle from X-axis to component in XY plane
:param xyz: [n*3] array of [x,y,z] coordinates
:param deg: if True, returns angles in degrees
:return: [r, theta, phi]
cell(lengths=[1, 1, 1], rotation=[0, 0, 0])
Returns a unit CELL with vectors defined by length and rotation (Deg)
:param lengths:
:param rotation:
:return:
complex2str(val, fmt='6.1f')
Convert complex number to string
detail(A)
Prints details about the given vector,
including length, min, max, number of elements
distance2line(line_start, line_end, point)
Calculate distance from a line between the start and end to an arbitary point in space
:param line_start: array, position of the start of the line
:param line_end:  array, position of the end of the line
:param point: array, arbitary position in space
:return: float
find_index(A, value)
Return the index of the closest number in array A to value
 
E.G.
A = [1,2,3,4,5]
find_index(A,3.5)
 >> 2
A = [[1,2],[3,4]]
find_index(A,3.2)
 >> (1,0)
 
 For multiple values, use:
 B = [find_index(A,x) for x in values]
find_vector(A, V, difference=0.01)
Return the index(s) of vectors in array A within difference of vector V
Comparison is based on vector difference A[n,:]-V
 
Returns None if no matching vector is present.
 
E.G.
A = [[1,2,3],
     [4,5,6],
     [7,8,9]]
find_index(A,[4,5,6])
 >> 1
 
A = [[0, 2.5, 0],
     [1, 0.1, 0],
     [1,-0.1, 0],
     [0, 0.5, ]]
find_vector(A, [1,0,0], difference=0.1)
 >> [1, 2]
findranges(scannos, sep=':')
Convert a list of numbers to a simple string
E.G.
findranges([1,2,3,4,5]) = '1:5'
findranges([1,2,3,4,5,10,12,14,16]) = '1:5,10:2:16'
frange(start, stop=None, step=1)
Returns a list of floats from start to stop in step increments
Like np.arange but ends at stop, rather than at stop-step
E.G.
A = frange(0,5,1) = [0.,1.,2.,3.,4.,5.]
gauss(x, y=None, height=1, centre=0, fwhm=0.5, bkg=0, centre_y=None, fwhm_y=None)
Define a Gaussian distribution in 1 or 2 dimensions
 
    g = height * exp( -ln2 (x-centre)^2 / (fwhm/2)^2 ) + bkg
From http://fityk.nieto.pl/model.html
 
    x = [1xn] array of values, defines size of gaussian in dimension 1
    y = None* or [1xm] array of values, defines size of gaussian in dimension 2
    height = peak height
    centre = peak centre
    fwhm = peak full width at half-max
    bkg = background
    centre_y = if 2D, centre along the y-axis (defaults to centre)
    fwhm_y = if 2D, fwhm along the y-axis (defaults to fwhm)
get_methods(object, include_special=True)
Returns a list of methods (functions) within object
grid_intensity(points, values, resolution=0.01, peak_width=0.1, background=0)
Generates array of intensities along a spaced grid, equivalent to a powder pattern.
  grid, values = generate_powder(points, values, resolution=0.01, peak_width=0.1, background=0)
:param points: [nx1] position of values to place on grid
:param values: [nx1] values to place at points
:param resolution: grid spacing size, with same units as points
:param peak_width: width of convolved gaussian, with same units as points
:param background: add a normal (random) background with width sqrt(background)
:return: points, values
group(A, tolerance=0.0001)
Group similear values in an array, returning the group and indexes
array will be sorted so lowest numbers are grouped first
  group_index, group_values, group_counts = group([2.1, 3.0, 3.1, 1.00], 0.1)
group_values = [1., 2., 3.] array of grouped numbers (rounded)
array_index = [1, 2, 2, 0] array matching A values to groups, such that A ~ group_values[group_index]
group_index = [3, 0, 1] array matching group values to A, such that group_values ~ A[group_index]
group_counts = [1, 1, 2] number of iterations of each item in group_values
:param A: list or numpy array of numbers
:param tolerance: values within this number will be grouped
:return: group_values, array_index, group_index, group_counts
index_coordinates(A, CELL)
Index A(x1,y1,1) on new coordinate system B(x2,y2,z2), defined by unit vectors CELL = [A,B,C]
  A = [[nx3]] array of vectors
  CELL = [[3x3]] matrix of vectors [a,b,c]
inline_help(func)
Return function spec and first line of help in line
isincell(A, cell_centre=[0, 0, 0], CELL=array([[1., 0., 0.], [0., 1., 0.], [0., 0., 1.]]))
 Return boolean of whether vector xyx is inside a cell defined by position, size and rotation
   A = [x,y,z] or [[x1,y1,z1]] array of positions
   cell_centre = [x,y,z] cell centre
   CELL = [[3x3]] matrix of vectors [a,b,c]
 
E.G.
   xyz = [[1,1,0],[0,0,0.5],[0.1,0,5]]
   centre = [0,0,0]
   CELL = cell([1,1,10],[0,0,0])
   isinbox(xyz,centre,CELL)
   >>> [False,True,True]
lastlines(filename, lines=1, max_line_length=255)
Returns the last n lines of a text file
list_methods(object, include_special=False)
Return list of methods (functions) in class object
lorentz(x, y=None, height=1, centre=0, fwhm=0.5, bkg=0, centre_y=None, fwhm_y=None)
Define a Lorentzian distribution in 1 or 2 dimensions
 
    l = height / ( 1 + (x - centre)^2 / (fwhm/2)^2 + bkg
 
    x = [1xn] array of values, defines size of gaussian in dimension 1
    y = None* or [1xm] array of values, defines size of gaussian in dimension 2
    height = peak height
    centre = peak centre
    fwhm = peak full width at half-max
    bkg = background
    centre_y = if 2D, centre along the y-axis (defaults to centre)
    fwhm_y = if 2D, fwhm along the y-axis (defaults to fwhm)
mag(A)
Returns the magnitude of vector A
If A has 2 dimensions, returns an array of magnitudes
 
E.G.
 mag([1,1,1]) = 1.732
 mag(array([[1,1,1],[2,2,2]]) = [1.732, 3.464]
map2grid(grid, points, values, widths=None, background=0)
Generates array of intensities along a spaced grid, equivalent to a powder pattern.
  grid, values = generate_powder(points, values, resolution=0.01, peak_width=0.1, background=0)
:param grid: [mx1] grid of positions
:param points: [nx1] position of values to place on grid
:param values: [nx1] values to place at points
:param widths: width of convolved gaussian, with same units as points
:param background: add a normal (random) background with width sqrt(background)
:return: points, values
multi_replace(string, old=[], new=[])
Replace multiple strings at once
:param string:
:param old:
:param new:
:return:
nice_print(precision=4, linewidth=300)
Sets default printing of arrays to a nicer format
norm(A)
Returns normalised vector A
If A has 2 dimensions, returns an array of normalised vectors
The returned array will be the same shape as the given array.
 
E.G.
 norm([1,1,1]) = [1,1,1]/1.732 = [ 0.57735,  0.57735,  0.57735]
 norm(array([[1,1,1],[2,2,2]]) = [ [ 0.57735,  0.57735,  0.57735] , [ 0.57735,  0.57735,  0.57735] ]
normal_basis(vec, normal=(1, 0, 0), azi=0)
Return a vector basis with 3 normal vectors such that:
    [|normal x vec|, |vec x (normal x vec)|, |vec|]
if azi is given, the resulting vectors [0,1] will be rotated about [2]
:param vec: [3*1] array of vector
:param normal: [3*1] array of normal vector
:param azi: float azimuthal rotation about vec in degrees
:return: array [3*3] as unit vectors [vec1, vec2, vec3]
numbers2string(scannos, sep=':')
Convert a list of numbers to a simple string
E.G.
numbers2string([50001,50002,50003]) = '5000[1:3]'
numbers2string([51020,51030,51040]) = '510[20:10:40]'
peak_function(func_name, *args, **kwargs)
Choose & create peak function, using either gaussian, lorentzian or pseudo-voight
:param func_name: 'gaussian', 'lorentzian', 'psudovoight' (raise ValueError if wrong)
:param args: x, y, height, centre, fwhm, bkg, (fraction), centre_y, fwhm_y
:param kwargs:
:return: array()
plane_intersection(line_point, line_direction, plane_point, plane_normal)
Calculate the point at which a line intersects a plane
:param line_point: [x,y],z] some coordinate on line
:param line_direction: [dx,dy],dz] the direction of line
:param plane_point:  [x,y],z] some coordinate on the plane
:param plane_normal: [dx,dy],dz] the normal vector of the plane
:return: [x,y],z]
print_arrays(arrays=[])
Prints values from several arrays with nice formatting
e.g. dgp.print_arrays(1e6*np.random.rand(5,5))
     0   6.04e+03   2.29e+05   7.19e+05   4.74e+05   9.59e+05
     1   9.16e+05   6.33e+05    1.6e+05   3.75e+05   3.01e+05
     2   4.21e+04   1.78e+05   4.78e+05   7.56e+05   2.47e+05
     3   9.26e+05   6.39e+05   3.96e+05   4.42e+05   4.52e+04
     4   3.38e+05   4.54e+05   4.29e+05   6.34e+04   6.06e+04
pvoight(x, y=None, height=1, centre=0, fwhm=0.5, bkg=0, l_fraction=0.5, centre_y=None, fwhm_y=None)
Define psuedo-voight distribution in 1 or 2 dimensions
 
    v = fraction * lorentz() + (1 - fraction) * gauss()
 
    x = [1xn] array of values, defines size of gaussian in dimension 1
    y = None* or [1xm] array of values, defines size of gaussian in dimension 2
    height = peak height
    centre = peak centre
    fwhm = peak full width at half-max
    bkg = background
    l_fraction = Lorentzian fraction (0-1)
    centre_y = if 2D, centre along the y-axis (defaults to centre)
    fwhm_y = if 2D, fwhm along the y-axis (defaults to fwhm)
quad(A)
Returns +/-1 depending on quadrant of 3D vector A
i.e.:    A      Returns
    [ 1, 1, 1]    1
    [-1, 1, 1]    1
    [ 1,-1, 1]    1
    [ 1, 1,-1]    1
    [-1,-1, 1]   -1
    [-1, 1,-1]   -1
    [ 1,-1,-1]   -1
    [-1,-1,-1]   -1
quadmag(A)
Returns +/- mag depending on quadrant of a 3D vector A
i.e.:    A      Returns
    [ 1, 1, 1]    1.732
    [-1, 1, 1]    1.732
    [ 1,-1, 1]    1.732
    [ 1, 1,-1]    1.732
    [-1,-1, 1]   -1.732
    [-1, 1,-1]   -1.732
    [ 1,-1,-1]   -1.732
    [-1,-1,-1]   -1.732
readstfm(string)
Read numbers written in standard form: 0.01(2), return value and error
Read numbers from string with form 0.01(2), returns floats 0.01 and 0.02
Errors and values will return 0 if not given.
 
E.G.
readstfm('0.01(2)') = (0.01, 0.02)
readstfm('1000(300)') = (1000.,300.)
readstfm('1.23(3)E4') = (12300.0, 300.0)
replace_bracket_multiple(name)
Replace any numbers in brackets with numbers multipled by bracket multiplyer
Assumes bracket multiplier is on the left side
e.g.
    replace_bracket_multiple('Mn0.3(Fe3.6(Co1.2)2)4(Mo0.7Pr44)3')
    >> 'Mn0.3Fe14.4Co9.6Mo2.1Pr132'
:param name: str
:return: str
rot3D(A, alpha=0.0, beta=0.0, gamma=0.0)
Rotate 3D vector A by euler angles
 A = rot3D(A,alpha=0.,beta=0.,gamma=0.)
where alpha = angle from X axis to Y axis (Yaw)
      beta  = angle from Z axis to X axis (Pitch)
      gamma = angle from Y axis to Z axis (Roll)
angles in degrees
In a right handed coordinate system.
    Z
   /|           |
    |________\Y
    \        /
                 _\/X
rotate_about_axis(point, axis, angle)
Rotate vector A about vector Axis by angle
Using Rodrigues' rotation formula: https://en.wikipedia.org/wiki/Rodrigues%27_rotation_formula
:param point: [x,y,z] coordinate to rotate
:param axis: [dx,dy,dz] vector about which to rotate
:param angle: angle to rotate in deg
:return: [x,y,z] rotated point
rotmat(a, b)
Determine rotation matrix from a to b
From Kuba Ober's answer on stackexchange:
http://math.stackexchange.com/questions/180418/calculate-rotation-matrix-to-align-vector-a-to-vector-b-in-3d/476311#476311
Usage:
 a = [1,0,0]
 b = [0,1,0]
 U = rotmat(a,b)
 np.dot(U,a)
 >> [0,1,0]
saveable(string)
Returns a string without special charaters.
Removes bad characters from a string, so it can be used as a filename
E.G.
saveable('Test#1<froot@5!') = 'TestS1_froot_5'
search_dict_lists(d, **kwargs)
Search equal length lists in a dictionary for specific values
e.g.
    idx = search_dict_lists(cif, _geom_bond_atom_site_label_1='Ru1',
                                 _geom_bond_atom_site_label_2='O1',
                                 _geom_bond_site_symmetry_2='.')
:param d: dict
:param kwargs: keyword=item
:return: boolean array index
shortstr(string)
Shorten string by removing long floats
:param string: string, e.g. '#810002 scan eta 74.89533603616637 76.49533603616636 0.02 pil3_100k 1 roi2'
:return: shorter string, e.g. '#810002 scan eta 74.895 76.495 0.02 pil3_100k 1 roi2'
sphere_array(A, max_angle1=90, max_angle2=90, step1=1, step2=1)
Rotate points in array A by multiple angles
 B = sphere_array(A, max_angle1, max_angle2, step1, step2)
 
  A = [nx3] array of 3D coordinates
  max_angle1 = max rotation angle
  max_angle1 = max rotation angle
  step1 = angular step size
  step2 = angular step size
 
Each coordinate in A will be rotated by angles:
    angles = np.arange(0, max_angle+step, step)
The output B will have size:
    [len(A) * len(angles1) * len(angles2), 3]
squaredata(xdata, ydata, data, repeat=None)
Generate square arrays from 1D data, automatically determinging the repeat value
:param xdata: [n] array
:param ydata: [n] array
:param data: [n] array
:param repeat: int m or None to deteremine m from differences in xdata and y data
:return: X, Y, D [n//m, m] arrays
stfm(val, err)
Create standard form string from value and uncertainty"
 str = stfm(val,err)
 Examples:
      '35.25 (1)' = stfm(35.25,0.01)
      '110 (5)' = stfm(110.25,5)
      '0.0015300 (5)' = stfm(0.00153,0.0000005)
      '1.56(2)E+6' = stfm(1.5632e6,1.53e4)
 
Notes:
 - Errors less than 0.01% of values will be given as 0
 - The maximum length of string is 13 characters
 - Errors greater then 10x the value will cause the value to be rounded to zero
str2array(string)
Convert string to array
unique_vector(vecarray, tol=0.05)
Find unique vectors in an array within a certain tolerance
 
newarray,uniqueidx,matchidx = unique_vector(vecarray,tol=0.05)
   vecarray = np.array([[:]])
   newarray = vecarray with duplicate vectors removed
   uniqueidx= index vectors of newarray such that vecarray[uniqueidx]=newarray
   matchidx = index vectors of vecarray such that newarray[matchidx]=vecarray
   tol = tolerance of vector difference to define unique vectors
 
E.G.
vecarray = [[1,1,1],[2,2,2],[3,3,3],[1,1,1],[4,4,4],[2,2,2]]
newarray,uniqueidx,matchidx = unique_vector(vecarray,tol=0.05)
  newarray:        uniqueidx:
 [[1, 1, 1],           [0,
  [2, 2, 2],            1,
  [3, 3, 3],            2,
  [4, 4, 4]]            4]
 
matchidx: [0, 1, 2, 0, 3, 1]
 
E.G.
newarray,uniqueidx,matchidx = unique_vector([[1,0,0],[1,0,0],[0,1,0]],tol=0.05)
>> newarray = [[1,0,0],[0,1,0]]
>> uniqueidx= [0,2]
>> matchidx = [0,0,1]
vector_intersection(point1, direction1, point2, direction2)
Calculate the point in 2D where two lines cross.
If lines are parallel, return nan
For derivation, see: http://paulbourke.net/geometry/pointlineplane/
:param point1: [x,y] some coordinate on line 1
:param direction1: [dx, dy] the direction of line 1
:param point2: [x,y] some coordinate on line 2
:param direction2: [dx, dy] the direction of line 2
:return: [x, y]
vector_intersection3d(point1, direction1, point2, direction2)
Calculate the point in 3D where two lines cross.
If lines are parallel, return nan
For derivation, see: https://math.stackexchange.com/questions/270767/find-intersection-of-two-3d-lines/271366
:param point1: [x,y,z] some coordinate on line 1
:param direction1: [dx, dy, dz] the direction of line 1
:param point2: [x,y,z] some coordinate on line 2
:param direction2: [dx, dy, dz] the direction of line 2
:return: [x, y, z]
vectorinvector(vecarray1, vecarray2, tol=0.05)
Return True if vecarray1 is replicated in vecarray2
:param vecarray1: [1xn] list or array
:param vecarray2: [mxn] list or array
:param tol: float
:return: Bool
vectors_angle_degrees(a, b)
Returns the angle, in degrees between vectors a and b.
Gives the absolute angle, negatives are not given for sectors.
    angle = acos(a.b / |a|.|b|)
:param a: [nx3] array of vectors [x,y,z]
:param b: [mx3] array of vectors [x,y,z]
:return: float angle in degrees is n==m==1
:return: [max(n, m)] array of angles in degrees if n or m == 1
:return: [n, m] array of angles in degrees if n and m > 1
whererun()
Returns the location where python was run
you_normal_vector(eta=0, chi=90, mu=0)
Determine the normal vector using the You diffractometer angles
  you_normal_vector(0, 0, 0) = [1, 0, 0]
  you_normal_vector(0, 90, 0) = [0, 1, 0]
  you_normal_vector(90, 90, 0) = [0, 0, -1]
  you_normal_vector(0, 0, 90) = [0, 0, -1]
:param eta: angle (deg) along the x-axis
:param mu: angle (deg) about the z-axis
:param chi: angle deg) a
:return: array

 
Data
        A = 1e-10
Cu = 8.048
Mo = 17.4808
Na = 6.022e+23
c = 299792458
directory = r'C:\Users\grp66007\OneDrive - Diamond Light Sourc...\PythonProjects\Dans_Diffraction\Dans_Diffraction'
e = 1.6021733e-19
h = 6.62606868e-34
me = 9.109e-31
mn = 1.6749e-27
pi = 3.141592653589793
r0 = 2.8179403227e-15
u0 = 1.2566370614359173e-06