Mathematical operations with ConvectionTerm() or DiffusionTerm() in FiPy - python

I'm currently learning how to use FiPy and eventually want to use it to solve some biological problems. I have been trying to implement the following PDE system which describes hyphal growth of fungi:
PDE system
I can implement the system without problems, as long as I ignore that the change of si over time depends on the absolute change of p over space abs(dp/dx) in the fourth equation dsi/dt. Here is my code without abs():
from fipy import *
##### produce mesh
nx= 100.
ny= nx
dx = 1/100
dy = dx
L = nx*dx
mesh = Grid2D(nx=nx,ny=ny,dx=dx,dy=dy)
x,y = mesh.cellCenters
##### parameters
b=1e+7 # branching rate (no. branches * cm^-1 * hyphae * day^-1 * (mol glucose)^-1)
f = 10. # fusion rate (no. fusions in cm * day^-1)
v = 1e+5 # cm * mol^-1 * day^-1
d = 0.5 # day^-1
r = 0. # degradation of hyphae
c1 = 9e+2 # cm * mol^-1 * day^-1
c2 = 1e-7 # mol * cm^-1
c3 = 1e+3 # cm * mol^-1 * day^-1, c1/c3 = 90% efficiency
c4 = 1e-8 # cm^-1
De = 1e-3 # diffusion of external substrate (cm² * s^-1)
Di = 1e-2 # diffusion of internal substrate (cm³ * day^-1)
Da = 0.
##### define state variables:
m = CellVariable(name="m",mesh=mesh,hasOld=True,value=0.)
mi = CellVariable(name="mi",mesh=mesh,hasOld=True,value=0.)
p = CellVariable(name="p",mesh=mesh,hasOld=True,value=0.)
si = CellVariable(name="si",mesh=mesh,hasOld=True,value=0.)
se = CellVariable(name="se",mesh=mesh,hasOld=True,value=0.)
##### differential equations
eqm = (TransientTerm(var=m)== si*v*p - ImplicitSourceTerm(var=m,coeff=d))
eqmi = (TransientTerm(var=mi)==m*d - ImplicitSourceTerm(var=mi,coeff=r))
eqp = (TransientTerm(var=p)== - ConvectionTerm(var=p,coeff=[[v]]*si) + b*si*m - ImplicitSourceTerm(var=p,coeff=f*m))
eqsi = (TransientTerm(var=si)== DiffusionTerm(var=si,coeff=Di*m)- DiffusionTerm(var=p,coeff=Da*m*si) + ImplicitSourceTerm(var=si,coeff=c1*m*se) - ImplicitSourceTerm(var=si,coeff=c2*v*p) - ConvectionTerm(var=p,coeff=[[c4*Da]]*(m*si)))
eqse = (TransientTerm(var=se) == DiffusionTerm(var=se,coeff=De) - ImplicitSourceTerm(var=se,coeff=c3*m*si))
# initial values
m0 = 100.
mi0 = 0.
p0 = 500.
si0 = 1e-5
se0 = 3e-5
r = 3. # radius of initial plug
m.setValue(m0,where=((x>(L/2-(r*dx)))&(x<(L/2+(r*dx)))&(y>(L/2-(r*dy)))&(y<(L/2+(r*dy)))))
mi.setValue(mi0,where=((x>(L/2-(r*dx)))&(x<(L/2+(r*dx)))&(y>(L/2-(r*dy)))&(y<(L/2+(r*dy)))))
p.setValue(p0,where=((x>(L/2-(r*dx)))&(x<(L/2+(r*dx)))&(y>(L/2-(r*dy)))&(y<(L/2+(r*dy)))))
si.setValue(si0,where=((x>(L/2-(r*dx)))&(x<(L/2+(r*dx)))&(y>(L/2-(r*dy)))&(y<(L/2+(r*dy)))))
se.setValue(se0)
# boundary conditions
#----------------
# simulation
eq = eqm & eqmi & eqp & eqsi & eqse
vi = Viewer((m))
from builtins import range
for t in range(100):
m.updateOld()
mi.updateOld()
p.updateOld()
si.updateOld()
se.updateOld()
eq.solve(dt=0.1)
print(t)
vi.plot()
Now, I tried to simply write
eqsi = (TransientTerm(var=si)== DiffusionTerm(var=si,coeff=Di*m)- DiffusionTerm(var=p,coeff=Da*m*si) + ImplicitSourceTerm(var=si,coeff=c1*m*se) - ImplicitSourceTerm(var=si,coeff=c2*v*p) - abs(ConvectionTerm(var=p,coeff=[[c4*Da]]*(m*si))))
which (expectedly) gives out an error: "bad operand type for abs(): 'PowerLawConvectionTerm'"
I tried to work my way around it by adding another CellVariable dpdx:
dpdx= CellVariable(name="dpdx",mesh=mesh,hasOld=True,value=0.)
eqdpdx= (dpdx == ConvectionTerm(var=p,coeff=[[1]]))
eqsi = (TransientTerm(var=si)== DiffusionTerm(var=si,coeff=Di*m)- DiffusionTerm(var=p,coeff=Da*m*si) + ImplicitSourceTerm(var=si,coeff=c1*m*se) - ImplicitSourceTerm(var=si,coeff=c2*v*p) - abs(dpdx)*c4*Da*m*si)
which then gives the error "ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()", which is probably caused by the fact that eqdpdx is not a derivative function?
My question is: Is it possible at all to perform mathematical operations with a ConvectionTerm? Can I somehow express the absolute change of p?
And also, how can I express non derivative function that change over space like eqdpdx (or any dynamic parameters)?
I looked through the FiPy manual and could not find a solution - I am sorry if my question is trivial or has been answered elsewhere.

