Closed virsto closed 5 years ago
See the docs here for more info on the function parameters
.
If your issue is with the left hand side of the expression, in python 3 you can tuple-unpack a sequence and allow all the remainder to be assigned to a single variable. In this case, parameters
returns a list of Parameter
objects. The first one is assigned to a0
, the rest to cos_a
.
Let me know if there are more thing unclear.
p.s. this line is just a shorthand for doing
a_0 = Parameter('a_0')
...
a_N = Parameter('a_n')
I will close this for now, if you have further questions you are always welcome to ask them, even if the issue is closed I'll keep following this discussion.
I have attached some code that might be useful for those who are interested in learning more about symfit applications.
Best regards, --V. Stokes
När du har kontakt med oss på Uppsala universitet med e-post så innebär det att vi behandlar dina personuppgifter. För att läsa mer om hur vi gör det kan du läsa här: http://www.uu.se/om-uu/dataskydd-personuppgifter/
E-mailing Uppsala University means that we will process your personal data. For more information on how this is performed, please read here: http://www.uu.se/en/about-uu/data-protection-policy
from symfit import parameters, variables, sin, cos, Fit, CallableModel import numpy as np import matplotlib.pyplot as plt
import matplotlib
matplotlib.use("WXAgg")
import sys
"""
Purpose: Example of using callable symfit Fourier Series model to fit
sinusoidal functions and a step function. Also, shows how to
extrapolate (frontend and backend) fits.
Note:
Python packages used: python - 3.6.5 (v3.6.5:f59c0932b4, Mar 28 2018, 17:00:18) matplotlib - vers. 3.0.2 numpy - vers. 1.15.2+mk1 symfit - vers. 0.4.6
Coded by: V. Stokes (vs@it.uu.se)
Date: 2019.02.28
"""
def ext_xdata(orgL,dx,extF,extB):
"""
Purpose: add x-values to frontend and/or to backend of xdata
Inputs:
orgL - original (unextended) length (number) of x-values
dx - delta (spacing) between x-values
extF - number of x-values to be added to frontend (>= 0)
extB - number of x-values to be added to backend (>= 0)
Otputs:
extx - extended xdata
"""
extx = np.fromiter([k*dx for k in np.arange(-extF,orgL+extB)],np.float_)
return extx
def addNoise(noiseSF,ydata):
"""
Purpose: add scaled noise, sfN(0,1) to signal to be modelled
where, N(0,1) is standardized normal distribution (mean=0,sd=1)
Inputs:
noiseSF - scale factor (sf) for noise
ydata - TS values to which noise is added
Otputs:
ydata - TS with noise added
"""
np.random.seed(131) # for repeatability (same noise series for each execution)
N = len(ydata)
ydata_rms = np.sqrt((1/N)np.sum(ydata2))
noiseTS = noiseSFnp.random.randn(np.size(ydata)) # white Gaussian noise
noi_rms = np.sqrt((1/N)np.sum(noiseTS2))
ydata += noiseTS
print(" Noise = %6.2fN(0,1)"%noiseSF)
print(" SNR(dB) = %8.3f"%(20np.log10(ydata_rms/noi_rms)))
return ydata
def genStepData():
"""
Purpose: generate x,y-values for step function centered about x = 0
"""
xdata = np.linspace(-np.pi, np.pi)
ydata = np.zeros_like(xdata)
ydata[xdata > 0] = 20 # step transition from y = 0 to 20 at x = 0
return xdata,ydata
def genSinusoidalData(N,Np,NSigs):
"""
Purpose: generate x,y-values for sinusoidal functions
Inputs:
N - number of samples in signal
Np - number of samples to be used for fit (Np <= N)
NSigs - number of sinusoidal functions added to generate composite signal
Otputs:
n - order of FS model to used for fit
arg - 2np.pit (xdata) (length = Np)
sig - composite signal (length = Np) to be approximated by FS
"""
sigs = NSigs*[0]
if NSigs == 1:
fc = [ 3]
am = [ 5]
n = 1
elif NSigs == 2:
fc = [ 1, 3]
am = [20, 5]
n = 3
elif NSigs == 3:
fc = [ 1, 2, 3]
am = [20, 10, 5]
n = 4
dt = 1/N # time between samples (sec)
fs = 1/dt # sample frequency (Hz)
fny = fs/2 # Nyquist frequency (Hz)
t = dt*np.arange(0.0,Np) # Np samples to be used for fit
# print('t = ',t)
print (" dt (time between samples) = %10.6f"%dt)
print (' fs (Sample freq) = %6.1f [Hz]'%fs)
print (' fny (Nyquist freq) = %6.1f [Hz]'%fny)
FMax = max(fc) # max freq present in in signals
print (' Fmax (max freq in signal) = %4d [Hz]'%FMax)
"""
if FMax >= fny:
print ('!WARNING -- sample frequency (fs) must be > Fmax for PSD estimation')
sys.exit()
"""
arg = 2*np.pi*t # (xdata)
if NSigs == 1:
sigs[0] = am[0]*np.sin(arg*fc[0])
elif NSigs == 2:
sigs[0] = am[0]*np.sin(arg*fc[0])
sigs[1] = am[1]*np.cos(arg*fc[1])
elif NSigs == 3:
sigs[0] = am[0]*np.cos(arg*fc[0])
sigs[1] = am[1]*np.sin(arg*fc[1])
sigs[2] = am[2]*np.cos(arg*fc[2])
sig = sum(sigs) # composite (sum) of sinusoidal functions
return n,arg,sig
def fourier_series(x, f, n=1): """ Purpose: generate symbolic Fourier series model of order-n Inputs: x - independent variable f - base (fundamental) frequency of the Fourier series (radians) n - order of the Fourier series. Otputs: series - symbolic equation for FS of order-n """
a0, *cos_a = parameters(','.join(['a{}'.format(i) for i in range(0, n + 1)]))
sin_b = parameters(','.join(['b{}'.format(i) for i in range(1, n + 1)]))
# Construct FS
series = a0 + sum(ai*cos(i*f*x) + bi*sin(i*f*x)
for i, (ai, bi) in enumerate(zip(cos_a, sin_b), start=1))
return series
def plotResults(TESTCASE,xdata,ydata,extx,y1_eval,y2_eval,title): """ Purpose: plot the results of Fourier series fit and any extensions to fit Inputs: TESTCASE - number of test case xdata - x-values for original (unextended) data ydata - y-values for original (unextended) data extx - extended x-values y1_eval - y-values for fit to unextended data y2eval - y-values for extended data from unextended fit title - title of plot figure """ fig, = plt.subplots(1,1,constrained_layout=True,figsize=(9.0,5.0)) fig.canvas.set_window_title('SymFit - Fourier Series Approximation, TESTCASE = %d'%TESTCASE)
pltManager = plt.get_current_fig_manager()
pltManager.window.SetPosition((10,10)) # pixels offset from top left corner of display
#p1, = plt.plot(xdata, ydata,lw=2.5,label='Data for FS fit') # data to be fitted with a FS
p1, = plt.plot(xdata, ydata,marker='.',ls=':',label='Data for FS fit') # data to be fitted with a FS
p2, = plt.plot(extx, y2_eval,ls=':',color='red',label='FS fit extended')
p3, = plt.plot(xdata,y1_eval,lw=2.0,color='yellow',label='FS fit')
plt.xlabel('x')
plt.ylabel('y')
plt.legend(handles=[p1,p3,p2])
plt.grid(True)
plt.title(title)
plt.show()
def indexChk(N,Np):
print (" N,Np: %4d,%4d"%(N,Np))
if Np > N:
print ("ERROR - Np > N not allowed!")
sys.exit()
def chkRange(name,value,Range):
print (' %s: %d'%(name,value) )
if value not in Range:
print("ERROR - %s not in range!"%name)
sys.exit()
if name == 'main':
testCaseMx = 2
TESTCASE = 1 # sinusoidal functions
#TESTCASE = 2 # step function
chkRange('TESTCASE',TESTCASE,[1,testCaseMx])
if TESTCASE == 1:
# Set maximum value for nsigMx (maximum number of sinusoidal functions that can be added)
nsigMx = 3
# Set noise level to be added to signal (or no noise)
noise_sf = 0.0 # no noise
noise_sf = 1.5
if TESTCASE == 1:
N = 64 # number of samples in input signal
# Select NP, the number of samples in input signal to be used for fit
Np = 56 # use Np samples of N for FS fit of input signal
indexChk(N,Np)
# Set the number of sinusoidal functions for input signal
nsigs = 1
nsigs = 2
nsigs = 3
chkRange('nsigs',nsigs,[1,nsigMx])
# Generate Sinusoidal data
n,xdata,ydata = genSinusoidalData(N,Np,nsigs)
lenx = len(xdata)
# Set Extensions (>= 0)
extF = lenx # extension to frontend
extB = lenx # extension to backend
title = 'Fourier series (order=%2d) fit to Sinusoids'%n
elif TESTCASE == 2:
# Generate step function data
xdata,ydata = genStepData()
N = len(xdata)
# Select NP, the number of samples in input data to be used for fit
Np = N # all samples in input data used for fit
indexChk(N,Np)
# Set FS order
n = 3
# Set Extensions (>= 0)
extF = 100
extB = N+10
title = 'Fourier series (order=%2d) fit to Step function'%n
else:
# This should never be executed!
sys.exit()
if noise_sf > 0.0:
ydata = addNoise(noise_sf,ydata)
title = title + ' + %3.1f*N(0,1)'%noise_sf
dx = xdata[1] - xdata[0]
print (" dx (delta between x) = %10.6f"%dx)
# Generate symbolic Fourier series (FS)
x, y = variables('x, y')
w, = parameters('w') # model is nonlinear in this parameter (base frequency)
Fourier_model = CallableModel({y: fourier_series(x, f=w, n=n)})
print ('\nFourier Series Model:')
print (Fourier_model)
# Define the Fit object for this model with data ...
fit = Fit(Fourier_model, x=xdata, y=ydata)
# ... and fit it
fit_result = fit.execute()
print(fit_result)
# Another way to extract Fourier coefficients
[print("%3s %20.12f" %(k,v)) for k, v in fit_result.params.items()]
# Extend xdata
extx = ext_xdata(Np,dx,extF,extB)
print (" x for FS fit in [%10.6f, %10.6f]"%(xdata[0],xdata[-1]))
print (' x for FS fit extended in [%10.6f, %10.6f]'%(extx[0], extx[-1]))
y1_eval, = Fourier_model(x=xdata,**fit_result.params) # evaluate FS model over xdata
y2_eval, = Fourier_model(x=extx, **fit_result.params) # evaluate FS model over extx
# Plot the results
plotResults(TESTCASE,xdata,ydata,extx,y1_eval,y2_eval,title)
Nice one, thanks.
Small question: why do you use a CallableModel
instead of a normal Model
?
Thanks for sharing, it's always nice to have more code examples out there.
Apparently I cannot add markdown to an email reply, so I'll repost the code here with code highlighting:
# coding:utf-8 # this allows utf-8 characters
from symfit import parameters, variables, sin, cos, Fit, CallableModel
import numpy as np
import matplotlib.pyplot as plt
# For setting size and position of matplotlib figure
import matplotlib
matplotlib.use("WXAgg")
import sys
"""
Purpose: Example of using callable symfit Fourier Series model to fit
sinusoidal functions and a step function. Also, shows how to
extrapolate (frontend and backend) fits.
Note:
1. FS model is linear in coefficients of sinusoidal functions but nonlinear in base
frequency (w)
2. This code can easily be modified to handlle more test cases and fit types
3. Code based on example at:
https://symfit.readthedocs.io/en/stable/examples/ex_fourier_series.html
4. Python packages used:
python - 3.6.5 (v3.6.5:f59c0932b4, Mar 28 2018, 17:00:18)
matplotlib - vers. 3.0.2
numpy - vers. 1.15.2+mk1
symfit - vers. 0.4.6
Coded by: V. Stokes (vs@it.uu.se)
Date: 2019.02.28
"""
def ext_xdata(orgL,dx,extF,extB):
"""
Purpose: add x-values to frontend and/or to backend of xdata
Inputs:
orgL - original (unextended) length (number) of x-values
dx - delta (spacing) between x-values
extF - number of x-values to be added to frontend (>= 0)
extB - number of x-values to be added to backend (>= 0)
Otputs:
extx - extended xdata
"""
# Simple numpy array comprehension for extended x-values
extx = np.fromiter([k*dx for k in np.arange(-extF,orgL+extB)],np.float_)
return extx
def addNoise(noiseSF,ydata):
"""
Purpose: add scaled noise, sf*N(0,1) to signal to be modelled
where, N(0,1) is standardized normal distribution (mean=0,sd=1)
Inputs:
noiseSF - scale factor (sf) for noise
ydata - TS values to which noise is added
Otputs:
ydata - TS with noise added
"""
np.random.seed(131) # for repeatability (same noise series for each execution)
N = len(ydata)
ydata_rms = np.sqrt((1/N)*np.sum(ydata**2))
noiseTS = noiseSF*np.random.randn(np.size(ydata)) # white Gaussian noise
noi_rms = np.sqrt((1/N)*np.sum(noiseTS**2))
ydata += noiseTS
print(" Noise = %6.2f*N(0,1)"%noiseSF)
print(" SNR(dB) = %8.3f"%(20*np.log10(ydata_rms/noi_rms)))
return ydata
def genStepData():
"""
Purpose: generate x,y-values for step function centered about x = 0
"""
xdata = np.linspace(-np.pi, np.pi)
ydata = np.zeros_like(xdata)
ydata[xdata > 0] = 20 # step transition from y = 0 to 20 at x = 0
return xdata,ydata
def genSinusoidalData(N,Np,NSigs):
"""
Purpose: generate x,y-values for sinusoidal functions
Inputs:
N - number of samples in signal
Np - number of samples to be used for fit (Np <= N)
NSigs - number of sinusoidal functions added to generate composite signal
Otputs:
n - order of FS model to used for fit
arg - 2*np.pi*t (xdata) (length = Np)
sig - composite signal (length = Np) to be approximated by FS
"""
# Center frequencies of signal components
sigs = NSigs*[0]
if NSigs == 1:
fc = [ 3]
am = [ 5]
n = 1
elif NSigs == 2:
fc = [ 1, 3]
am = [20, 5]
n = 3
elif NSigs == 3:
fc = [ 1, 2, 3]
am = [20, 10, 5]
n = 4
dt = 1/N # time between samples (sec)
fs = 1/dt # sample frequency (Hz)
fny = fs/2 # Nyquist frequency (Hz)
t = dt*np.arange(0.0,Np) # Np samples to be used for fit
# print('t = ',t)
print (" dt (time between samples) = %10.6f"%dt)
print (' fs (Sample freq) = %6.1f [Hz]'%fs)
print (' fny (Nyquist freq) = %6.1f [Hz]'%fny)
FMax = max(fc) # max freq present in in signals
print (' Fmax (max freq in signal) = %4d [Hz]'%FMax)
"""
if FMax >= fny:
print ('!WARNING -- sample frequency (fs) must be > Fmax for PSD estimation')
sys.exit()
"""
arg = 2*np.pi*t # (xdata)
if NSigs == 1:
sigs[0] = am[0]*np.sin(arg*fc[0])
elif NSigs == 2:
sigs[0] = am[0]*np.sin(arg*fc[0])
sigs[1] = am[1]*np.cos(arg*fc[1])
elif NSigs == 3:
sigs[0] = am[0]*np.cos(arg*fc[0])
sigs[1] = am[1]*np.sin(arg*fc[1])
sigs[2] = am[2]*np.cos(arg*fc[2])
sig = sum(sigs) # composite (sum) of sinusoidal functions
return n,arg,sig
def fourier_series(x, f, n=1):
"""
Purpose: generate symbolic Fourier series model of order-n
Inputs:
x - independent variable
f - base (fundamental) frequency of the Fourier series (radians)
n - order of the Fourier series.
Otputs:
series - symbolic equation for FS of order-n
"""
# Make the parameter objects for all the terms
a0, *cos_a = parameters(','.join(['a{}'.format(i) for i in range(0, n + 1)]))
sin_b = parameters(','.join(['b{}'.format(i) for i in range(1, n + 1)]))
# Construct FS
series = a0 + sum(ai*cos(i*f*x) + bi*sin(i*f*x)
for i, (ai, bi) in enumerate(zip(cos_a, sin_b), start=1))
return series
def plotResults(TESTCASE,xdata,ydata,extx,y1_eval,y2_eval,title):
"""
Purpose: plot the results of Fourier series fit and any extensions to fit
Inputs:
TESTCASE - number of test case
xdata - x-values for original (unextended) data
ydata - y-values for original (unextended) data
extx - extended x-values
y1_eval - y-values for fit to unextended data
y2_eval - y-values for extended data from unextended fit
title - title of plot figure
"""
fig,_ = plt.subplots(1,1,constrained_layout=True,figsize=(9.0,5.0))
fig.canvas.set_window_title('SymFit - Fourier Series Approximation, TESTCASE = %d'%TESTCASE)
# Used to set position of the plot
pltManager = plt.get_current_fig_manager()
pltManager.window.SetPosition((10,10)) # pixels offset from top left corner of display
#p1, = plt.plot(xdata, ydata,lw=2.5,label='Data for FS fit') # data to be fitted with a FS
p1, = plt.plot(xdata, ydata,marker='.',ls=':',label='Data for FS fit') # data to be fitted with a FS
p2, = plt.plot(extx, y2_eval,ls=':',color='red',label='FS fit extended')
p3, = plt.plot(xdata,y1_eval,lw=2.0,color='yellow',label='FS fit')
plt.xlabel('x')
plt.ylabel('y')
plt.legend(handles=[p1,p3,p2])
plt.grid(True)
plt.title(title)
plt.show()
def indexChk(N,Np):
print (" N,Np: %4d,%4d"%(N,Np))
if Np > N:
print ("ERROR - Np > N not allowed!")
sys.exit()
def chkRange(name,value,Range):
print (' %s: %d'%(name,value) )
if value not in Range:
print("ERROR - %s not in range!"%name)
sys.exit()
#--------------------------------------------------------------------------------
if __name__ == '__main__':
# Set maximum value for TESTCASE
testCaseMx = 2
TESTCASE = 1 # sinusoidal functions
#TESTCASE = 2 # step function
chkRange('TESTCASE',TESTCASE,[1,testCaseMx])
if TESTCASE == 1:
# Set maximum value for nsigMx (maximum number of sinusoidal functions that can be added)
nsigMx = 3
# Set noise level to be added to signal (or no noise)
noise_sf = 0.0 # no noise
noise_sf = 1.5
if TESTCASE == 1:
N = 64 # number of samples in input signal
# Select NP, the number of samples in input signal to be used for fit
Np = 56 # use Np samples of N for FS fit of input signal
indexChk(N,Np)
# Set the number of sinusoidal functions for input signal
nsigs = 1
nsigs = 2
nsigs = 3
chkRange('nsigs',nsigs,[1,nsigMx])
# Generate Sinusoidal data
n,xdata,ydata = genSinusoidalData(N,Np,nsigs)
lenx = len(xdata)
# Set Extensions (>= 0)
extF = lenx # extension to frontend
extB = lenx # extension to backend
title = 'Fourier series (order=%2d) fit to Sinusoids'%n
elif TESTCASE == 2:
# Generate step function data
xdata,ydata = genStepData()
N = len(xdata)
# Select NP, the number of samples in input data to be used for fit
Np = N # all samples in input data used for fit
indexChk(N,Np)
# Set FS order
n = 3
# Set Extensions (>= 0)
extF = 100
extB = N+10
title = 'Fourier series (order=%2d) fit to Step function'%n
else:
# This should never be executed!
sys.exit()
if noise_sf > 0.0:
ydata = addNoise(noise_sf,ydata)
title = title + ' + %3.1f*N(0,1)'%noise_sf
dx = xdata[1] - xdata[0]
print (" dx (delta between x) = %10.6f"%dx)
# Generate symbolic Fourier series (FS)
x, y = variables('x, y')
w, = parameters('w') # model is nonlinear in this parameter (base frequency)
Fourier_model = CallableModel({y: fourier_series(x, f=w, n=n)})
print ('\nFourier Series Model:')
print (Fourier_model)
# Define the Fit object for this model with data ...
fit = Fit(Fourier_model, x=xdata, y=ydata)
# ... and fit it
fit_result = fit.execute()
print(fit_result)
# Another way to extract Fourier coefficients
[print("%3s %20.12f" %(k,v)) for k, v in fit_result.params.items()]
# Extend xdata
extx = ext_xdata(Np,dx,extF,extB)
print (" x for FS fit in [%10.6f, %10.6f]"%(xdata[0],xdata[-1]))
print (' x for FS fit extended in [%10.6f, %10.6f]'%(extx[0], extx[-1]))
y1_eval, = Fourier_model(x=xdata,**fit_result.params) # evaluate FS model over xdata
y2_eval, = Fourier_model(x=extx, **fit_result.params) # evaluate FS model over extx
# Plot the results
plotResults(TESTCASE,xdata,ydata,extx,y1_eval,y2_eval,title)
I do not understand the use of
a0, *cos_a = parameters( . . .)
sincea0
is defined in the string generated. Would you please explain why this is neccessary.