GeezerVison is showing github.com — describing_function_response reports sin(w*dt), not w, for discrete-time systems, and refines at z = jw · Issue #1269 · python-control/python-control
describing_function_response reports sin(w*dt), not w, for discrete-time systems, and refines at z = jw #1269DescriptionFor a discrete-time system, describing_function_response finds the right amplitude but reports the wrong frequency. It returns about sin(ω·dt), the same number whatever dt is. Its refinement step, and the returned positions, evaluate H at z = jω rather than on the unit circle.
import warnings
import numpy as np
import control as ct
warnings.simplefilter("ignore")
for dt in (1, 0.5):
H = ct.tf([0.5], [1, -0.5, 0], dt=dt)
# where the Nyquist curve, H(exp(j w dt)), crosses the negative real axis
w = np.linspace(1e-4, np.pi / dt, 200_001)
Hw = H(np.exp(1j * w * dt))
k = np.flatnonzero((np.diff(np.sign(Hw.imag)) != 0) & (Hw.real[:-1] < 0))[0]
r = ct.describing_function_response(H, np.sign, np.linspace(0.01, 5, 400), omega=np.linspace(1e-3, np.pi / dt, 2000))
a, om = r.intersections[0]
print(f"dt={dt}: crossing at w={w[k]:.4f}, H={Hw[k].real:.3f} -> a={4 / np.pi * abs(Hw[k].real):.4f}; "
f"describing_function_response: a={a:.4f}, omega={om:.4f}")
Output, with control 0.10.2 and the same on main (bfe9380):
dt=1: crossing at w=1.3181, H=-0.500 -> a=0.6366; describing_function_response: a=0.6339, omega=0.9703
dt=0.5: crossing at w=2.6362, H=-0.500 -> a=0.6366; describing_function_response: a=0.6339, omega=0.9703
With the default refine=True it also warns not able to refine result; returning estimate.
Cause (control/descfcn.py on main):
- The frequency. Line 349 is
H_omega, H_vals = response.contour.imag, H(response.contour). For a discrete system, nyquist_response's contour is in z, exp(jω·dt), so its imaginary part is about sin(ω·dt), not ω (sin(1.318) = 0.968). The intersection itself is found on H(response.contour), which is why the amplitude is right.
- The refinement and the positions.
_cost (line 378) and pos (line 389) use H(1j * omega), which for a discrete system evaluates at z = jω.
Suggested fix: when H.isdtime(), take the frequencies as np.angle(response.contour) / H.dt, and evaluate H(np.exp(1j * omega * H.dt)) in _cost and for pos. A test on a discrete loop with a known crossing, such as the one above, would cover it.
Workaround: compute the crossing on the unit circle directly, as in the first lines of the example, and use describing_function for N(a). Activityslivingston commented on Nov 5, 2026 gimballock commented on Nov 5, 2026 Disclosure: yes, this was written with an AI agent, Claude Code (Anthropic's Claude Opus 5.5 model).
- How it was used: the agent hit the problem while testing a describing-function analysis of a sampled loop for a research project of mine. It wrote the reproduction, ran it against control 0.10.2 and against
main, wrote the issue text, and posted it through the GitHub CLI at my request.
- What's AI-generated: all of the text and code in the issue. The numbers are program output from those runs, and the code block runs as posted.
- My part: I asked for it to be filed, but didn't review the text before it was posted.
I'm responsible for what's posted under my account, and questions are welcome. slivingston commented on Nov 5, 2026 @gimballock You didn't even review the text before posting it? So you just posted generated text without review and expect us to review it for you? slivingston commented on Nov 5, 2026 Anyway, you are welcome to use AI tools, but you absolutely must review stuff before you post it. (That said, I appreciate your honesty.) gimballock commented on Nov 5, 2026
Anyway, you are welcome to use AI tools, but you absolutely must review stuff before you post it. (That said, I appreciate your honesty.)
Hi, sorry for the frustration. You're point is fair, it was a relatively low effort issue filing on my part. I'm prototyping analysis tools a bit ahead of my complete understanding with regard to control theory analysis and the algorithms underlying all that.
Having said that I was involved in checking that the analysis of the bug, I did an adversarial review with ponytail to simplify/minimize the issue content (which i find works pretty well) but i trusted the agent to largely copy/paste what i read from the adversarial review to github. So the 'without review' phrasing is referring to the final copy not the whole interaction. But to your point i'm adding in a rule to have the agent quiz me on key points as an automated guard rail to check comprehension before posting publicly. slivingston commented on Nov 5, 2026 @gimballock Thanks, that is a good plan. I appreciate that you are making your contributions sincerely and carefully.
As you know, the cost of submitting plausible issues and PRs is now almost zero, but the effort of review has not meaningfully become less. AI tools can help with review, but humans must sign-off, and maintainers of open source bear responsibility of the consequences of new/edited code. (Fire-and-forget AI users like RoboX2020 are thus very frustrating to work with.) slivingston commented on Nov 6, 2026 @gimballock I read over this again, and I think this generation process is unreliable but maybe is the best you can do. Ideally, to me, you would write the description yourself, assuming you understand the issue/PR. At the very least, you need to commit to review 100% of the generated issue, PR, comment, etc. text before posting it. Only having the chat bot "quiz" you is unacceptable. (If you expect me to read the post, then you should read it first, from beginning to end.) slivingston commented on Nov 6, 2026
MetadataMetadataDevelopmentNo branches or pull requestsIssue actions
|