Skip to content

visualdynamics.gui.sweep_rate_panel

sweep_rate_panel

The modes a sine sweep is checked against, beside the sweep drawn through them.

The Sweep Rate reading of a sine specification draws one tone's sweep as frequency against time with each mode's half-power band across it (plot.sweep_rate), and lists every pass in the table under the plot (core.sweep_rate.check_sweep_rate). This panel holds what the check is made against: the modes, each a natural frequency and a Q, typed as pairs, and the multiple of the rule's rate at which a pass is flagged. Under them are the counts that follow, and, where a tracked level of the tone is selected with the specification, the judgment of the Q read off that level at each mode (core.sweep_rate.read_swept_q).

Every finished edit redraws the plot and the table. Nothing is made: the check is a reading of a specification that already exists, as the points rule is a reading of a shock spectrum, so there is no Apply button. A script gets the same numbers from check_sweep_rate.

Classes:

Name Description
SweepRatePanel

The modes and the margin, and the counts. Edits are emitted as

Functions:

Name Description
parse_modes

Read '22 40, 180 20' as [(22.0, 40.0), (180.0, 20.0)]: pairs of

modes_text

The modes as the field writes them: '22 40, 180 20'.

Classes

SweepRatePanel

SweepRatePanel(parent: QWidget | None = None)

Bases: QWidget

The modes and the margin, and the counts. Edits are emitted as settings().

Methods:

Name Description
settings

The check's arguments as check_sweep_rate takes them:

set_settings

Set the fields without a redraw per field, then emit once.

show_results

Write the counts, a short line per pass, and a line per Q

Source code in src/visualdynamics/gui/sweep_rate_panel.py
def __init__(self, parent: QWidget | None = None) -> None:
    super().__init__(parent)
    self.title: QLabel = QLabel('Sweep Rate')
    grid = panel_grid(self, self.title)
    self._loading = False
    self._modes: list[tuple[float, float]] = list(MODES)

    self.modes_box: QLineEdit = QLineEdit(modes_text(self._modes))
    self.modes_box.setPlaceholderText('22 40, 180 20')
    self.modes_box.setToolTip(
        'The modes to check the sweep against: each a natural '
        'frequency in Hz and its Q, as pairs separated by commas, '
        'such as 22 40, 180 20')
    self.margin_box: DoubleSpinBox = DoubleSpinBox()
    self.margin_box.setRange(0.05, 100.0)
    self.margin_box.setDecimals(2)
    self.margin_box.setSingleStep(0.25)
    self.margin_box.setValue(MARGIN)
    self.margin_box.setToolTip(
        'Flag a pass whose rate is above this multiple of the '
        'rule\'s rate, 87 fn / Q² oct/min on a log sweep or '
        'fn² / Q² Hz/s on a linear one. At 1 a mode reads about '
        '94 percent of its steady height')
    commit_on_enter(self.margin_box)
    self.names, self.editors = add_keyed_settings(grid, [
        ('modes', 'Modes [Hz Q]', self.modes_box),
        ('margin', 'Flag above [× rule]', self.margin_box),
    ])
    add_rule(grid, 3)
    self.derived: dict[str, QLabel] = {
        key: value for key, (_name, value) in add_derived(
            grid, (('passes', 'Passes'), ('fast', 'Too fast')),
            4).items()}
    # one line per pass, and per Q read off a level, as many as there
    # are, so a label that wraps rather than a fixed set of rows
    self.readout: QLabel = QLabel('')
    self.readout.setWordWrap(True)
    grid.addWidget(self.readout, 6, 0, 1, 2)
    grid.setRowStretch(7, 1)

    self.modes_box.editingFinished.connect(self._modes_edited)
    self.margin_box.valueChanged.connect(self._edited)
Methods:
settings
settings() -> dict[str, Any]

The check's arguments as check_sweep_rate takes them: 'modes', a list of (frequency, Q), and 'margin'.

Source code in src/visualdynamics/gui/sweep_rate_panel.py
def settings(self) -> dict[str, Any]:
    """The check's arguments as `check_sweep_rate` takes them:
    'modes', a list of (frequency, Q), and 'margin'."""
    return {'modes': list(self._modes),
            'margin': self.margin_box.value()}
