Frequency Assignment Problem - QAOA¶

Standard QAOA with annealing schedule (with optimizer COBYLA or SPSA)¶

In [1]:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as patches
from qiskit import QuantumCircuit
from qiskit.circuit.library import TwoLocal #based
from qiskit.circuit.library import EfficientSU2 #I used this, for this example is the best
from qiskit.circuit.library.n_local import n_local #In qiskit v3 it replaces TwoLocal or EfficientSU2
from qiskit.quantum_info import Statevector, SparsePauliOp
from qiskit_algorithms.optimizers import SPSA
from scipy.optimize import minimize #minimize or SPSA
from matplotlib.lines import Line2D
import warnings
warnings.filterwarnings('ignore', category=DeprecationWarning) #For TwoLocal and EfficientSU2

np.random.seed(42)

N = 6                     # number of transmitters
positions = np.random.rand(N, 2) * 80   # 80×80 meters
freq_count = 3
num_qubits = N * freq_count   # 18 qubits (one-hot encoding)
lambda_penalty = 30.0          # penalty for invalid assignment
interf_range = 30.0            # maximum interference range

p = 3  # Increased depth for better results
n_iters = 180

print(f"Number of transmitters: {N}")
print(f"Number of frequencies: {freq_count}")
print(f"Total qubits: {num_qubits}")
print(f"\nTransmitter positions:")
for i, pos in enumerate(positions):
    print(f"  Transmitter {i}: x={pos[0]:.2f}m, y={pos[1]:.2f}m")
Number of transmitters: 6
Number of frequencies: 3
Total qubits: 18

Transmitter positions:
  Transmitter 0: x=29.96m, y=76.06m
  Transmitter 1: x=58.56m, y=47.89m
  Transmitter 2: x=12.48m, y=12.48m
  Transmitter 3: x=4.65m, y=69.29m
  Transmitter 4: x=48.09m, y=56.65m
  Transmitter 5: x=1.65m, y=77.59m
In [2]:
def draw_transmitter(ax, x, y, color, label, size=8):
    """Draw transmitter as triangle with antenna"""
    # Antenna (line)
    ax.plot([x, x], [y, y + size*0.8], color='black', linewidth=3, zorder=5)
    # Base (triangle)
    triangle = patches.RegularPolygon((x, y), 3, radius=size*0.6, 
                                     orientation=0, 
                                     facecolor=color, 
                                     edgecolor='black', 
                                     linewidth=2,
                                     zorder=4)
    ax.add_patch(triangle)
    # Label
    ax.text(x, y-size*0.3, label, fontsize=12, color='blue', 
            ha='center', va='center', weight='bold', zorder=6)

fig, ax = plt.subplots(figsize=(10, 10))
for i, (x, y) in enumerate(positions):
    draw_transmitter(ax, x, y, '#D3D3D3', str(i))  # Pastel gray
    circle = plt.Circle((x, y), 50, color='#B4E7F5', alpha=0.3, zorder=1)  # Pastel blue
    ax.add_patch(circle)

ax.set_title('Transmitter Positions (before frequency assignment)', fontsize=14, weight='bold')
ax.set_xlabel('Distance [m]', fontsize=12)
ax.set_ylabel('Distance [m]', fontsize=12)
ax.grid(True, alpha=0.3)
ax.set_aspect('equal')
ax.legend(['Transmitter', 'Interference zone (r=50m)'], loc='upper right')
plt.tight_layout()
plt.show()

distance_matrix = np.zeros((N, N))
for i in range(N):
    for j in range(N):
        distance_matrix[i, j] = np.linalg.norm(positions[i] - positions[j])

print("Distances between transmitters [m]:")
print("     ", end="")
for j in range(N):
    print(f"T{j:2d}   ", end="")
print()
for i in range(N):
    print(f"T{i:2d}: ", end="")
    for j in range(N):
        if i == j:
            print("  -   ", end="")
        else:
            print(f"{distance_matrix[i,j]:5.1f} ", end="")
    print()
No description has been provided for this image
Distances between transmitters [m]:
     T 0   T 1   T 2   T 3   T 4   T 5   
T 0:   -    40.1  65.9  26.2  26.6  28.4 
T 1:  40.1   -    58.1  58.0  13.6  64.2 
T 2:  65.9  58.1   -    57.4  56.7  66.0 
T 3:  26.2  58.0  57.4   -    45.2   8.8 
T 4:  26.6  13.6  56.7  45.2   -    50.9 
T 5:  28.4  64.2  66.0   8.8  50.9   -   
In [3]:
pauli_terms = []

print(f"Penalty coefficient λ = {lambda_penalty}")
print(f"Interference range = {interf_range}m")