It's important to understand what FiPy Terms are. They are human-readable expressions of parts of PDEs that can be discretized to linear algebra. If you apply some potentially non-linear function to that linear algebra, then it's not linear algebra anymore. This use is not supported and I'm not sure how it even could be.
Fortunately, you don't need it. dp/dx is not a ConvectionTerm, it's a gradient. I would write this term as
- ImplicitSourceTerm(coeff=c4*Da*m*p.grad.mag, var=si)
As a side note, 1D equations are the Devil's work. They will lead you astray every single time. You can use nabla notation like we do in the manual or you can use Einstein notation, but always keep clear in your head whether your expression is scalar or vector (or tensor or ...).

Related

Fipy error:’’The Factor is exactly singular’’, when applying Neumann boundary conditions

We’re trying to solve a one-dimensional Coupled Continuity-Poisson problem in Fipy. When applying
Dirichlet’s conditions, it gives the correct results, but when we change the boundaries conditions to Neumann’s which is closer to our problem, it gives “The Factor is exactly singular’’ error.
Any help is highly appreciated. The code is as follows (0<x<2.5):
from fipy import *
from fipy import Grid1D, CellVariable, TransientTerm, DiffusionTerm, Viewer
import numpy as np
import math
import matplotlib.pyplot as plt
from matplotlib import cm
from cachetools import cached, TTLCache #caching to increase the speed of python
cache = TTLCache(maxsize=100, ttl=86400) #creating the cache object: the
#first argument= the number of objects we store in the cache.
#____________________________________________________
nx=50
dx=0.05
L=nx*dx
e=math.e
m = Grid1D(nx=nx, dx=dx)
print(np.log(e))
#____________________________________________________
phi = CellVariable(mesh=m, hasOld=True, value=0.)
ne = CellVariable(mesh=m, hasOld=True, value=0.)
phi_face = phi.faceValue
ne_face = ne.faceValue
x = m.cellCenters[0]
t0 = Variable()
phi.setValue((x-1)**3)
ne.setValue(-6*(x-1))
#____________________________________________________
#cached(cache)
def S(x,t):
f=6*(x-1)*e**(-t)+54*((x-1)**2)*e**(-2.*t)
return f
#____________________________________________________
#Boundary Condition:
valueleft_phi=3*e**(-t0)
valueright_phi=6.75*e**(-t0)
valueleft_ne=-6*e**(-t0)
valueright_ne=-6*e**(-t0)
phi.faceGrad.constrain([valueleft_phi], m.facesLeft)
phi.faceGrad.constrain([valueright_phi], m.facesRight)
ne.faceGrad.constrain([valueleft_ne], m.facesLeft)
ne.faceGrad.constrain([valueright_ne], m.facesRight)
#____________________________________________________
eqn0 = DiffusionTerm(1.,var=phi)==ImplicitSourceTerm(-1.,var=ne)
eqn1 = TransientTerm(1.,var=ne) ==
VanLeerConvectionTerm(phi.faceGrad,var=ne)+S(x,t0)
eqn = eqn0 & eqn1
#____________________________________________________
steps = 1.e4
dt=1.e-4
T=dt*steps
F=dt/(dx**2)
print('F=',F)
#____________________________________________________
vi = Viewer(phi)
with open('out2.txt', 'w') as output:
while t0()<T:
print(t0)
phi.updateOld()
ne.updateOld()
res=1.e30
#for sweep in range(steps):
while res > 1.e-4:
res = eqn.sweep(dt=dt)
t0.setValue(t0()+dt)
for m in range(nx):
output.write(str(phi[m])+' ') #+ os.linesep
output.write('\n')
if __name__ == '__main__':
vi.plot()
#____________________________________________________
data = np.loadtxt('out2.txt')
X, T = np.meshgrid(np.linspace(0, L, len(data[0,:])), np.linspace(0, T,
len(data[:,0])))
fig = plt.figure(3)
ax = fig.add_subplot(111,projection='3d')
ax.plot_surface(X, T, Z=data)
plt.show(block=True)
The issue with these equations, particularly eqn0, is that they admit an infinite number of solutions when Neumann boundary conditions are applied on both boundaries. You can fix this by pinning a value somewhere with an internal fixed value. E.g., based on the analytical solution given in the comments, phi = (x-1)**3 * exp(-t), we can pin phi = 0 at x = 1 with
mask = (m.x > 1-dx/2) & (m.x < 1+dx/2)
largeValue = 1e6
value = 0.
#____________________________________________________
eqn0 = (DiffusionTerm(1.,var=phi)==ImplicitSourceTerm(-1.,var=ne)
+ ImplicitSourceTerm(mask * largeValue, var=phi) - mask * largeValue * value)
At this point, the solutions still do not agree with the expected solutions. This is because, while you have called ne.faceGrad.constrain() for the left and right boundaries, does not appear in the discretized equations. You can see this if you plot ne; the gradient is zero at both boundaries despite the constraint because FiPy never "sees" the constraint.
What does appear is the flux . By applying fixed flux boundary conditions, I obtain the expected solutions:
ne_left = 6 * numerix.exp(-t0)
ne_right = -9 * numerix.exp(-t0)
eqn1 = (TransientTerm(1.,var=ne)
== VanLeerConvectionTerm(phi.faceGrad * m.interiorFaces,var=ne)
+ S(x,t0)
+ (m.facesLeft * ne_left * phi.faceGrad).divergence
+ (m.facesRight * ne_right * phi.faceGrad).divergence)
You can probably get better convergence properties with
eqn1 = (TransientTerm(1.,var=ne)
== DiffusionTerm(coeff=ne.faceValue * m.interiorFaces, var=phi)
+ S(x,t0)
+ (m.facesLeft * ne_left * phi.faceGrad).divergence
+ (m.facesRight * ne_right * phi.faceGrad).divergence)
but either formulation seems to work.
Note: phi.faceGrad.constrain() is fine, because the flux does appear in DiffusionTerm(coeff=1., var=phi).
Separately, it appears (based on "The Factor is exactly singular") that you are solving with the SciPy LinearLUSolver. The PETSc LinearLUSolver does better, but the baseline value of the solution wanders all over the place. Calling
res = eqn.sweep(dt=dt, solver=LinearGMRESSolver())
also seems to produce stable results (without pinning an internal value). This behavior probably shouldn't be relied on; pinning a value is the right thing to do.

