Trying to implement the WDF in Fig 5(a) of this publication. The response of the ideal op-amp implementation is given by the black curve in Fig 7:
Here is the plot I get when trying to implement the circuit myself:
I used the same scattering matrix as given in the paper:
However, the paper doesn't say what port resistance was used to adapt Rc, so I used SymPy to find S using the scattering matrix equation for R-type adaptors:
and the matrix X for the circuit, given in Fig 6:
Here is my code:
from __future__ import division
from IPython.display import display
from sympy import *
#from math import sqrt
init_printing()
RA = symbols('Ra')
RB = symbols('Rb')
RC = symbols('Rc')
RD = symbols('Rd')
RE = symbols('Re')
RF = symbols('Rf')
GA = 1/RA
GB = 1/RB
GC = 1/RC
GD = 1/RD
GE = 1/RE
GF = 1/RF
I = eye(6)
zIz = zeros(6, 10).row_join(I).row_join(zeros(6,1))
R = Matrix([
[RA, 0, 0, 0, 0, 0],
[0, RB, 0, 0, 0, 0],
[0, 0, RC, 0, 0, 0],
[0, 0, 0, RD, 0, 0],
[0, 0, 0, 0, RE, 0],
[0, 0, 0, 0, 0, RF]
])
#datum node 1 omitted to make X invertible
X = Matrix([
[GA, 0, 0, 0, -GA, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, -1, 0, 0, 0],
[0, 0, GB, 0, 0, -GB, 0, 0, 0, 0, 0, 0, -1, 0, -1, 0, 0],
[0, 0, 0, GD+GE+GF, 0, 0, 0, -GD, -GE, -GF, 0, 0, 0, 0, 0, 0, 1],
[-GA, 0, 0, 0, GA, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0],
[0, 0, -GB, 0, 0, GB, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, GC, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
[0, 0, 0, -GD, 0, 0, 0, GD, 0, 0, 0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, -GE, 0, 0, 0, 0, GE, 0, 0, 0, 0, 0, 1, 0, 0],
[0, 0, 0, -GF, 0, 0, 0, 0, 0, GF, 0, 0, 0, 0, 0, 1, 0],
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, -1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, -1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, -1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, -1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
[-1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
])
X_inv = X.inv()
S = I + 2*R*zIz*X_inv*zIz.T
S = S.applyfunc(simplify)
By setting Rc's port resistance to RbRe/(Rb + Rd + Re), and using fs=44.1kHz, S becomes the expected scattering matrix.
*the scattering matrix I found is actually almost identical to the one in the paper, but col[0],row[1:5] have signs flipped. I've tried running my circuit code with either S matrix and I end up getting the same response.
I used a WDF library from this notebook with a few modifications. Here's the code for the WDF circuit components themselves:
#One-port
class WDFOnePort(object):
def __init__(self):
self.a, self.b = 0, 0
# v = (a + b)/2
def wave_to_voltage(self):
voltage = (self.a + self.b)/2
return voltage
#Resistor
class Resistor(WDFOnePort):
def __init__(self, R):
WDFOnePort.__init__(self)
self.Rp = R
def get_reflected_wave(self, a):
self.a = a
self.b = 0 # to avoid delay-free loop, reflected wave is always zero
return self.b
class RootResistor(WDFOnePort):
def __init__(self, R, Rp):
WDFOnePort.__init__(self)
self.R = R
self.Rp = Rp
# instantaneous reflection permitted
def get_reflected_wave(self, a):
self.b = a*(self.R - self.Rp)/(self.R + self.Rp)
self.a = a
return self.b
# Capacitor
class Capacitor(WDFOnePort):
def __init__(self, C, fs=44100):
WDFOnePort.__init__(self)
print("WDF Capacitor fs: " + str(fs))
self.Rp = 1/(2*fs*C)
def get_reflected_wave(self, a):
self.b = self.a
self.a = a
return self.b
def set_incident_wave(self, a):
self.a = a
# Resistive Voltage Source
class ResistiveVoltageSource(WDFOnePort):
def __init__(self, Rs):
WDFOnePort.__init__(self)
self.Rp = Rs
def get_reflected_wave(self, a, vs=0):
self.a = a
self.b = vs
return self.b
Then I used the values from the paper to instantiate these components, form the circuit and return the impulse response:
def nullorBasedBridgedTResonator_WDF(steps=2**14):
input = np.zeros(steps)
input[0] = 1 #unit impulse
output = np.zeros(steps)
fs = 44100
#https://pureadmin.qub.ac.uk/ws/portalfiles/portal/158209014/1570255463.pdf
S = np.array([
[1, 0, 0, 0, 0, 0],
[-1, -0.001, -1, 0.001, -0.001, 0],
[-1, -0.999, 0, -0.001, 0.001, 0],
[882, 882.998, 882, 0.002, 0.998, 0],
[883, 881.999, 883, 1.001, -0.001, 0],
[884, 882.998, 882, 1.002, 0.998, -1]
])
Cap_value = 1e-9
Ra_value = 1
Rb_value = 1/(2*Cap_value*fs)
Rc_value = 500
Rd_value = 10000000
Re_value = Rb_value
Rf_value = 10000
# root element RC = R1
Rc_port_value = Rb_value*Re_value/(Rb_value + Rd_value + Re_value)
print(Rc_port_value)
Vin = ResistiveVoltageSource(Ra_value)
C1 = Capacitor(Cap_value)
R1 = RootResistor(Rc_value, Rc_port_value)
R2 = Resistor(Rd_value)
C2 = Capacitor(Cap_value)
RL = Resistor(Rf_value)
b = [0, 0, 0, 0, 0, 0]
a = [0, 0, 0, 0, 0, 0]
for i in range(steps):
#gather leaf node incident waves
a[5] = RL.get_reflected_wave(b[5])
a[4] = C2.get_reflected_wave(b[4])
a[3] = R2.get_reflected_wave(0) # don't care
a[1] = C1.get_reflected_wave(b[1])
a[0] = Vin.get_reflected_wave(0, input[i]) # don't care
# wave-up
b = np.dot(S, a)
# root, instantaneous reflection
a[2] = R1.get_reflected_wave(b[2])
#wave down
b = np.dot(S,a)
output[i] = RL.wave_to_voltage()
C1.set_incident_wave(b[1])
C2.set_incident_wave(b[4])
return output
And the code to plot the output:
x = nullorBasedBridgeTResonator_WDF()
f, h = signal.freqz(x, 1, worN=4096, fs=44100)
H = 20*np.log10(np.abs(h))
ax_mag.semilogx(f, H, label=label)
There aren't any other obvious discrepancies I can see between the methods in the paper and my methods, and the Python library I've used for these circuits has been pretty consistent with other circuits I've tried implementing.