#!/usr/bin/env python
# -*- coding: utf-8 -*-
# The MIT License (MIT)
# Copyright (c) 2020 Daniel Schick
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
# OR OTHER DEALINGS IN THE SOFTWARE.
__all__ = ['make_hash_md5', 'make_hashable', 'm_power_x',
'm_times_n', 'finderb', 'multi_gauss', 'convert_cartesian_to_polar',
'convert_polar_to_cartesian']
__docformat__ = 'restructuredtext'
import hashlib
import numpy as np
[docs]
def make_hash_md5(obj):
"""make_hash_md5
Args:
obj (any): anything that can be hashed.
Returns:
hash (str): hash from object.
"""
hasher = hashlib.md5()
hasher.update(repr(make_hashable(obj)).encode())
return hasher.hexdigest()
[docs]
def make_hashable(obj):
"""make_hashable
Recursive calls to elements of tuples, lists, dicts, set, and frozensets.
Args:
obj (any): anything that can be hashed..
Returns:
obj (tuple): hashable object.
"""
if isinstance(obj, (tuple, list)):
return tuple((make_hashable(e) for e in obj))
if isinstance(obj, dict):
return tuple(sorted((k, make_hashable(v)) for k, v in obj.items()))
if isinstance(obj, (set, frozenset)):
return tuple(sorted(make_hashable(e) for e in obj))
return obj
[docs]
def m_power_x(m, x):
"""m_power_x
Apply ``numpy.linalg.matrix_power`` to last 2 dimensions of 4-dimensional
input matrix.
Args:
m (ndarray[float, complex]): 4-dimensional input matrix.
x (float): exponent.
Returns:
m (ndarray[float, complex]): resulting matrix.
"""
if x > 1:
for i in range(np.size(m, 0)):
for j in range(np.size(m, 1)):
m[i, j, :, :] = np.linalg.matrix_power(m[i, j, :, :], x)
return m
[docs]
def m_times_n(m, n):
"""m_times_n
Matrix multiplication of last 2 dimensions for two 4-dimensional input
matrices.
Args:
m (ndarray[float, complex]): 4-dimensional input matrix.
n (ndarray[float, complex]): 4-dimensional input matrix.
Returns:
res (ndarray[float, complex]): 4-dimensional multiplication result.
"""
return np.einsum("lmij,lmjk->lmik", m, n)
[docs]
def finderb(key, array):
"""finderb
Binary search algorithm for sorted array. Searches for the first index
``i`` of array where ``key`` >= ``array[i]``. ``key`` can be a scalar or
a np.ndarray of keys. ``array`` must be a sorted np.ndarray.
Author: André Bojahr.
Licence: BSD.
Args:
key (float, ndarray[float]): single or multiple sorted keys.
array (ndarray[float]): sorted array.
Returns:
i (ndarray[float]): position indices for each key in the array.
"""
key = np.array(key, ndmin=1)
n = len(key)
i = np.zeros([n], dtype=int)
for m in range(n):
i[m] = finderb_nest(key[m], array)
return i
def finderb_nest(key, array):
"""finderb_nest
Nested sub-function of :func:`.finderb` for one single key.
Author: André Bojahr.
Licence: BSD.
Args:
key (float): single key.
array (ndarray[float]): sorted array.
Returns:
a (float): position index of key in the array.
"""
a = 0 # start of intervall
b = len(array) # end of intervall
# if the key is smaller than the first element of the
# vector we return 1
if key < array[0]:
return 0
while (b-a) > 1: # loop until the intervall is larger than 1
c = int(np.floor((a+b)/2)) # center of intervall
if key < array[c]:
# the key is in the left half-intervall
b = c
else:
# the key is in the right half-intervall
a = c
return a
[docs]
def multi_gauss(x, s=[1], x0=[0], A=[1]):
"""multi_gauss
Multiple gauss functions with width ``s`` given as FWHM and area normalized
to input ``A`` and maximum of gauss at ``x0``.
Args:
x (ndarray[float]): argument of multi_gauss.
s (ndarray[float], optional): FWHM of Gaussians. Defaults to 1.
x0 (ndarray[float], optional): centers of Gaussians. Defaults to 0.
A (ndarray[float], optional): amplitudes of Gaussians. Defaults to 1.
Returns:
y (ndarray[float]): multiple Gaussians.
"""
s = np.asarray(s)/(2*np.sqrt(2*np.log(2)))
a = np.asarray(A)/np.sqrt(2*np.pi*s**2) # normalize area to 1
x0 = np.asarray(x0)
y = np.zeros_like(x)
for i in range(len(s)):
y = y + a[i] * np.exp(-((x-x0[i])**2)/(2*s[i]**2))
return y
[docs]
def convert_polar_to_cartesian(polar):
r"""convert_polar_to_cartesian
Convert a vector or field from polar coordinates
:math:`(r, \phi, \gamma)` to cartesian coordinates :math:`(x, y, z)`:
.. math::
F_x & = r \sin(\phi)\cos(\gamma) \\
F_y & = r \sin(\phi)\sin(\gamma) \\
F_z & = r \cos(\phi)
where :math:`r`, :math:`\phi`, :math:`\gamma` are the radius
(amplitude), azimuthal, and polar angles of vector field
:math:`\mathbf{F}`, respectively.
Args:
polar (ndarray[float]): vector of field to convert.
Returns:
cartesian (ndarray[float]): converted vector or field.
"""
cartesian = np.zeros_like(polar)
amplitudes = polar[..., 0]
phis = polar[..., 1]
gammas = polar[..., 2]
cartesian[..., 0] = amplitudes*np.sin(phis)*np.cos(gammas)
cartesian[..., 1] = amplitudes*np.sin(phis)*np.sin(gammas)
cartesian[..., 2] = amplitudes*np.cos(phis)
return cartesian
[docs]
def convert_cartesian_to_polar(cartesian):
r"""convert_cartesian_to_polar
Convert a vector or field from cartesian coordinates :math:`(x, y, z)`
to polar coordinates :math:`(r, \phi, \gamma)`:
.. math::
F_r & = \sqrt{F_x^2 + F_y^2+F_z^2}\\
F_{\phi} & = \begin{cases}\\
\arctan\left(\frac{F_y}{F_x} \right) & \mathrm{for}\ F_x > 0 \\
\pi + \arctan\left(\frac{F_y}{F_x}\right)
& \mathrm{for}\ F_x < 0 \ \mathrm{and}\ F_y \geq 0 \\
\arctan\left(\frac{F_y}{F_x}\right) - \pi
& \mathrm{for}\ F_x < 0 \ \mathrm{and}\ F_y < 0 \\
0 & \mathrm{for}\ F_x = F_y = 0
\end{cases} \\
F_{\gamma} & = \arccos\left(\frac{F_z}{F_r} \right)
where :math:`F_r`, :math:`F_{\phi}`, :math:`F_{\gamma}` are the radial
(amplitude), azimuthal, and polar component of vector field
:math:`\mathbf{F}`, respectively.
Args:
cartesian (ndarray[float]): vector of field to convert.
Returns:
polar (ndarray[float]): converted vector or field.
"""
polar = np.zeros_like(cartesian)
xs = cartesian[..., 0]
ys = cartesian[..., 1]
zs = cartesian[..., 2]
amplitudes = np.sqrt(xs**2 + ys**2 + zs**2)
mask = amplitudes != 0. # mask for non-zero amplitudes
polar[..., 0] = amplitudes
polar[mask, 1] = np.arccos(np.divide(zs[mask], amplitudes[mask]))
polar[..., 2] = np.arctan2(ys, xs)
return polar