perturbation.PerturbationResponse
perturbation.PerturbationResponse(
freqs,
X,
L,
est,
n_solve,
forcing,
forcing_kinds,
cidx=(0, 1, 2),
terminals=None,
node_names=(),
geometry=None,
frozen=(),
scalar_names=(),
)Stored independent perturbation fields; extracts N x N matrices on demand.
Attributes
| Name | Description |
|---|---|
| n | Matrix dimension: the number of driven perturbation waves (2, 3, or more with scalars). |
| n_char | Characteristic count per edge: 3 (f, g, h) for inert flow, plus one per transported scalar. |
Methods
| Name | Description |
|---|---|
| acoustic_scattering_matrix | 2x2 acoustic scattering matrix, incoming (f_a, g_b) -> outgoing (g_a, f_b). |
| acoustic_transfer_matrix | The 2x2 acoustic (f, g) transfer matrix (incoming entropy = 0). |
| animate_field | Animate the forced spatial field at one frequency over a phase cycle (slider + play). |
| contribution_labels | Labels for :meth:contributions at edge: (output_labels, source_labels). |
| contributions | Break the wave at edge into the contribution of each terminal’s incoming wave. |
| field_along_network | Reconstruct the forced spatial field at one frequency along every root->leaf path. |
| intensity_along_network | Acoustic intensity (or energy density) along the developed length, at one frequency. |
| multiport_scattering_labels | Per-wave symbols for the multiport columns (incoming) and rows (outgoing). |
| multiport_scattering_matrix | Whole-network scattering matrix mapping every terminal’s incoming wave to every outgoing one. |
| plot_contributions | Plot the source attribution of the wave at edge: one panel per output wave. |
| plot_multiport_scattering_matrix | Plot the whole-network multiport scattering matrix with terminal-tagged labels. |
| plot_scattering_matrix | Plot the scattering matrix between a and b with station-tagged labels. |
| plot_transfer_matrix | Plot the transfer matrix T_ba in basis, with edge-subscripted labels. |
| scattering_labels | Ordered (station, char-index) tags of the SM’s incoming and outgoing waves. |
| scattering_matrix | Scattering matrix between stations a (upstream) and b. |
| transfer_matrix | Transfer matrix T_ba mapping the driven waves at a to those at b. |
| transfer_residual | Seriality residual of the fitted transfer matrix between edges a and b. |
acoustic_scattering_matrix
perturbation.PerturbationResponse.acoustic_scattering_matrix(a, b)2x2 acoustic scattering matrix, incoming (f_a, g_b) -> outgoing (g_a, f_b).
Ordering matches the full :meth:scattering_matrix (reflection at a first, transmission to b second); tm_fg_to_sm2 yields the classic (f_b, g_a) ordering, so the two rows are swapped.
acoustic_transfer_matrix
perturbation.PerturbationResponse.acoustic_transfer_matrix(a, b)The 2x2 acoustic (f, g) transfer matrix (incoming entropy = 0).
Reconstructed from the two acoustic forcings alone – well conditioned even at a quiescent mean state, where the full 3x3 degenerates because the entropy wave stops convecting (tau_0 -> inf). The acoustic columns pin the incoming entropy to zero, so this is the clean acoustic block whether or not the entropy wave is also driven (no entropy-noise contamination).
animate_field
perturbation.PerturbationResponse.animate_field(
freq,
*,
incoming=None,
variable='p',
basis=None,
root=None,
n_x=160,
n_frames=60,
normalize=True,
envelope=True,
**layout,
)Animate the forced spatial field at one frequency over a phase cycle (slider + play).
Pass a list of variable names, or a basis flavor (which expands to its three components), to overlay several quantities; all share the single forcing frequency, so they are phase-locked.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| freq | float | Target frequency (Hz); the nearest value in :attr:freqs is used. |
required |
| incoming | array_like of complex | Per-source amplitudes (see :meth:field_along_network); default unit on the first source. |
None |
| variable | str or sequence of str | Plotted quantity, or several to overlay (see :meth:field_along_network); default "p". Ignored when basis is given. |
'p' |
| basis | str | A flavor from :data:nefes.perturbation.operator.characteristics.BASIS_LABELS; overlays its three components and overrides variable. |
None |
| root | int | Developed-length origin element (default: a mean-flow inlet). | None |
| n_x | int | Interior samples per duct (default 160) and phase frames per cycle (default 60). | 160 |
| n_frames | int | Interior samples per duct (default 160) and phase frames per cycle (default 60). | 160 |
| normalize | bool | Scale each overlaid quantity’s peak magnitude to 1 (default True). | True |
| envelope | bool | Shade the +/- \|psi(x)\| span behind each animated line (default True). |
True |
| **layout | Forwarded to Figure.update_layout. |
{} |
Returns
| Name | Type | Description |
|---|---|---|
| plotly.graph_objects.Figure |
contribution_labels
perturbation.PerturbationResponse.contribution_labels(edge)Labels for :meth:contributions at edge: (output_labels, source_labels).
output_labels are the driven characteristics at edge (rows, f/g/h subscripted by the edge id – edge names are not meaningful); source_labels are the incoming wave each excitation drives, subscripted by its terminal node id and name (columns), so a plotted entry reads source → output (e.g. g₇:Outlet1 → f₅: the incoming wave at terminal Outlet1 contributing to f at edge 5).
contributions
perturbation.PerturbationResponse.contributions(edge, *, incoming=None)Break the wave at edge into the contribution of each terminal’s incoming wave.
Every driven excitation is a unit incoming wave at one terminal with all others zero, so by linearity the perturbation field at edge is the exact superposition of one contribution per source. This is the physically honest “where does what I see at this edge come from” decomposition: each term is a genuine one-way path gain (a multiport scattering entry generalized to an internal edge), free of the common-driver confounding that makes a least-squares transfer matrix overstate one edge’s influence on another (the residual measures predictability, this measures contribution).
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| edge | int | Edge whose wave is decomposed. | required |
| incoming | array_like of complex | Complex amplitude assigned to each source, as a 1-D array with one entry per source in the order of :meth:contribution_labels (the excitation-column order). A source is the incoming characteristic wave entering at one terminal – f driven into the inlet, g driven into an outlet, etc. – expressed in characteristic units. Use it to set the operating scenario you care about: e.g. [1, 0, 0] for unit forcing at the first terminal and silence elsewhere, or the actual/measured incoming amplitudes at each terminal. The contribution of source k is then its unit response scaled by incoming[k], and the columns sum to the total field for that scenario. Default (None): unit amplitude on every source, so each column is the bare per-source transfer function (gain) and the columns are directly comparable. |
None |
Returns
| Name | Type | Description |
|---|---|---|
| ndarray | Shape (n_omega, n, n_source): entry [:, c, k] is the amplitude of driven characteristic c (see :attr:cidx) at edge produced by source k. |
Raises
| Name | Type | Description |
|---|---|---|
| ValueError | If edge is out of range or incoming has the wrong length. |
field_along_network
perturbation.PerturbationResponse.field_along_network(
freq,
*,
incoming=None,
variable='p',
spec=None,
root=None,
n_x=160,
)Reconstruct the forced spatial field at one frequency along every root->leaf path.
Picks the stored frequency nearest freq and superposes the driven sources by incoming, then reconstructs the continuous perturbation field inside every duct (see :func:nefes.perturbation.fields.modeshape.reconstruct_field).
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| freq | float | Target frequency (Hz); the nearest value in :attr:freqs is used. |
required |
| incoming | array_like of complex | One amplitude per driven source (column of :attr:X), in excitation order. Default: unit amplitude on the first source, zero on the rest. |
None |
| variable | str | Plotted quantity ("p", "u", "rho", "mdot", "f", "g", "h"); default "p". Ignored when spec is given. |
'p' |
| spec | tuple | A (basis_flavor, component) pair (e.g. from :func:nefes.perturbation.fields.modeshape.resolve_specs) selecting any basis component directly; overrides variable. |
None |
| root | int | Developed-length origin element (default: a mean-flow inlet). | None |
| n_x | int | Interior samples per duct (default 160). | 160 |
Returns
| Name | Type | Description |
|---|---|---|
| list of nefes.perturbation.fields.modeshape.PathField |
Raises
| Name | Type | Description |
|---|---|---|
| ValueError | If the result carries no geometry, or incoming has the wrong length. |
intensity_along_network
perturbation.PerturbationResponse.intensity_along_network(
freq,
*,
incoming=None,
energy_density=False,
root=None,
n_x=160,
)Acoustic intensity (or energy density) along the developed length, at one frequency.
The spatial companion of :meth:field_along_network: instead of a primitive variable it reconstructs the Myers acoustic intensity I(x) [W/m^2] (downstream positive) – or the energy density e(x) [J/m^3] when energy_density=True – along every root->leaf path, so one can read where acoustic power flows and where it is produced or absorbed.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| freq | float | Target frequency (Hz); the nearest value in :attr:freqs is used. |
required |
| incoming | array_like of complex | One amplitude per driven source (column of :attr:X); default unit on the first source. |
None |
| energy_density | bool | Return the energy density instead of the intensity (default False). |
False |
| root | int | Developed-length origin element (default: a mean-flow inlet). | None |
| n_x | int | Interior samples per duct (default 160). | 160 |
Returns
| Name | Type | Description |
|---|---|---|
| list of nefes.perturbation.fields.modeshape.PathField | values is the real intensity (or energy density). |
multiport_scattering_labels
perturbation.PerturbationResponse.multiport_scattering_labels()Per-wave symbols for the multiport columns (incoming) and rows (outgoing).
Each wave is its characteristic symbol (f/g/h) subscripted by the node id of the terminal it lives on (e.g. f<sub>0</sub>). The bare id keeps the matrix labels uncrowded – a multiport entry reads f₀ → g₀ – while still uniquely identifying the terminal (cross-reference :attr:node_names for the element name).
multiport_scattering_matrix
perturbation.PerturbationResponse.multiport_scattering_matrix()Whole-network scattering matrix mapping every terminal’s incoming wave to every outgoing one.
The rigorous boundary-independent descriptor of a network with more than two terminals (where pairwise edge transfer matrices across a branch do not exist). Generally rectangular: with entropy the incoming set is #terminals + #inflow-terminals and the outgoing set #terminals + #outflow-terminals (acoustic-only it is the square N x N, N = #terminals). Columns follow the excitation order, rows are terminal-major; both are tagged by :meth:multiport_scattering_labels.
Returns
| Name | Type | Description |
|---|---|---|
| ndarray | Shape (n_omega, n_outgoing, n_incoming). |
Raises
| Name | Type | Description |
|---|---|---|
| ValueError | If the network was not driven at every terminal (rebuild with forcing=None). |
plot_contributions
perturbation.PerturbationResponse.plot_contributions(
edge,
freqs=None,
*,
incoming=None,
normalize='auto',
**kwargs,
)Plot the source attribution of the wave at edge: one panel per output wave.
The honest “where does what I see at this edge come from” view. There is one panel per driven characteristic at edge (f/g/h), and within each panel one curve per source (each terminal’s incoming wave), magnitude over phase, so the sources can be compared directly against one another for that output wave – which dominates, where they cross over. Unlike a transfer matrix this is well defined across branch points: it decomposes a contribution rather than asserting one edge determines another.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| edge | int | Edge whose wave is decomposed. | required |
| freqs | array_like | x-axis values (default: self.freqs, in Hz). |
None |
| incoming | array_like | Per-source incoming amplitudes for a specific scenario (see :meth:contributions); default is unit amplitude on every source. |
None |
| normalize | (auto, True, False) | Scale each panel by its dominant source’s peak magnitude (a scalar – never by the anchor’s frequency curve, which would blow up at its nulls), so the leading source peaks at 1 and the rest read as honest fractions of it. "auto" (default) normalizes only when incoming is None (per-source gains have no absolute scale), and shows absolute magnitudes once an incoming scenario fixes the amplitudes. |
"auto" |
| **kwargs | Forwarded to :func:nefes.plotting.plot_complex_matrix. |
{} |
Returns
| Name | Type | Description |
|---|---|---|
| plotly.graph_objects.Figure |
plot_multiport_scattering_matrix
perturbation.PerturbationResponse.plot_multiport_scattering_matrix(
freqs=None,
**kwargs,
)Plot the whole-network multiport scattering matrix with terminal-tagged labels.
Wraps :meth:multiport_scattering_matrix and labels every entry by its own terminal-subscripted waves (e.g. f₀ → g₀ for the inlet reflection, f₀ → f₇ for transmission to terminal 7).
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| freqs | array_like | x-axis values (default: self.freqs, in Hz). |
None |
| **kwargs | Forwarded to :func:nefes.plotting.plot_scattering_matrix. |
{} |
Returns
| Name | Type | Description |
|---|---|---|
| plotly.graph_objects.Figure |
plot_scattering_matrix
perturbation.PerturbationResponse.plot_scattering_matrix(
a,
b,
freqs=None,
*,
basis='char',
**kwargs,
)Plot the scattering matrix between a and b with station-tagged labels.
Convenience wrapper that converts the matrix to basis (via :meth:scattering_matrix) and labels it to match, supplying both the station edges and the incoming/outgoing wave partition so each entry is titled by its own station-subscripted waves (e.g. f₁ -> g₁ for a reflection at edge a).
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| a | int | Upstream / downstream edge ids of the cut. | required |
| b | int | Upstream / downstream edge ids of the cut. | required |
| freqs | array_like | x-axis values (default: self.freqs, in Hz). |
None |
| basis | str | Wave flavor ("char" or "riemann" – diagonal in the waves). Default "char". |
'char' |
| **kwargs | Forwarded to :func:nefes.plotting.plot_scattering_matrix. |
{} |
Returns
| Name | Type | Description |
|---|---|---|
| plotly.graph_objects.Figure |
plot_transfer_matrix
perturbation.PerturbationResponse.plot_transfer_matrix(
a,
b,
freqs=None,
*,
basis='char',
**kwargs,
)Plot the transfer matrix T_ba in basis, with edge-subscripted labels.
Convenience wrapper that converts the matrix to basis (via :meth:transfer_matrix) and labels it to match, supplying the station edges so each entry reads var_a -> var_b (e.g. f₁ -> f₂) instead of the ambiguous f -> f. Unlike the free :func:nefes.plotting.plot_transfer_matrix, the basis here genuinely changes the matrix values, not just the labels.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| a | int | Upstream / downstream edge ids; the matrix maps the waves at a to those at b. |
required |
| b | int | Upstream / downstream edge ids; the matrix maps the waves at a to those at b. |
required |
| freqs | array_like | x-axis values (default: self.freqs, in Hz). |
None |
| basis | str | Variable flavor (characteristics.BASIS_LABELS; e.g. "char", "primitive", "network"). Default "char". |
'char' |
| **kwargs | Forwarded to :func:nefes.plotting.plot_transfer_matrix. |
{} |
Returns
| Name | Type | Description |
|---|---|---|
| plotly.graph_objects.Figure |
scattering_labels
perturbation.PerturbationResponse.scattering_labels(a, b)Ordered (station, char-index) tags of the SM’s incoming and outgoing waves.
scattering_matrix
perturbation.PerturbationResponse.scattering_matrix(a, b, basis='char')Scattering matrix between stations a (upstream) and b.
Dimension is self.n (2 acoustic-only, 3 with entropy). Incoming waves (a’s downstream-running + b’s upstream-running) map to the outgoing ones; ordering follows matrices.scattering_labels. basis may only be a flavor diagonal in the characteristics (char or riemann); mixed flavors are undefined for a scattering matrix.
It delegates to :meth:transfer_matrix, so the same :class:TransferMatrixWarning fires when a and b straddle an internal branch point or the response is under-determined (see :meth:transfer_residual).
transfer_matrix
perturbation.PerturbationResponse.transfer_matrix(a, b, basis='char')Transfer matrix T_ba mapping the driven waves at a to those at b.
The dimension is self.n (2 for the default acoustic excitation, 3 with entropy). Read along each edge’s own arrow; basis selects the variable flavor (characteristics.BASIS_LABELS). Shape (n_omega, n, n).
Always returns a matrix. If a and b lie on opposite sides of an internal branch point no transfer matrix exists, so the returned T is only the least-squares best fit and a :class:TransferMatrixWarning is emitted – inspect :meth:transfer_residual and prefer :meth:multiport_scattering_matrix. A second warning flavor fires when the response is too under-determined to tell either way. In both cases a :class:TransferMatrixWarning is emitted.
transfer_residual
perturbation.PerturbationResponse.transfer_residual(a, b)Seriality residual of the fitted transfer matrix between edges a and b.
The max-over-frequency relative residual of T = Wb @ pinv(Wa) (see :meth:_seriality_residual). Near zero means a genuine transfer matrix exists (the edges are in series); a large value means they straddle an internal branch point and :meth:transfer_matrix returns only a least-squares best fit. Computed in the characteristic basis, so it is independent of the basis the matrix is later expressed in. Beware: when the response is under-determined (:meth:_underdetermined) this is structurally ~0 and does not certify seriality.
Returns
| Name | Type | Description |
|---|---|---|
| float |