Working sos filter test

This commit is contained in:
PhilJ 2022-01-12 16:53:48 -08:00
parent ef4a5b7fd1
commit 45fde86060
23 changed files with 7364 additions and 78 deletions

5
.idea/.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/

View file

@ -0,0 +1,5 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="PROJECT_PROFILE" />
</settings>
</component>

6
.idea/misc.xml Normal file
View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptSettings">
<option name="languageLevel" value="ES6" />
</component>
</project>

8
.idea/modules.xml Normal file
View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/ulab.iml" filepath="$PROJECT_DIR$/.idea/ulab.iml" />
</modules>
</component>
</project>

12
.idea/ulab.iml Normal file
View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/temp" />
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
<excludeFolder url="file://$MODULE_DIR$/tmp" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

8
.idea/vcs.xml Normal file
View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
<mapping directory="$PROJECT_DIR$/micropython" vcs="Git" />
<mapping directory="$PROJECT_DIR$/micropython/lib/axtls" vcs="Git" />
</component>
</project>

View file

@ -1,8 +1,9 @@
from .overrides import set_module
from .multiarray import asarray
from ulab import numpy as np
from ... import numpy
@set_module('numpy')
def prod(arr):
result = 1
for x in arr:
@ -47,4 +48,28 @@ def size(a, axis=None):
try:
return a.shape[axis]
except AttributeError:
return asarray(a).shape[axis]
return asarray(a).shape[axis]
def nonzero(a):
x = a.shape
row = x[0]
if len(x) == 1:
column = 0
else:
column = x[1]
nonzero_row = np.array([],dtype=np.float)
nonzero_col = np.array([],dtype=np.float)
if column == 0:
for i in range(0,row):
if a[i] != 0:
nonzero_row = numpy.append(nonzero_row,i)
return (nonzero_row,)
for i in range(0,row):
for j in range(0,column):
if a[i,j] != 0:
nonzero_row = numpy.append(nonzero_row,i)
nonzero_col = numpy.append(nonzero_col,j)
return (nonzero_row,nonzero_col)

View file

@ -4,15 +4,16 @@ def asarray(a, dtype=None):
if isinstance(a,(np.ndarray)):
return a
try:
a = np.array(a)
if dtype is not None:
a = np.array([a], dtype=dtype)
else:
a = np.array([a])
return a
except Exception as e:
if "can't convert complex to float" in e.args:
if "can't convert complex to float" in e.args or "'complex' object isn't iterable" in e.args:
try:
a = np.array(a, dtype=np.complex)
a = np.array([a], dtype=np.complex).flatten()
return a
except:
pass
raise ValueError('Could not cast %s to array' % (a))

View file

@ -1,3 +1,58 @@
from ulab import numpy as np
from .multiarray import (asarray)
from .multiarray import asarray
def zeros_like(a, dtype=None, order='K', subok=True, shape=None):
"""
Return an array of zeros with the same shape and type as a given array.
Parameters
----------
a : array_like
The shape and data-type of `a` define these same attributes of
the returned array.
dtype : data-type, optional
Overrides the data type of the result.
.. versionadded:: 1.6.0
order : {'C', 'F', 'A', or 'K'}, optional
Overrides the memory layout of the result. 'C' means C-order,
'F' means F-order, 'A' means 'F' if `a` is Fortran contiguous,
'C' otherwise. 'K' means match the layout of `a` as closely
as possible.
.. versionadded:: 1.6.0
subok : bool, optional.
If True, then the newly created array will use the sub-class
type of `a`, otherwise it will be a base-class array. Defaults
to True.
shape : int or sequence of ints, optional.
Overrides the shape of the result. If order='K' and the number of
dimensions is unchanged, will try to keep order, otherwise,
order='C' is implied.
.. versionadded:: 1.17.0
Returns
-------
out : ndarray
Array of zeros with the same shape and type as `a`.
See Also
--------
empty_like : Return an empty array with shape and type of input.
ones_like : Return an array of ones with shape and type of input.
full_like : Return a new array with shape of input filled with value.
zeros : Return a new array setting values to zero.
Examples
--------
>>> x = np.arange(6)
>>> x = x.reshape((2, 3))
>>> x
array([[0, 1, 2],
[3, 4, 5]])
>>> np.zeros_like(x)
array([[0, 0, 0],
[0, 0, 0]])
>>> y = np.arange(3, dtype=float)
>>> y
array([0., 1., 2.])
>>> np.zeros_like(y)
array([0., 0., 0.])
"""
res = np.full(a.shape, 0, dtype=a.dtype)
return res