Using python built-in functions for coupled ODEs

THIS PART IS JUST BACKGROUND IF YOU NEED IT
I am developing a numerical solver for the Second-Order Kuramoto Model. The functions I use to find the derivatives of theta and omega are given below.
# n-dimensional change in omega
def d_theta(omega):
return omega
# n-dimensional change in omega
def d_omega(K,A,P,alpha,mask,n):
def layer1(theta,omega):
T = theta[:,None] - theta
A[mask] = K[mask] * np.sin(T[mask])
return - alpha*omega + P - A.sum(1)
return layer1
These equations return vectors.
QUESTION 1
I know how to use odeint for two dimensions, (y,t). for my research I want to use a built-in Python function that works for higher dimensions.
QUESTION 2
I do not necessarily want to stop after a predetermined amount of time. I have other stopping conditions in the code below that will indicate whether the system of equations converges to the steady state. How do I incorporate these into a built-in Python solver?
WHAT I CURRENTLY HAVE
This is the code I am currently using to solve the system. I just implemented RK4 with constant time stepping in a loop.
# This function randomly samples initial values in the domain and returns whether the solution converged
# Inputs:
# f change in theta (d_theta)
# g change in omega (d_omega)
# tol when step size is lower than tolerance, the solution is said to converge
# h size of the time step
# max_iter maximum number of steps Runge-Kutta will perform before giving up
# max_laps maximum number of laps the solution can do before giving up
# fixed_t vector of fixed points of theta
# fixed_o vector of fixed points of omega
# n number of dimensions
# theta initial theta vector
# omega initial omega vector
# Outputs:
# converges true if it nodes restabilizes, false otherwise
def kuramoto_rk4_wss(f,g,tol_ss,tol_step,h,max_iter,max_laps,fixed_o,fixed_t,n):
def layer1(theta,omega):
lap = np.zeros(n, dtype = int)
converges = False
i = 0
tau = 2 * np.pi
while(i < max_iter): # perform RK4 with constant time step
p_omega = omega
p_theta = theta
T1 = h*f(omega)
O1 = h*g(theta,omega)
T2 = h*f(omega + O1/2)
O2 = h*g(theta + T1/2,omega + O1/2)
T3 = h*f(omega + O2/2)
O3 = h*g(theta + T2/2,omega + O2/2)
T4 = h*f(omega + O3)
O4 = h*g(theta + T3,omega + O3)
theta = theta + (T1 + 2*T2 + 2*T3 + T4)/6 # take theta time step
mask2 = np.array(np.where(np.logical_or(theta > tau, theta < 0))) # find which nodes left [0, 2pi]
lap[mask2] = lap[mask2] + 1 # increment the mask
theta[mask2] = np.mod(theta[mask2], tau) # take the modulus
omega = omega + (O1 + 2*O2 + 2*O3 + O4)/6
if(max_laps in lap): # if any generator rotates this many times it probably won't converge
break
elif(np.any(omega > 12)): # if any of the generators is rotating this fast, it probably won't converge
break
elif(np.linalg.norm(omega) < tol_ss and # assert the nodes are sufficiently close to the equilibrium
np.linalg.norm(omega - p_omega) < tol_step and # assert change in omega is small
np.linalg.norm(theta - p_theta) < tol_step): # assert change in theta is small
converges = True
break
i = i + 1
return converges
return layer1
Thanks for your help!
You can wrap your existing functions into a function accepted by odeint (option tfirst=True) and solve_ivp as
def odesys(t,u):
theta,omega = u[:n],u[n:]; # or = u.reshape(2,-1);
return [ *f(omega), *g(theta,omega) ]; # or np.concatenate([f(omega), g(theta,omega)])
u0 = [*theta0, *omega0]
t = linspan(t0, tf, timesteps+1);
u = odeint(odesys, u0, t, tfirst=True);
#or
res = solve_ivp(odesys, [t0,tf], u0, t_eval=t)
The scipy methods pass numpy arrays and convert the return value into same, so that you do not have to care in the ODE function. The variant in comments is using explicit numpy functions.
While solve_ivp does have event handling, using it for a systematic collection of events is rather cumbersome. It would be easier to advance some fixed step, do the normalization and termination detection, and then repeat this.
If you want to later increase efficiency somewhat, use directly the stepper classes behind solve_ivp.

