Skip to content

Commit 502d191

Browse files
committed
Use Scope.plot() and Spectrum.plot() in example notebooks
1 parent e21da9b commit 502d191

4 files changed

Lines changed: 94 additions & 173 deletions

File tree

docs/source/examples/rf_amplifier_compression.ipynb

Lines changed: 69 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
"plt.style.use('../pathsim_docs.mplstyle')\n",
3636
"\n",
3737
"from pathsim import Simulation, Connection\n",
38-
"from pathsim.blocks import Source, Scope, Spectrum\n",
38+
"from pathsim.blocks import Source, Scope\n",
3939
"from pathsim.solvers import RKCK54\n",
4040
"\n",
4141
"from pathsim_rf import RFAmplifier"
@@ -45,9 +45,9 @@
4545
"cell_type": "markdown",
4646
"metadata": {},
4747
"source": [
48-
"## System Setup\n",
48+
"## Time-Domain Waveforms\n",
4949
"\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."
50+
"First, let's look at the amplifier output in the linear and compressed regimes. We drive the amplifier with a sinusoidal input and observe the output using `Scope.plot()`."
5151
]
5252
},
5353
{
@@ -62,6 +62,71 @@
6262
"Z0 = 50.0 # Reference impedance [Ohm]\n",
6363
"f0 = 100.0 # Signal frequency [Hz] (scaled for simulation)\n",
6464
"\n",
65+
"# Linear regime: -20 dBm input\n",
66+
"p_watts = 10.0 ** (-20.0 / 10.0) * 1e-3\n",
67+
"v_peak = np.sqrt(2.0 * Z0 * p_watts)\n",
68+
"\n",
69+
"src = Source(func=lambda t: v_peak * np.sin(2 * np.pi * f0 * t))\n",
70+
"amp = RFAmplifier(gain=gain_dB, IIP3=IIP3_dBm, Z0=Z0)\n",
71+
"sco = Scope(labels=['input', 'output'])\n",
72+
"\n",
73+
"sim = Simulation(\n",
74+
" [src, amp, sco],\n",
75+
" [Connection(src, amp, sco[0]), Connection(amp, sco[1])],\n",
76+
" dt=1 / (40 * f0),\n",
77+
" Solver=RKCK54\n",
78+
")\n",
79+
"\n",
80+
"sim.run(3 / f0)\n",
81+
"\n",
82+
"fig, ax = sco.plot()\n",
83+
"ax.set_title('Linear Regime (-20 dBm Input)')\n",
84+
"plt.show()"
85+
]
86+
},
87+
{
88+
"cell_type": "code",
89+
"execution_count": null,
90+
"metadata": {},
91+
"outputs": [],
92+
"source": [
93+
"# Compressed regime: +5 dBm input\n",
94+
"p_watts = 10.0 ** (5.0 / 10.0) * 1e-3\n",
95+
"v_peak = np.sqrt(2.0 * Z0 * p_watts)\n",
96+
"\n",
97+
"src = Source(func=lambda t: v_peak * np.sin(2 * np.pi * f0 * t))\n",
98+
"amp = RFAmplifier(gain=gain_dB, IIP3=IIP3_dBm, Z0=Z0)\n",
99+
"sco = Scope(labels=['input', 'output'])\n",
100+
"\n",
101+
"sim = Simulation(\n",
102+
" [src, amp, sco],\n",
103+
" [Connection(src, amp, sco[0]), Connection(amp, sco[1])],\n",
104+
" dt=1 / (40 * f0),\n",
105+
" Solver=RKCK54\n",
106+
")\n",
107+
"\n",
108+
"sim.run(3 / f0)\n",
109+
"\n",
110+
"fig, ax = sco.plot()\n",
111+
"ax.set_title('Compressed Regime (+5 dBm Input)')\n",
112+
"plt.show()"
113+
]
114+
},
115+
{
116+
"cell_type": "markdown",
117+
"metadata": {},
118+
"source": [
119+
"## Compression Curve\n",
120+
"\n",
121+
"Now we sweep the input power and measure the output power at each level to trace the compression curve. This requires custom plotting since we aggregate results from many simulations."
122+
]
123+
},
124+
{
125+
"cell_type": "code",
126+
"execution_count": null,
127+
"metadata": {},
128+
"outputs": [],
129+
"source": [
65130
"# Sweep input power levels\n",
66131
"pin_dbm = np.arange(-30, 15, 1.0)\n",
67132
"pout_dbm = np.zeros_like(pin_dbm)\n",
@@ -84,10 +149,9 @@
84149
" Solver=RKCK54\n",
85150
" )\n",
86151
"\n",
87-
" # Run for several cycles to reach steady state\n",
88152
" sim.run(10 / f0)\n",
89153
"\n",
90-
" # Read output and measure peak amplitude (last 2 cycles)\n",
154+
" # Read output and measure peak amplitude (last half)\n",
91155
" t, [y] = sco.read()\n",
92156
" n_last = int(len(t) * 0.5)\n",
93157
" v_out_peak = np.max(np.abs(y[n_last:]))\n",
@@ -97,15 +161,6 @@
97161
" pout_dbm[i] = 10 * np.log10(p_out / 1e-3) if p_out > 0 else -100"
98162
]
99163
},
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-
},
109164
{
110165
"cell_type": "code",
111166
"execution_count": null,
@@ -114,13 +169,9 @@
114169
"source": [
115170
"fig, ax = plt.subplots(dpi=120)\n",
116171
"\n",
117-
"# Ideal linear response\n",
118172
"ax.plot(pin_dbm, pin_dbm + gain_dB, '--', label='Ideal (linear)', alpha=0.7)\n",
119-
"\n",
120-
"# Simulated response\n",
121173
"ax.plot(pin_dbm, pout_dbm, linewidth=2, label='Simulated')\n",
122174
"\n",
123-
"# Mark P1dB point\n",
124175
"p1db_in = IIP3_dBm - 9.6\n",
125176
"ax.axvline(x=p1db_in, color='grey', linestyle=':', alpha=0.5, label=f'P1dB = {p1db_in:.1f} dBm')\n",
126177
"\n",
@@ -132,56 +183,6 @@
132183
"plt.tight_layout()\n",
133184
"plt.show()"
134185
]
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-
]
185186
}
186187
],
187188
"metadata": {

docs/source/examples/rf_mixer_downconversion.ipynb

Lines changed: 4 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,8 @@
9797
"outputs": [],
9898
"source": [
9999
"connections = [\n",
100-
" Connection(rf_src, mixer[\"rf\"], sco[0]),\n",
101-
" Connection(lo_src, mixer[\"lo\"], sco[1]),\n",
100+
" Connection(rf_src, mixer[0], sco[0]),\n",
101+
" Connection(lo_src, mixer[1], sco[1]),\n",
102102
" Connection(mixer, sco[2], spc),\n",
103103
"]\n",
104104
"\n",
@@ -129,28 +129,7 @@
129129
"metadata": {},
130130
"outputs": [],
131131
"source": [
132-
"t, [rf, lo, if_out] = sco.read()\n",
133-
"t = np.array(t) * 1000 # Convert to ms\n",
134-
"\n",
135-
"fig, axes = plt.subplots(3, 1, figsize=(8, 6), dpi=120, sharex=True)\n",
136-
"\n",
137-
"axes[0].plot(t, rf)\n",
138-
"axes[0].set_ylabel('RF [V]')\n",
139-
"axes[0].set_title(f'RF Signal ({f_rf:.0f} Hz)')\n",
140-
"axes[0].grid(True, alpha=0.3)\n",
141-
"\n",
142-
"axes[1].plot(t, lo)\n",
143-
"axes[1].set_ylabel('LO [V]')\n",
144-
"axes[1].set_title(f'LO Signal ({f_lo:.0f} Hz)')\n",
145-
"axes[1].grid(True, alpha=0.3)\n",
146-
"\n",
147-
"axes[2].plot(t, if_out)\n",
148-
"axes[2].set_ylabel('IF [V]')\n",
149-
"axes[2].set_xlabel('Time [ms]')\n",
150-
"axes[2].set_title('Mixer Output (IF)')\n",
151-
"axes[2].grid(True, alpha=0.3)\n",
152-
"\n",
153-
"plt.tight_layout()\n",
132+
"sco.plot()\n",
154133
"plt.show()"
155134
]
156135
},
@@ -169,18 +148,7 @@
169148
"metadata": {},
170149
"outputs": [],
171150
"source": [
172-
"f, [G_if] = spc.read()\n",
173-
"\n",
174-
"fig, ax = plt.subplots(dpi=120)\n",
175-
"ax.plot(f, np.abs(G_if), linewidth=2)\n",
176-
"ax.axvline(x=f_if, color='red', linestyle='--', alpha=0.5, label=f'IF = {f_if:.0f} Hz')\n",
177-
"ax.axvline(x=f_rf + f_lo, color='orange', linestyle='--', alpha=0.5, label=f'Sum = {f_rf + f_lo:.0f} Hz')\n",
178-
"ax.set_xlabel('Frequency [Hz]')\n",
179-
"ax.set_ylabel('Magnitude')\n",
180-
"ax.set_title('Mixer Output Spectrum')\n",
181-
"ax.legend()\n",
182-
"ax.grid(True, alpha=0.3)\n",
183-
"plt.tight_layout()\n",
151+
"spc.plot()\n",
184152
"plt.show()"
185153
]
186154
}