View file

@ -1,3 +1,4 @@
from .function_base import *
from .polynomial import *
from .polynomial import *
from .type_check import *

View file

@ -1,11 +1,20 @@
from ulab import numpy as np
from ..core.multiarray import asarray
from ..core.multiarray import (asarray)
from ..core.overrides import set_module
@set_module('numpy')
def append(arr, values, axis=None):
arr = asarray(arr)
values = asarray(values)
if axis is None:
if len(arr.shape) != 1:
arr.flatten()
values.flatten()
arr = arr.flatten()
values = values.flatten()
axis = len(arr.shape)-1
return np.concatenate((arr, values), axis=axis)
return np.concatenate((arr, values), axis=axis)
def delete(arr, obj, axis=None):
mask = np.ones(len(arr), dtype=np.bool)
mask[obj] = 0
return arr[mask]

View file

@ -0,0 +1,64 @@
from ulab import numpy as np
from ..core.multiarray import (asarray)
from ..core.overrides import set_module
@set_module('numpy')
def _isreal(a):
result = []
for x in a:
if isinstance(x, float):
result.append(True)
elif isinstance(x, complex) and x.imag == 0:
result.append(True)
else:
result.append(False)
return result
def isreal(x):
"""
Returns a bool array, where True if input element is real.
If element has complex type with zero complex part, the return value
for that element is True.
Parameters
----------
x : array_like
Input array.
Returns
-------
out : ndarray, bool
Boolean array of same shape as `x`.
Notes
-----
`isreal` may behave unexpectedly for string or object arrays (see examples)
See Also
--------
iscomplex
isrealobj : Return True if x is not a complex type.
Examples
--------
>>> a = np.array([1+1j, 1+0j, 4.5, 3, 2, 2j], dtype=complex)
>>> np.isreal(a)
array([False, True, True, True, True, False])
The function does not work on string arrays.
>>> a = np.array([2j, "a"], dtype="U")
>>> np.isreal(a) # Warns about non-elementwise comparison
False
Returns True for all elements in input array of ``dtype=object`` even if
any of the elements is complex.
>>> a = np.array([1, "2", 3+4j], dtype=object)
>>> np.isreal(a)
array([ True, True, True])
isreal should not be used with object arrays
>>> a = np.array([1+2j, 2+1j], dtype=object)
>>> np.isreal(a)
array([ True, True])
"""
x = asarray(x)
result = _isreal(x)
return result if len(result) > 1 else result[0]

View file

