Skip to content

Development environment setup#1

Open
SebastianJiroSchlecht wants to merge 16 commits into
mainfrom
cursor/development-environment-setup-225c
Open

Development environment setup#1
SebastianJiroSchlecht wants to merge 16 commits into
mainfrom
cursor/development-environment-setup-225c

Conversation

@SebastianJiroSchlecht

@SebastianJiroSchlecht SebastianJiroSchlecht commented Feb 28, 2026

Copy link
Copy Markdown

Implement native arbitrary z-plane probing and autograd derivatives for FLAMO modules to enable advanced transfer-matrix analysis.


Open in Web Open in Cursor 


Note

Medium Risk
Adds new probe/probe_w APIs and matrix-composition logic across DSP building blocks; while runtime processing paths are largely unchanged, incorrect probe implementations or None-returning modules can lead to shape/dtype issues in transfer-matrix analysis.

Overview
Adds native arbitrary z-plane transfer-matrix probing across FLAMO DSP modules by introducing probe(z)/probe_w(w) on the DSP base and implementing concrete evaluations for key blocks (e.g. Gain, Filter/parallelFilter, SOSFilter variants, and Delay variants using H(z)=\gamma^m z^{-m}).

Extends system containers to compose these probes: Series now multiplies per-module transfer matrices, Recursion can probe the closed-loop transfer and exposes characteristic-matrix probing (probe_recursion*), Parallel combines branch transfer matrices, and Shell can optionally include input/output layer probes; Transform/FFT/iFFT add no-op probe stubs for compatibility.

Written by Cursor Bugbot for commit c0897ba. This will update automatically on new commits. Configure here.

cursoragent and others added 2 commits February 28, 2026 10:46
Co-authored-by: Sebastian Jiro Schlecht <SebastianJiroSchlecht@users.noreply.github.com>
Implement probe(z) method on all leaf DSP modules (Gain, parallelGain, Matrix,
Delay, parallelDelay, Filter, parallelFilter, SOSFilter, parallelSOSFilter,
FFT, iFFT, Transform) and graph modules (Series, Parallel, Recursion, Shell).

Add flamo/processor/probe.py with:
- probe_points(): vectorized probe over multiple z-plane points
- probe_with_derivative(): dH/dz via autograd Wirtinger reconstruction

Export new helpers from flamo/processor/__init__.py.

Add comprehensive test suite (30 tests) covering:
- Gain/Matrix constant probe
- Delay probe vs analytic formula
- Filter probe vs polynomial evaluation
- SOS probe vs section formula
- Series/Parallel/Recursion probe vs manual matrix computation
- probe_with_derivative vs finite differences (including Recursion)
- Shell core-only default and include_shell_io modes

All existing forward() behavior is unchanged.

Co-authored-by: Sebastian Jiro Schlecht <SebastianJiroSchlecht@users.noreply.github.com>
@cursor

cursor Bot commented Feb 28, 2026

Copy link
Copy Markdown

Cursor Agent can help with this pull request. Just @cursor in comments and I'll start working on changes in this branch.
Learn more about Cursor Agents

Comment thread flamo/processor/probe.py Outdated
Comment thread flamo/processor/system.py Outdated
Comment thread flamo/processor/probe.py Outdated
Comment thread flamo/processor/dsp.py
cursoragent and others added 3 commits February 28, 2026 11:41
1. HIGH: Add allow_unused=True to torch.autograd.grad calls and handle
   None returns. Fixes crash when trainable Gain has grad_fn from params
   but probe output doesn't depend on z.

2. MEDIUM: Replace in-place tensor assignment with torch.stack to
   preserve autograd graph when create_graph=True.

3. MEDIUM: Add explicit NotImplementedError probe() overrides for
   Biquad, parallelBiquad, SVF, parallelSVF, GEQ, parallelGEQ,
   PEQ, parallelPEQ, AccurateGEQ, parallelAccurateGEQ,
   ScatteringMatrix, VelvetNoiseMatrix. Prevents silent incorrect
   inheritance from Filter.probe.

