GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — stability_margins misses a phase crossover at the Nyquist frequency (w = pi/dt) for discrete-time systems, so gm is inf when L(-1) < 0 · Issue #1267 · python-control/python-control

stability_margins misses a phase crossover at the Nyquist frequency (w = pi/dt) for discrete-time systems, so gm is inf when L(-1) < 0 #1267

New issue
Copy link
New issue
Copy link
Open
Open
stability_margins misses a phase crossover at the Nyquist frequency (w = pi/dt) for discrete-time systems, so gm is inf when L(-1) < 0#1267
Copy link

Description

@gimballock
gimballock
opened on Nov 4, 2026
Issue body actions

For a discrete-time loop with L(-1) < 0, stability_margins returns gm = inf under every method. L(-1) = L(e^{jπ}) is real for real coefficients, so when it is negative the Nyquist plot meets the negative real axis at w = π/dt, and the gain margin is -1/L(-1).

The simplest case is an integrator, L(z) = 1/(z - 1): the closed-loop pole z = 1 - K reaches z = -1 at K = 2, so gm = 2.

import warnings
import control as ct

warnings.simplefilter("ignore")
L = ct.tf([1], [1, -1], dt=1)
print(complex(L(-1)))                                   # (-0.5-0j)
for method in ("poly", "best", "frd"):
    print(method, ct.stability_margins(L, method=method)[:2])   # gm = inf, pm = 60 for all three (control 0.10.2)
print(ct.feedback(2 * L, 1).poles())                    # [-1.]: marginal at K = 2

1.5/(z - 0.5) (true gm 1) and 0.4/(z + 0.2) (true gm 2) also return gm = inf under all three methods.

Cause.

  • _z_filter keeps only 0 <= arg(z) < π (margins.py line 105 on main), so the phase-crossover root at z = -1 is dropped.
  • The frd path finds phase crossovers as sign changes inside the grid, so one at the grid's end, w = π/dt, is missed.

Suggested fix. Use zarg <= np.pi for the phase crossings, and in the FRD path check w = π/dt for discrete-time systems: if L(e^{jπ}) is negative, count a phase crossover with gm = -1/L(e^{jπ}).

Workaround.

at_pi = complex(L(-1)).real
gm = min(gm, -1 / at_pi) if at_pi < 0 else gm

Activity

  1. slivingston commented on Nov 5, 2026

    @slivingston
    slivingston
    on Nov 5, 2026
    Member
    More actions

    @gimballock You opened two big issues within 2 minutes of each other, so I assume you are using a chat bot to generate some or all of the above. Please add a disclosure about tool use: https://numpy.org/doc/stable/dev/ai_policy.html

  2. gimballock commented on Nov 5, 2026

    @gimballock
    gimballock
    on Nov 5, 2026
    Author
    More actions

    Disclosure: yes, this was written with an AI agent, Claude Code (Anthropic's Claude Opus 5.5 model).

    • How it was used: the agent found the problem while analysing discrete-time control loops 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. That's why the two issues went up within minutes of each other. Later, also at my request, it shortened the body.
    • What's AI-generated: all of the text and code in the issue. The numbers are program output from those runs, not model text, and the reproduction runs as posted.
    • My part: I asked for the reports 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      You are visitor 01402503 — GeezerVison does not store the pages you look up.
      Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.