@ -4,7 +4,7 @@ from ulab import numpy
from ulab import numpy as np
from ulab import scipy as spy
from ...numpy import (atleast_1d, poly, asarray, prod, size, append)
from ...numpy import (atleast_1d, poly, asarray, prod, size, append, nonzero, zeros_like, delete, isreal)
def butter(N, Wn, btype='low', analog=False, output='ba', fs=None):
"""
@ -393,7 +393,31 @@ def zpk2tf(z, p, k):
return b, a
def cplxreal(z, tol=None):
def _to_tuple(a):
result = []
for x in a:
result.append([x.real, x.imag])
return result
def _to_complex(a):
result = np.array([], dtype=np.complex)
for x in a:
t = np.array([complex(x[0] + x[1] * 1j)], dtype=np.complex)
result = np.concatenate((result, t), axis=0)
return result
def lexsort(z):
z = _to_tuple(z)
return sorted(range(len(z)), key=lambda i: (z[i][0],abs(z[i][1])))
def do_lexsort(z):
# z = z[np.lexsort((abs(z.imag), z.real))]
z = _to_tuple(z)
z = sorted(z,key=lambda x:(x[0], abs(x[1])))
return _to_complex(z)
def _cplxreal(z, tol=None):
"""
Split into complex and real parts, combining conjugate pairs.
The 1-D input vector `z` is split up into its complex (`zc`) and real (`zr`)
@ -447,20 +471,23 @@ def cplxreal(z, tol=None):
# Get tolerance from dtype of input
tol = 100 * abs(7./3 - 4./3 - 1) #np.finfo((1.0 * z).dtype).eps
print(z)
# Sort by real part, magnitude of imaginary part (speed up further sorting)
z = z[np.lexsort((abs(z.imag), z.real))]
#z = z[np.lexsort((abs(z.imag), z.real))]
#z = _to_tuple(z)
#z = sorted(z,key=lambda x:(x[0], abs(x[1])))
#z = _to_complex(z)
z = do_lexsort(z)
# Split reals from conjugate pairs
real_indices = abs(z.imag) <= tol * abs(z)
zr = z[real_indices].real
if len(zr) == len(z):
# Input is entirely real
return array([]), zr
return np.array([],dtype=np.float), zr
# Split positive and negative halves of conjugates
z = z[~real_indices]
inv_real_indices = np.array([not elem for elem in real_indices], dtype=np.bool)
z = z[inv_real_indices]
zp = z[z.imag > 0]
zn = z[z.imag < 0]
@ -470,28 +497,34 @@ def cplxreal(z, tol=None):
# Find runs of (approximately) the same real part
same_real = np.diff(zp.real) <= tol * abs(zp[:-1])
diffs = numpy.diff(concatenate(([0], same_real, [0])))
run_starts = numpy.nonzero(diffs > 0)[0]
run_stops = numpy.nonzero(diffs < 0)[0]
same_real = np.array(same_real * 1, dtype=np.uint8)
a = np.array([0], dtype=np.uint8)
b = np.array([0], dtype=np.uint8)
x = np.concatenate((a, same_real, b))
diffs = numpy.diff(np.array(x, dtype=np.float))
start = np.array((diffs > 0) * 1, dtype=np.uint16)
stop = np.array((diffs < 0) * 1, dtype=np.uint16)
run_starts = nonzero(start)[0]
run_stops = nonzero(stop)[0]
# Sort each run by their imaginary parts
for i in range(len(run_starts)):
start = run_starts[i]
stop = run_stops[i] + 1
start = int(run_starts[i])
stop = int(run_stops[i]) + 1
for chunk in (zp[start:stop], zn[start:stop]):
chunk[...] = chunk[np.lexsort([abs(chunk.imag)])]
a = 1
# Check that negatives match positives
if any(abs(zp - zn.conj()) > tol * abs(zn)):
if any(abs(zp - np.conjugate(zn)) > tol * abs(zn)):
raise ValueError('Array contains complex value with no matching '
'conjugate.')
# Average out numerical inaccuracy in real vs imag parts of pairs
zc = (zp + zn.conj()) / 2
zc = (zp + np.conjugate(zn)) / 2
return zc, zr
def zpk2sos(z, p, k, pairing='nearest'):
"""
Return second-order sections from zeros, poles, and gain of a system
@ -680,78 +713,84 @@ def zpk2sos(z, p, k, pairing='nearest'):
z = np.concatenate((z, [0.]))
assert len(p) == len(z)
# Ensure we have complex conjugate pairs
# (note that _cplxreal only gives us one element of each complex pair):
z = np.concatenate(_cplxreal(z))
p = np.concatenate(_cplxreal(p))
cplx, real = _cplxreal(p)
p = cplx
cplx, real = _cplxreal(z)
z = real
p_sos = np.zeros((n_sections, 2))
z_sos = zeros_like(p_sos)
p_sos = np.zeros((n_sections, 2), np.complex128)
z_sos = np.zeros_like(p_sos)
for si in range(n_sections):
# Select the next "worst" pole
p1_idx = np.argmin(np.abs(1 - np.abs(p)))
p1_idx = np.argmin(abs(1 - abs(p)))
p1 = p[p1_idx]
p = np.delete(p, p1_idx)
p = delete(p, p1_idx)
# Pair that pole with a zero
if np.isreal(p1) and np.isreal(p).sum() == 0:
if isreal(p1) and np.sum(isreal(p)) == 0:
# Special case to set a first-order section
z1_idx = _nearest_real_complex_idx(z, p1, 'real')
z1 = z[z1_idx]
z = np.delete(z, z1_idx)
z = delete(z, z1_idx)
p2 = z2 = 0
else:
if not np.isreal(p1) and np.isreal(z).sum() == 1:
if not isreal(p1) and np.sum(isreal(z)) == 1:
# Special case to ensure we choose a complex zero to pair
# with so later (setting up a first-order section)
z1_idx = _nearest_real_complex_idx(z, p1, 'complex')
assert not np.isreal(z[z1_idx])
assert not isreal(z[z1_idx])
else:
# Pair the pole with the closest zero (real or complex)
z1_idx = np.argmin(np.abs(p1 - z))
z1_idx = np.argmin(abs(p1 - z))
z1 = z[z1_idx]
z = np.delete(z, z1_idx)
z = delete(z, z1_idx)
# Now that we have p1 and z1, figure out what p2 and z2 need to be
if not np.isreal(p1):
if not np.isreal(z1): # complex pole, complex zero
p2 = p1.conj()
z2 = z1.conj()
if not isreal(p1):
if not isreal(z1): # complex pole, complex zero
p2 = np.conjugate(p1)
z2 = np.conjugate(z1)
else: # complex pole, real zero
p2 = p1.conj()
p2 = np.conjugate(p1) #p1.conj()
z2_idx = _nearest_real_complex_idx(z, p1, 'real')
z2 = z[z2_idx]
assert np.isreal(z2)
z = np.delete(z, z2_idx)
assert isreal(z2)
z = delete(z, z2_idx)
else:
if not np.isreal(z1): # real pole, complex zero
z2 = z1.conj()
if not isreal(z1): # real pole, complex zero
z2 = np.conjugate(z1)
p2_idx = _nearest_real_complex_idx(p, z1, 'real')
p2 = p[p2_idx]
assert np.isreal(p2)
assert isreal(p2)
else: # real pole, real zero
# pick the next "worst" pole to use
idx = np.nonzero(np.isreal(p))[0]
assert len(idx) > 0
p2_idx = idx[np.argmin(np.abs(np.abs(p[idx]) - 1))]
p2_idx = idx[np.argmin(np.abs(abs(p[idx]) - 1))]
p2 = p[p2_idx]
# find a real zero to match the added pole
assert np.isreal(p2)
assert isreal(p2)
z2_idx = _nearest_real_complex_idx(z, p2, 'real')
z2 = z[z2_idx]
assert np.isreal(z2)
z = np.delete(z, z2_idx)
p = np.delete(p, p2_idx)
p_sos[si] = [p1, p2]
z_sos[si] = [z1, z2]
assert isreal(z2)
z = delete(z, z2_idx)
p = delete(p, p2_idx)
p_sos = np.array(p_sos, dtype=np.complex)
p_sos[si] = np.array([p1, p2], dtype=np.complex)
z_sos[si] = np.array([z1, z2], dtype=np.float)
assert len(p) == len(z) == 0 # we've consumed all poles and zeros
del p, z
# Construct the system, reversing order so the "worst" are last
p_sos = np.reshape(p_sos[::-1], (n_sections, 2))
z_sos = np.reshape(z_sos[::-1], (n_sections, 2))
gains = np.ones(n_sections, np.array(k).dtype)
p_sos = p_sos[::-1].reshape((n_sections, 2))
z_sos = z_sos[::-1].reshape((n_sections, 2))
gains = np.ones(n_sections, dtype=np.float)
gains[0] = k
for si in range(n_sections):
x = zpk2tf(z_sos[si], p_sos[si], gains[si])
@ -1030,6 +1069,7 @@ def bilinear_zpk(z, p, k, fs):
# Any zeros that were at infinity get moved to the Nyquist frequency
a = -np.ones(degree) + 0j
z_z = append(z_z, a)
# Compensate for gain change
@ -1040,11 +1080,16 @@ def bilinear_zpk(z, p, k, fs):
def _nearest_real_complex_idx(fro, to, which):
"""Get the next closest real or complex element based on distance"""
assert which in ('real', 'complex')
order = np.argsort(np.abs(fro - to))
mask = np.isreal(fro[order])
a = np.array(abs(fro - to), dtype=np.float)
order = np.argsort(a, axis=0) # Differs from numpy TODO
fo = [fro[i] for i in order]
sorted_array_list = [fro[i] for i in order]
mask = isreal(np.array(sorted_array_list, dtype=np.float))
if which == 'complex':
mask = ~mask
return order[np.nonzero(mask)[0][0]]
mask = np.array([mask], dtype=np.uint16)
nzm = np.array(nonzero(mask)[0],dtype=np.int8)
return order[nzm[0]]
def _relative_degree(z, p):
"""

