# Using qml.fourier for function approximation

Hi!

I am trying to using `qml.fourier` module to approximate a function say `cos(x)`. I am able to approximate the function with my quantum circuit. However, I was trying to extract the fourier coefficients of this trained quantum circuit and plot the Fourier series expansion. As I already know that the Fourier coefficients are c_0= 0 c_-1 = 0.5 and c_1=0.5. I need only one data encoding gate (RX) to approximate this simple function. However, if I use 2 rotation gates in the encoding layer, the output of the `qml.fourier.coefficients` is c_-2=0.5 and c_2=0.5 and the other coefficients to zero. I get the correct results if I use only one gate, by setting `r=1` and removing `qml.RX(x, wires=1, id='x')` in the code. Can someone please let me know how to resolve this issue? I know that lowpass filter is used to solve the problem of showing higher order coefficients to the lower degree expansion. The problem I stated is the other way around. Thank you in advance for your kind support! Please see the code that I use below:

``````import pennylane as qml
from pennylane import numpy as np
from pennylane.fourier import *
from functools import partial
import numpy as nnp
import matplotlib.pyplot as plt

r = 2 #number of wires
dev = qml.device('default.qubit', wires=r)

@qml.qnode(dev)
def circuit(w, x):
qml.RX(w[0], wires=0)
qml.RX(x, wires=0, id="x")
qml.RX(x, wires=1, id='x')
qml.RX(w[1], wires=0)
return qml.expval(qml.PauliZ(wires=0))

weights = np.array([0.1, 0.1], requires_grad=True) #initial weights

X = np.linspace(0, 2 * np.pi, 20, requires_grad=True) #input points

def cost(circuit, w, x):
'''Cost function which account for function approximation of cos(x)'''
return np.mean((circuit(w, x) - nnp.cos(x)) ** 2)

#Training the model
for step in range(20):
# select batch of data
batch_index = np.random.randint(0, len(X), (20,))
x_batch = X[batch_index]
# update the weights by one optimizer step
weights = opt.step(lambda w: cost(circuit, w, x_batch), weights)

freqs = circuit_spectrum(circuit)(weights, 0.2)  #frequencies available in the circuit
print(freqs['x'])

degree = r
partial_circuit = partial(circuit, weights) #fixing the weights of the circuit
coeffs = coefficients(partial_circuit, 1, degree, lowpass_filter=True) #fourier coeffcients
print(coeffs)
conj_coeff = np.conjugate(coeffs)

def fourier_expansion(x):
"""Generate a truncated Fourier series of degree"""
res = coeffs[0]
for i in range(1, 2 * degree):
exponent = complex(0, (freqs['x'][i-1]) * x)
res += coeffs[i] * np.exp(exponent) + conj_coeff[i] * np.exp(-exponent)
return np.real(res)

f_points = []
p_points = []
a_points = []

for j in X:
f_points.append(fourier_expansion(j))
p_points.append(circuit(weights, j))
a_points.append(nnp.cos(j))

plt.plot(f_points, label='fourier series')
plt.plot(p_points, label='output')
plt.plot(a_points, label='target func')
plt.legend()
plt.show()

``````

And, finally, make sure to include the versions of your packages. Specifically, show us the output of `qml.about()`:

Name: PennyLane
Version: 0.35.1
Summary: PennyLane is a cross-platform Python library for quantum computing, quantum machine learning, and quantum chemistry. Train a quantum computer the same way as a neural network.
Home-page: GitHub - PennyLaneAI/pennylane: PennyLane is a cross-platform Python library for quantum computing, quantum machine learning, and quantum chemistry. Train a quantum computer the same way as a neural network.
Author:
Author-email:
Location: c:\users\hedge\miniconda3\envs\quantum_fourier_model\lib\site-packages
Requires: appdirs, autograd, autoray, cachetools, networkx, numpy, pennylane-lightning, requests, rustworkx, scipy, semantic-version, toml, typing-extensions
Required-by: PennyLane_Lightning

Platform info: Windows-10-10.0.22631-SP0
Python version: 3.10.13
Numpy version: 1.26.4
Scipy version: 1.12.0
Installed devices:

• default.clifford (PennyLane-0.35.1)
• default.gaussian (PennyLane-0.35.1)
• default.mixed (PennyLane-0.35.1)
• default.qubit (PennyLane-0.35.1)
• default.qubit.jax (PennyLane-0.35.1)
• default.qubit.legacy (PennyLane-0.35.1)
• default.qubit.tf (PennyLane-0.35.1)
• default.qubit.torch (PennyLane-0.35.1)
• default.qutrit (PennyLane-0.35.1)
• null.qubit (PennyLane-0.35.1)
• lightning.qubit (PennyLane_Lightning-0.35.1)

I found a mistake in defining the fourier series. I think that the range in the for loop should be `(1, degree)`. Thank you!

1 Like

Thank you for posting the solution!

Hi @CatalinaAlbornoz , thank you for your reply. This was one issue I found with my code. However, it didn’t fix my problem I described previously. If you have any comments, I would very much appreciate it. Thanks in advance!

Hi @Pratibha_Hegde,
Also please post your output for qml.about. One of my colleagues will try to replicate the issue next week. Thanks!

1 Like

Here is my updated code:

``````import pennylane as qml
from pennylane import numpy as np
from pennylane.fourier import *
from functools import partial
import numpy as nnp
import matplotlib.pyplot as plt

r = 2 #number of wires
dev = qml.device('default.qubit', wires=r)

@qml.qnode(dev)
def circuit(w, x):
qml.RX(w[0], wires=0)
qml.RX(x, wires=0, id="x")
qml.RY(x, wires=1, id='x')
qml.RX(w[1], wires=0)
return qml.expval(qml.PauliZ(wires=0))

weights = np.array([0.1, 0.1], requires_grad=True) #initial weights

X = np.linspace(0, 2 * np.pi, 20, requires_grad=True) #input points

def cost(circuit, w, x):
'''Cost function which account for function approximation of cos(x)'''
return np.mean((circuit(w, x) - nnp.cos(x)) ** 2)

#Training the model
for step in range(200):
# select batch of data
batch_index = np.random.randint(0, len(X), (20,))
x_batch = X[batch_index]
# update the weights by one optimizer step
weights = opt.step(lambda w: cost(circuit, w, x_batch), weights)

freqs = circuit_spectrum(circuit)(weights, 0.2)  #frequencies available in the circuit
print(freqs['x'])

degree = r
partial_circuit = partial(circuit, weights) #fixing the weights of the circuit
coeffs = coefficients(partial_circuit, 1, degree, lowpass_filter=True) #fourier coeffcients
print(coeffs)
conj_coeff = np.conjugate(coeffs)

def fourier_expansion(x):
"""Generate a truncated Fourier series of degree"""
res = coeffs[0]
for i in range(1, degree):
exponent = complex(0, (freqs['x'][i-1]) * x)
res += coeffs[i] * np.exp(exponent) + conj_coeff[i] * np.exp(-exponent)
return np.real(res)

f_points = []
p_points = []
a_points = []

for j in X:
f_points.append(fourier_expansion(j))
p_points.append(circuit(weights, j))
a_points.append(nnp.cos(j))

plt.plot(f_points, label='fourier series')
plt.plot(p_points, label='output')
plt.plot(a_points, label='target func')
plt.legend()
plt.show()

``````

Here is the output of qml.about()

``````Name: PennyLane
Version: 0.37.0
Summary: PennyLane is a cross-platform Python library for quantum computing, quantum machine learning, and quantum chemistry. Train a quantum computer the same way as a neural network.
Home-page: https://github.com/PennyLaneAI/pennylane
Author:
Author-email:
Location: c:\users\hedge\pycharmprojects\cfd-quantum\venv\lib\site-packages
Requires: appdirs, autograd, autoray, cachetools, networkx, numpy, packaging, pennylane-lightning, requests, rustworkx, scipy, semantic-version, toml, typing-extensions
Required-by: PennyLane_Lightning

Platform info:           Windows-10-10.0.22631-SP0
Python version:          3.11.9
Numpy version:           1.26.4
Scipy version:           1.14.1
Installed devices:
- default.clifford (PennyLane-0.37.0)
- default.gaussian (PennyLane-0.37.0)
- default.mixed (PennyLane-0.37.0)
- default.qubit (PennyLane-0.37.0)
- default.qubit.jax (PennyLane-0.37.0)
- default.qubit.legacy (PennyLane-0.37.0)
- default.qubit.tf (PennyLane-0.37.0)
- default.qubit.torch (PennyLane-0.37.0)
- default.qutrit (PennyLane-0.37.0)
- default.qutrit.mixed (PennyLane-0.37.0)
- default.tensor (PennyLane-0.37.0)
- null.qubit (PennyLane-0.37.0)
- lightning.qubit (PennyLane_Lightning-0.37.0)

``````

I think you’re doing everything exactly right, up to a re-ordering of the Fourier coefficients, introduced by `np.fft.fftn`. Basically, the coefficients are ordered according to the frequencies

``````[0, 1.0, 2.0, ... r, -r, -r+1, ..., -1.0]
``````

which is… unexpected, I suppose

So modifying your `fourier_expansion` to this:

``````ordered_coeffs = [*coeffs[r+1:], *coeffs[:r+1]]
def fourier_expansion(x):
"""Generate a truncated Fourier series of degree"""
return np.dot(np.array(ordered_coeffs), np.exp(1j * np.array(freqs["x"]) * x))
``````

will fix the issue, basically simply by virtue of the `ordered_coeffs` which undo this funny quirk of the numpy output.

I hope this helps! Let us know in case there are any other issues

Happy Fourier transforming

1 Like

Dear @Wierichs, thank you very much for clarifying this and correcting my code! Indeed, the order of coefficients was not as expected! Now the code is working fine and consistent also for other examples. Thank you once again!

Amazing, happy to hear that!