Source code for pyepr.utils

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))