View file

@ -15,3 +15,13 @@ print(numpy.prod([]))
print(numpy.prod([1.,2.]))
a = np.array([1,2,3])
print(numpy.nonzero(a))
b = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print(numpy.nonzero(b > 3))
c = np.array([0,1,0,-1])
print(numpy.nonzero(c > 0))
print(numpy.nonzero(c < 0))

View file

@ -0,0 +1,14 @@
import math
import sys
sys.path.append('.')
from ulab import numpy as np
from snippets import numpy
x = np.array([[0, 1, 2],
[3, 4, 5]])
print(numpy.zeros_like(x))
y = np.array([[0, 1j, -2j],[3, 4, 5]], dtype=np.complex)
print(numpy.zeros_like(y))

View file

@ -0,0 +1 @@
array([[0, 0, 0],[0, 0, 0]])

View file

@ -1,11 +1,6 @@
from ulab import numpy as np
from ..core.multiarray import asarray
import math
import sys
sys.path.append('.')
def append(arr, values, axis=None):
arr = asarray(arr)
if axis is None:
if len(arr.shape) != 1:
arr.flatten()
values.flatten()
axis = len(arr.shape)-1
return np.concatenate((arr, values), axis=axis)
from snippets import numpy
from ulab import numpy as np

View file

@ -0,0 +1,9 @@
import math
import sys
sys.path.append('.')
from snippets import numpy
from ulab import numpy as np
a = np.array([1, 2j, 3, 4j], dtype=np.complex)
print (numpy.isreal(a))