# 1. ONE-HOT CONSTRAINT: (Σ_k Z_k - 1)^2
# For one-hot encoding (one |1⟩): we want Σ Z_k = +1
# Since Z|0⟩ = +1, Z|1⟩ = -1:
#   - {|1⟩,|0⟩,|0⟩}: (-1)+(+1)+(+1) = +1 ✓
#   - {|0⟩,|1⟩,|0⟩}: (+1)+(-1)+(+1) = +1 ✓
#   - {|0⟩,|0⟩,|1⟩}: (+1)+(+1)+(-1) = +1 ✓
# 
# Expand (Z_0 + Z_1 + Z_2 - 1)^2:
# = Z_0^2 + Z_1^2 + Z_2^2 + 2*Z_0*Z_1 + 2*Z_0*Z_2 + 2*Z_1*Z_2 - 2*Z_0 - 2*Z_1 - 2*Z_2 + 1
# Since Z^2 = I:
# = 3*I + 2*Z_0*Z_1 + 2*Z_0*Z_2 + 2*Z_1*Z_2 - 2*Z_0 - 2*Z_1 - 2*Z_2 + 1
# = 4*I + 2*Z_0*Z_1 + 2*Z_0*Z_2 + 2*Z_1*Z_2 - 2*Z_0 - 2*Z_1 - 2*Z_2

print("Exactly one bit |1⟩ per transmitter")
print("Method: (Σ Z - 1)^2 with CORRECT signs")

constraint_count = 0
for i in range(N):
    base = i * freq_count
    
    # Quadratic terms Z_i * Z_j: +2*lambda (cross terms penalize multiple |1⟩)
    for k1 in range(freq_count):
        for k2 in range(k1+1, freq_count):
            label = ['I'] * num_qubits
            label[base + k1] = 'Z'
            label[base + k2] = 'Z'
            pauli_string = ''.join(label)
            pauli_terms.append((pauli_string, lambda_penalty * 2.0))
            constraint_count += 1
    
    # Linear terms Z_i: -2*lambda (prefer |1⟩ since Z|1⟩=-1)
    for k in range(freq_count):
        label = ['I'] * num_qubits
        label[base + k] = 'Z'
        pauli_string = ''.join(label)
        pauli_terms.append((pauli_string, lambda_penalty * (-2.0)))  # NEGATIVE!
        constraint_count += 1
    
    # Constant term: +4*lambda (offset)
    label = ['I'] * num_qubits
    pauli_string = ''.join(label)
    pauli_terms.append((pauli_string, lambda_penalty * 4.0))
    constraint_count += 1
    
    if i == 0:  # Example for first transmitter
        print(f"  Transmitter {i}: 3 cross(+) + 3 linear(-) + 1 offset = 7 terms")

print(f"Total constraint terms: {constraint_count}")

#INTERFERENCE TERMS
interference_count = 0
interference_details = []

for i in range(N):
    for j in range(i+1, N):
        dist = distance_matrix[i, j]
        if dist >= interf_range:
            continue
        
        loss_db = 20 * (interf_range / dist)
        
        for f in range(freq_count):
            p1 = i * freq_count + f
            p2 = j * freq_count + f
            label = ['I'] * num_qubits
            label[p1] = 'Z'
            label[p2] = 'Z'
            pauli_string = ''.join(label)
            coef = loss_db / 4.0
            pauli_terms.append((pauli_string, coef))
            interference_count += 1
            
            interference_details.append({
                'tx1': i, 'tx2': j, 'freq': f, 
                'dist': dist, 'loss': loss_db, 'coef': coef
            })

print(f"Total interference terms: {interference_count}")
print(f"\nExamples:")
for idx, detail in enumerate(interference_details):
    print(f"  Transmitter {detail['tx1']} <-> {detail['tx2']}, "
          f"f={detail['freq']}: dist={detail['dist']:.1f}m, "
          f"loss={detail['loss']:.1f}dB, coef={detail['coef']:.2f}")

# Create Hamiltonian
H = SparsePauliOp.from_list(pauli_terms)
print(f"\nHamiltonian created: {len(pauli_terms)} Pauli terms")
Penalty coefficient λ = 30.0
Interference range = 30.0m
Exactly one bit |1⟩ per transmitter
Method: (Σ Z - 1)^2 with CORRECT signs
  Transmitter 0: 3 cross(+) + 3 linear(-) + 1 offset = 7 terms
Total constraint terms: 42
Total interference terms: 15

