Evaluating convolution integral using scipy.quad - python-3.x

I have a data set of {x2} values for which two arrays f[x2] and g[x2] are known. The data set {x2} is not uniformly spaced; and I would like to evaluate the convolution integral of f,g using these known samples. A minimal code for this would be something like:
#irregular grid for data points
x2 = np.geomspace( 5, 10, 100 )
x2n =-np.flip( x2 )
x2 = np.concatenate( ( x2n, x2 ) )
x2 = np.concatenate( (np.array([0.0]) , x2 ), axis=0 )
x_inner = np.linspace( -5,5, 1000 )
x2 = np.concatenate( ( x_inner, x2 ) )
x2 = np.sort(x2)
f2 = np.zeros( x2.shape[0], dtype=np.complex128 )
f2[ np.abs(x2)<=2 ] = 1.0 + 2j
g2 = np.zeros( x2.shape[0], dtype=np.complex128 )
g2 = np.sin( x2**3 )*np.exp( -x2**2 ) + 1j*np.sin( x2 )*np.exp( -x2**2 )
def fg_x( f, g ):
return f*g
def convolution_quad( f , g ):
return quad( fg_x, -np.inf, np.inf, args=(g) )
from scipy.integrate import quad
#evaluate convolution of the two arrays over the irregular sample data x2
res2 = convolution_quad( f2, g2)
However, this function call does not work at all, it gives the error:
return _quadpack._qagie(func,bound,infbounds,args,full_output,epsabs,epsrel,limit)
TypeError: only size-1 arrays can be converted to Python scalars
how can one calculate such convolution integrals over discrete data set by using scipy's quad? Such integrals can be evaluated with the trapezoid rule or Simpsons rule, but here I am looking for an accurate evaluation.

quad will require a continuous function as input.
Since your data is discrete you should use discrete convolution from
numpy.convolve
res2 = np.convolve(f2, g2)

Related

Truncation error vs dt and truncation error vs dx graph of Crank Nicolson scheme for the Nagumo's equation in python

For a problem, I implemented the Nagumo equation via Crank-Nicolson's scheme. Now the problem is that I plotted the truncation error vs dt but the graph should give me a line with a slope of about 62° while the other graph should give a line with a negative slope (angle of 117°).
With the code below I get the following graphs:
# -*- coding: utf-8 -*-
"""
Created on Sun Feb 5 13:21:29 2023
#author: theha
"""
import numpy as np
#import itertools
import numpy.linalg as l
import matplotlib.pyplot as plt
def generateMatrix(N, sigma):
""" Computes the matrix for the Nagumo's equation with Crank-Nicolson
Dirichlet condition at i=0 and at i=1
Parameters:
----------
N: int
Number of discretization points
sigma: float
dt/(2*dx^2)
Returns:
-------
A: 2D numpy array of float
Matrix for Nagumo's equation
"""
# Setup the diagonal
d = np.diag(np.ones(N+1)*(2*r+1))
# Setup upper diagonal
ud = np.diag(np.ones(N)*-r, 1)
# Setup lower diagonal
ld = np.diag(np.ones(N)*-r, -1)
A = d + ud + ld
return A
def generateRHS(u1, sigma,alpha):
""" Computes right-hand side of linear system for Nagumo's equation
with Crank-Nicolson scheme
Parameters:
----------
T: array of float
Nagumo's solution at current time step
sigma: float
dt/(2*dx^2)
Returns:
-------
b: array of float
Right-hand side of Nagumo's equation
with Crank-Nicolson scheme
"""
N=len(u1)
F=((1-2*sigma)*u1)+sigma*((np.append(0,u1[0:N-1]))+np.append(u1[1:N+1],1))+ dt* ((u1)*(1-u1)*(u1-alpha))
F[0]=0
F[-1]=1
return F
def CrankNicolson(T, A, nt, sigma,alpha):
""" Nagumo's equation in time with Crank-Nicolson
Parameters:
----------
T: array of float
initial Nagumo profile
A: 2D array of float
Matrix with discretized Nagumo equation
nt: int
number of time steps
sigma: float
dt/(2*(dx^2))
Returns:
-------
T: array of floats
Nagumo profile after nt time steps
"""
for t in range(nt):
Tn = T.copy()
b = generateRHS(Tn, sigma,alpha)
# Use numpy.linalg.solve
T_interior = np.linalg.solve(A,b)
T = T_interior
return T
#Domain x in [-L,L]
L = 100
nx = 400 # Partition in x
alpha = 0.25
dx = 2*(L/nx) # Step size
dt= .00001 #Time step
r = dt/(2*(dx**2))
sigma = r
nt = 5 # Partition in time
x = np.linspace(-L,L,nx+1)
u0=np.zeros(nx)
t=0
gamma1 = np.sqrt(2)/2*x + (0.5-alpha)*t
gamma2 = (np.sqrt(2)/2*alpha*x) + alpha*(alpha-2)*t/2
u0=(np.exp(gamma1)+alpha*np.exp(gamma2))/(np.exp(gamma1)+np.exp(gamma2)+1) #Initial condition u(x,0)
#initial time step
Ti = u0;
A = generateMatrix(nx, sigma) # A matrix
T = CrankNicolson(Ti.copy(), A, nt, sigma, alpha) #Solution of system Ax=b
def T_analytical(x, t,n_max, alpha):
"""Computes the exact solution for Nagumo's equation
Paramters:
---------
x : array of float
Spatial position
t : float
Evaluation time
n_max: int
Number of terms to evaluate expression
alpha: float
r coefficient of A matrix associated
L : float
Size of rod
Returns:
-------
T : array of float
u(x,t) at each location x
"""
#T = 100
for n in range(1,n_max+1):
gamma1=(np.sqrt(2)*(x))/(2) + (0.5- alpha)*(t)
gamma2=(np.sqrt(2)*(alpha*x))/2 + (alpha*(alpha-2))*((t)/2)
ue=(np.exp(gamma1)+(alpha*np.exp(gamma2)))/(np.exp(gamma1)+np.exp(gamma2)+1)
T=ue
return T
"Graph of approximate solution and exact solution"
T_exact = T_analytical(x, dt*nt, 100, alpha)
fig=plt.figure()
plt.plot(x,T,'x-', label='Aproximada',color='#003366')
plt.plot(x,T_exact,'c|-' ,label='Solucion exacta',color='red')
plt.xlabel('x (espacio)', fontsize = 12)
plt.ylabel('u', fontsize = 12)
plt.xticks(fontsize = 12)
plt.yticks(fontsize = 12)
plt.axis([-L,L,0,1])
plt.grid(True)
plt.legend()
plt.show()
def L2_error(T, T_exact):
"""Computes L2 norm of error
Parameters:
----------
T : array of float
array with numerical solution
T_exact: array of float
array with exact solution
Returns:
-------
e: L2 norm of error
"""
e = l.norm(T_exact-T)
return e
"Calculation of the error in time"
nx = 5
t_final = 1
t_initial = 0
dt_values = np.asanyarray([2.0000e-01,4.0000e-01,6.0000e-01,8.0000e-01,1.0000]) #Values of dt
error = np.zeros(len(dt_values)) #error's array
x = np.linspace(-L,L,nx+1) #Discretization in space
Ti = T_analytical(x, t_initial, 100, alpha) #Simulation of Initial condition, u(x,0) = u0(x)
T_exact = T_analytical(x, t_final, 100, alpha) #Simulation of analytical solution
"Loop for the error calculation in time"
for i,dt in enumerate(dt_values):
#print(i, dt)
sigma = dt/(2*(dx**2))
nt = int((t_final-t_initial)/dt)
A = generateMatrix(nx, sigma)
T = CrankNicolson(Ti.copy(), A, nt, sigma,alpha)
error[i] = L2_error(T,T_exact)
"Plot of error vs dt on logarithmic scale"
plt.figure(figsize=(8,8))
plt.xlabel(r'$\Delta t$', fontsize=18)
plt.ylabel(r'norma $L_2$ del error', fontsize=18)
plt.axis('equal')
plt.loglog(dt_values, error, color='k', ls='--', lw=2, marker='o')
plt.xticks(dt_values,dt_values)
plt.grid(True)
plt.legend(['Crank-Nicolson']);
#loop=nx
#rate =np.log(error[1:loop-1]/error[2:loop])/np.log(dt_values[1:loop-1]/dt_values[2:loop])
"Loop for the error calculation in space"
dx_values = np.asanyarray([0.5,0.25,0.2,0.125,0.0625]) #Values of dx
#dx_values = np.asanyarray([0.0625,0.125,0.2,0.25,0.5]) #Values of dx
error_x = np.zeros(len(dx_values)) #error's array
nt = 5
dt = 0.01
for i,dx in enumerate(dx_values):
sigma = dt/(2*(dx_values[i]**2))
nx = int((2*L)/dx_values[i])
#int((t_final-t_initial)/dt)
x =np.linspace(-L,L,nx+1)
Ti = T_analytical(x, t_initial, nx+1, alpha) #Simulation of Initial condition, u(x,0) = u0(x)
T_exact = T_analytical(x, t_final, nx+1, alpha) #Simulation of analytical solution
A = generateMatrix(nx, sigma)
T = CrankNicolson(Ti.copy(), A, nt, sigma,alpha)
error_x[i] = round(l.norm(T_exact - T), 2)
error_x
"Plot of error vs dx on logarithmic scale"
plt.figure(figsize=(8,8))
plt.xlabel(r'$\Delta x$', fontsize=18)
plt.ylabel(r'norma $L_2$ del error', fontsize=18)
plt.axis('equal')
plt.loglog(dx_values,error_x , color='k', ls='--', lw=2, marker='o')
plt.xticks(np.round(dx_values,2),np.round(dx_values,2))
plt.grid(True)
plt.legend(['Crank-Nicolson']);
The graphs that I got
I would like someone to tell me what the error is in the propagation or if indeed the results I obtained are correct.

Generate 2D distribution avoiding loops

I want to construct a 2D Gassuian-like distribution on a (Nx, Ny) array of the form:
return np.exp(-0.5*((x-xp)**2 + (y-yp)**2)/SG**2)
where (x,y), in this case, would correspond to [i, j] matrix indices.
I am doing this by looping through a np.zeros((Nx,Ny)) matrix and updating its values with the defined function.
Basically, I would like to know if there is a way to generate a similar result but avoid the for loops that I am using here. My intuition tells me that np.meshgrid or zip(x, y) should do it but I have been unable to replicate it.
(I would like to avoid using the auxiliar distribution_Gp function and to be able to use directly normaldist function).
Here is my sample code of how I am using it all together:
import numpy as np
def normaldist(x, y, Nx, Ny, xp, yp, SG=1):
"""2D-mesh (Nx,Ny) with Gaussian distribution values."""
z = np.exp(-0.5*((x-xp)**2 + (y-yp)**2)/SG**2)
# /(SG*np.sqrt(np.pi*2.))) # non-normalized
return z
def distribution_Gp(Nx, Ny, xp, yp, SG=1):
"""Fill up the C0(Nx, Ny) array for the specified values and conditions."""
mask = np.zeros((Nx, Ny))
for j in range(0, Ny):
for i in range(0, Nx):
if(i <= Nx*Ny*normaldist(i, j, Nx, Ny, xp, yp, SG)):
mask[i, j] = normaldist(i, j, Nx, Ny, xp, yp, SG)
return mask
Nx = 11
Ny = Nx
arr_img = distribution_Gp(Nx, Ny, Nx//2, Ny//3, SG=2)
A matrix with values sampled from a normal distribution can be accomplished by :
np.random.normal(mean, std, (Nx, Ny))
where Nx and Ny are shapes of the output, as in your code.
If you want to apply any custom function to a matrix then this can be accomplished by:
arr = np.zeros((Nx, Ny))
f = lambda x: x + 3
result = f(arr)
By using lambda and with two arguments and meshgrid it is possible to replicate distribution_Gp.
Using lambda and avoiding using the intermediate function:
x = np.linspace(0, 10, Nx)
y = np.linspace(0, 10, Ny)
arr = np.zeros((Nx, Ny))
f = lambda x, y: normaldist(x, y, Nx//2, Ny//3, SG=2).T
X, Y = np.meshgrid(x, y)
result = f(X, Y)
which produces the same result as:
result = distribucio_de_puntsG(Nx, Ny, Nx//2, Ny//L, SG=2)

Different shape arrays operations

A bit of background:
I want to calculate the array factor of a MxN antenna array, which is given by the following equation:
Where w_i are the complex weight of the i-th element, (x_i,y_i,z_i) is the position of the i-th element, k is the wave number, theta and phi are the elevation and azimuth respectively, and i ranges from 0 to MxN-1.
In the code I have:
-theta and phi are np.mgrid with shape (200,200) each,
-w_i, and (x,y,z)_i are np.array with shape (NxM,) each
so AF is a np.array with shape (200,200) (sum over i).There is no problem so far, and I can get AF easily doing:
af = zeros([theta.shape[0],phi.shape[0]])
for i in range(self.size[0]*self.size[1]):
af = af + ( w[i]*e**(-1j*(k * x_pos[i]*sin(theta)*cos(phi) + k * y_pos[i]* sin(theta)*sin(phi)+ k * z_pos[i] * cos(theta))) )
Now, each w_i depends on frequency, so AF too, and now I have w_i with shape (NxM,1000) (I have 1000 samples of each w_i in frequency). I tried to use the above code changing
af = zeros([1000,theta.shape[0],phi.shape[0]])
but I get 'operands could not be broadcast together'. I can solve this by using a for loop through the 1000 values, but it is slow and is a bit ugly. So, what is the correct way to do the summation, or the correct way to properly define w_i and AF ?
Any help would be appreciated. Thanks.
edit
The code with the new dimension I'm trying to add is the next:
from numpy import *
class AntennaArray:
def __init__(self,f,asize=None,tipo=None,dx=None,dy=None):
self.Lambda = 299792458 / f
self.k = 2*pi/self.Lambda
self.size = asize
self.type = tipo
self._AF_DATA_SIZE = 200
self.theta,self.phi = mgrid[0 : pi : self._AF_DATA_SIZE*1j,0 : 2*pi : self._AF_DATA_SIZE*1j]
self.element_pos = None
self.element_amp = None
self.element_pha = None
if dx == None:
self.dx = self.Lambda/2
else:
self.dx = dx
if dy == None:
self.dy = self.Lambda/2
else:
self.dy = dy
self.generate_array()
def generate_array(self):
M = self.size[0]
N = self.size[1]
dx = self.dx
dy = self.dy
x_pos = arange(0,dx*N,dx)
y_pos = arange(0,dy*M,dy)
z_pos = 0
ele = zeros([N*M,3])
for i in range(M):
ele[i*N:(i+1)*N,0] = x_pos[:]
for i in range(M):
ele[i*N:(i+1)*N,1] = y_pos[i]
self.element_pos = ele
#self.array_factor = self.calculate_array_factor()
def calculate_array_factor(self):
theta,phi = self.theta,self.phi
k = self.k
x_pos = self.element_pos[:,0]
y_pos = self.element_pos[:,1]
z_pos = self.element_pos[:,2]
w = self.element_amp*exp(1j*self.element_pha)
if len(self.element_pha.shape) > 1:
#I have f_size samples of w_i(f)
f_size = self.element_pha.shape[1]
af = zeros([f_size,theta.shape[0],phi.shape[0]])
else:
#I only have w_i
af = zeros([theta.shape[0],phi.shape[0]])
for i in range(self.size[0]*self.size[1]):
**strong text**#This for loop does the summation over i
af = af + ( w[i]*e**(-1j*(k * x_pos[i]*sin(theta)*cos(phi) + k * y_pos[i]* sin(theta)*sin(phi)+ k * z_pos[i] * cos(theta))) )
return af
I tried to test it with the next main
from numpy import *
f_points = 10
M = 2
N = 2
a = AntennaArray(5.8e9,[M,N])
a.element_amp = ones([M*N,f_points])
a.element_pha = zeros([M*N,f_points])
af = a.calculate_array_factor()
But I get
ValueError: 'operands could not be broadcast together with shapes (10,) (200,200) '
Note that if I set
a.element_amp = ones([M*N])
a.element_pha = zeros([M*N])
This works well.
Thanks.
I had a look at the code, and I think this for loop:
af = zeros([theta.shape[0],phi.shape[0]])
for i in range(self.size[0]*self.size[1]):
af = af + ( w[i]*e**(-1j*(k * x_pos[i]*sin(theta)*cos(phi) + k * y_pos[i]* sin(theta)*sin(phi)+ k * z_pos[i] * cos(theta))) )
is wrong in many ways. You are mixing dimensions, you cannot loop that way.
And by the way, to make full use of numpy efficiency, never loop over the arrays. It slows down the execution significantly.
I tried to rework that part.
First, I advice you to not use from numpy import *, it's bad practice (see here). Use import numpy as np. I reintroduced the np abbreviation, so you can understand what comes from numpy.
Frequency independent case
This first snippet assumes that w is a 1D array of length 4: I am neglecting the frequency dependency of w, to show you how you can get what you already obtained without the for loop and using instead the power of numpy.
af_points = w[:,np.newaxis,np.newaxis]*np.e**(-1j*
(k * x_pos[:,np.newaxis,np.newaxis]*np.sin(theta)*np.cos(phi) +
k * y_pos[:,np.newaxis,np.newaxis]*np.sin(theta)*np.sin(phi) +
k * z_pos[:,np.newaxis,np.newaxis]*np.cos(theta)
))
af = np.sum(af_points, axis=0)
I am using numpy broadcasting to obtain a 3D array named af_points, whose shape is (4, 200, 200). To do it, I use np.newaxis to extend the number of axis of an array in order to use broadcasting correctly. More here on np.newaxis.
So, w[:,np.newaxis,np.newaxis] is an array of shape (4, 1, 1). Similarly for x_pos[:,np.newaxis,np.newaxis], y_pos[:,np.newaxis,np.newaxis] and z_pos[:,np.newaxis,np.newaxis]. Since the angles have shape (200, 200), broadcasting can be done, and af_points has shape (4, 200, 200).
Finally the sum is done by np.sum, summing over the first axis to obtain a (200, 200) array.
Frequency dependent case
Now w has shape (4, 10), where 10 are the frequency points. The idea is the same, just consider that the frequency is an additional dimension in your numpy arrays: now af_points will be an array of shape (4, 10, 200, 200) where 10 are the f_points you have defined.
To keep it understandable, I've split the calculation:
#exp_point is only the exponent, frequency independent. Will be a (4, 200, 200) array.
exp_points = np.e**(-1j*
(k * x_pos[:,np.newaxis,np.newaxis]*np.sin(theta)*np.cos(phi) +
k * y_pos[:,np.newaxis,np.newaxis]*np.sin(theta)*np.sin(phi) +
k * z_pos[:,np.newaxis,np.newaxis]*np.cos(theta)
))
af_points = w[:,:,np.newaxis,np.newaxis] * exp_points[:,np.newaxis,:,:]
af = np.sum(af_points, axis=0)
And now af has shape (10, 200, 200).

How to find the slope: output format ((A,slope), B)

I'm enrolled in a Data Science course, and I'm trying to solve some programming problems, I haven't worked with Python in a long time, but I'm trying to improve my knowledge of the language.
Here is my problem:
def find_slope(x1, y1, x2, y2):
if (x1) == (x2):
return "inf"
else:
return ((float)(y2-y1)/(x2-x1))
Here is my driver code:
x1 = 1
y1 = 2
x2 = -7
y2 = -2
print(find_slope(x1, y1, x2, y2))
This is my output:
0.5
I'm not sure how to get it in the correct format, such as (((1, 2), .5), (3, 4))
NOTE: I wrote the code for the driver.
You can do this:
def find_slope(input):
x1 = input[0][0]
y1 = input[0][1]
x2 = input[1][0]
y2 = input[1][1]
if (x1) == (x2):
slope = "inf"
else:
slope = ((float)(y2-y1)/(x2-x1))
output = (((x1, y1), slope), (x2, y2))
return output
I changed the input to match the input format given in the screenshot.
Now the input is a single tuple, containing two tuples. Each of the inner tuples contain a x coordinate and a y coordinate.
You can call the function using
input = ((1, 2), (-7, -2))
output = find_slope(input)
The output will be in the format ((A, slope), B), where A and B are tuples containing the x and y coords.

add columns to a a numpy matrix based on degree of polynomial

firstly, apologize for little cryptic title to my question. Let me try to explain my need:-
I am reading two features namely X1, X2 from a CSV file. I have a training set of data in a csv file containing 1000 records with each line corresponding to the value of X1, X2. To make my training set fit better to my machine learning code, I want to do feature mapping that would take X1, X2 and create polynomial terms to the power of 4. for example if X1 =a, X2=b, I want to add newer features a^2, a*b, b^2, a^3,a^2*b,a*b^2,a^4...and so on.
Now if I read them as a numpy matrix , I want to see the data like this:
[ [ 1 a b a^2 a*b, b^2 a^3 a^2*b......]
[.... ............ ............ ]
[ ..
..] ]
Note that the number of rows are fixed , but the number of columns are determined by the degree selected. Also first three columns need to be
[[1 a b ..]
[1 c d ..]
..
..]
The pseudo code I am thinking of is as follows:-
def poly(X): # where X is a numpy matrix with X1, X2 columns,
degree = 4;
r= X.shape[0]
c=1 # number of columns
val_matrix= np.ones(shape=(r,c)) # creating a (r,1) matrix init with 1s
# *start of psuedo code*
while i<=degree:
while j <=i:
val_matrix[:, c+1] = (X1.^(i-j)).*(X2.^j)
I am not sure how to get this working in python?. would appreciate some suggestion. Note that ^ refers to the power of.
Starting with two vectors X1 and X2 you could create the monomials:
X1p = X1[:, None]**np.arange(max_deg + 1)
X2p = X2[:, None]**np.arange(max_deg + 1)
and then combine them using mgrid
i, j = np.mgrid[:max_deg + 1,:max_deg + 1]
m = i+j <= max_deg
result = X1p[:, i[m]]*X2p[:, j[m]]
Alternatively you could apply the indices directly to X1 and X2:
result = X1[:, None]**i[m] * X2[:, None]**j[m]
This requires fewer lines of code but uses more multiplications.
If the number of multiplications is a concern, X1p and X2p could also be computed cheaper; X1p:
X1p = np.empty((len(X1), max_deg + 1), X1.dtype)
X1p[:, 0] = 1
X1p[:, 1:] = X1[:, None]
np.multiply.accumulate(X1p[:,1:], axis=-1, out=X1p[:, 1:])
and similar for X2p

Resources