Is there a better way to simulate PID control in Python with Scipy's solve_ivp()?

I am working on a homework problem. I'm trying to simulate a PID control in Python with Scipy's integrate.solve_ivp() function.
My method is to run the PID code within the right-hand-side of the function, using global variables and appending them to a global matrix at the end of each timestep, like so:
solution = integrate.solve_ivp(rhs, tspan, init, t_eval=teval)
Here is my code:
def rhs(dt, init):
global old_time, omega0dot, rhs_t, omega0dotmat
timestep = dt - old_time
old_time = dt
# UNPACK INITIAL
x = init[0]
y = init[1]
z = init[2]
xdot = init[3]
ydot = init[4]
zdot = init[5]
alpha = init[6]
beta = init[7]
gamma = init[8]
alphadot = init[9]
betadot = init[10]
gammadot = init[11]
# SOLVE EQUATIONS
(xddot, yddot, zddot, alphaddot, betaddot, gammaddot) = dynamics(k_d, k_m, x, y, z, xdot, ydot, zdot, alpha, beta, gamma, alphadot, betadot, gammadot, omega0dot)
# CONTROL SYSTEMS
z_des = 10
err_z = z_des - z
zPID = (1*err_z) + hover
omega0dot = zPID
rhs_t.append(dt)
omega0dotmat.append(omega0dot)
return [xdot, ydot, zdot, xddot, yddot, zddot, alphadot, betadot, gammadot, alphaddot, betaddot, gammaddot]
The global variables are initialized outside this function. You might notice that I am specifically trying to simulate a quadcopter, where the linear and angular motion of the quadrotor are dependent on omega0dot, which represents rotor velocity and which I am trying to control with PID.
My difficulty is with the timestep of integrate.solve_ivp(). Both the integral and derivative part of the PID control rely on the timestep, but the solve_ivp() function has a variable time step and seems to even go backwards in time sometimes, and sometimes makes no timestep (i.e. dt <= 0).
I was wondering if there was a better way to go about this PID control, or if maybe I'm interpreting the dt term in solve_ivp() wrong.
Let's look at a more simple system, the ubiquitous spring with dampening
y'' + c*y' + k*y = u(t)
where u(t) could represent the force exerted by an electro-magnet (which immediately suggests ways to make the system more complicated by introducing a more realistic relation of voltage and magnetic field).
Now in a PID controller we have the error to a reference output e = yr - y and
u(t) = kD*e'(t) + kP*e(t) + kI*integral(e(t))
To treat this with an ODE solver we immediately see that the state needs to be extended with a new component E(t) where E'(t)=e(t). The next difficulty is to implement the derivative of an expression that is not necessarily differentiable. This is achievable by avoiding to differentiate this expression at all by using a non-standard first-order implementation (where the standard would be to use [y,y',E] as state).
Essentially, collect all derived expressions in the formula in their integrated form as
v(t)=y'(t)+c*y-kD*e(t).
Then going back to the derivative one gets the first order system
v'(t) = y''(t) + c*y'(t) - kD*e'(t)
= kP*e(t) + kI*E(t) - k*y(t)
y'(t) = v(t) - c*y(t) + kD*e(t)
E'(t) = e(t)
This now allows to implement the controlled system as ODE system without tricks involving a global memory or similar
def odePID(t,u,params):
c,k,kD,kP,kI = params
y,v,E = u
e = yr(t)-y
return [ v-c*y+kD*e, kP*e+kI*E-k*y, e ]
You should be able to use similar transformations of the first order system in your more complex model.

