$\newcommand{\Vt}[1]{\mathbf{#1}}$ $\newcommand{\Mt}[1]{\mathbf{#1}}$ $\newcommand{\vtA}{\Vt{A}}$ $\newcommand{\vtB}{\Vt{B}}$ $\newcommand{\vtC}{\Vt{C}}$ $\newcommand{\vtD}{\Vt{D}}$ $\newcommand{\vtE}{\Vt{E}}$ $\newcommand{\vtF}{\Vt{F}}$ $\newcommand{\vtG}{\Vt{G}}$ $\newcommand{\vtH}{\Vt{H}}$ $\newcommand{\vtI}{\Vt{I}}$ $\newcommand{\vtJ}{\Vt{J}}$ $\newcommand{\vtK}{\Vt{K}}$ $\newcommand{\vtL}{\Vt{L}}$ $\newcommand{\vtM}{\Vt{M}}$ $\newcommand{\vtN}{\Vt{N}}$ $\newcommand{\vtO}{\Vt{P}}$ $\newcommand{\vtP}{\Vt{P}}$ $\newcommand{\vtQ}{\Vt{Q}}$ $\newcommand{\vtR}{\Vt{R}}$ $\newcommand{\vtS}{\Vt{S}}$ $\newcommand{\vtT}{\Vt{T}}$ $\newcommand{\vtU}{\Vt{U}}$ $\newcommand{\vtV}{\Vt{V}}$ $\newcommand{\vtW}{\Vt{W}}$ $\newcommand{\vtX}{\Vt{X}}$ $\newcommand{\vtY}{\Vt{Y}}$ $\newcommand{\vtZ}{\Vt{Z}}$ $\newcommand{\mtA}{\Mt{A}}$ $\newcommand{\mtB}{\Mt{B}}$ $\newcommand{\mtC}{\Mt{C}}$ $\newcommand{\mtD}{\Mt{D}}$ $\newcommand{\mtE}{\Mt{E}}$ $\newcommand{\mtF}{\Mt{F}}$ $\newcommand{\mtG}{\Mt{G}}$ $\newcommand{\mtH}{\Mt{H}}$ $\newcommand{\mtI}{\Mt{I}}$ $\newcommand{\mtJ}{\Mt{J}}$ $\newcommand{\mtK}{\Mt{K}}$ $\newcommand{\mtL}{\Mt{L}}$ $\newcommand{\mtM}{\Mt{M}}$ $\newcommand{\mtN}{\Mt{N}}$ $\newcommand{\mtO}{\Mt{P}}$ $\newcommand{\mtP}{\Mt{P}}$ $\newcommand{\mtQ}{\Mt{Q}}$ $\newcommand{\mtR}{\Mt{R}}$ $\newcommand{\mtS}{\Mt{S}}$ $\newcommand{\mtT}{\Mt{T}}$ $\newcommand{\mtU}{\Mt{U}}$ $\newcommand{\mtV}{\Mt{V}}$ $\newcommand{\mtW}{\Mt{W}}$ $\newcommand{\mtX}{\Mt{X}}$ $\newcommand{\mtY}{\Mt{Y}}$ $\newcommand{\mtZ}{\Mt{Z}}$

Interference Alignment Algorithms

This notebook illustrates some Interference Alignment algorithms.

The channel model used is the $K$-user MIMO IC channel, where we have $K$ pairs of transmitters and receivers and each transmitter sends useful information only to its intended receiver, while interfering with the other receivers. This is shown in the figure below

The received signal at the $i$-th receiver is given by

$$\vtY_i = \mtH_{ii}\mtP_i\vtX_i + \sum_{j=1,i \neq j}^{K}\mtH_{ij}\mtP_j \vtX_j + \vtN_i$$

Max SINR Algorithm


In [1]:
# xxxxxxxxxx Add the parent folder to the python path. xxxxxxxxxxxxxxxxxxxx
import sys
import os
parent_dir = os.path.abspath("../")
sys.path.append(parent_dir)
# xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

# Imports
import numpy as np
from pyphysim.ia.ia import MaxSinrIASolver
from pyphysim.comm.channels import MultiUserChannelMatrix

In [2]:
# Parameters
K = 3
Nt = np.ones(K, dtype=int) * 2
Nr = np.ones(K, dtype=int) * 2
Ns = np.ones(K, dtype=int) * 1

# Transmit power of all users
#P = np.array([1.2, 1.5, 0.9])
P = np.array([1.0, 1.0, 1.0])

multiUserChannel = MultiUserChannelMatrix()

In [3]:
iasolver = MaxSinrIASolver(multiUserChannel)
# xxxxx Debug xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
np.random.seed(42)  # Used in the generation of the random precoder
iasolver._multiUserChannel.set_channel_seed(324)
# xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

In [4]:
multiUserChannel.randomize(Nr, Nt, K)
iasolver.randomizeF(Ns, P)
iasolver._W = iasolver._calc_Uk_all_k()

In [5]:
H00 = multiUserChannel.H[0,0]
H01 = multiUserChannel.H[0,1]
H02 = multiUserChannel.H[0,2]
H10 = multiUserChannel.H[1,0]
H11 = multiUserChannel.H[1,1]
H12 = multiUserChannel.H[1,2]
H20 = multiUserChannel.H[2,0]
H21 = multiUserChannel.H[2,1]
H22 = multiUserChannel.H[2,2]

print "H00:\n{0}".format(H00)
print "H01:\n{0}".format(H01)
print "H02:\n{0}".format(H02)
print "H10:\n{0}".format(H10)
print "H11:\n{0}".format(H11)
print "H12:\n{0}".format(H12)
print "H20:\n{0}".format(H20)
print "H21:\n{0}".format(H21)
print "H22:\n{0}".format(H22)


H00:
[[ 0.01608358-0.28801973j -0.50580390+0.46957611j]
 [ 0.39462287+0.62481233j -1.02069290-1.5030812j ]]
H01:
[[-0.11873262+1.42888543j -0.31994329+0.13836513j]
 [ 0.57738181+0.41599236j -0.78665621-0.14339029j]]
H02:
[[ 0.49340765-0.25458154j  0.03147854+0.43807291j]
 [ 1.18848527-0.104588j   -0.17561667+0.74935185j]]
H10:
[[-0.42117955+0.48372503j  0.04705041-0.09973857j]
 [ 0.92956390+0.09824229j  0.08322531-0.67695677j]]
H11:
[[-1.34734628-0.53649153j -0.38577536-0.48610577j]
 [-0.91668658+0.95406676j -0.22285468-0.53978479j]]
H12:
[[ 0.32888506-0.76865339j  0.72286753-1.29639442j]
 [ 0.41708429-0.7573189j  -0.61649953-0.27770005j]]
H20:
[[ 0.23383923+0.20593993j  0.86344260-0.43217091j]
 [ 1.14296838-1.03600672j  1.20329661+0.51232803j]]
H21:
[[ 0.11485867-0.58304759j -0.07661408-0.27170194j]
 [ 1.51098154-0.52919436j  1.31794940-0.65805912j]]
H22:
[[-0.51944144+0.16765058j -0.27394494-1.16611525j]
 [ 0.15608834-1.16981583j  0.97095685-0.67274147j]]

Step 0


In [6]:
# Different precoders: Fkl is column vector
F00 = iasolver.F[0][:, 0:1]
F10 = iasolver.F[1][:, 0:1]
F20 = iasolver.F[2][:, 0:1]
print "F00:\n{0}".format(F00)
print "F10:\n{0}".format(F10)
print "F20:\n{0}".format(F20)


F00:
[[ 0.28654116+0.37363426j]
 [-0.07976099+0.87859535j]]
F10:
[[-0.13104903+0.8838408j ]
 [-0.13103984+0.42951154j]]
F20:
[[-0.48257617-0.47635045j]
 [ 0.55770145-0.47872704j]]

Lets calculate the covariance matrices $\mtB^{[kl]}$


In [7]:
# Bkl for the different k and l
# k=0, l=0
second_part00 = np.dot(np.dot(np.dot(H00, F00), F00.transpose().conjugate()), H00.transpose().conjugate())
first_part00 = np.dot(np.dot(np.dot(H00, F00), F00.transpose().conjugate()), H00.transpose().conjugate()) + \
    np.dot(np.dot(np.dot(H01, F10), F10.transpose().conjugate()), H01.transpose().conjugate()) + \
    np.dot(np.dot(np.dot(H02, F20), F20.transpose().conjugate()), H02.transpose().conjugate())

B00 = first_part00 - second_part00 + np.eye(Nr[0])
print "B00:\n{0}".format(B00)
print

# k=1, l=0
second_part10 = np.dot(np.dot(np.dot(H11, F10), F10.transpose().conjugate()), H11.transpose().conjugate())
first_part10 = np.dot(np.dot(np.dot(H10, F00), F00.transpose().conjugate()), H10.transpose().conjugate()) + \
    np.dot(np.dot(np.dot(H11, F10), F10.transpose().conjugate()), H11.transpose().conjugate()) + \
    np.dot(np.dot(np.dot(H12, F20), F20.transpose().conjugate()), H12.transpose().conjugate())
B10 = first_part10 - second_part10 + np.eye(Nr[0])
print "B10:\n{0}".format(B10)
print

# k=2, l=0
second_part20 = np.dot(np.dot(np.dot(H22, F20), F20.transpose().conjugate()), H22.transpose().conjugate())
first_part20 = np.dot(np.dot(np.dot(H20, F00), F00.transpose().conjugate()), H20.transpose().conjugate()) + \
    np.dot(np.dot(np.dot(H21, F10), F10.transpose().conjugate()), H21.transpose().conjugate()) + \
    np.dot(np.dot(np.dot(H22, F20), F20.transpose().conjugate()), H22.transpose().conjugate())
B20 = first_part20 - second_part20 + np.eye(Nr[0])
print "B20:\n{0}".format(B20)


B00:
[[ 2.83148088 -1.68010679e-16j  0.33755361 +2.53439312e-01j]
 [ 0.33755361 -2.53439312e-01j  1.22798217 +6.24500451e-17j]]

B10:
[[ 2.32998211 -1.07552856e-16j  0.34611099 +1.25018065e+00j]
 [ 0.34611099 -1.25018065e+00j  3.09472296 +1.38777878e-17j]]

B20:
[[ 2.39889878 -3.81639165e-17j  1.73744062 -1.40795424e+00j]
 [ 1.73744062 +1.40795424e+00j  6.71883808 -2.22044605e-16j]]

Lets calculate the combining vectors $\mtU^{[kl]}_{\star l}$


In [8]:
Uk_all_K = iasolver._calc_Uk_all_k()

# k = 0, l=0
U00 = np.dot(np.dot(np.linalg.inv(B00), H00), F00)
U00 = U00/np.linalg.norm(U00, 'fro')
print "U00:\n{0}".format(U00)
print "Uk_all_K[0]:\n{0}".format(Uk_all_K[0])
print

U10 = np.dot(np.dot(np.linalg.inv(B10), H11), F10)
U10 = U10/np.linalg.norm(U10, 'fro')
print "U10:\n{0}".format(U10)
print "Uk_all_K[1]:\n{0}".format(Uk_all_K[1])
print

U20 = np.dot(np.dot(np.linalg.inv(B20), H22), F20)
U20 = U20/np.linalg.norm(U20, 'fro')
print "U20:\n{0}".format(U20)
print "Uk_all_K[2]:\n{0}".format(Uk_all_K[2])
print


U00:
[[-0.20584350-0.20403706j]
 [ 0.91702222-0.27398464j]]
Uk_all_K[0]:
[[-0.21999136-0.10892635j]
 [ 0.93527733-0.2549415j ]]

U10:
[[ 0.51510901-0.83342555j]
 [ 0.03967541-0.19618974j]]
Uk_all_K[1]:
[[ 0.43764353-0.83442539j]
 [ 0.30269154+0.14345815j]]

U20:
[[-0.47352179-0.82427513j]
 [-0.29071615+0.1087738j ]]
Uk_all_K[2]:
[[-0.41174665-0.84878259j]
 [-0.17624769+0.28101523j]]

Now lets reverse the comunication


In [9]:
# The precoders correspond to the combining vectors
Fr00 = U00
Fr10 = U10
Fr20 = U20

# The channel Hkl correspods to Hlk^H
Hr00 = H00.transpose().conjugate()
Hr01 = H10.transpose().conjugate()
Hr02 = H20.transpose().conjugate()
Hr10 = H01.transpose().conjugate()
Hr11 = H11.transpose().conjugate()
Hr12 = H21.transpose().conjugate()
Hr20 = H02.transpose().conjugate()
Hr21 = H12.transpose().conjugate()
Hr22 = H22.transpose().conjugate()

Lets calculate the covariance matrices in the reversed network


In [10]:
# Bkl for the different k and l
# k=0, l=0
second_part_r_00 = np.dot(np.dot(np.dot(Hr00, Fr00), Fr00.transpose().conjugate()), Hr00.transpose().conjugate())
first_part_r_00 = np.dot(np.dot(np.dot(Hr00, Fr00), Fr00.transpose().conjugate()), Hr00.transpose().conjugate()) + \
    np.dot(np.dot(np.dot(Hr01, Fr10), Fr10.transpose().conjugate()), Hr01.transpose().conjugate()) + \
    np.dot(np.dot(np.dot(Hr02, Fr20), Fr20.transpose().conjugate()), Hr02.transpose().conjugate())

Br00 = first_part_r_00 - second_part_r_00 + np.eye(Nr[0])
print "Br00:\n{0}".format(Br00)
print

# k=1, l=0
second_part_r_10 = np.dot(np.dot(np.dot(Hr11, Fr10), Fr10.transpose().conjugate()), Hr11.transpose().conjugate())
first_part_r_10 = np.dot(np.dot(np.dot(Hr10, Fr00), Fr00.transpose().conjugate()), Hr10.transpose().conjugate()) + \
    np.dot(np.dot(np.dot(Hr11, Fr10), Fr10.transpose().conjugate()), Hr11.transpose().conjugate()) + \
    np.dot(np.dot(np.dot(Hr12, Fr20), Fr20.transpose().conjugate()), Hr12.transpose().conjugate())
Br10 = first_part_r_10 - second_part_r_10 + np.eye(Nr[0])
print "Br10:\n{0}".format(Br10)
print

# k=2, l=0
second_part_r_20 = np.dot(np.dot(np.dot(Hr22, Fr20), Fr20.transpose().conjugate()), Hr22.transpose().conjugate())
first_part_r_20 = np.dot(np.dot(np.dot(Hr20, Fr00), Fr00.transpose().conjugate()), Hr20.transpose().conjugate()) + \
    np.dot(np.dot(np.dot(Hr21, Fr10), Fr10.transpose().conjugate()), Hr21.transpose().conjugate()) + \
    np.dot(np.dot(np.dot(Hr22, Fr20), Fr20.transpose().conjugate()), Hr22.transpose().conjugate())
Br20 = first_part_r_20 - second_part_r_20 + np.eye(Nr[0])
print "Br20:\n{0}".format(Br20)


Br00:
[[ 1.97043611 -6.93889390e-17j  0.27611275 -3.74309636e-01j]
 [ 0.27611275 +3.74309636e-01j  1.58517760 -5.98479599e-17j]]

Br10:
[[ 1.20576922 -2.77555756e-17j -0.13857722 +1.39275408e-01j]
 [-0.13857722 -1.39275408e-01j  1.66033061 +1.38777878e-17j]]

Br20:
[[ 3.24491561 -6.93889390e-17j  1.17830631 +6.81990213e-01j]
 [ 1.17830631 -6.81990213e-01j  3.75970701 -1.38777878e-16j]]

Calculate the combining vectors in the reverse network


In [11]:
Urk_all_K = iasolver._calc_Uk_all_k_rev()

# k = 0, l=0
Ur00 = np.dot(np.dot(np.linalg.inv(Br00), Hr00), Fr00)
Ur00 = Ur00/np.linalg.norm(Ur00, 'fro')
print "Ur00:\n{0}".format(Ur00)
print "Urk_all_K[0]:\n{0}".format(Urk_all_K[0])
print

Ur10 = np.dot(np.dot(np.linalg.inv(Br10), Hr11), Fr10)
Ur10 = Ur10/np.linalg.norm(Ur10, 'fro')
print "Ur10:\n{0}".format(Ur10)
print "Urk_all_K[1]:\n{0}".format(Urk_all_K[1])
print

Ur20 = np.dot(np.dot(np.linalg.inv(Br20), Hr22), Fr20)
Ur20 = Ur20/np.linalg.norm(Ur20, 'fro')
print "Ur20:\n{0}".format(Ur20)
print "Urk_all_K[2]:\n{0}".format(Urk_all_K[2])
print


Ur00:
[[-0.03669770-0.42489184j]
 [-0.30720274+0.85073302j]]
Urk_all_K[0]:
[[-0.27275417-0.31606511j]
 [-0.38410481+0.82351169j]]

Ur10:
[[-0.22912719+0.91781329j]
 [ 0.03032394+0.32280018j]]
Urk_all_K[1]:
[[-0.16985253+0.89471661j]
 [-0.08443462+0.40435517j]]

Ur20:
[[-0.43086990+0.21088222j]
 [ 0.72220855-0.49829171j]]
Urk_all_K[2]:
[[-0.61046437-0.08501988j]
 [ 0.67625799-0.40346005j]]

Reverse the comunication again


In [12]:
iasolver._F = Urk_all_K
print "F00:\n{0}".format(iasolver.F[0])
print "F10:\n{0}".format(iasolver.F[1])
print "F20:\n{0}".format(iasolver.F[2])


F00:
[[-0.27275417-0.31606511j]
 [-0.38410481+0.82351169j]]
F10:
[[-0.16985253+0.89471661j]
 [-0.08443462+0.40435517j]]
F20:
[[-0.61046437-0.08501988j]
 [ 0.67625799-0.40346005j]]