Skip to content

Commit e21da9b

Browse files
committed
Add example notebooks for RF blocks
1 parent b8ffe12 commit e21da9b

4 files changed

Lines changed: 811 additions & 0 deletions

File tree

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"metadata": {},
6+
"source": [
7+
"# RF Amplifier Compression\n",
8+
"\n",
9+
"Simulation of an RF amplifier with gain compression demonstrating the third-order nonlinearity model. We sweep the input power and observe the 1 dB compression point and gain saturation behavior."
10+
]
11+
},
12+
{
13+
"cell_type": "markdown",
14+
"metadata": {},
15+
"source": [
16+
"## Amplifier Model\n",
17+
"\n",
18+
"The `RFAmplifier` block implements a third-order polynomial nonlinearity:\n",
19+
"\n",
20+
"$$y(t) = a_1 x(t) + a_3 x^3(t)$$\n",
21+
"\n",
22+
"where $a_1$ is the linear voltage gain and $a_3$ is derived from the input-referred third-order intercept point (IIP3). The output is hard-clipped at the gain compression peak to prevent unphysical sign reversal."
23+
]
24+
},
25+
{
26+
"cell_type": "code",
27+
"execution_count": null,
28+
"metadata": {},
29+
"outputs": [],
30+
"source": [
31+
"import numpy as np\n",
32+
"import matplotlib.pyplot as plt\n",
33+
"\n",
34+
"# Apply PathSim docs matplotlib style\n",
35+
"plt.style.use('../pathsim_docs.mplstyle')\n",
36+
"\n",
37+
"from pathsim import Simulation, Connection\n",
38+
"from pathsim.blocks import Source, Scope, Spectrum\n",
39+
"from pathsim.solvers import RKCK54\n",
40+
"\n",
41+
"from pathsim_rf import RFAmplifier"
42+
]
43+
},
44+
{
45+
"cell_type": "markdown",
46+
"metadata": {},
47+
"source": [
48+
"## System Setup\n",
49+
"\n",
50+
"We create an amplifier with 20 dB gain and an IIP3 of +10 dBm, and drive it with a single-tone sinusoidal source at 1 GHz. We sweep the input amplitude to trace out the compression curve."
51+
]
52+
},
53+
{
54+
"cell_type": "code",
55+
"execution_count": null,
56+
"metadata": {},
57+
"outputs": [],
58+
"source": [
59+
"# Amplifier parameters\n",
60+
"gain_dB = 20.0 # Small-signal gain [dB]\n",
61+
"IIP3_dBm = 10.0 # Input-referred IP3 [dBm]\n",
62+
"Z0 = 50.0 # Reference impedance [Ohm]\n",
63+
"f0 = 100.0 # Signal frequency [Hz] (scaled for simulation)\n",
64+
"\n",
65+
"# Sweep input power levels\n",
66+
"pin_dbm = np.arange(-30, 15, 1.0)\n",
67+
"pout_dbm = np.zeros_like(pin_dbm)\n",
68+
"\n",
69+
"for i, p_in in enumerate(pin_dbm):\n",
70+
"\n",
71+
" # Input amplitude from power in dBm\n",
72+
" p_watts = 10.0 ** (p_in / 10.0) * 1e-3\n",
73+
" v_peak = np.sqrt(2.0 * Z0 * p_watts)\n",
74+
"\n",
75+
" # Build simulation\n",
76+
" src = Source(func=lambda t, vp=v_peak: vp * np.sin(2 * np.pi * f0 * t))\n",
77+
" amp = RFAmplifier(gain=gain_dB, IIP3=IIP3_dBm, Z0=Z0)\n",
78+
" sco = Scope()\n",
79+
"\n",
80+
" sim = Simulation(\n",
81+
" [src, amp, sco],\n",
82+
" [Connection(src, amp), Connection(amp, sco)],\n",
83+
" dt=1 / (40 * f0),\n",
84+
" Solver=RKCK54\n",
85+
" )\n",
86+
"\n",
87+
" # Run for several cycles to reach steady state\n",
88+
" sim.run(10 / f0)\n",
89+
"\n",
90+
" # Read output and measure peak amplitude (last 2 cycles)\n",
91+
" t, [y] = sco.read()\n",
92+
" n_last = int(len(t) * 0.5)\n",
93+
" v_out_peak = np.max(np.abs(y[n_last:]))\n",
94+
"\n",
95+
" # Convert to output power in dBm\n",
96+
" p_out = v_out_peak ** 2 / (2 * Z0)\n",
97+
" pout_dbm[i] = 10 * np.log10(p_out / 1e-3) if p_out > 0 else -100"
98+
]
99+
},
100+
{
101+
"cell_type": "markdown",
102+
"metadata": {},
103+
"source": [
104+
"## Compression Curve\n",
105+
"\n",
106+
"The plot shows output power vs. input power. The dashed line represents ideal linear gain. The deviation from linearity shows the gain compression characteristic."
107+
]
108+
},
109+
{
110+
"cell_type": "code",
111+
"execution_count": null,
112+
"metadata": {},
113+
"outputs": [],
114+
"source": [
115+
"fig, ax = plt.subplots(dpi=120)\n",
116+
"\n",
117+
"# Ideal linear response\n",
118+
"ax.plot(pin_dbm, pin_dbm + gain_dB, '--', label='Ideal (linear)', alpha=0.7)\n",
119+
"\n",
120+
"# Simulated response\n",
121+
"ax.plot(pin_dbm, pout_dbm, linewidth=2, label='Simulated')\n",
122+
"\n",
123+
"# Mark P1dB point\n",
124+
"p1db_in = IIP3_dBm - 9.6\n",
125+
"ax.axvline(x=p1db_in, color='grey', linestyle=':', alpha=0.5, label=f'P1dB = {p1db_in:.1f} dBm')\n",
126+
"\n",
127+
"ax.set_xlabel('Input Power [dBm]')\n",
128+
"ax.set_ylabel('Output Power [dBm]')\n",
129+
"ax.set_title('RF Amplifier Compression Curve')\n",
130+
"ax.legend()\n",
131+
"ax.grid(True, alpha=0.3)\n",
132+
"plt.tight_layout()\n",
133+
"plt.show()"
134+
]
135+
},
136+
{
137+
"cell_type": "markdown",
138+
"metadata": {},
139+
"source": [
140+
"## Time-Domain Waveforms\n",
141+
"\n",
142+
"Let's compare the output waveform in the linear and compressed regimes to visualize the clipping behavior."
143+
]
144+
},
145+
{
146+
"cell_type": "code",
147+
"execution_count": null,
148+
"metadata": {},
149+
"outputs": [],
150+
"source": [
151+
"fig, axes = plt.subplots(1, 2, figsize=(10, 4), dpi=120)\n",
152+
"\n",
153+
"for ax, p_in, title in zip(axes, [-20, 5], ['Linear regime (-20 dBm)', 'Compressed regime (+5 dBm)']):\n",
154+
" p_watts = 10.0 ** (p_in / 10.0) * 1e-3\n",
155+
" v_peak = np.sqrt(2.0 * Z0 * p_watts)\n",
156+
"\n",
157+
" src = Source(func=lambda t, vp=v_peak: vp * np.sin(2 * np.pi * f0 * t))\n",
158+
" amp = RFAmplifier(gain=gain_dB, IIP3=IIP3_dBm, Z0=Z0)\n",
159+
" sco_in = Scope(labels=['input'])\n",
160+
" sco_out = Scope(labels=['output'])\n",
161+
"\n",
162+
" sim = Simulation(\n",
163+
" [src, amp, sco_in, sco_out],\n",
164+
" [Connection(src, amp, sco_in), Connection(amp, sco_out)],\n",
165+
" dt=1 / (40 * f0),\n",
166+
" Solver=RKCK54\n",
167+
" )\n",
168+
"\n",
169+
" sim.run(3 / f0)\n",
170+
"\n",
171+
" t_in, [y_in] = sco_in.read()\n",
172+
" t_out, [y_out] = sco_out.read()\n",
173+
"\n",
174+
" ax.plot(np.array(t_in) * f0, y_in * 1000, label='Input')\n",
175+
" ax.plot(np.array(t_out) * f0, y_out * 1000, label='Output')\n",
176+
" ax.set_xlabel('Cycles')\n",
177+
" ax.set_ylabel('Voltage [mV]')\n",
178+
" ax.set_title(title)\n",
179+
" ax.legend()\n",
180+
" ax.grid(True, alpha=0.3)\n",
181+
"\n",
182+
"plt.tight_layout()\n",
183+
"plt.show()"
184+
]
185+
}
186+
],
187+
"metadata": {
188+
"kernelspec": {
189+
"display_name": "Python 3",
190+
"language": "python",
191+
"name": "python3"
192+
},
193+
"language_info": {
194+
"name": "python",
195+
"version": "3.12.0"
196+
}
197+
},
198+
"nbformat": 4,
199+
"nbformat_minor": 4
200+
}

0 commit comments

Comments
 (0)