MIMO Precoding
Precoding: Shaping the Signal Before Transmission
When the transmitter knows the channel (CSI at TX), it can precode the signal to pre-compensate for the channel, simplifying the receiver and improving performance. Precoding moves complexity from RX to TX.
Definition: SVD-Based Precoding
SVD-Based Precoding
Using the SVD :
TX precoder: (right singular vectors) RX combiner: (left singular vectors)
The effective channel becomes diagonal:
Each stream sees an independent AWGN channel with gain .
def svd_precoding(H):
U, S, Vh = np.linalg.svd(H, full_matrices=False)
W = Vh.conj().T # precoder
G = U.conj().T # combiner
return W, G, S
Definition: Zero-Forcing Precoding
Zero-Forcing Precoding
ZF precoding applies the right pseudoinverse at the transmitter:
with power normalization. The received signal becomes: (no inter-user interference).
def zf_precoder(H):
W = H.conj().T @ np.linalg.inv(H @ H.conj().T)
# Normalize columns to unit power
W /= np.sqrt(np.sum(np.abs(W)**2, axis=0, keepdims=True))
return W
ZF precoding requires (more TX antennas than streams). It is widely used in multi-user MIMO downlink.
Definition: Water-Filling Power Allocation
Water-Filling Power Allocation
Given SVD channel gains , the capacity-maximizing power allocation is:
where is chosen so that and .
def water_filling(channel_gains, total_power, N0):
gains = np.sort(channel_gains)[::-1]
N = len(gains)
for n_active in range(N, 0, -1):
mu = (total_power + np.sum(N0/gains[:n_active]**2)) / n_active
powers = mu - N0/gains[:n_active]**2
if np.all(powers >= 0):
result = np.zeros(N)
result[:n_active] = powers
return result
return np.zeros(N)
Theorem: SVD Precoding with Water-Filling Achieves MIMO Capacity
The MIMO capacity with perfect CSI at TX and RX is:
where , are singular values, and are water-filling powers. SVD precoding with water-filling achieves this capacity.
SVD decomposes the MIMO channel into parallel scalar channels. Water-filling allocates more power to stronger channels, maximizing the total rate.
Theorem: Power Penalty of ZF Precoding
ZF precoding wastes power projecting away from inter-user interference. The effective per-user SNR is:
This is always less than the no-interference bound .
ZF precoding "wastes" some transmit power ensuring zero interference, analogous to ZF detection wasting SNR to suppress noise.
Example: SVD Precoding for 4x4 MIMO
Implement SVD precoding with water-filling for a 4x4 MIMO system and show the resulting parallel channels.
Implementation
H = (np.random.randn(4,4) + 1j*np.random.randn(4,4)) / np.sqrt(2)
U, S, Vh = np.linalg.svd(H)
W = Vh.conj().T
G = U.conj().T
H_eff = G @ H @ W
print("Effective channel (should be diagonal):")
print(np.abs(H_eff).round(3))
print(f"Singular values: {S.round(3)}")
Precoding BER Comparison
Compare BER of SVD precoding, ZF precoding, and no precoding.
Parameters
Common Mistake: Forgetting Power Normalization in Precoding
Mistake:
Using without normalizing, so the total transmit power exceeds the power budget.
Correction:
Always normalize: W /= np.sqrt(np.trace(W @ W.conj().T) / Nt).
Or use W /= np.linalg.norm(W, 'fro') * sqrt(Nt).
Precoding
Linear transformation applied at the transmitter to shape the signal based on channel knowledge, either diagonalizing the channel (SVD) or nulling inter-user interference (ZF).
Related: Water-Filling
Water-Filling
The optimal power allocation across parallel channels that maximizes total capacity: allocate more power to stronger channels.