Examples:
  Transmitter 0 <-> 3, f=0: dist=26.2m, loss=22.9dB, coef=5.72
  Transmitter 0 <-> 3, f=1: dist=26.2m, loss=22.9dB, coef=5.72
  Transmitter 0 <-> 3, f=2: dist=26.2m, loss=22.9dB, coef=5.72
  Transmitter 0 <-> 4, f=0: dist=26.6m, loss=22.6dB, coef=5.65
  Transmitter 0 <-> 4, f=1: dist=26.6m, loss=22.6dB, coef=5.65
  Transmitter 0 <-> 4, f=2: dist=26.6m, loss=22.6dB, coef=5.65
  Transmitter 0 <-> 5, f=0: dist=28.4m, loss=21.2dB, coef=5.29
  Transmitter 0 <-> 5, f=1: dist=28.4m, loss=21.2dB, coef=5.29
  Transmitter 0 <-> 5, f=2: dist=28.4m, loss=21.2dB, coef=5.29
  Transmitter 1 <-> 4, f=0: dist=13.6m, loss=44.0dB, coef=10.99
  Transmitter 1 <-> 4, f=1: dist=13.6m, loss=44.0dB, coef=10.99
  Transmitter 1 <-> 4, f=2: dist=13.6m, loss=44.0dB, coef=10.99
  Transmitter 3 <-> 5, f=0: dist=8.8m, loss=68.0dB, coef=17.00
  Transmitter 3 <-> 5, f=1: dist=8.8m, loss=68.0dB, coef=17.00
  Transmitter 3 <-> 5, f=2: dist=8.8m, loss=68.0dB, coef=17.00

Hamiltonian created: 57 Pauli terms
In [4]:
print(f"Total: {constraint_count} terms")
print("\nFirst transmitter (T0) - all 7 terms:")
constraint_idx = 0
for i in range(1):  # Only first transmitter for detailed view
    base = i * freq_count
    print(f"\nTransmitter {i} constraint terms:")
    
    # Cross terms
    for k1 in range(freq_count):
        for k2 in range(k1+1, freq_count):
            pauli_word, coef = pauli_terms[constraint_idx]
            highlighted = [f"q{base+k1}:Z", f"q{base+k2}:Z"]
            print(f"  [{constraint_idx:2d}] {pauli_word} | coef={coef:6.1f} | Cross: {highlighted}")
            constraint_idx += 1
    
    # Linear terms
    for k in range(freq_count):
        pauli_word, coef = pauli_terms[constraint_idx]
        highlighted = [f"q{base+k}:Z"]
        print(f"  [{constraint_idx:2d}] {pauli_word} | coef={coef:6.1f} | Linear: {highlighted}")
        constraint_idx += 1
    
    # Constant term
    pauli_word, coef = pauli_terms[constraint_idx]
    print(f"  [{constraint_idx:2d}] {pauli_word} | coef={coef:6.1f} | Offset (Identity)")
    constraint_idx += 1

# Display ALL interference terms
print(f"Total: {interference_count} terms")
print("\nAll interference terms (full listing):")
interf_start_idx = constraint_count
for idx, detail in enumerate(interference_details):
    pauli_idx = interf_start_idx + idx
    pauli_word, coef = pauli_terms[pauli_idx]
    q1 = detail['tx1'] * freq_count + detail['freq']
    q2 = detail['tx2'] * freq_count + detail['freq']
    print(f"  [{pauli_idx:2d}] T{detail['tx1']} <-> T{detail['tx2']} f{detail['freq']}: "
          f"q{q1}:Z x q{q2}:Z | "
          f"dist={detail['dist']:4.1f}m, loss={detail['loss']:5.1f}dB, coef={coef:5.2f}")
    if idx < len(interference_details) - 1:
        # Show the actual Pauli word every 5 terms for verification
        if idx % 5 == 4:
            print(f"       Pauli word: {pauli_word}")

# Matrix visualization of Pauli operators
print("Legend: Z=Pauli-Z, I=Identity\n")

# Create a visual matrix for first few terms
print("Constraint terms (T0 example):")
print("     ", end="")
for q in range(num_qubits):
    print(f"q{q:2d}", end=" ")
print()

for idx in range(min(7, len(pauli_terms))):
    pauli_word, coef = pauli_terms[idx]
    print(f"[{idx:2d}] ", end="")
    for q, char in enumerate(pauli_word):
        if char == 'Z':
            print(f" Z ", end=" ")
        else:
            print(f" · ", end=" ")
    print(f"| coef={coef:6.1f}")

print("\nInterference terms (first 10):")
print("     ", end="")
for q in range(num_qubits):
    print(f"q{q:2d}", end=" ")
print()

for idx in range(constraint_count, min(constraint_count + 10, len(pauli_terms))):
    pauli_word, coef = pauli_terms[idx]
    print(f"[{idx:2d}] ", end="")
    for q, char in enumerate(pauli_word):
        if char == 'Z':
            print(f" Z ", end=" ")
        else:
            print(f" · ", end=" ")
    print(f"| coef={coef:6.1f}")

# Summary statistics
z_counts = []
for pauli_word, _ in pauli_terms:
    z_count = pauli_word.count('Z')
    z_counts.append(z_count)

print(f"Total Pauli terms: {len(pauli_terms)}")
print(f"Average Z operators per term: {np.mean(z_counts):.2f}")
print(f"Terms with 1 Z: {z_counts.count(1)} (linear)")
print(f"Terms with 2 Z: {z_counts.count(2)} (quadratic)")
print(f"Terms with 0 Z: {z_counts.count(0)} (constant)")