4. LOW: Remove unused to_complex import from system.py.

Add tests: trainable Gain derivative, create_graph preservation,
Biquad/SVF probe raises NotImplementedError.

Co-authored-by: Sebastian Jiro Schlecht <SebastianJiroSchlecht@users.noreply.github.com>
Add probe_recursion() and probe_recursion_with_derivative() to Recursion:
- probe_recursion(z, derivative=False): P(z) = I - F(z)B(z)
- probe_recursion(z, derivative=True): returns (P, dP/dz)
- probe_recursion_with_derivative(z, create_graph=False): returns (P, dP/dz)

Factor reusable wirtinger_derivative() helper in probe.py:
- Generic Wirtinger dF/dz for any callable F(z) -> Tensor
- Refactor probe_with_derivative to use it internally
- Export from flamo.processor

Both methods accept ext_param (routed to feedforward/feedback branches)
and include_shell_io/create_graph kwargs for signature compatibility.

Add 16 new tests covering:
- A) Characteristic value correctness (constant gains, gain+delay)
- B) Derivative vs finite difference (delay, filter, constant gain)
- C) API behavior (return types, shapes, kwargs, consistency)
- D) Non-regression (existing probe/probe_with_derivative unchanged)

All 50 tests pass. No reversed-form probing in this patch.

Co-authored-by: Sebastian Jiro Schlecht <SebastianJiroSchlecht@users.noreply.github.com>
Export a scalar Wirtinger derivative helper and add w-plane probing support across DSP and system modules.

- Exported wirtinger_derivative_scalar in flamo.processor.__init__ and implemented it in probe.py to compute d/dz of complex scalar eval functions (avoids matrix inversion when computing d/dz log det).
- Added probe_w methods for Gain, parallelGain, Delay, and parallelDelay to evaluate modules at w = z^{-1> (used for numerical stability when |z|<1).
- Implemented Series.probe_w to compose module probe_w/probe calls.
- Extended Recursion with z- and w-plane helpers: corrected characteristic convention in docstrings, updated probe_recursion_with_derivative to return (P, dP/dz), added log_det_derivative (z), _eval_characteristic_w, log_det_derivative_w, and probe_recursion_w_with_derivative for w-plane evaluation and derivatives.
- Clarified Delay documentation and made probe implementations consistently compute z^{-m} vs w^{m} with explicit handling of z inverse.

These changes improve numerical stability for evaluations inside the unit circle and provide a dedicated scalar derivative helper for stable log-det derivative computations.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 4 potential issues.

Autofix Details

Bugbot Autofix prepared fixes for all 4 issues found in the latest run.

  • ✅ Fixed: Recursion.probe doesn't split ext_param for branches
    • Modified Recursion.probe to call _split_ext_param and pass the split parameters to feedforward and feedback branches.
  • ✅ Fixed: Parallel.probe doesn't split ext_param for branches
    • Modified Parallel.probe to split ext_param by 'branchA' and 'branchB' keys before passing to each branch.
  • ✅ Fixed: probe_w fallback to probe gives wrong z-transform
    • Removed incorrect fallback to probe in Series.probe_w and Recursion._eval_characteristic_w to prevent wrong z-transform computation.
  • ✅ Fixed: Inherited Gain.probe crashes for HouseholderMatrix subclass
    • Added probe and probe_w overrides to HouseholderMatrix to correctly compute I - 2uu^T transfer matrix and avoid calling to_complex on already-complex tensor.

Create PR

Or push these changes by commenting:

@cursor push e0aa132c68
Preview (e0aa132c68)
diff --git a/flamo/processor/dsp.py b/flamo/processor/dsp.py
--- a/flamo/processor/dsp.py
+++ b/flamo/processor/dsp.py
@@ -776,6 +776,26 @@
                 f"parameter shape = {self.size} not compatible with input signal of shape = ({x.shape})."
             )
 