docs/source/examples/superheterodyne_receiver.ipynb

Lines changed: 9 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,7 @@
7878
"if_amp = RFAmplifier(gain=20.0, IIP3=15.0, Z0=Z0)\n",
7979
"\n",
8080
"# Observation points at each stage\n",
81-
"sco_rf = Scope(labels=['RF input'])\n",
82-
"sco_lna = Scope(labels=['LNA output'])\n",
83-
"sco_if = Scope(labels=['IF output'])\n",
81+
"sco = Scope(labels=['RF input', 'LNA output', 'IF output'])\n",
8482
"\n",
8583
"# Spectrum at output\n",
8684
"freq = np.linspace(0, 2500, 500)\n",
@@ -103,15 +101,15 @@
103101
"outputs": [],
104102
"source": [
105103
"connections = [\n",
106-
" Connection(rf_src, lna, sco_rf),\n",
107-
" Connection(lo_src, mixer[\"lo\"]),\n",
108-
" Connection(lna, mixer[\"rf\"], sco_lna),\n",
104+
" Connection(rf_src, lna, sco[0]),\n",
105+
" Connection(lo_src, mixer[1]),\n",
106+
" Connection(lna, mixer[0], sco[1]),\n",
109107
" Connection(mixer, if_amp),\n",
110-
" Connection(if_amp, sco_if, spc),\n",
108+
" Connection(if_amp, sco[2], spc),\n",
111109
"]\n",
112110
"\n",
113111
"sim = Simulation(\n",
114-
" [rf_src, lo_src, lna, mixer, if_amp, sco_rf, sco_lna, sco_if, spc],\n",
112+
" [rf_src, lo_src, lna, mixer, if_amp, sco, spc],\n",
115113
" connections,\n",
116114
" dt=1 / (20 * (f_rf + f_lo)),\n",
117115
" Solver=RKCK54,\n",
@@ -128,7 +126,7 @@
128126
"source": [
129127
"## Signal at Each Stage\n",
130128
"\n",
131-
"Let's visualize how the signal evolves through the receiver chain."
129+
"The scope shows the signal evolving through the receiver chain: the weak RF input, the amplified LNA output, and the downconverted IF output."
132130
]
133131
},
134132
{
@@ -137,28 +135,7 @@
137135
"metadata": {},
138136
"outputs": [],
139137
"source": [
140-
"fig, axes = plt.subplots(3, 1, figsize=(8, 7), dpi=120, sharex=True)\n",
141-
"\n",
142-
"t, [y] = sco_rf.read()\n",
143-
"axes[0].plot(np.array(t) * 1000, np.array(y) * 1000, linewidth=1)\n",
144-
"axes[0].set_ylabel('Voltage [mV]')\n",
145-
"axes[0].set_title(f'RF Input ({p_in_dbm:.0f} dBm at {f_rf:.0f} Hz)')\n",
146-
"axes[0].grid(True, alpha=0.3)\n",
147-
"\n",
148-
"t, [y] = sco_lna.read()\n",
149-
"axes[1].plot(np.array(t) * 1000, np.array(y) * 1000, linewidth=1)\n",
150-
"axes[1].set_ylabel('Voltage [mV]')\n",
151-
"axes[1].set_title('After LNA (+15 dB)')\n",
152-
"axes[1].grid(True, alpha=0.3)\n",
153-
"\n",
154-
"t, [y] = sco_if.read()\n",
155-
"axes[2].plot(np.array(t) * 1000, np.array(y) * 1000, linewidth=1)\n",
156-
"axes[2].set_ylabel('Voltage [mV]')\n",
157-
"axes[2].set_xlabel('Time [ms]')\n",
158-
"axes[2].set_title('IF Output After Mixer + IF Amp (+20 dB)')\n",
159-
"axes[2].grid(True, alpha=0.3)\n",
160-
"\n",
161-
"plt.tight_layout()\n",
138+
"sco.plot()\n",
162139
"plt.show()"
163140
]
164141
},
@@ -177,18 +154,7 @@
177154
"metadata": {},
178155
"outputs": [],
179156
"source": [
180-
"f, [G] = spc.read()\n",
181-
"\n",
182-
"fig, ax = plt.subplots(dpi=120)\n",
183-
"ax.plot(f, 20 * np.log10(np.abs(G) + 1e-15), linewidth=2)\n",
184-
"ax.axvline(x=f_if, color='red', linestyle='--', alpha=0.5, label=f'IF = {f_if:.0f} Hz')\n",
185-
"ax.axvline(x=f_rf + f_lo, color='orange', linestyle='--', alpha=0.5, label=f'Image = {f_rf + f_lo:.0f} Hz')\n",
186-
"ax.set_xlabel('Frequency [Hz]')\n",
187-
"ax.set_ylabel('Magnitude [dB]')\n",
188-
"ax.set_title('IF Output Spectrum')\n",
189-
"ax.legend()\n",
190-
"ax.grid(True, alpha=0.3)\n",
191-
"plt.tight_layout()\n",
157+
"spc.plot()\n",
192158
"plt.show()"
193159
]
194160
}

0 commit comments

Comments
 (0)