# Export all Pauli words to file
with open('pauli_words_complete.txt', 'w') as f:
    f.write("CONSTRAINT TERMS:\n")
    for idx in range(constraint_count):
        pauli_word, coef = pauli_terms[idx]
        f.write(f"[{idx:3d}] {pauli_word} | coef={coef:8.2f}\n")
        print(f"[{idx:3d}] {pauli_word} | coef={coef:8.2f}\n")
    
    f.write("\n\nINTERFERENCE TERMS:\n")
    for idx in range(constraint_count, len(pauli_terms)):
        pauli_word, coef = pauli_terms[idx]
        detail_idx = idx - constraint_count
        if detail_idx < len(interference_details):
            detail = interference_details[detail_idx]
            f.write(f"[{idx:3d}] {pauli_word} | coef={coef:8.2f} | "
                   f"T{detail['tx1']}<->T{detail['tx2']} f{detail['freq']}\n")
            print(f"[{idx:3d}] {pauli_word} | coef={coef:8.2f} | "
                   f"T{detail['tx1']}<->T{detail['tx2']} f{detail['freq']}\n")
        else:
            f.write(f"[{idx:3d}] {pauli_word} | coef={coef:8.2f}\n")
Total: 42 terms

First transmitter (T0) - all 7 terms:

Transmitter 0 constraint terms:
  [ 0] ZZIIIIIIIIIIIIIIII | coef=  60.0 | Cross: ['q0:Z', 'q1:Z']
  [ 1] ZIZIIIIIIIIIIIIIII | coef=  60.0 | Cross: ['q0:Z', 'q2:Z']
  [ 2] IZZIIIIIIIIIIIIIII | coef=  60.0 | Cross: ['q1:Z', 'q2:Z']
  [ 3] ZIIIIIIIIIIIIIIIII | coef= -60.0 | Linear: ['q0:Z']
  [ 4] IZIIIIIIIIIIIIIIII | coef= -60.0 | Linear: ['q1:Z']
  [ 5] IIZIIIIIIIIIIIIIII | coef= -60.0 | Linear: ['q2:Z']
  [ 6] IIIIIIIIIIIIIIIIII | coef= 120.0 | Offset (Identity)
Total: 15 terms

All interference terms (full listing):
  [42] T0 <-> T3 f0: q0:Z x q9:Z | dist=26.2m, loss= 22.9dB, coef= 5.72
  [43] T0 <-> T3 f1: q1:Z x q10:Z | dist=26.2m, loss= 22.9dB, coef= 5.72
  [44] T0 <-> T3 f2: q2:Z x q11:Z | dist=26.2m, loss= 22.9dB, coef= 5.72
  [45] T0 <-> T4 f0: q0:Z x q12:Z | dist=26.6m, loss= 22.6dB, coef= 5.65
  [46] T0 <-> T4 f1: q1:Z x q13:Z | dist=26.6m, loss= 22.6dB, coef= 5.65
       Pauli word: IZIIIIIIIIIIIZIIII
  [47] T0 <-> T4 f2: q2:Z x q14:Z | dist=26.6m, loss= 22.6dB, coef= 5.65
  [48] T0 <-> T5 f0: q0:Z x q15:Z | dist=28.4m, loss= 21.2dB, coef= 5.29
  [49] T0 <-> T5 f1: q1:Z x q16:Z | dist=28.4m, loss= 21.2dB, coef= 5.29
  [50] T0 <-> T5 f2: q2:Z x q17:Z | dist=28.4m, loss= 21.2dB, coef= 5.29
  [51] T1 <-> T4 f0: q3:Z x q12:Z | dist=13.6m, loss= 44.0dB, coef=10.99
       Pauli word: IIIZIIIIIIIIZIIIII
  [52] T1 <-> T4 f1: q4:Z x q13:Z | dist=13.6m, loss= 44.0dB, coef=10.99
  [53] T1 <-> T4 f2: q5:Z x q14:Z | dist=13.6m, loss= 44.0dB, coef=10.99
  [54] T3 <-> T5 f0: q9:Z x q15:Z | dist= 8.8m, loss= 68.0dB, coef=17.00
  [55] T3 <-> T5 f1: q10:Z x q16:Z | dist= 8.8m, loss= 68.0dB, coef=17.00
  [56] T3 <-> T5 f2: q11:Z x q17:Z | dist= 8.8m, loss= 68.0dB, coef=17.00
Legend: Z=Pauli-Z, I=Identity

Constraint terms (T0 example):
     q 0 q 1 q 2 q 3 q 4 q 5 q 6 q 7 q 8 q 9 q10 q11 q12 q13 q14 q15 q16 q17 