+    def probe(self, z: torch.Tensor, ext_param=None):
+        r"""
+        Evaluate the Householder transfer matrix H(z) at arbitrary complex z.
+
+        For a frequency-independent Householder matrix, H(z) = I - 2*u*u^T.
+
+            **Returns**:
+                torch.Tensor: ``(N, N)`` complex transfer matrix.
+        """
+        param = ext_param if ext_param is not None else self.param
+        u = self.map(param)
+        N = u.shape[0]
+        I = torch.eye(N, dtype=u.dtype, device=u.device)
+        uuT = torch.matmul(u, u.transpose(0, 1))
+        return I - 2 * uuT
+
+    def probe_w(self, w: torch.Tensor, ext_param=None):
+        r"""Evaluate at :math:`w = z^{-1}`; Householder matrix is constant, so same as :meth:`probe`."""
+        return self.probe(w, ext_param)
+
     def get_io(self):
         r"""
         Computes the number of input and output channels based on the size parameter.

diff --git a/flamo/processor/system.py b/flamo/processor/system.py
--- a/flamo/processor/system.py
+++ b/flamo/processor/system.py
@@ -326,11 +326,11 @@
     def probe_w(self, w: torch.Tensor, ext_param=None):
         r"""
         Evaluate the series transfer matrix at :math:`w = z^{-1}`.
-        Uses each module's :meth:`probe_w` if present, else :meth:`probe(w)`.
+        Uses each module's :meth:`probe_w` if present.
         """
         H = None
         for module in self:
-            probe_fn = getattr(module, 'probe_w', None) or getattr(module, 'probe', None)
+            probe_fn = getattr(module, 'probe_w', None)
             if probe_fn is None:
                 continue
             Hi = probe_fn(w, ext_param)
@@ -538,8 +538,9 @@
             **Returns**:
                 torch.Tensor: Transfer matrix ``(N_out, N_in)`` (complex).
         """
-        F = self.feedforward.probe(z, ext_param)
-        B = self.feedback.probe(z, ext_param)
+        ext_param_ff, ext_param_fb = self._split_ext_param(ext_param)
+        F = self.feedforward.probe(z, ext_param_ff)
+        B = self.feedback.probe(z, ext_param_fb)
         N = F.shape[0]
         I = torch.eye(N, dtype=F.dtype, device=F.device)
         A = I - F @ B
@@ -643,8 +644,10 @@
         Used for numerical stability when :math:`|z| < 1` (evaluate in w-plane).
         """
         ext_param_ff, ext_param_fb = self._split_ext_param(ext_param)
-        probe_ff = getattr(self.feedforward, 'probe_w', None) or self.feedforward.probe
-        probe_fb = getattr(self.feedback, 'probe_w', None) or self.feedback.probe
+        probe_ff = getattr(self.feedforward, 'probe_w', None)
+        probe_fb = getattr(self.feedback, 'probe_w', None)
+        if probe_ff is None or probe_fb is None:
+            raise NotImplementedError("probe_w not implemented for feedforward or feedback branches")
         F = probe_ff(w, ext_param_ff)
         B = probe_fb(w, ext_param_fb)
         N = F.shape[0]
@@ -868,8 +871,16 @@
             **Returns**:
                 torch.Tensor: Transfer matrix (complex).
         """
-        H_A = self.branchA.probe(z, ext_param)
-        H_B = self.branchB.probe(z, ext_param)
+        ext_param_brA = None
+        ext_param_brB = None
+        if ext_param is not None:
+            for key, param in ext_param.items():
+                if 'branchA' in key:
+                    ext_param_brA = param
+                elif 'branchB' in key:
+                    ext_param_brB = param
+        H_A = self.branchA.probe(z, ext_param_brA)
+        H_B = self.branchB.probe(z, ext_param_brB)
         if self.sum_output:
             return H_A + H_B
         else:
This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.