View file

@ -5,8 +5,15 @@ sys.path.append('.')
from snippets import scipy
from ulab import numpy as np
np.set_printoptions(threshold=100)
a = [4, 3, 1, 2-2j, 2+2j, 2-1j, 2+1j, 2-1j, 2+1j, 1+1j, 1-1j]
print('_cplxreal: ', scipy.cplxreal(a))
#print('_cplxreal: ', scipy.cplxreal(a))
f = np.array([-1.0, -1.0, -1.0, -1.0, 1.0, 1.0, 1.0], dtype=np.float)
t = (0.9984772174419884+0.01125340518638924j)
w = 'real'
#print('nearest_real_complex_idx: ', scipy.nearest_real_complex_idx(f,t,w))
nyquistRate = 48000 * 2
centerFrequency_Hz = 480.0

32
tests/1d/complex/all.py Normal file
View file

@ -0,0 +1,32 @@
import math
from ulab import numpy
from ulab import numpy as np
from ulab import scipy as spy
def to_tuple(a):
result = []
for x in a:
result.append([x.real, x.imag])
return result
def to_complex(a):
result = np.array([], dtype=np.complex)
for x in a:
t = np.array([complex(x[0] + x[1] * 1j)], dtype=np.complex)
result = np.concatenate((result, t), axis=0)
return result
roots = np.array([0.99847722-0.01125341j, 0.99552224-0.01283305j, 0.99552224+0.01283305j, 0.99847722+0.01125341j,
0.99698268+0.02147022j, 0.9940139500000001+0.01703982j, 0.9940139500000001-0.01703982j, 0.99698268-0.02147022j], dtype=np.complex)
print(roots)
r = to_tuple(roots)
s = sorted(r,key=lambda x:(x[0], x[1]))
f = to_complex(s)
print(f)
#print()
#print(sorted(r,key=lambda x:(x[0], x[1])))
#print(np.sort_complex(np.array([-0.9800000000000001+1.0j, 0.9800000000000001-1.0j, 0.99+1.0j, 0.99-1.0j], dtype=np.complex)))