[ 0]  Z   Z   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·  | coef=  60.0
[ 1]  Z   ·   Z   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·  | coef=  60.0
[ 2]  ·   Z   Z   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·  | coef=  60.0
[ 3]  Z   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·  | coef= -60.0
[ 4]  ·   Z   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·  | coef= -60.0
[ 5]  ·   ·   Z   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·  | coef= -60.0
[ 6]  ·   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·  | coef= 120.0

Interference terms (first 10):
     q 0 q 1 q 2 q 3 q 4 q 5 q 6 q 7 q 8 q 9 q10 q11 q12 q13 q14 q15 q16 q17 
[42]  Z   ·   ·   ·   ·   ·   ·   ·   ·   Z   ·   ·   ·   ·   ·   ·   ·   ·  | coef=   5.7
[43]  ·   Z   ·   ·   ·   ·   ·   ·   ·   ·   Z   ·   ·   ·   ·   ·   ·   ·  | coef=   5.7
[44]  ·   ·   Z   ·   ·   ·   ·   ·   ·   ·   ·   Z   ·   ·   ·   ·   ·   ·  | coef=   5.7
[45]  Z   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·   Z   ·   ·   ·   ·   ·  | coef=   5.6
[46]  ·   Z   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·   Z   ·   ·   ·   ·  | coef=   5.6
[47]  ·   ·   Z   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·   Z   ·   ·   ·  | coef=   5.6
[48]  Z   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·   Z   ·   ·  | coef=   5.3
[49]  ·   Z   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·   Z   ·  | coef=   5.3
[50]  ·   ·   Z   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·   ·   Z  | coef=   5.3
[51]  ·   ·   ·   Z   ·   ·   ·   ·   ·   ·   ·   ·   Z   ·   ·   ·   ·   ·  | coef=  11.0
Total Pauli terms: 57
Average Z operators per term: 1.47
Terms with 1 Z: 18 (linear)
Terms with 2 Z: 33 (quadratic)
Terms with 0 Z: 6 (constant)
[  0] ZZIIIIIIIIIIIIIIII | coef=   60.00

[  1] ZIZIIIIIIIIIIIIIII | coef=   60.00

[  2] IZZIIIIIIIIIIIIIII | coef=   60.00

[  3] ZIIIIIIIIIIIIIIIII | coef=  -60.00

[  4] IZIIIIIIIIIIIIIIII | coef=  -60.00

[  5] IIZIIIIIIIIIIIIIII | coef=  -60.00

[  6] IIIIIIIIIIIIIIIIII | coef=  120.00

[  7] IIIZZIIIIIIIIIIIII | coef=   60.00

[  8] IIIZIZIIIIIIIIIIII | coef=   60.00

[  9] IIIIZZIIIIIIIIIIII | coef=   60.00

[ 10] IIIZIIIIIIIIIIIIII | coef=  -60.00

[ 11] IIIIZIIIIIIIIIIIII | coef=  -60.00

[ 12] IIIIIZIIIIIIIIIIII | coef=  -60.00

[ 13] IIIIIIIIIIIIIIIIII | coef=  120.00

[ 14] IIIIIIZZIIIIIIIIII | coef=   60.00

[ 15] IIIIIIZIZIIIIIIIII | coef=   60.00

[ 16] IIIIIIIZZIIIIIIIII | coef=   60.00

[ 17] IIIIIIZIIIIIIIIIII | coef=  -60.00

[ 18] IIIIIIIZIIIIIIIIII | coef=  -60.00

[ 19] IIIIIIIIZIIIIIIIII | coef=  -60.00

[ 20] IIIIIIIIIIIIIIIIII | coef=  120.00

[ 21] IIIIIIIIIZZIIIIIII | coef=   60.00

[ 22] IIIIIIIIIZIZIIIIII | coef=   60.00

[ 23] IIIIIIIIIIZZIIIIII | coef=   60.00

[ 24] IIIIIIIIIZIIIIIIII | coef=  -60.00

[ 25] IIIIIIIIIIZIIIIIII | coef=  -60.00

[ 26] IIIIIIIIIIIZIIIIII | coef=  -60.00

[ 27] IIIIIIIIIIIIIIIIII | coef=  120.00

[ 28] IIIIIIIIIIIIZZIIII | coef=   60.00

[ 29] IIIIIIIIIIIIZIZIII | coef=   60.00

[ 30] IIIIIIIIIIIIIZZIII | coef=   60.00

[ 31] IIIIIIIIIIIIZIIIII | coef=  -60.00

[ 32] IIIIIIIIIIIIIZIIII | coef=  -60.00

[ 33] IIIIIIIIIIIIIIZIII | coef=  -60.00

[ 34] IIIIIIIIIIIIIIIIII | coef=  120.00

[ 35] IIIIIIIIIIIIIIIZZI | coef=   60.00

[ 36] IIIIIIIIIIIIIIIZIZ | coef=   60.00