set_settings
set_settings(modes: Sequence[tuple[float, float]] | None = None, margin: float | None = None) -> None

Set the fields without a redraw per field, then emit once.

Source code in src/visualdynamics/gui/sweep_rate_panel.py
def set_settings(self, modes: Sequence[tuple[float, float]] | None = None,
                 margin: float | None = None) -> None:
    """Set the fields without a redraw per field, then emit once."""
    self._loading = True
    try:
        if modes is not None:
            self._modes = [(float(f), float(q)) for f, q in modes]
            self.modes_box.setText(modes_text(self._modes))
        if margin is not None:
            self.margin_box.setValue(margin)
    finally:
        self._loading = False
    self._edited()
show_results
show_results(checks: Sequence[Any], swept: Sequence[Any] = ()) -> None

Write the counts, a short line per pass, and a line per Q read off a level (SweptQ.describe). With no modes given, say how to give them.

Source code in src/visualdynamics/gui/sweep_rate_panel.py
def show_results(self, checks: Sequence[Any],
                 swept: Sequence[Any] = ()) -> None:
    """Write the counts, a short line per pass, and a line per Q
    read off a level (`SweptQ.describe`). With no modes given, say
    how to give them."""
    if not self._modes:
        self.derived['passes'].setText('—')
        self.derived['fast'].setText('—')
        self.readout.setText('Type the modes above, a frequency in '
                             'Hz and a Q for each')
        return
    self.derived['passes'].setText(str(len(checks)))
    self.derived['fast'].setText(
        str(sum(check.too_fast for check in checks)))
    lines = [f'{check.frequency:.4g} Hz, Q {check.q:.3g}, '
             f'{check.direction}: {check.ratio:.2g} × rule, '
             f'{check.verdict}' for check in checks]
    if not checks:
        lines.append('No mode lies inside this tone\'s sweep')
    lines += [reading.describe() for reading in swept]
    self.readout.setText('\n'.join(lines))

Functions:

parse_modes

parse_modes(text: str) -> list[tuple[float, float]]

Read '22 40, 180 20' as [(22.0, 40.0), (180.0, 20.0)]: pairs of natural frequency in Hz and Q, the pairs separated by commas or semicolons and the two numbers of a pair by spaces or a slash.

Raises ValueError, naming the pair, for a pair that is not two numbers, a frequency that is not positive or a Q at or under 0.5 (critical damping, where there is no band to sweep through).

Source code in src/visualdynamics/gui/sweep_rate_panel.py
def parse_modes(text: str) -> list[tuple[float, float]]:
    """Read '22 40, 180 20' as [(22.0, 40.0), (180.0, 20.0)]: pairs of
    natural frequency in Hz and Q, the pairs separated by commas or
    semicolons and the two numbers of a pair by spaces or a slash.

    Raises ValueError, naming the pair, for a pair that is not two
    numbers, a frequency that is not positive or a Q at or under 0.5
    (critical damping, where there is no band to sweep through)."""
    modes = []
    for piece in re.split(r'[;,]', text):
        piece = piece.strip()
        if not piece:
            continue
        numbers = [part for part in re.split(r'[\s/]+', piece) if part]
        try:
            frequency, q = (float(number) for number in numbers)
        except ValueError:
            raise ValueError(f'{piece!r} is not a frequency and a Q, '
                             'such as 22 40') from None
        if not frequency > 0.0:
            raise ValueError(f'{piece!r}: the frequency must be positive')
        if not q > 0.5:
            raise ValueError(f'{piece!r}: a Q of 0.5 or less is critically '
                             'damped and has no band to sweep through')
        modes.append((frequency, q))
    return modes

modes_text

modes_text(modes: Sequence[tuple[float, float]]) -> str

The modes as the field writes them: '22 40, 180 20'.

Source code in src/visualdynamics/gui/sweep_rate_panel.py
def modes_text(modes: Sequence[tuple[float, float]]) -> str:
    """The modes as the field writes them: '22 40, 180 20'."""
    return ', '.join(f'{frequency:g} {q:g}' for frequency, q in modes)