How to write equation terms with independent variable in FiPy?

I am looking to solve a diffusion equation using FiPy and have read some of their documentation, but can't seem to find anything that relates to writing a diffusion term that includes additional terms that are functions of the independent variable (i.e. space). The closest thing that I found was on the FAQ, where they suggest rewriting additional terms as a ConvectionTerm. However, I believe this only applies to the case where the additional terms are functions of the solution variable rather than the independent variable. For example, I am trying to solve a 1D diffusion equation with the following diffusion term (where derivatives are w.r.t. to the independent variable x, and y is the solution variable):
D * sin(x) * Div_x {sin(x) * Grad_x {y}}
I feel that this is a pretty simple expression, but I can't find how to express it in FiPy notation. Any help would be hugely appreciated!
Exact Code:
from fipy import Variable,FaceVariable,CellVariable,Grid1D,ImplicitSourceTerm,TransientTerm,DiffusionTerm,Viewer,ConvectionTerm
from fipy.tools import numerix
D = 1
c0 = 1
ka = 1
r0 = 1
nx = 100
dx = 2*math.pi/100
mesh = Grid1D(nx=nx, dx=dx)
conc = CellVariable(name="concentration", mesh=mesh, value=0.) # This is the "phi" in the docs
valueLeft = c0
valueRight = 0
conc.constrain(valueRight, mesh.facesRight)
conc.constrain(valueLeft, mesh.facesLeft)
timeStepDuration = 0.9 * dx**2 / (2 * D)
steps = 100
show_per_steps = 50
A = 1 / (r0**2 * numerix.sin(mesh.x)[0])
dA = -(numerix.cos(mesh.x)[0])/(r0**2 * numerix.sin(mesh.x)[0]**2)
dsindA = (numerix.cos(mesh.x)[0])**3/(numerix.sin(mesh.x)[0])**2
eqX = TransientTerm() + ImplicitSourceTerm(ka) == DiffusionTerm(D*A*numerix.sin(mesh.x)[0]) - ConvectionTerm(D*dA*numerix.cos(mesh.x)[0])+ D*conc*dsindA
from builtins import range
for step in range(steps):
eqX.solve(var=conc, dt=timeStepDuration)
if __name__ == '__main__' and step % show_per_steps == 0:
viewer = Viewer(vars=(conc), datamin=0., datamax=c0)
viewer.plot()
FiPy allows the terms' coefficients to be functions of space. For example, the following works in FiPy,
from fipy import Grid1D, CellVariable, Viewer
from fipy import TransientTerm, numerix, DiffusionTerm
from fipy import LinearLUSolver
m = Grid1D(nx=100, Lx=numerix.pi / 4.)
v = CellVariable(mesh=m)
v[:] = m.x**2
eqn = TransientTerm() == DiffusionTerm(numerix.sin(m.x))
vi = Viewer(v, colorbar=None)
vi.plot()
solver = LinearLUSolver()
for i in range(10):
eqn.solve(v, dt=0.1, solver=solver)
vi.plot()
print('step', i)
input('stopped')
In the above, the diffusion coefficient is a function of space. The m.x is a CellVariable that holds the cell center positions. numerix is used which enables operations on FiPy variables in the same way as Numpy does for Numpy arrays.
Now, in the above question there is a sin(x) outside of the derivative which is not allowed in FiPy. Everything needs to fit inside of the derivative to work with FiPy. So, we need to rewrite the term so that all the coefficients are inside of the derivative. For any general case, we can write
which allows us to use a diffusion, convection and source to represent the term in FiPy. If f=sin(x) and g=sin(x) then the FiPy code would be
s = numerix.sin(m.cellCenters)
c = numerix.cos(m.cellCenters)
eqn = ... + DiffusionTerm(D * s[0] * s[0]) - ConvectionTerm(D * s * c) + D * y * (c[0] * c[0] - s[0] * s[0])
The ... are included as I don't know the full equation.