1436
tests/1d/complex/butter.py Normal file

File diff suppressed because it is too large Load diff

5468
tests/1d/complex/filters.py Normal file

File diff suppressed because it is too large Load diff

70
tests/1d/complex/solve.py Normal file
View file

@ -0,0 +1,70 @@
import math
from ulab import numpy
from ulab import numpy as np
from ulab import scipy as spy
def asarray(a, dtype=None):
if isinstance(a,(np.ndarray)):
return a
return a
def atleast_1d(*arys):
res = []
for ary in arys:
ary = asarray(ary)
if not isinstance(ary,(np.ndarray)):
ary = np.array([ary])
result = ary.reshape((1,))
else:
result = ary
res.append(result)
if len(res) == 1:
return res[0]
else:
return res
def poly(seq_of_zeros):
seq_of_zeros = atleast_1d(seq_of_zeros)
sh = seq_of_zeros.shape
if len(sh) == 2 and sh[0] == sh[1] and sh[0] != 0:
seq_of_zeros = eigvals(seq_of_zeros)
elif len(sh) == 1:
dt = seq_of_zeros.dtype
# Let object arrays slip through, e.g. for arbitrary precision
if dt != object:
seq_of_zeros = seq_of_zeros #seq_of_zeros.astype(mintypecode(dt.char))
else:
raise ValueError("input must be 1d or non-empty square 2d array.")
if len(seq_of_zeros) == 0:
return 1.0
dt = seq_of_zeros.dtype
print(dt)
a = np.ones((1,), dtype=dt)
print(a)
print(seq_of_zeros)
for k in range(len(seq_of_zeros)):
a = np.convolve(a, np.array([1, -seq_of_zeros[k]], dtype=dt))
print(a)
#if issubclass(a.dtype.type, NX.complexfloating):
# if complex roots are all complex conjugates, the roots are real.
roots = asarray(seq_of_zeros, complex)
print('roots',roots)
p = np.sort_complex(roots)
print(p)
q = np.conjugate(roots)
s = np.sort_complex(q)
print(s)
#if NX.all(NX.sort(roots) == NX.sort(roots.conjugate())):
a = a.real.copy()
return a
p = np.array([-0.3826834323650898+0.9238795325112868j, -0.9238795325112868+0.3826834323650898j,
-0.9238795325112868-0.3826834323650898j, -0.3826834323650898-0.9238795325112868j], dtype=np.complex)
print(poly(p))