[ 37] IIIIIIIIIIIIIIIIZZ | coef=   60.00

[ 38] IIIIIIIIIIIIIIIZII | coef=  -60.00

[ 39] IIIIIIIIIIIIIIIIZI | coef=  -60.00

[ 40] IIIIIIIIIIIIIIIIIZ | coef=  -60.00

[ 41] IIIIIIIIIIIIIIIIII | coef=  120.00

[ 42] ZIIIIIIIIZIIIIIIII | coef=    5.72 | T0<->T3 f0

[ 43] IZIIIIIIIIZIIIIIII | coef=    5.72 | T0<->T3 f1

[ 44] IIZIIIIIIIIZIIIIII | coef=    5.72 | T0<->T3 f2

[ 45] ZIIIIIIIIIIIZIIIII | coef=    5.65 | T0<->T4 f0

[ 46] IZIIIIIIIIIIIZIIII | coef=    5.65 | T0<->T4 f1

[ 47] IIZIIIIIIIIIIIZIII | coef=    5.65 | T0<->T4 f2

[ 48] ZIIIIIIIIIIIIIIZII | coef=    5.29 | T0<->T5 f0

[ 49] IZIIIIIIIIIIIIIIZI | coef=    5.29 | T0<->T5 f1

[ 50] IIZIIIIIIIIIIIIIIZ | coef=    5.29 | T0<->T5 f2

[ 51] IIIZIIIIIIIIZIIIII | coef=   10.99 | T1<->T4 f0

[ 52] IIIIZIIIIIIIIZIIII | coef=   10.99 | T1<->T4 f1

[ 53] IIIIIZIIIIIIIIZIII | coef=   10.99 | T1<->T4 f2

[ 54] IIIIIIIIIZIIIIIZII | coef=   17.00 | T3<->T5 f0

[ 55] IIIIIIIIIIZIIIIIZI | coef=   17.00 | T3<->T5 f1

[ 56] IIIIIIIIIIIZIIIIIZ | coef=   17.00 | T3<->T5 f2

In [10]:
ansatz = TwoLocal(num_qubits, 'ry', 'cz', entanglement='linear', reps=p)
#ansatz = EfficientSU2(num_qubits, entanglement='linear', reps=p)
#ansatz = n_local(num_qubits=num_qubits, rotation_blocks='ry', entanglement_blocks='cz', entanglement='linear', reps=p)

print(f"QAOA depth: p={p}")
print(f"Number of parameters: {ansatz.num_parameters}")

def qaoa_circuit(params):
    qc = QuantumCircuit(num_qubits)
    qc.h(range(num_qubits))
    bound = ansatz.assign_parameters(params)
    qc.compose(bound, inplace=True)
    return qc

iteration_data = []

def cost_function(params):
    qc = qaoa_circuit(params)
    state = Statevector(qc)
    energy = state.expectation_value(H).real
    
    iteration_data.append({
        'params': params.copy(),
        'energy': energy,
        'state': state
    })
    
    if len(iteration_data) % 10 == 0:
        print(f"  Iteration {len(iteration_data):3d}: energy = {energy:8.2f}")
    
    return energy

initial_params = np.random.uniform(0, 2*np.pi, ansatz.num_parameters)
#result = minimize(cost_function, initial_params, method='COBYLA', 
#                 options={'maxiter': n_iters, 'disp': False})

#optimizer = SPSA(maxiter=n_iters, learning_rate=0.01, perturbation=0.1)
optimizer = SPSA(maxiter=n_iters)
result = optimizer.minimize(fun=cost_function, x0=initial_params)

optimal_params = result.x
final_energy = result.fun

print(f"  Number of iterations: {len(iteration_data)}")
print(f"  Best energy: {final_energy:.2f}")
print(f"  Initial energy: {iteration_data[0]['energy']:.2f}")
print(f"  Improvement: {iteration_data[0]['energy'] - final_energy:.2f}")
QAOA depth: p=3
Number of parameters: 72
  Iteration  10: energy =   489.32
  Iteration  20: energy =   595.88
  Iteration  30: energy =   608.86
  Iteration  40: energy =   588.00
  Iteration  50: energy =   638.37
  Iteration  60: energy =   499.08
  Iteration  70: energy =   347.95
  Iteration  80: energy =   328.27
  Iteration  90: energy =   371.17
  Iteration 100: energy =   373.88
  Iteration 110: energy =   277.79
  Iteration 120: energy =   264.09
  Iteration 130: energy =   304.44
  Iteration 140: energy =   263.08
  Iteration 150: energy =   239.07
  Iteration 160: energy =   226.18
  Iteration 170: energy =   230.14
  Iteration 180: energy =   188.94
  Iteration 190: energy =   226.49
  Iteration 200: energy =   223.56
  Iteration 210: energy =   190.62
  Iteration 220: energy =   158.19
  Iteration 230: energy =   163.64
  Iteration 240: energy =   155.37
  Iteration 250: energy =   142.08
  Iteration 260: energy =   147.37
  Iteration 270: energy =   118.18
  Iteration 280: energy =   121.78
  Iteration 290: energy =   130.40
  Iteration 300: energy =   131.84
  Iteration 310: energy =   116.72
  Iteration 320: energy =   113.51
  Iteration 330: energy =   133.30
  Iteration 340: energy =   113.35
  Iteration 350: energy =    90.81
  Iteration 360: energy =    94.29
  Iteration 370: energy =    83.99
  Iteration 380: energy =    75.64
  Iteration 390: energy =    70.22
  Iteration 400: energy =    84.29
  Iteration 410: energy =    55.27
  Number of iterations: 411
  Best energy: 38.72
  Initial energy: 622.66
  Improvement: 583.94
In [11]:
energies = [d['energy'] for d in iteration_data]
plt.figure(figsize=(12, 5))

plt.subplot(1, 2, 1)
plt.plot(energies, linewidth=2, color='#A8DADC')  # blue
plt.axhline(y=final_energy, color='#E63946', linestyle='--', linewidth=2, 
            label=f'Minimum: {final_energy:.2f}')  # red
plt.xlabel('Iteration', fontsize=12)
plt.ylabel('Energy', fontsize=12)
plt.title('Optimization Convergence', fontsize=14, weight='bold')
plt.grid(True, alpha=0.3)
plt.legend()

plt.subplot(1, 2, 2)
improvements = np.abs(np.array(energies) - final_energy) + 1e-10
plt.semilogy(improvements, linewidth=2, color='#90BE6D')  # green
plt.xlabel('Iteration', fontsize=12)
plt.ylabel('Distance from minimum', fontsize=12)
plt.title('Convergence (log scale)', fontsize=14, weight='bold')
plt.grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

final_qc = qaoa_circuit(optimal_params)
final_state = Statevector(final_qc)
probs = final_state.probabilities()

top_k = 10
top_indices = np.argsort(probs)[-top_k:][::-1]
print(f"\nTop {top_k} most probable states:")
for rank, idx in enumerate(top_indices, 1):
    binary = format(idx, f'0{num_qubits}b')
    prob = probs[idx]
    
    assignment_str = []
    for i in range(N):
        chunk = binary[i*3:(i+1)*3]
        assignment_str.append(f"T{i}:{chunk}")
    
    print(f"  {rank:2d}. |{binary}⟩  P={prob:.4f}  ({prob*100:.2f}%)")
    print(f"      → {' '.join(assignment_str)}")

# Probability distribution visualization
plt.figure(figsize=(14, 5))

plt.subplot(1, 2, 1)
top_50 = np.argsort(probs)[-50:][::-1]
plt.bar(range(len(top_50)), probs[top_50], color='#A8DADC', edgecolor='#457B9D', linewidth=1.5)  # blue
plt.xlabel('State rank', fontsize=12)
plt.ylabel('Probability', fontsize=12)
plt.title('Top 50 most probable states', fontsize=14, weight='bold')
plt.grid(True, alpha=0.3, axis='y')

plt.subplot(1, 2, 2)
plt.hist(probs[probs > 1e-6], bins=50, color='#F4A261', edgecolor='#E76F51', linewidth=1.5)  # orange
plt.xlabel('Probability', fontsize=12)
plt.ylabel('Number of states', fontsize=12)
plt.title('Probability distribution', fontsize=14, weight='bold')
plt.yscale('log')
plt.grid(True, alpha=0.3)

plt.tight_layout()
plt.show()
No description has been provided for this image
Top 10 most probable states:
   1. |100100100001010010⟩  P=0.2588  (25.88%)
      → T0:100 T1:100 T2:100 T3:001 T4:010 T5:010
   2. |100100010001010010⟩  P=0.2023  (20.23%)
      → T0:100 T1:100 T2:010 T3:001 T4:010 T5:010
   3. |100000100001010010⟩  P=0.0576  (5.76%)
      → T0:100 T1:000 T2:100 T3:001 T4:010 T5:010
   4. |100100100001010001⟩  P=0.0531  (5.31%)
      → T0:100 T1:100 T2:100 T3:001 T4:010 T5:001
   5. |100000010001010010⟩  P=0.0450  (4.50%)
      → T0:100 T1:000 T2:010 T3:001 T4:010 T5:010
   6. |100100010001010001⟩  P=0.0415  (4.15%)
      → T0:100 T1:100 T2:010 T3:001 T4:010 T5:001
   7. |101100100001010010⟩  P=0.0291  (2.91%)
      → T0:101 T1:100 T2:100 T3:001 T4:010 T5:010
   8. |100100100001100010⟩  P=0.0259  (2.59%)
      → T0:100 T1:100 T2:100 T3:001 T4:100 T5:010
   9. |101100010001010010⟩  P=0.0227  (2.27%)
      → T0:101 T1:100 T2:010 T3:001 T4:010 T5:010
  10. |100100010001100010⟩  P=0.0202  (2.02%)
      → T0:100 T1:100 T2:010 T3:001 T4:100 T5:010