Any suggestions for a Python Lasso solver that handles complex values?

I am looking for a Python Lasso solver that works with complex numbers to use in beamforming problems. The objective function is affine, XW - Y. I believe that there at least one such solver implemented for Matlab,
http://www.cs.ubc.ca/~schmidtm/Software/code.html
I have tried to use scikit-learn MultiTaskLasso, following a suggestion from
Is it possible to use complex numbers as target labels in scikit learn?
The matrix 21 norm in the MultiTaskLasso is the correct way to handle the L1 norm for complex numbers. However, my approach requires some gymnastics to force the solver to follow the rules of complex multiplication. Essentially, I need to minimize the L2 norm of
[Re{X}, Im{X}] * [[Re{W}, Im{W}], [-Im{W}, Re{W}]] - [Re{Y}, Im{Y}]
I attempted to enforce the relationship between the two columns of W by adding another row to the X matrix, [-Im{X}, Re{X}], and the row [-Im{Y}, Re{Y}] to Y. This ideally would equate the cost of a change a value each column of W with the corresponding value in the other column
import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import MultiTaskLasso
# experiment specifications
numEl = 20
numLook = 1e2
arrayPosition = np.arange(numEl) * np.pi
thetaLook = np.r_[0 : np.pi : numLook * 1j] - (np.pi / 2)
thetaSource = -0.3758
# Make grid of look vectors
W = np.exp(1j * np.sin(thetaLook)[:,None] * arrayPosition)
data = np.exp(1j * np.sin(thetaSource) * arrayPosition).T
# Bartlet beamformer
bartlet = np.abs(np.dot(W.conj(), data))**2
B_bart = 10 * np.log10(np.abs(bartlet)); B_bart-=np.max(B_bart)
# Lasso setup
X = W.T
XSplit = np.vstack((np.hstack((X.real, X.imag)),\
np.hstack((-X.imag, X.real))))
YSplit = np.hstack((np.vstack((data.real, data.imag)),\
np.vstack((-data.imag, data.real)))).T
lasso_solver = MultiTaskLasso(alpha=0.1)
lasso = lasso_solver.fit(XSplit, YSplit).coef_
# Manipulate result back into complex values
stack1 = np.squeeze(lasso[0,:])
stack1 = np.squeeze(stack1[:numLook] + 1j * stack1[numLook:])
B_lasso = 10 * np.log10(np.abs(stack1) + np.spacing(1)); B_lasso -= np.max(B_lasso)
# stack1 ?= stack2 (Should be exact)
stack2 = np.squeeze(lasso[1,:])
stack2 = np.squeeze(-1j * stack2[:numLook] + stack2[numLook:])
np.testing.assert_almost_equal(stack1, stack2, decimal=1)
# Plot both beamformer results
_ = plt.plot(np.rad2deg(thetaLook), B_bart)
_ = plt.plot(np.rad2deg(thetaLook), B_lasso, 'r.')
_ = plt.ylim(-40,3); plt.ylabel('Beamformer Output, dB')
_ = plt.xlabel('Look Direction, deg')
While this approach seems to work for simple problems like the one above, it fails when the problems get more complicated. I define failure when the relationship between the first and second column of W no longer holds. A simple way to create small divergent behavior in the above example is to substitute a Ridge solver for the MultiTaskLasso.
Does anyone know of a Lasso solver that can solve the complex valued problem with rigorous treatment of complex numbers?

Categories