import re
import numpy as np
from scipy.sparse import bsr_array
import uuid
import base64
[docs]
def build_table(source, params, params_widths):
string = ""
params_used = []
title_str = ""
line_str = ""
title_fmt = []
for i, param in enumerate(params):
if hasattr(source[0], param):
attr = getattr(source[0], param)
line_str += f" {{:<{params_widths[i]}}}"
tmp = re.findall(r'(\d+)', params_widths[i])[0]
title_str += f" {{:<{tmp}}}"
if attr.unit is None:
title_fmt.append(attr.name)
else:
title_fmt.append(attr.name + ' (' + attr.unit + ")")
params_used.append(param)
elif param in ['iD', 'type' ,'Phase Cycle']:
line_str += f" {{:<{params_widths[i]}}}"
tmp = re.findall(r'(\d+)', params_widths[i])[0]
title_str += f" {{:<{tmp}}}"
title_fmt.append(param)
params_used.append(param)
title_str += "\n"
line_str += "\n"
string += title_str.format(*title_fmt)
for i, pulse in enumerate(source):
elements = []
for param in params_used:
if param == "type":
elements.append(type(pulse).__name__)
elif param == "iD":
elements.append(i)
elif param == 'Phase Cycle':
elements.append(pulse._pcyc_str())
elif hasattr(pulse, param):
if getattr(pulse, param) is None:
elements.append("N/A")
elif getattr(pulse, param).value is None:
elements.append("None")
else:
elements.append(f"{getattr(pulse, param).value:>5.5g}")
else:
elements.append("N/A")
string += line_str.format(*elements)
return string
[docs]
def sop(spins, comps):
"""Spin Operator Matricies.
This function is ported from EasySpin (https://easyspin.org/easyspin/documentation/sop.html)
References:
+++++++++++
[1] Stefan Stoll, Arthur Schweiger
EasySpin, a comprehensive software package for spectral simulation and analysis in EPR
J. Magn. Reson. 178(1), 42-55 (2006)
[2] Stefan Stoll, R. David Britt
General and efficient simulation of pulse EPR spectra
Phys. Chem. Chem. Phys. 11, 6614-6625 (2009)
Parameters
----------
spins : list
A list of each spin and its spin qunatum number
comps : str
The type of spin operator matrix to create. Options are: x,y,z,+,-,e
"""
num_spins = len(spins)
OP=np.array([1])
for spin_num in range(num_spins):
I = spins[spin_num]
sop_type = comps[spin_num]
n = int(I * 2 + 1)
if sop_type == 'x':
m = np.arange(1,n)
r = np.hstack((m-1,m))
c = np.hstack((m,m-1))
dia = 0.5 * np.sqrt(m*m[::-1])
val = np.hstack((dia,dia))
elif sop_type == 'y':
m = np.arange(1,n)
r = np.hstack((m-1,m))
c = np.hstack((m,m-1))
dia = -0.5*1j * np.sqrt(m*m[::-1])
val = np.hstack((dia,-dia))
elif sop_type == 'z':
m = np.arange(1, n+ 1)
r = m - 1
c = m - 1
val = -m + I + 1
elif sop_type == '+':
m = np.arange(1,n)
r = m -1
c = m
val = np.sqrt(m * m[::-1])
elif sop_type == '-':
m = np.arange(1,n)
r = m + 1
c = m
val = np.sqrt(m * m[::-1])
elif sop_type == 'e':
m = np.arange(1,n+1)
r = m-1
c = m-1
val = np.ones(n)
else:
raise ValueError(f"Incorect specification of comps: ",
f"{sop_type} is not a valid input")
M_ = bsr_array((val, (r,c)), shape=(n,n)).toarray()
OP = np.kron(OP, M_)
return OP
[docs]
def transpose_dict_of_list(d):
"""Turns a dictionary of lists into a list of dictionaries.
"""
return [dict(zip(d, col)) for col in zip(*d.values())]
[docs]
def transpose_list_of_dicts(d):
"""Turns a list of dictionaries into a dictionary of lists.
"""
if len(d) == 0:
return {}
else:
return {key: [i[key] for i in d] for key in d[0]}
[docs]
def save_file(path, str):
with open(path, "w") as file:
file.write(str)
[docs]
def autoEPRDecoder(dct):
if isinstance(dct, dict) and '__uuid__' in dct:
return uuid.UUID(dct["__uuid__"])
if isinstance(dct, dict) and '__ndarray__' in dct:
data = base64.b64decode(dct['__ndarray__'][2:-1])
return np.frombuffer(data, dct['dtype']).reshape(dct['shape'])
return dct
[docs]
def gcd(values:list):
"""Generates the greatest common dividor on a list of floats
Parameters
----------
values : list
_description_
"""
values = values.copy()
if len(values) == 1:
return values[0]
if len(values) == 2:
a = values[0]
b = values[1]
while b:
a, b = b, a % b
return a
if len(values) > 2:
a = values[0]
b = values[1]
while b:
a, b = b, a % b
values[0] = a
values.pop(1)
return gcd(values)
[docs]
def val_in_us(Param, axis=True):
"""Returns the value or axis of a parameter in microseconds
Parameters
----------
Param : autodeer.Parameter
The parameter to be converted
Returns
-------
float or numpy.ndarray
"""
if (len(Param.axis) == 0) or not axis:
if Param.unit == "us":
return Param.value
elif Param.unit == "ns":
return Param.value / 1e3
elif len(Param.axis) == 1 and axis:
if Param.unit == "us":
return Param.value + Param.axis[0]['axis']
elif Param.unit == "ns":
return (Param.value + Param.axis[0]['axis']) / 1e3
else:
raise ValueError("Parameter must have 0 or 1 axes")
[docs]
def val_in_ns(Param):
"""Returns the value or axis of a parameter in nanoseconds
Parameters
----------
Param : autodeer.Parameter
The parameter to be converted
Returns
-------
float or numpy.ndarray
"""
if len(Param.axis) == 0:
if Param.unit == "us":
return Param.value * 1e3
elif Param.unit == "ns":
return Param.value
elif len(Param.axis) == 1:
if Param.unit == "us":
return (Param.tau1.value + Param.axis[0]['axis']) * 1e3
elif Param.unit == "ns":
return (Param.value + Param.axis[0]['axis'])
else:
raise ValueError("Parameter must have 0 or 1 axes")
[docs]
def add_phaseshift(data, phase):
"""
Adds a phase shift to the data
Parameters
----------
data : numpy.ndarray
The data to be phase shifted
phase : float
The phase shift in degrees
Returns
-------
numpy.ndarray
"""
data = data.astype(np.complex128) * np.exp(-1j*phase*np.pi)
return data
[docs]
def _gen_ESEEM(t,freq,depth):
"""
Add an ESEEM modulation to a time domain signal
Parameters
----------
t : numpy.ndarray
The time domain signal
freq : float
The modulation frequency
depth : float
The modulation depth
Returns
-------
numpy.ndarray
The
"""
# Generate an ESEEM modulation
modulation = np.ones_like(t,dtype=np.float64)
modulation -= depth *(0.5 + 0.5*np.cos(2*np.pi*t*freq)) + depth * (0.5+0.5*np.cos(2*np.pi*t*freq/2))
return modulation
[docs]
def round_step(value, step):
return step * np.floor(np.round(value/step))