No description has been provided for this image
In [12]:
best_idx = np.argmax(probs)
best_bin = format(best_idx, f'0{num_qubits}b')
print(f"\nMost probable state: |{best_bin}⟩")
print(f"Probability: {probs[best_idx]:.4f} ({probs[best_idx]*100:.2f}%)")

assignment = []
print("\nDecoding one-hot encoding:")
for i in range(N):
    chunk = best_bin[i*3:(i+1)*3]
    print(f"  Transmitter {i}: {chunk}", end="")
    
    freq = chunk.find('1')
    if freq == -1:
        print(f" INVALID (no bit=1), fallback → f0")
        freq = 0
    else:
        ones_count = chunk.count('1')
        if ones_count > 1:
            print(f" INVALID ({ones_count} bits=1), fallback → f{freq}")
        else:
            print(f" frequency {freq}")
    
    assignment.append(freq)

colors = ['#FFB3BA', '#BAFFC9', '#BAE1FF']  # red, green, blue
freq_names = ['f0 (red)', 'f1 (green)', 'f2 (blue)']

print("\nAssigned frequencies:")
for i, f in enumerate(assignment):
    print(f"  Transmitter {i} → {freq_names[f]}")

# Check collisions
print("\nInterference collision check:")
collision_count = 0
for i in range(N):
    for j in range(i+1, N):
        if assignment[i] == assignment[j]:
            dist = distance_matrix[i, j]
            if dist < interf_range:
                collision_count += 1
                loss = 20 * (interf_range / dist)
                print(f"COLLISION: Transmitter {i} <-> {j} on {freq_names[assignment[i]]}, "
                      f"dist={dist:.1f}m, loss={loss:.1f}dB")

if collision_count == 0:
    print("No interference collisions!")
else:
    print(f"Total collisions: {collision_count}")
Most probable state: |100100100001010010⟩
Probability: 0.2588 (25.88%)

Decoding one-hot encoding:
  Transmitter 0: 100 frequency 0
  Transmitter 1: 100 frequency 0
  Transmitter 2: 100 frequency 0
  Transmitter 3: 001 frequency 2
  Transmitter 4: 010 frequency 1
  Transmitter 5: 010 frequency 1

Assigned frequencies:
  Transmitter 0 → f0 (red)
  Transmitter 1 → f0 (red)
  Transmitter 2 → f0 (red)
  Transmitter 3 → f2 (blue)
  Transmitter 4 → f1 (green)
  Transmitter 5 → f1 (green)

Interference collision check:
No interference collisions!
In [13]:
freq_colors = [colors[f] for f in assignment]

fig, ax = plt.subplots(figsize=(12, 10))

# Interference lines
for i in range(N):
    for j in range(i+1, N):
        dist = distance_matrix[i, j]
        if dist < interf_range and assignment[i] == assignment[j]:
            # Collision - red line
            ax.plot([positions[i,0], positions[j,0]], 
                   [positions[i,1], positions[j,1]], 
                   color='#FF6B6B', linewidth=3, alpha=0.7, zorder=2) 
        elif dist < interf_range:
            # In range but different frequencies - green line
            ax.plot([positions[i,0], positions[j,0]], 
                   [positions[i,1], positions[j,1]], 
                   color='#95E1D3', linewidth=1.5, linestyle='--', alpha=0.5, zorder=1)

# Transmitters
for i, (x, y) in enumerate(positions):
    draw_transmitter(ax, x, y, freq_colors[i], str(i), size=6)
    circle = plt.Circle((x, y), interf_range, 
                       color=freq_colors[i], alpha=0.3, zorder=0)
    ax.add_patch(circle)

legend_elements = [
    Line2D([0], [0], marker='^', color='w', label=f'Frequency {i} ({freq_names[i].split()[0]})', 
           markerfacecolor=colors[i], markersize=15, markeredgecolor='black', markeredgewidth=2)
    for i in range(freq_count)
]
legend_elements.extend([
    Line2D([0], [0], color='#FF6B6B', linewidth=3, label='Interference (collision)'),
    Line2D([0], [0], color='#95E1D3', linewidth=2, linestyle='--', label='In range (OK)')
])

ax.legend(handles=legend_elements, loc='upper left', fontsize=11)
ax.set_title('Result: Frequency Assignment', fontsize=16, weight='bold')
ax.set_xlabel('Distance [m]', fontsize=12)
ax.set_ylabel('Distance [m]', fontsize=12)
ax.grid(True, alpha=0.3)
ax.set_aspect('equal')
plt.tight_layout()
plt.show()
No description has been provided for this image