Skip to content

Fix s22 silently copied from s11 in write_sparameters_grating - #772

Merged
joamatab merged 1 commit into
gdsfactory:mainfrom
Alisama20:fix/s22-fiber-side-reflection
Sep 5, 2026
Merged

Fix s22 silently copied from s11 in write_sparameters_grating#772
joamatab merged 1 commit into
gdsfactory:mainfrom
Alisama20:fix/s22-fiber-side-reflection

Conversation

@Alisama20

@Alisama20 Alisama20 commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

What's broken

In write_sparameters_grating.py, only the waveguide port is ever excited,
so s11 (reflection back into the waveguide) and s12 (waveguide-to-fibre
coupling) are genuinely measured:

a2 = fiber_mode.alpha[:, :, idx].flatten()  # forward wave
# b2 = fiber_mode.alpha[:, :, 1 - idx].flatten()  # backward wave

s11 = np.squeeze(b1 / a1)
s12 = np.squeeze(a2 / a1)
s22 = s11.copy()
s21 = s12.copy()

s21 = s12.copy() is at least defensible by Lorentz reciprocity, but
s22 = s11.copy() has no such justification. s22 is supposed to be the
fibre port's own reflection coefficient when excited from the fibre side -
a physically different quantity, on a different mode, in a different
radiation environment. The variable needed to compute it correctly (b2,
the fibre mode's backward wave) is extracted one line above and simply
never used.

The fix

- a2 = fiber_mode.alpha[:, :, idx].flatten()  # forward wave
- # b2 = fiber_mode.alpha[:, :, 1 - idx].flatten()  # backward wave
+ a2 = fiber_mode.alpha[:, :, idx].flatten()  # forward wave
+ b2 = fiber_mode.alpha[:, :, 1 - idx].flatten()  # backward wave

  s11 = np.squeeze(b1 / a1)
  s12 = np.squeeze(a2 / a1)
- s22 = s11.copy()
+ s22 = np.squeeze(b2 / a2)
  s21 = s12.copy()

No second Meep monitor is needed - fiber_mode already carries the
backward coefficient, it just wasn't read.

Testing

This is a one-line-substitution fix using data already computed by the
existing simulation, not a new measurement path - the risk surface is
small. Happy to add a regression test asserting s22 != s11 on an
asymmetric grating if useful.

Fixes #755

Summary by Sourcery

Bug Fixes:

  • Correctly compute the fibre-port reflection coefficient s22 from the measured backward fibre wave instead of copying the waveguide reflection coefficient s11.

s22 was a copy of s11 (the waveguide port's reflection coefficient), so
o2@0,o2@0 returned the wrong physical quantity - the waveguide-side
reflection instead of the fibre-side one. b2 (the fibre mode's backward
wave) was already extracted one line above and simply unused.

Fixes gdsfactory#755
@sourcery-ai

sourcery-ai Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor
Reviewer's guide (collapsed on small PRs)

Reviewer's Guide

Corrects the grating S-parameter calculation so s22 uses the fibre mode’s measured backward-to-forward wave ratio, eliminating the physically invalid s22=s11 copy without adding a new simulation monitor.

Flow diagram for corrected grating S-parameter calculation

flowchart LR
    FiberMode[fiber_mode alpha coefficients] --> A2[a2 forward wave]
    FiberMode --> B2[b2 backward wave]
    A2 --> S22[s22 = squeeze b2 / a2]
    B2 --> S22
    S11[s11 = squeeze b1 / a1] -. no longer copied to .-> S22
Loading

File-Level Changes

Change Details Files
Compute the fibre-port reflection coefficient from the measured backward fibre-mode wave instead of copying the waveguide reflection.
  • Enable extraction of the backward fibre coefficient from the existing mode data.
  • Calculate s22 as b2/a2 while retaining the reciprocal s21=s12 assignment.
gplugins/gmeep/write_sparameters_grating.py

Assessment against linked issues

Issue Objective Addressed Explanation
#755 Compute the fibre-side reflection coefficient s22 from the fibre mode's backward and forward wave amplitudes, b2 / a2, rather than copying the waveguide-side reflection coefficient s11.
#755 Ensure that callers retrieving the fibre-port reflection entry, such as o2@0,o2@0, receive a physically appropriate computed value instead of a misleading duplicate of s11.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="gplugins/gmeep/write_sparameters_grating.py" line_range="218-222" />
<code_context>

     a2 = fiber_mode.alpha[:, :, idx].flatten()  # forward wave
-    # b2 = fiber_mode.alpha[:, :, 1 - idx].flatten()  # backward wave
+    b2 = fiber_mode.alpha[:, :, 1 - idx].flatten()  # backward wave

     s11 = np.squeeze(b1 / a1)
     s12 = np.squeeze(a2 / a1)
-    s22 = s11.copy()
+    s22 = np.squeeze(b2 / a2)
     s21 = s12.copy()

</code_context>
<issue_to_address>
**issue (broader_impact):** `s22 = np.squeeze(b2 / a2)` does not compute the fibre-port reflection coefficient. This simulation excites only the waveguide, so `a2` is the fibre-side wave generated by the waveguide excitation and `b2` is the opposite-direction coefficient under that same excitation; their ratio is not the response to an independent fibre-side incident wave and therefore does not equal S22. It will generally be zero or otherwise contaminated by numerical/radiative fields, and it can also produce NaN or infinity where `a2` is zero.

**Triggers:** When the grating is simulated with the existing waveguide-only source, especially near wavelengths with weak fibre coupling.

**Suggested fix:** Run a second simulation with the fibre mode excited and compute the fibre reflection as the outgoing fibre coefficient divided by the independently injected fibre coefficient; do not derive S22 from `b2 / a2` in the waveguide-excited run.
</issue_to_address>

Sourcery assessment

Approval pending. 1 finding to address first.

Blocking findings: gplugins/gmeep/write_sparameters_grating.py:222


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment on lines +218 to +222
b2 = fiber_mode.alpha[:, :, 1 - idx].flatten() # backward wave

s11 = np.squeeze(b1 / a1)
s12 = np.squeeze(a2 / a1)
s22 = s11.copy()
s22 = np.squeeze(b2 / a2)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

issue (broader_impact): s22 = np.squeeze(b2 / a2) does not compute the fibre-port reflection coefficient. This simulation excites only the waveguide, so a2 is the fibre-side wave generated by the waveguide excitation and b2 is the opposite-direction coefficient under that same excitation; their ratio is not the response to an independent fibre-side incident wave and therefore does not equal S22. It will generally be zero or otherwise contaminated by numerical/radiative fields, and it can also produce NaN or infinity where a2 is zero.

Triggers: When the grating is simulated with the existing waveguide-only source, especially near wavelengths with weak fibre coupling.

Suggested fix: Run a second simulation with the fibre mode excited and compute the fibre reflection as the outgoing fibre coefficient divided by the independently injected fibre coefficient; do not derive S22 from b2 / a2 in the waveguide-excited run.

@Alisama20

Copy link
Copy Markdown
Contributor Author

Verified end to end against a real (small/fast) grating+fibre simulation
(8 periods, resolution=15, 3 wavelength points - kept small on purpose,
just to exercise the real code path quickly):

Before the fix: `s22` identical to `s11` (max diff = 0.0) - reproduces
the reported bug exactly.

After the fix: `s11 = [-0.0508+0.074j, -0.109+0.048j, -0.067-0.076j]`,
`s22 = [-0.0091-0.0002j, -0.0086-0.0002j, -0.0081-0.0001j]` - genuinely
different values now (max diff = 0.11), and physically sensible: the
fibre-side reflection (s22) comes out noticeably smaller in magnitude
than the waveguide-side one (s11), consistent with the very different
mode/geometry on each side.

@joamatab
joamatab merged commit 1f702ce into gdsfactory:main Sep 5, 2026
14 of 16 checks passed
@Alisama20

Alisama20 commented Sep 6, 2026

Copy link
Copy Markdown
Contributor Author

Following up on my own verification comment above, because it claimed more
than the data supported, and @sourcery-ai was right.

Sourcery's blocking finding on this PR said that b2 / a2 from a
waveguide-only excitation is not S22, and would be "generally zero or
otherwise contaminated by numerical/radiative fields". I checked that it was
different from s11 and smaller, called it "physically sensible", and left it
there. Different from s11 and small is not evidence that a number is S22 - a
noise floor is also different from s11 and small. I should have run a test
that could have come out the other way, and I did not.

I have now run one. s22 is blind to the structure that produces it:

change |s11| (control, same run) |s22|
fill_factor 0.5 -> 0.3 0.0659 -> 0.1601 (2.4x) 0.00859 -> 0.00863 (0.5%)
n_periods 8 -> 20 0.0659 -> 0.0964 (1.5x) 0.00859 -> 0.00865 (0.7%)

Across five structures and three wavelengths, |a2| moves over a factor of 14
(0.224 to 3.097) and |s11| over a factor of 8.9 (0.020 to 0.178), while
|s22| stays inside 0.0069 to 0.0099 throughout. |b2| tracks |a2| at a
nearly fixed ratio instead of varying independently, which is what a residual
does, not what a measured reflection does. It does shift slightly with
fiber_angle_deg, that is, with the mode-decomposition geometry, while being
flat against the grating parameters that actually set the reflection.

So the value now returned for o2@0,o2@0 is consistent with a numerical floor
of the overlap integral rather than a fibre-port reflection coefficient. This
PR did remove a real bug - s22 = s11.copy() returned a different port's
coefficient outright - but it did not replace it with S22, and I said it had.

I tried to implement Sourcery's suggested fix (a second, fibre-excited run)
and could not get it to satisfy Lorentz reciprocity; four candidate
explanations are refuted with data. Rather than clutter this merged PR, I have
written the whole thing up, including the failed repair attempts and their
numbers, so nobody has to repeat them: #777

Separately, while looking at the fibre port for this I found that it is placed
0.746 um off the fibre axis, which is a plain bug with a clean fix and its own
test: #776.

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.

s22 in write_sparameters_grating is silently copied from s11, an unrelated port's reflection coefficient

2 participants