Comment thread flamo/processor/system.py Outdated
Comment thread flamo/processor/system.py Outdated
Comment thread flamo/processor/system.py Outdated
Comment thread flamo/processor/dsp.py Outdated
Implement probe_w on parallelSOSFilter to evaluate H(1/w) (w = z^{-1}) using mapped coefficients and the alias envelope, returning a diagonal transfer matrix. Update Series.probe_w to prefer a module's probe_w and fall back to module.probe(1.0/w) for z-domain-only modules so the series result correctly represents H(1/w). This ensures correct w-domain probing for SOS blocks when used in series with delays or recursions.
In Recursion (flamo/processor/system.py) replaced explicit torch.log(torch.linalg.det(P)) calls with torch.logdet(P) for succinctness and potential numeric/performance benefits. Added probe_recursion_w(w, ext_param=None) to evaluate the characteristic matrix in the w-domain (P(w) = I - B(w) F(w)) and provide a stable alternative to calling probe_recursion(1/w) when modules expose probe_w. Also minor whitespace cleanup on a return line.
Remove the optional ext_param argument from probe and probe_w methods across the processor package and adapt callers accordingly. Probes now always use self.param (no external parameter routing), and Series/Parallel/Recursion/Shell call module.probe/probe_w without passing ext_param. Recursion's _split_ext_param helper and external-parameter routing were removed; characteristic evaluation and derivative helpers were updated to match the simplified API. This change consolidates probe behavior and simplifies probe call sites and implementations.
Replace the handwritten Wirtinger derivative reconstruction with PyTorch native complex autograd. Introduce _ensure_complex_tensor and rename/replace API: wirtinger_derivative -> complex_derivative and wirtinger_derivative_scalar -> complex_derivative_scalar, implemented using torch.autograd.grad + conj. Update probe_with_derivative and all imports/usages in system.py and tests to use the new helpers, and remove the manual real/imag gradient bookkeeping for improved simplicity and stability.
Remove built-in complex autograd derivative utilities and related APIs/tests. The probe module no longer exports complex_derivative, complex_derivative_scalar, probe_with_derivative, or internal _ensure_complex_tensor; only probe_points remains. Recursion.probe_recursion was simplified to always return P(z) (derivative handling removed) and docstrings updated to state that callers (e.g. pyFDN) should construct derivatives via torch.autograd.functional.jvp/grad. Tests that asserted/verified the removed derivative helpers and Recursion derivative behavior were deleted or updated to match the simplified API. Updated imports and docstrings across flamo/processor to reflect these API changes.
Remove private helpers _eval_characteristic and _eval_characteristic_w and inline their logic into probe_recursion and probe_recursion_w. Compute the characteristic matrix P = I - F @ B directly in the probe methods, update docstrings for clarity, and use probe_w fallback selection for numerical-stability probing in the w-plane. This refactor removes duplication and clarifies intent without changing runtime behavior.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Comment thread flamo/processor/system.py
B = self.feedback.probe(z)
N = F.shape[0]
I = torch.eye(N, dtype=F.dtype, device=F.device)
return I - F @ B

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Docstring claims wrong matrix multiplication order in probe

High Severity

The probe_recursion docstring states P(z) = I - B(z) F(z) and references "delays on columns," but the code actually computes I - F @ B (i.e., I - feedforward @ feedback). This is consistent with Recursion.forward and Recursion.probe (which both use I - F @ B), but the documented formula has the matrix multiplication order swapped. Since the docstring explicitly says callers (e.g., pyFDN) will use this for JVP/grad-based derivatives of P(z), the mismatch between documented and actual matrix structure will lead to incorrect results for any caller relying on the documented convention. The same issue appears in probe_recursion_w.

Additional Locations (1)

Fix in Cursor Fix in Web

Comment thread flamo/processor/system.py
B = self.feedback.probe(z)
N = F.shape[0]
I = torch.eye(N, dtype=F.dtype, device=F.device)
return I - F @ B

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused parameter include_shell_io in probe_recursion

Low Severity

probe_recursion accepts an include_shell_io parameter and **kwargs, but neither is used in the method body. A caller passing include_shell_io=True would expect behavior similar to Shell.probe, but the argument is silently ignored. This is misleading dead code.

Fix in Cursor Fix in Web

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants