β: This English translation is in beta — the Traditional-Chinese original is the authoritative version.
Lab 04 — impulse injection sweep and LTI vs LTV
This lab turns the "operational ISF definition" from impulse_to_phase_shift into a measurable experiment: inject a very small charge impulse at different phases of the waveform, measure the permanent phase shift it causes, divide that shift by the injected amount, and thereby back out the ISF (impulse sensitivity function — where on its waveform the oscillator is most vulnerable to a kick).
The extracted numerical ISF nearly coincides with the analytic solution for the ideal LC (maximum error about ). The same lab also uses impulse responses to draw out the difference between LTI (linear time-invariant) vs LTV (linear time-variant — the system parameters vary periodically with time) — the key reason the whole ISF theory "cannot treat oscillator phase with an ordinary LTI transfer function."
The two things this lab wants you to see with your own eyes: (1) the ISF is not an abstract definition; it is the result of the action "kick at phase , measure the persistent phase jump"; (2) the same-size impulse barely changes the phase when it kicks at the peak, but causes the largest phase jump at the zero crossing — this "kick effect varying periodically with the injection instant" is LTV, and can never be drawn by an LTI impulse response that depends only on .
1. Learning objectives
- Turn the operational ISF definition into a numerical experimental flow: sweep the injection phase → measure the persistent phase → back out .
- Verify that the ISF of the ideal LC oscillator (current injected into the capacitor node) is exactly , and quantify the numerical-vs-theory error (~ level).
- Understand the difference between the "persistent phase shift" and the "decaying amplitude perturbation" — only the tangential (phase) component remains permanently.
- Use the impulse-response comparison plot to build the physical picture of LTV: the step height varies periodically with the injection phase.
- Maps to [P1] Eq.(10),(11) and Fig. 4.
2. Mathematical model
The whole lab stands on this LTV impulse response ([P1] Eq.(10), p.182):
Its meaning: injecting unit charge at time (corresponding to phase ) makes the phase jump in a single step to , and because of the unit step the jump is kept permanently. Superposing it over all past noise currents gives the central LTV phase response ([P1] Eq.(11), p.182):
The single-impulse experiment that backs out the ISF: if is just one narrow pulse located at phase with total charge , Eq.(11) degenerates to a single step (the step version of [P1] Eq.(10); see also the operational definition in impulse_to_phase_shift):
- dimension check: the right-hand side looks like it carries units, but is rad (dimensionless) and is dimensionless, so is dimensionless ✓.
- Theory reference: for the ideal lossless parallel LC with current injected into the capacitor node, the state is ; a charge impulse pushes by , and projecting onto the limit-cycle tangential direction gives ; combined with this yields
At the peak , (pure amplitude change); at the zero crossing , is maximum (pure phase change). Full geometric derivation: isf_definition.
The mathematical difference of LTV vs LTV: an LTI system's impulse response depends only on the time difference, ; the oscillator's phase impulse response explicitly depends on the absolute injection instant (through the -periodic weight ) — precisely the definition of LTV.
3. Block diagram
4. Core Python code
The core of backing out the ISF is extract_isf_by_injection: for each phase run one
injection simulation, measure the mean phase difference over "the last period" relative to a
no-injection reference trajectory (this keeps only the permanent phase and filters out the
already-decayed amplitude perturbation), then divide by . Quoted verbatim from
the real code (simulations/common/oscillator_models.py):
def extract_isf_by_injection(f0, fs, n_inject_periods=6, settle_periods=4,
dq_over_qmax=1e-3, n_points=64, mu=0.3):
T = 1.0 / f0
settle = settle_periods * T
t_end = (settle_periods + n_inject_periods) * T
thetas = np.linspace(0.0, TWO_PI, n_points, endpoint=False)
gamma_num = np.zeros(n_points)
# Reference (no impulse), started from the limit cycle.
t_ref, xr, yr = simulate_lc(f0, t_end, fs, mu=mu, x0=1.0, y0=0.0)
phi_ref = excess_phase(t_ref, xr, yr, f0)
for i, th in enumerate(thetas):
t_inj = settle + th / (TWO_PI * f0) # phase th occurs at this time
t_p, xp, yp = simulate_lc(f0, t_end, fs, mu=mu, x0=1.0, y0=0.0,
impulse_time=t_inj, impulse_dx=dq_over_qmax)
phi_p = excess_phase(t_p, xp, yp, f0)
# persistent phase difference measured over the last period
m = t_p >= (t_end - T)
dphi = np.mean(phi_p[m] - phi_ref[m])
gamma_num[i] = dphi / dq_over_qmax
gamma_analytic = -np.sin(thetas)
return thetas, gamma_num, gamma_analytic
The core of the LTV vs LTV comparison figure plots "LTI: step height fixed, only time-shifted"
next to "LTV: step height varying with the injection phase"
(simulations/lab_04_impulse_sweep.py):
# LTV (oscillator phase): step of height Gamma(w0 tau)/qmax * u(t - tau).
ax = axes[1]
for tau, c in zip([0.0, 0.25, 0.5], ["tab:blue", "tab:orange", "tab:green"]):
gamma = -np.sin(2 * np.pi * f0 * tau) # ISF at injection phase
h = gamma * (t >= tau)
ax.plot(t, h, color=c,
label=fr"$\tau={tau}$, $\Gamma=-\sin(2\pi\tau)={gamma:+.2f}$")
- Why measure "the mean over the last period": the transient right after injection contains both
an amplitude perturbation and a phase jump; the Van der Pol-type amplitude restoration with
mu=0.3pulls the radial (amplitude) component back to the limit cycle within a few periods, leaving only the tangential (phase) component permanently. Measuring aftern_inject_periodsperiods yields the clean persistent phase. - Why
endpoint=False: and are the same phase — do not duplicate it in the sweep. - Why
dq_over_qmax=1e-3is small: keeps small-signal linearity (); otherwise the ISF itself would be altered by the large injection and the extraction would be inaccurate.
5. Full script path
simulations/lab_04_impulse_sweep.py
(calls simulate_lc, excess_phase, extract_isf_by_injection from
simulations/common/oscillator_models.py; plotting utilities in simulations/common/plot_utils.py).
How to run: python scripts/run_all_sims.py; figures are written to static/figures/.
6. Parameter table
| Parameter | Code variable | Value | Notes |
|---|---|---|---|
| Oscillation frequency | f0 | 1.0 (normalized) | the toy model uses a dimensionless frequency; the real-world 5 GHz mapping is in the units table below |
| Sampling rate | fs | 8000 (sweep) / 2000 (LTV figure) | points per period, enough to resolve the tangential projection |
| Injected charge ratio | dq_over_qmax | , keeps small signal | |
| Sweep points | n_points | 48 | 48 injection phases within one period |
| Post-injection observation | n_inject_periods | 6 | run 6 more periods after injection so the amplitude perturbation decays |
| Settle periods | settle_periods | 4 | let the system settle onto the limit cycle before injecting |
| Restoration strength | mu | 0.3 | Van der Pol-type amplitude restoration; is the ideal lossless LC |
| LTV-figure injection phases | tau | 0.0, 0.25, 0.5 (periods) | correspond to |
7. Units table
| Quantity | Symbol | toy-model unit | Physical unit |
|---|---|---|---|
| Time | periods (normalized) | s | |
| Injection phase |