Public analysis edition. Public derivative of the original study evidence. Hardware names and selected operational metadata are omitted. Numerical arrays and statistics are unchanged. Original content hashes and public file hashes are separate in public-manifest.json. These public summaries cannot replace original records in hash-bound replay. Reproduction needs fresh runs or the original matching records and raw outputs.
Broadband curved directional coupler¶
Two silicon waveguides exchange power through a curved coupling section and tangent straight sections. This local study reconstructs the 250 nm silicon device in Chen et al., Scientific Reports 7, 7246 (2017). The primary targets are both simulated output curves in Fig. 4(a).
Validation pending
Pairwise pass/fail is evidence, not a completed validation. Successive fine meshes, duration at the reporting mesh, and required controls must be audited together. This status comes from verified completed runs in the linked evidence. Rendering this page starts no simulation.
Download source bundle Paper reference v2 Historical reference v1 Recorded spectra and settings Cumulative execution bundles Convergence evidence Control evidence Build evidence
Device and setup¶
The waveguide widths are 0.4 and 0.4 µm, with a 0.2 µm gap and 0.25 µm silicon thickness. The center radius is 26 µm and the curved length is 8.1 µm. Each physical tangent straight is 1.8 µm. The paper's effective straight length is a model parameter, not the physical tangent length.
The figure uses execution mesh125_full and its original saved simulation input. It shows a hard-sampled permittivity slice; the simulation itself uses contour subpixel averaging. The field monitor, sources, and PML are included.

Lossless, constant indices are assumptions: silicon 3.48, oxide 1.444. Material dispersion is disabled. The model assumes vertical walls, homogeneous oxide to the boundaries, and ideal smooth guides. It omits fiber access tapers and fabrication roughness.
Recorded field¶
The saved electric-field magnitude at 1565 nm comes from execution mesh125_full, with a full-execution observation time of 2.000021 ps. This map uses the complete field recording; it is not a cut-observation field. It illustrates propagation and coupling; the amplitude colors do not measure transmission efficiency.

Transmission compared with Fig. 4(a)¶
For each arm, normalized power is its forward TE₀ output power divided by the
sum of both forward TE₀ outputs. The input-normalized transmitted power is
reported separately below. The paper's dB data is converted with
normalized_power = 10**(dB/10). Digitized values are not renormalized to sum
to one. Lines show PhotonHub; points show reference v2. The simulated curves are
the replication target; the measured curves provide experimental context.


Corrected reference v2: paper comparison¶
Version 2 corrects how the ends of the paper's dashed curves were extracted from the PDF, while preserving their bends, calibration and independent arm powers. The measured curves and the 0.05 dB extraction uncertainty are unchanged. No simulation input or output was changed. The plots above use v2.
The following statistics cover the selected full observations, using the versioned reassessment for CPU data and the verified conventional GPU analyses. Historical and source-control results remain in the downloadable records. The frozen paper criterion is 0.01 in both arms. A numerical pass would still require independent convergence.
| Full observation | Output | Maximum power difference, v2 | Worst wavelength (nm) | Decision |
|---|---|---|---|---|
| mesh30_windows_retry1 | bar | 0.0287141271 | 1530 | fail |
| mesh30_windows_retry1 | cross | 0.0285152117 | 1530 | fail |
| mesh25_full | bar | 0.0200854866 | 1550 | fail |
| mesh25_full | cross | 0.0195275770 | 1530 | fail |
| mesh20_full | bar | 0.0238826417 | 1530 | fail |
| mesh20_full | cross | 0.0236837263 | 1530 | fail |
| mesh125_full | bar | 0.0260293213 | 1620 | fail |
| mesh125_full | cross | 0.0281611545 | 1620 | fail |
The finest plotted mesh is 12.5 nm. Its largest paper gap is 2.8161154 percentage points, against the frozen 1 percentage-point limit.
Historical v1 metrics, preserved separately
Historical v1 comparison¶
These preserved v1 statistics come directly from the original comparison record. Simulation normalized power is linearly interpolated onto all 101 original paper samples at 1 nm intervals, then converted to dB. Paper dB values are compared as recorded. No paper-arm renormalization or extrapolation is applied. The line plots retain the saved simulation samples. A numerical paper-agreement pass would still require independent convergence before a replication claim.
| Run | Output | Maximum power difference | Worst wavelength (nm) | Maximum dB difference | Paper tolerance |
|---|---|---|---|---|---|
| 30 nm, full | bar | 0.030641 | 1525 | 0.2619 | fail |
| 30 nm, full | cross | 0.030969 | 1530 | 0.2877 | fail |
| 25 nm, full | bar | 0.021670 | 1525 | 0.1889 | fail |
| 25 nm, full | cross | 0.021981 | 1530 | 0.2022 | fail |
| 20 nm, full | bar | 0.026206 | 1525 | 0.2106 | fail |
| 20 nm, full | cross | 0.026137 | 1530 | 0.2416 | fail |
| 12.5 nm, full | bar | 0.027203 | 1616 | 0.2271 | fail |
| 12.5 nm, full | cross | 0.027691 | 1619 | 0.2545 | fail |
Settings and convergence evidence¶
Every run uses contour subpixel averaging, lossless nondispersive materials, and fixed-duration recording with automatic shutoff disabled. The mesh has uniform lateral cells and silicon-aligned vertical cells. PML is the absorbing boundary. The table reports the layer count actually present in the saved input.
| Plotted result | Lateral cell (nm) | Core z cell (nm) | Observation end (ps) | PML layers | PML thickness (µm) | z symmetry | Wavelengths |
|---|---|---|---|---|---|---|---|
| 30 nm, full | 30 | 25 | 2.000307 | 20 | 0.6 | (0, 0, 1) | 21 |
| 25 nm, full | 25 | 25 | 2.000045 | 24 | 0.6 | (0, 0, 1) | 21 |
| 20 nm, full | 20 | 17.8571 | 2.000010 | 30 | 0.6 | (0, 0, 1) | 21 |
| 12.5 nm, full | 12.5 | 12.5 | 2.000021 | 48 | 0.6 | (0, 0, 1) | 21 |
Cumulative observations from one execution¶
Each cut and full result below is an observation of the same completed FDTD trajectory. The spectrum uses fields accumulated through the included step. Observation times are separate from the full execution duration and runtime. The nearby-cut observation in the evidence measures endpoint sensitivity.
| Plotted result | Execution | Observation | Requested end (ps) | Actual end (ps) | Included step |
|---|---|---|---|---|---|
| 30 nm, full | mesh30_windows_retry1 | full | 2 | 2.000307440 | 37449 |
The pre-run mesh stop rule is a maximum absolute change of 0.005 in either normalized output over 1520–1620 nm between successive finest meshes. Paper agreement requires a maximum absolute difference of 0.01 for both simulated curves after convergence. Required controls remain separate.
The selected full observations have verified mesh comparisons in the conventional GPU section below. Spatial registration and final-time sampling qualifications remain part of those comparisons.
Required control studies¶
Statuses below are taken from the verified control record. They cover the completed evidence available to that record, including controls not plotted above. An executed control or a passing pair does not imply a global pass.
| Study | Overall status | Recorded single-control comparisons |
|---|---|---|
| duration | pending | 3 |
| readout window | pending | 0 |
| padding | pending | 0 |
| pml | pending | 0 |
| symmetry | pending | 1 |
| polygon sampling | pending | 0 |
| material index | pending | 0 |
Coarse symmetry control¶
The completed 100 nm comparison changes from a vertical symmetry plane to the full vertical domain at the same 2 ps duration. It compares both outputs across the full saved wavelength band.
| Comparison | Maximum arm change | Worst wavelength (nm) | Declared limit | Decision |
|---|---|---|---|---|
| time100_20 → sym100_20 | 0.02208381695 | 1610 | 1e-05 | fail |
The source and readout modes were solved independently in finite windows. Their profiles differ, so this failure does not isolate the solver's symmetry behavior. It remains a failed control for this complete simulation procedure.
Settings: 62.5 nm core cells vertically, 8 PML layers with 0.8 µm realized thickness, contour subpixel averaging, and lossless constant silicon/oxide indices 3.48/1.444. Dispersion is disabled. This coarse control does not establish convergence on finer meshes.
Setup diagnostic: matching execution evidence is complete. The original setup record retains its preparation-time status; the completed transmission decision comes from the comparison above. Its mode-profile residuals do not bound transmission error.
Symmetry with matched source profiles¶
These completed 100 nm diagnostics reflect the original source profiles into the full vertical domain and reuse the original half-domain readout modes. They keep the preceding control's grid, material indices and 2 ps duration. The original half-domain and ordinary full-domain readouts replay exactly.
| Diagnostic | Maximum arm change | Worst wavelength (nm) | Declared limit | Decision |
|---|---|---|---|---|
| matchedsym100_20 | 0.0219743 | 1610 | 1e-05 | fail |
| matchedsym100_nodecaps20 | 4.60743e-15 | 1545 | 1e-05 | pass |
matchedsym100_nodecaps20 evidence.
Changing only four serialized slab-bound coordinate values restores the 100 nm CPU transmission agreement with the half-domain result. The two guides' bounds change from [1.8000000000000007, 2.0500000000000007] µm to [1.8, 2.05] µm, exactly on the existing grid nodes. All other saved input values are unchanged from the failed matched-source execution. Independent readout replay is exact. This demonstrates the effect of that coordinate representation for this setup; the earlier failures remain recorded and the 30 nm paper comparison still fails.
Physical readouts remain finite. The diagnostic odd-field powers are zero, so ratios normalized by their zero incident power are stored as null. Those undefined diagnostic ratios are excluded from physical transmission comparisons.
Material representation: setup comparison¶
A separate native-library setup probe places the serialized silicon slab bounds exactly on existing grid nodes. For this 100 nm setup, all six comparisons of Ex, Ey and Ez permittivity have zero unequal cells: the upper half matches the half-domain setup, and each full-domain mirror pair matches. This is a comparison of stored material values, without propagation.
The exact library bytes used by the probe are not proven to be the bytes linked into the earlier solver executable. That limitation applies to the setup probe. The separately completed FDTD comparison above tests the effect of changing only the serialized slab bounds using the recorded CPU solver.
Material setup evidence. FDTD finding: cap-only effect verified for the 100 nm CPU setup. Original and corrected inputs, all component comparisons and file hashes are retained.
Padding material comparison¶
The 30 nm material setup was compared with a +0.21 µm aligned padding change in y and z. The cropped comparison covers the material region and verifies the omitted background. Exact equality fails at three sites:
| Component | Compared sites | Unequal sites | Maximum permittivity difference |
|---|---|---|---|
| Ex | 2,442,496 | 0 | 0 |
| Ey | 2,442,496 | 1 | 4.76837e-07 |
| Ez | 2,442,496 | 2 | 2.38419e-07 |
The recorded nonzero maxima are one step between adjacent 32-bit floating-point values. This does not bound transmission error or establish padding convergence. A propagation comparison would include both the boundary change and this small material representation change. The library-to-solver linkage remains unproved. Padding setup evidence.
Source-profile analysis¶
This analysis reconstructs the source currents using the completed 40 nm input. It does not propagate fields through the coupler or test the change in either output. This source-profile analysis is not a propagation-control pass.
Using 21 frequency-specific mode profiles, the worst relative spatial errors for electric and magnetic source currents are 0.00832291308 and 0.015388253, both at 1620 nm. The source pulse ends at 77.148784 fs. These source-current errors do not bound normalized output power. The main profile and carrier metrics reproduce the independent analysis; two auxiliary bounds for rounding currents to 32-bit values remain unresolved. Source analysis; independent observed values.
Completed endpoint source control¶
The source mode and Gaussian carrier were changed together to 1520 nm on the unchanged 30 nm device. This control uses its own incident modal power and the completed full observation. Its decision applies only at its source endpoint. The 1620 nm companion remains prepared, without a result.
| Control | Endpoint (nm) | Maximum endpoint arm change | Limit | Decision |
|---|---|---|---|---|
| source30_1520_run2 | 1520 | 3.31365e-06 | 0.001 | pass |
The full-band source-perturbation diagnostic is 1.54514e-05 at 1560 nm; it is not a full-band source-error bound. All seven baseline power arrays replay exactly. The original execution used 6 CPU threads and 6239.143 s of driver wall time. Only the full unit-weight observation is interpreted; no six-thread cutoff certificate is claimed. Endpoint-control evidence. Its v2 paper comparison appears above and remains a failure; v1 remains in the historical record.
Straight-pair modes and material sensitivity¶
This separate study solves modes of two straight 400 × 250 nm silicon guides with a 200 nm gap. It is not a simulation of the curved coupler. Fixed indices are 3.48/1.444, contour subpixel averaging is enabled, and the vertical TE symmetry is retained. Mesh refinement and expanded mode windows pass their declared final comparisons at 1520, 1570 and 1620 nm.
The final mesh is 6.25 nm, with mode-window half-width/height 1.8/1.4 µm. At 1570 nm the effective-index splitting is 0.0491869019. The final mesh limit is 0.002 relative splitting change; the window limit is 0.0001. The record also checks absolute mode-index changes and mode identity. Independent review reproduced all 21 canonical inputs and the record arithmetic, without rerunning eigenvectors. Mode-study evidence.
| Wavelength (nm) | Relative splitting change with independent bulk materials |
|---|---|
| 1520 | -0.199% |
| 1570 | +0.712% |
| 1620 | +1.482% |
The bulk-material assumptions are independently sourced, not fitted to the coupler. This sensitivity has no normalized-output error bound or separate material-model mesh proof. The Fig. 2 graphical extraction remains qualitative: unknown curved-mode overlap factors prevent an exact mode-index target.
Conventional GPU full observations¶
The plotted results are 30 nm, full, 25 nm, full, 20 nm, full, 12.5 nm, full. The following completed conventional GPU observations use full, unit-weight recording. They make no cumulative cutoff claim. Geometry, constant material indices and the frozen criteria are unchanged; no parameter is fitted to the paper. Overall validation remains pending.
| Completed observation | Maximum v2 bar error | Maximum v2 cross error | Paper decision | Full endpoint (ps) | Steps | Accumulation cadence | Extra terminal sample |
|---|---|---|---|---|---|---|---|
| 25 nm, full | 0.0200854866 | 0.0195275770 | fail | 2.000044702510 | 41,961 | 10 | yes |
| 20 nm, full | 0.0238826417 | 0.0236837263 | fail | 2.000010484827 | 54,629 | 13 | yes |
| 12.5 nm, full | 0.0260293213 | 0.0281611545 | fail | 2.000020870324 | 83,921 | 20 | yes |
Full-observation mesh comparisons¶
A numerical tolerance result is separate from the remaining physical and time-sampling qualifications. The earlier 40 → 30 nm result remains in the historical report; it does not cancel later failed mesh comparisons.
| Comparison | Maximum normalized-arm change | Limit | Numerical decision |
|---|---|---|---|
| 30 nm, full → 25 nm, full | 0.00909177485 | 0.005 | fail |
| 25 nm, full → 20 nm, full | 0.01188225994 | 0.005 | fail |
| 25 nm, full → 12.5 nm, full | 0.01786059643 | 0.005 | fail |
| 20 nm, full → 12.5 nm, full | 0.00687913469 | 0.005 | fail |
30 nm, full → 25 nm, full: Mesh, source/readout registration, final-time sampling and backend differ; qualified backend anchor is separate. Historical 40 → 30 nm acceptance is unchanged.
25 nm, full → 20 nm, full: Full conventional observations. Spatial registration and final-time sampling outside the regular cadence differ. The backend comparison is a separate prerequisite, not a cumulative certificate.
25 nm, full → 12.5 nm, full: Full conventional observations. Spatial registration and final-time sampling outside the regular cadence differ. The backend comparison is a separate prerequisite, not a cumulative certificate.
20 nm, full → 12.5 nm, full: Full conventional observations. Spatial registration and final-time sampling outside the regular cadence differ. The backend comparison is a separate prerequisite, not a cumulative certificate.
The 12.5 nm result belongs to the completed retry. The first attempt was skipped by a runtime guard; the second ended during setup without a completed result. These attempts remain distinct in the execution record. The retry reused the qualified binary after fresh device/runtime and focused-test checks; no new 40 nm propagation was performed. The 25 → 12.5 nm comparison tests aligned meshes; it and the adjacent 20 → 12.5 nm comparison both fail the frozen mesh limit.
Independent saved-field replay passed: all nine arrays match exactly numerically, with zero drift in 16 reference-statistics blocks and 14 mesh-statistics blocks. The auditor checked 23 frozen input/source hashes and 22 execution hashes before and after replay. This verifies the saved-data analysis and provenance; paper agreement and mesh convergence still fail. Independent replay receipt.
The separate 40 nm GPU check built from saved source passes: maximum normalized power, transmission or reflection change 6.31988e-08, against 1e-5. The CPU binary reports a modified source tree, so its exact build-time source linkage remains unproved. This check covers the saved 40 nm case and does not certify GPU cumulative cutoffs. The earlier managed-GPU comparison using a different build remains a failure. Backend evidence.
Boundary controls: declared before execution¶
The boundary plan declares a limit of 0.001 maximum absolute normalized-arm change across 1520–1620 nm for padding and PML studies. Each study changes one setting on the eventual reporting mesh with the same geometry, materials, source, readout and duration. Its status is planned not executed; declaring a limit does not establish a pass.
The limit is one tenth of the paper-agreement criterion and equals the duration criterion. Padding tests truncation within homogeneous oxide. It does not test the omitted finite buried oxide or overcladding interfaces, and the plan sets no acceptance limit for material-interface sensitivity.
Saved-field readout checks¶
These checks re-project the same saved fields with larger modal readout windows. They leave the source and FDTD execution unchanged. A pass applies to the stated readout window and mesh; it does not establish source-width, time, or mesh convergence.
| Run | Half-width (µm) | Half-height (µm) | Maximum normalized-arm change | Tolerance | Scoped decision |
|---|---|---|---|---|---|
| mesh30_windows_retry1 | 0.8 → 1 | 0.8 → 1 | 1.25710674e-05 | 0.0001 | pass |
| mesh50_16 | 0.8 → 1 | 0.8 → 1 | 1.87183349e-05 | 0.0001 | pass |
mesh30_windows_retry1: 30 nm full-observation saved-field readout-window sensitivity only. Both readout halfwidth and halfheight change together. Source width, launch, other meshes, and other controls are not tested by this replay. Control JSON. The baseline readout reproduces the original normalized and input-normalized arrays exactly.
mesh50_16: Saved-field modal readout/window truncation only; source-width convergence and other meshes remain untested. Control JSON. The baseline readout reproduces the original normalized and input-normalized arrays exactly.
Straight-guide control straight100_12: execution pass; tolerance decision pending. Maximum |T − 1| is 0.001117; T at 1565 nm is 1.000560. No pre-run straight-guide numerical tolerance was declared; measured bias is reported without inventing one.
Control scope still open: source frequency and mode-source configuration sensitivity; finite BOX and overcladding interfaces. The completed 1520 nm source test above covers that endpoint; the remaining source checks are pending.
Padding in homogeneous oxide tests boundary truncation; it does not test omitted material interfaces. Material sensitivities need physical interpretation, not a small-drift assumption.
Guided power balance near the source center¶
The table uses the saved sample nearest each run's actual source center (1565 nm for this device), and states both wavelengths. These powers are normalized to the incident TE₀ mode. Unaccounted power is one minus the two guided outputs and both backward input-guide powers. Radiation is not directly integrated, so the remainder is not a measured radiation loss. A normalized split near one half can coexist with low total transmission.
| Plotted result | Source center (nm) | Saved sample (nm) | Bar + cross | Both backward inputs | Unaccounted |
|---|---|---|---|---|---|
| 30 nm, full | 1565 | 1565 | 0.999025 | 0.000024 | 0.000951 |
| 25 nm, full | 1565 | 1565 | 0.999093 | 0.000060 | 0.000846 |
| 20 nm, full | 1565 | 1565 | 0.999307 | 0.000042 | 0.000651 |
| 12.5 nm, full | 1565 | 1565 | 0.999296 | 0.000011 | 0.000693 |
Full executions: runtime and provenance¶
Each execution appears once. Cut and full observations share this complete execution metadata; their runtime is not counted again. Solver stepping time and original driver wall time are distinct from later analysis time.
| Execution | Full duration (ps) | Steps | Solver setup (s) | Solver stepping (s) | Original driver wall (s) | Solver version / commit / backend |
|---|---|---|---|---|---|---|
| mesh30_windows_retry1 | 2.000307440 | 37449 | 378.57 | 7444.64 | 7823.94 | 0.1.3 / 71c57d742dad-dirty / cpu |
| mesh25_full | 2.000044703 | 41961 | 153.13 | 59.41 | unreported | 0.1.3 / 71c57d742dad8efcdeaaa6676485e1de67a1c805 / gpu |
| mesh20_full | 2.000010485 | 54629 | 294.21 | 139.26 | unreported | 0.1.3 / 71c57d742dad8efcdeaaa6676485e1de67a1c805 / gpu |
| mesh125_full | 2.000020870 | 83921 | 967.11 | 748.92 | unreported | 0.1.3 / 71c57d742dad8efcdeaaa6676485e1de67a1c805 / gpu |
Reproduce and adapt¶
The source ZIP includes the current Python driver, PaperSpec, reference JSON,
report.py, this renderer, and the readout replay script. For cumulative
executions it also includes cumulative_run.py and time_windows.py.
The symmetry setup, source-profile replay and source-record assembly scripts
are included with their records and independent comparison values. The boundary
plan is a declared input, installed at its usual path. Historical spectra,
control results, and report records are included as public summaries where hardware metadata was omitted under the
example's recorded-results/ archive, separate from the live results/,
cumulative_results/, readout_controls/, window_controls/,
symmetry_controls/, and source_controls/ folders.
The bundle manifest
maps each archived file to its original location and records both original and public hashes.
Run labels and numerical arrays are unchanged. Public summaries cannot replace original provenance records in hash-bound replay.
For new runs, unpack the ZIP into a fresh PhotonHub source checkout where
these run labels have no existing results or raw outputs. Use the checkout's
installed SDK and a compatible CPU solver at build/phsolver. Geometry
parameters appear in the PaperSpec; the commands below state every run option.
The run commands start new local CPU simulations. Review an estimate first by
changing run to estimate. Saved outputs go under artifacts/ and compact
spectra under the driver's results/ folder.
The delivered HTML site can be viewed on its own. Rebuilding its plots or
regenerating the evidence also requires the corresponding raw simulation
outputs, including raw/sim.json, raw/manifest.json, and field arrays.
Those raw outputs are not in the source ZIP. In an existing working checkout,
retain its live result and control folders plus the artifacts/ evidence and
use only the report and page commands below. Existing run labels are immutable;
do not rerun their FDTD commands or replace their evidence with downloaded files.
In a fresh checkout, the bundled reproduction commands
start every completed ordinary run, prepare and check the symmetry inputs,
verify cumulative cutoffs, run the cumulative execution once, and recompute
the readout and source-profile analyses in dependency order. Source replay
requires the audited input and implementation hashes to match; a changed
checkout must be investigated rather than bypassing that check. New
outputs populate the canonical live folders, while the downloaded public records
remain in recorded-results/. This performs additional local computation.
The source ZIP alone does not supply the original raw fields.
The additional reproduction notes cover v2 digitization, straight-pair modes and the endpoint control's pinned-baseline limit. The main script does not reproduce every archived diagnostic. In particular, the endpoint driver has no supported new-plan/launch interface; its old plan must not be silently rebound to freshly generated baseline results. The endpoint-dependent v2 reassessment requires a verified matching source control before it can be regenerated. The delivered snapshot already contains the reviewed historical evidence and keeps those limitations explicit.
The script regenerates the evidence before the 30 nm readout replay, which uses the completed cumulative full observation and its frozen readout criterion. Its newly emitted JSON becomes a live control record; downloaded pass metadata is never substituted for a replay. The 50 nm replay is unchanged. Matched-source reproduction records the new process's actual return code before analyzing its saved fields.
Cumulative results require the bundled cutoff-equivalence control first. It uses the completed 100 nm duration runs and starts one additional local CPU execution:
OMP_NUM_THREADS=2 OPENBLAS_NUM_THREADS=2 .venv/bin/python benchmarks/replicate/chen2017_curved_coupler/time_windows.py run --label windows100_20
One execution for mesh30_windows_retry1¶
Prepare its base input, run the full trajectory once, then analyze its observations. Preparation and analysis include local mode solves. The cut and full plots do not require separate executions.
OMP_NUM_THREADS=2 .venv/bin/python benchmarks/replicate/chen2017_curved_coupler/run.py prepare --label prepare30t20 --dl 0.03 --time-ps 2.0 --n-freqs 21 --input-port bar --field --padding 1.0 --pml 0.6 --mode-half-width 0.8 --mode-half-height 0.8 --n-si 3.48 --n-oxide 1.444 --straight-bias 0.0 --polygon-step 0.02 --timeout 21600.0
OMP_NUM_THREADS=2 OPENBLAS_NUM_THREADS=2 .venv/bin/python benchmarks/replicate/chen2017_curved_coupler/cumulative_run.py run --label mesh30_windows_retry1 --prepared prepare30t20 --cut-ps 1.6 --full-ps 2 --timeout 21600
OMP_NUM_THREADS=2 OPENBLAS_NUM_THREADS=2 .venv/bin/python benchmarks/replicate/chen2017_curved_coupler/cumulative_run.py analyze --label mesh30_windows_retry1
After reproducing the baseline runs in a fresh checkout, regenerate the readout-window control. This reuses saved fields and performs local mode solves, but no new FDTD:
Inspect readout replay commands
.venv/bin/python benchmarks/replicate/chen2017_curved_coupler/report.py
test ! -e benchmarks/replicate/chen2017_curved_coupler/readout_controls/mesh30_windows_retry1_windows.json
test ! -e artifacts/chen2017_curved_coupler/readout30_windows_replay.json
OMP_NUM_THREADS=2 OPENBLAS_NUM_THREADS=2 .venv/bin/python benchmarks/replicate/chen2017_curved_coupler/readout30_windows.py > artifacts/chen2017_curved_coupler/readout30_windows_replay.json
.venv/bin/python - <<'PY'
import json
from pathlib import Path
record = json.loads(Path('artifacts/chen2017_curved_coupler/readout30_windows_replay.json').read_text())
assert record['label'] == 'mesh30_windows_retry1' and record['observation'] == 'full'
assert record['new_fdtd_runs'] == 0 and all(record['baseline_exact_by_array'].values())
target = Path('benchmarks/replicate/chen2017_curved_coupler/readout_controls/mesh30_windows_retry1_windows.json')
target.parent.mkdir(parents=True, exist_ok=True)
with target.open('x') as stream:
stream.write(json.dumps(record, indent=2, allow_nan=False) + '\n')
PY
OMP_NUM_THREADS=2 OPENBLAS_NUM_THREADS=2 .venv/bin/python benchmarks/replicate/chen2017_curved_coupler/readout_windows.py
The source bundle also preserves the material-probe scripts and their historical
records. These setup-only tools require the original raw inputs, a compatible
existing build/libphcore.a, the repository headers and the macOS arm64 C++/
OpenMP toolchain specified in the records. They are not run by the main
reproduction script. Their library-to-solver linkage limitation still applies.
New probe output must be inspected and assembled into fresh material evidence;
the archived material decision must not be copied into the live control folder.
The corrected-cap propagation result is included as archived evidence, with
its driver and setup sources. Its platform-specific prerequisite audit is
outside the main reproduction script. Reproducing this control requires a new
successful setup audit, a new execution through nodecaps_run.py, recording
that command's actual exit, and analyze_symmetry.py --label for the new run.
Do not substitute matched_symmetry.py for its corrected input or install the
archived pass as a fresh result.
The reproduction script also prepares and checks the full-domain symmetry input, then rebuilds the source-profile record from new replay output and the archived independent values. These diagnostic steps include local mode solves but no additional FDTD:
Inspect diagnostic reproduction commands
test ! -e artifacts/chen2017_curved_coupler/prepare100fullz20/preflight.json
test ! -e benchmarks/replicate/chen2017_curved_coupler/symmetry_controls/setup100fullz20.json
OMP_NUM_THREADS=2 .venv/bin/python benchmarks/replicate/chen2017_curved_coupler/run.py prepare --label prepare100fullz20 --dl 0.1 --time-ps 2.0 --n-freqs 21 --input-port bar --full-z --field --padding 1.0 --pml 0.6 --mode-half-width 0.8 --mode-half-height 0.8 --n-si 3.48 --n-oxide 1.444 --straight-bias 0.0 --polygon-step 0.02 --timeout 7200
build/phsolver validate artifacts/chen2017_curved_coupler/prepare100fullz20/sim.json > artifacts/chen2017_curved_coupler/prepare100fullz20/native_validation.json
OMP_NUM_THREADS=2 OPENBLAS_NUM_THREADS=2 .venv/bin/python benchmarks/replicate/chen2017_curved_coupler/symmetry_setup.py
mkdir -p benchmarks/replicate/chen2017_curved_coupler/source_controls
test ! -e benchmarks/replicate/chen2017_curved_coupler/source_controls/profile40.json
test ! -e artifacts/chen2017_curved_coupler/source_profile40_replay.json
OMP_NUM_THREADS=2 OPENBLAS_NUM_THREADS=2 .venv/bin/python benchmarks/replicate/chen2017_curved_coupler/source_profile_replay.py > artifacts/chen2017_curved_coupler/source_profile40_replay.json
.venv/bin/python benchmarks/replicate/chen2017_curved_coupler/assemble_source_profile.py --replay artifacts/chen2017_curved_coupler/source_profile40_replay.json --independent benchmarks/replicate/chen2017_curved_coupler/recorded-results/source_controls/independent_observed_values.json --output benchmarks/replicate/chen2017_curved_coupler/source_controls/profile40.json --rtol 1e-9 --atol 1e-12
After the saved runs finish, refresh the evidence, then rebuild the page. These two commands use saved data and do not run FDTD or mode solves:
.venv/bin/python benchmarks/replicate/chen2017_curved_coupler/report.py
.venv/bin/python benchmarks/replicate/chen2017_curved_coupler/build_html.py --runs '30 nm, full=cumulative_results/mesh30_windows_retry1.json#full' '25 nm, full=gpu_results/mesh25_full.json' '20 nm, full=gpu_results/mesh20_full.json' '12.5 nm, full=gpu_results/mesh125_full.json'
To adapt this study, change the PaperSpec's gap or coupling length and use a
new run label. Refine lateral mesh size with --dl, extend duration with
--time-ps, or test the full vertical domain with --full-z. Compare both
outputs and the guided power balance after every change.
Inspect the complete Python driver
"""Local CPU 3D FDTD replication of Chen et al. 2017, Fig. 4.
Run from repository root. All lengths are micrometres unless named otherwise.
Example: OMP_NUM_THREADS=2 .venv/bin/python benchmarks/replicate/chen2017_curved_coupler/run.py run --label coarse100 --dl .1
"""
from __future__ import annotations
import argparse
from dataclasses import asdict
import hashlib
import json
import math
from pathlib import Path
import subprocess
import time
import numpy as np
import photonhub as ph
from photonhub.analysis import mode_launch, mode_monitor, solve_mode_on_cross_section
from photonhub.replicate.spec import PaperSpec
HERE = Path(__file__).resolve().parent
SPEC_PATH = HERE.parent/"specs/chen2017_curved_coupler.yaml"
SPEC = PaperSpec.from_yaml(SPEC_PATH)
C0 = 299792458.
def require_fresh_output(out, result_path, action):
"""Reject an existing run before any preparation can replace its evidence."""
if action!="analyze" and any(p.exists() for p in (
result_path,out/"raw/manifest.json",out/"raw/sim.json")):
raise FileExistsError("This label already contains run evidence. Use a new --label; use analyze only to reprocess saved raw data.")
def arc(point, heading, change, radius, step):
"""Points/headings of a circular centreline turning by signed change."""
angles = np.linspace(heading, heading+change, max(3, math.ceil(abs(change)*radius/step)+1))
sign = np.sign(change)
xy = np.column_stack((radius/sign*(np.sin(angles)-math.sin(heading)),
radius/sign*(math.cos(heading)-np.cos(angles)))) + point
return xy, angles
def right_path(radius, p):
alpha = p["length_curved_um"]/(2*p["radius_center_um"])
step = p["polygon_step_um"]
r2 = p["radius_center_um"]-(p["width2_um"]+p["gap_um"])/2
theta = np.linspace(0, alpha, math.ceil(radius*alpha/step)+1)
xy = np.column_stack((radius*np.sin(theta),radius*np.cos(theta)-p["radius_center_um"]))
heading = -theta
end = xy[-1] + p["length_straight_um"]*np.array([math.cos(alpha),-math.sin(alpha)])
xy=np.vstack((xy,end)); heading=np.r_[heading,-alpha]
turns = [alpha] if radius>p["radius_center_um"] else [-alpha*p["beta_over_alpha"],alpha*(1+p["beta_over_alpha"])]
for change in turns:
section, hh = arc(xy[-1],heading[-1],change,r2,step)
xy=np.vstack((xy,section[1:]));heading=np.r_[heading,hh[1:]]
return xy,heading
def build(a):
p=dict(SPEC.device.params)
p["n_si"]=a.n_si; p["n_oxide"]=a.n_oxide
p["length_straight_um"]+=a.straight_bias
p["polygon_step_um"]=a.polygon_step
r1=p["radius_center_um"]+(p["width1_um"]+p["gap_um"])/2
r2=p["radius_center_um"]-(p["width2_um"]+p["gap_um"])/2
right={"bar":right_path(r1,p),"cross":right_path(r2,p)}
dl=a.dl
snap=lambda v: math.ceil(v/dl-1e-9)*dl
layers=max(8,math.ceil(a.pml/dl));pml=layers*dl
clear=a.padding
xmax=max(xy[-1,0] for xy,_ in right.values())
ymin=min(np.min(xy[:,1]) for xy,_ in right.values())-.2
ymax=max(np.max(xy[:,1]) for xy,_ in right.values())+.2
xc=snap(xmax+pml+1.6)
yc=snap(-ymin+clear+pml)
sx=2*xc;sy=snap(yc+ymax+clear+pml)
h=SPEC.stack.layers[0].thickness_um
core_dz=h/(2*math.ceil(h/(2*dl)))
halfsteps=[core_dz]*math.ceil(h/(2*dl))+[dl]*math.ceil((clear+pml)/dl)
zz=np.r_[0.,np.cumsum(halfsteps)]
if a.full_z:
zc=float(zz[-1]);z=np.r_[zc-zz[:0:-1],zc+zz];sym=(0,0,0)
else:
zc=0.;z=zz;sym=(0,0,1)
zcoords=tuple(float(round(v,12)) for v in z[:-1]);sz=zcoords[-1]+dl
si=ph.Medium(permittivity=a.n_si**2)
def polygon(xy):
return ph.Structure(medium=si,geometry=ph.Polygon(axis="z",
vertices_um=tuple(map(tuple,xy)),slab_bounds_um=(zc-h/2,zc+h/2)))
structures=[]
ports={}
for name,(half,hh) in right.items():
width=p["width1_um"] if name=="bar" else p["width2_um"]
xy=np.vstack((half[:0:-1]*[-1,1],half))
headings=np.r_[-hh[:0:-1],hh]
xy=np.vstack(([-xc-dl,xy[0,1]],xy,[xc+dl,xy[-1,1]]))
headings=np.r_[0,headings,0]
normal=np.column_stack((-np.sin(headings),np.cos(headings)))
outline=np.vstack((xy+normal*width/2,(xy-normal*width/2)[::-1]))+[xc,yc]
structures.append(polygon(outline))
ports[name]=float(yc+half[-1,1])
if a.straight:
yy=ports[a.input_port]
structures=[polygon(np.array([[-dl,yy-.2],[sx+dl,yy-.2],[sx+dl,yy+.2],[-dl,yy+.2]]))]
ports={"through":yy}
wavelengths=np.linspace(*SPEC.optical.band_nm,a.n_freqs)/1000 if a.n_freqs>1 else np.array([SPEC.optical.center_nm/1000])
f0=C0/(SPEC.optical.center_nm*1e-9)
freqs=tuple(C0/(wavelengths*1e-6))
sim=ph.Simulation(size_um=(sx,sy,sz),
grid=ph.GradedMesh(dl_um=dl,coords=ph.GradedMeshAxis(z=zcoords)),
structures=tuple(structures),background=ph.Background(permittivity=a.n_oxide**2),
boundaries=ph.Boundaries(x="pml",y="pml",z="pml"),symmetry=sym,
pml_num_layers=layers,subpixel=True,subpixel_method="contour",
run=ph.RunSpec(run_time_s=a.time_ps*1e-12,shutoff=0))
xsrc=pml+.4;xin=pml+1.0;xout=sx-pml-.7
cy=ports["through"] if a.straight else ports[a.input_port]
layout=dict(xcenter_um=xc,yshift_um=yc,zcenter_um=zc,alpha_rad=p["length_curved_um"]/(2*p["radius_center_um"]),
R1_um=r1,R2_um=r2,port_y_um=ports,source_x_um=xsrc,input_x_um=xin,output_x_um=xout,
core_z_cell_um=core_dz,core_height_um=h,full_device_span_um=2*xmax,
full_device_height_um=ymax-ymin,physical_pml_um=pml,
source_mode_center_y_um=cy,parameters=p)
if a.action=="estimate":
return sim,{},layout,wavelengths,freqs
options=dict(half_w_um=a.mode_half_width,half_v_um=a.mode_half_height,
v_center_um=zc,dl_um=dl)
if cy-a.mode_half_width<=pml or cy+a.mode_half_width>=sy-pml:
raise ValueError("Input mode window touches PML; increase padding")
mode=solve_mode_on_cross_section(sim,"x",xsrc,SPEC.optical.center_nm/1000,"TE",0,h_center_um=cy,**options)
layout.update(n_eff=float(mode.n_eff),te_fraction=float(mode.te_fraction))
sources=mode_launch(sim,mode,axis="x",position_um=xsrc,direction="+",launch="eq_current",
source_time=ph.GaussianPulse(freq0_hz=f0,fwidth_hz=.14*f0))
monitors={"in":mode_monitor(sim,mode,axis="x",position_um=xin,name="in",freqs_hz=freqs,thickness_axis="z")}
for name,yy in ports.items():
out_mode=solve_mode_on_cross_section(sim,"x",xout,SPEC.optical.center_nm/1000,"TE",0,h_center_um=yy,**options)
monitors[name]=mode_monitor(sim,out_mode,axis="x",position_um=xout,name="out",freqs_hz=freqs,thickness_axis="z")
if not a.straight:
other="cross" if a.input_port=="bar" else "bar"
other_mode=solve_mode_on_cross_section(sim,"x",xin,SPEC.optical.center_nm/1000,"TE",0,h_center_um=ports[other],**options)
monitors["unused_in"]=mode_monitor(sim,other_mode,axis="x",position_um=xin,name="in",freqs_hz=freqs,thickness_axis="z")
field_mon={m.name:m.field_monitor for m in monitors.values()}
if a.field:
field_mon["field"]=ph.ProfileMonitor(name="field",center_um=(sx/2,sy/2,zc+core_dz/4),
size_um=(sx-1e-6,sy-1e-6,0),freqs_hz=(f0,),fields=("Ex","Ey","Ez"))
sim=sim.model_copy(update={"sources":tuple(sources),"monitors":tuple(field_mon.values())})
sim=ph.Simulation.from_wire_json(sim.to_wire_json())
return sim,monitors,layout,wavelengths,freqs
def main():
parser=argparse.ArgumentParser(description=__doc__)
parser.add_argument("action",choices=["estimate","prepare","run","analyze"])
parser.add_argument("--label",required=True)
parser.add_argument("--dl",type=float,default=.1)
parser.add_argument("--time-ps",type=float,default=.8)
parser.add_argument("--n-freqs",type=int,default=21)
parser.add_argument("--straight",action="store_true")
parser.add_argument("--input-port",choices=["bar","cross"],default="bar")
parser.add_argument("--full-z",action="store_true")
parser.add_argument("--field",action="store_true")
parser.add_argument("--padding",type=float,default=1.)
parser.add_argument("--pml",type=float,default=.6)
parser.add_argument("--mode-half-width",type=float,default=.8)
parser.add_argument("--mode-half-height",type=float,default=.8)
parser.add_argument("--n-si",type=float,default=float(SPEC.device.params["n_si"]))
parser.add_argument("--n-oxide",type=float,default=float(SPEC.device.params["n_oxide"]))
parser.add_argument("--straight-bias",type=float,default=0.,help="Explicit diagnostic only, no fit to paper")
parser.add_argument("--polygon-step",type=float,default=float(SPEC.device.params["polygon_step_um"]))
parser.add_argument("--timeout",type=float,default=7200)
a=parser.parse_args()
if not a.label.replace("_","").replace("-","").isalnum():parser.error("Invalid label")
result_path=HERE/"results"/f"{a.label}.json"
out=Path("artifacts/chen2017_curved_coupler")/a.label
try:require_fresh_output(out,result_path,a.action)
except FileExistsError as exc:parser.error(str(exc))
previous_result=(json.loads(result_path.read_text())
if a.action=="analyze" and result_path.exists() else {})
start=time.monotonic()
sim,mon,layout,lam,freqs=build(a)
out.mkdir(parents=True,exist_ok=True)
wire=sim.to_wire_json()
planning=asdict(ph.quote(sim,throughput_gcells_per_s=.025,rate_usd_per_tcell_step=0))
analysis_environment=dict(settings=vars(a),layout=layout,planning=planning,
photonhub_version=ph.__version__,
repository_commit=subprocess.check_output(["git","rev-parse","HEAD"],text=True).strip(),
source_files_sha256={str(p.relative_to(HERE.parent)):hashlib.sha256(p.read_bytes()).hexdigest()
for p in (Path(__file__),SPEC_PATH,HERE/"refs/chen2017_fig4.json")},
solver_info=json.loads(subprocess.check_output(["build/phsolver","info"],text=True)),
solver_binary_sha256=hashlib.sha256(Path("build/phsolver").read_bytes()).hexdigest(),
input_sha256=hashlib.sha256(wire.encode()).hexdigest())
if a.action=="analyze":
preflight=json.loads((out/"preflight.json").read_text())
if preflight["input_sha256"]!=hashlib.sha256(wire.encode()).hexdigest():
raise ValueError("Analysis simulation differs from original preflight input")
else:
preflight=analysis_environment
print(json.dumps(preflight,indent=2),flush=True)
if a.action!="analyze":
(out/"sim.json").write_text(wire+"\n")
(out/"preflight.json").write_text(json.dumps(preflight,indent=2)+"\n")
if a.action=="estimate":return
if a.action=="prepare":
import matplotlib.pyplot as plt
ax=sim.plot_index(z=layout["zcenter_um"]+.04)
ax.figure.savefig(out/"geometry.png",dpi=160,bbox_inches="tight");plt.close(ax.figure)
return
if a.action=="analyze":
if ph.Simulation.from_file(out/"raw/sim.json").to_wire_json()!=wire:raise ValueError("Run input changed")
data=ph.RunResult(out/"raw")
else:
data=ph.run_local(sim,output_dir=out/"raw",solver_path="build/phsolver",device="cpu",quiet=True,
log_file=out/"engine.jsonl",timeout=a.timeout)
if data.run.get("aborted",False):raise ValueError("Aborted run is not evidence")
raw_sha=hashlib.sha256((out/"raw/sim.json").read_bytes()).hexdigest()
if raw_sha!=data.provenance.get("input_sha256"):
raise ValueError("Executed input SHA256 does not match raw/sim.json")
pin=mon["in"].mode_power(data,direction="+")
ref=mon["in"].mode_power(data,direction="-")
p=np.array([pin[f] for f in freqs])
if np.any(p<=0) or not np.all(np.isfinite(p)):raise ValueError("Invalid incident power")
trans={name:np.array([v[f] for f in freqs])/p
for name in layout["port_y_um"] for v in [mon[name].mode_power(data,direction="+")]}
reflection=np.array([ref[f] for f in freqs])/p
back_other=np.zeros_like(p)
if "unused_in" in mon:
r=mon["unused_in"].mode_power(data,direction="-")
back_other=np.array([r[f] for f in freqs])/p
physical_trans={k:v.copy() for k,v in trans.items()}
# Geometry keys identify upper/lower strips. Observable bar/cross roles
# follow the launched guide; swapping the input swaps those output roles.
if a.input_port=="cross" and not a.straight:
trans={"bar":physical_trans["cross"],"cross":physical_trans["bar"]}
total=sum(trans.values())
if not np.all(np.isfinite(total)) or np.any(total<=0):raise ValueError("Invalid output power")
q={name:v/total for name,v in trans.items()}
final_decay=None
if (out/"engine.jsonl").exists():
for line in (out/"engine.jsonl").read_text().splitlines():
try: event=json.loads(line)
except json.JSONDecodeError:continue
if "field_decay" in event:final_decay=event["field_decay"]
result=dict(wavelength_nm=(lam*1000).tolist(),transmission={k:v.tolist() for k,v in trans.items()},
normalized={k:v.tolist() for k,v in q.items()},normalized_db={k:(10*np.log10(v)).tolist() for k,v in q.items()},
total_transmission=total.tolist(),reflection=reflection.tolist(),unused_input_reflection=back_other.tolist(),
unaccounted_power=(1-total-reflection-back_other).tolist(),
unaccounted_definition="1 - guided output power - guided backward power at both input guides; radiation is not directly integrated",
run=dict(data.run),provenance=dict(data.provenance),preflight=preflight,
executed_input_file_sha256=raw_sha,
physical_transmission={k:v.tolist() for k,v in physical_trans.items()},
output_role_definition="bar=same strip as launch, cross=other strip; layout port keys bar/cross mean physical upper/lower respectively",
analysis_environment=analysis_environment,
final_field_decay=final_decay,
elapsed_wall_s=previous_result.get("elapsed_wall_s") if a.action=="analyze" else time.monotonic()-start,
elapsed_wall_definition="Original full driver runtime; null when an orphan raw run is analyzed without its original result. Solver timing is always in run.",
analysis_elapsed_wall_s=time.monotonic()-start,paid_compute_usd=0,
photonhub_version=preflight.get("photonhub_version",previous_result.get("photonhub_version")),
settings=preflight["settings"],layout=preflight["layout"],
repo_commit=preflight.get("repository_commit",previous_result.get("repo_commit")))
(HERE/"results").mkdir(exist_ok=True)
encoded=json.dumps(result,indent=2,allow_nan=False).replace(str(Path.cwd()),"<repo>")
temporary_result=result_path.with_suffix(".json.tmp")
temporary_result.write_text(encoded+"\n")
temporary_result.replace(result_path)
if a.field:
import matplotlib.pyplot as plt
ax=data.plot_field("field",field="E",val="abs",simulation=sim)
ax.figure.savefig(out/"field.png",dpi=160,bbox_inches="tight");plt.close(ax.figure)
mid=int(np.argmin(abs(lam-SPEC.optical.center_nm/1000)))
print(json.dumps(dict(label=a.label,normalized_center={k:v[mid] for k,v in q.items()},
total_center=total[mid],reflection_center=reflection[mid],wall_s=result["elapsed_wall_s"])),flush=True)
if __name__=="__main__":main()
Reference and provenance¶
Chen et al., Scientific Reports 7, 7246 (2017), Fig. 4(a). The digitized data is reused under CC BY 4.0. Method: Versioned Fig4 dashed-ribbon centerlines; preserve v1 and internal bends. PDF geometry only. Pair corresponding long edges of each ribbon, average its two line equations, and intersect successive center lines at internal bends. Tiny bevel join edges are excluded (all actual joins <.021 point, all actual body edges >.179 point). Quantized near-collinear joins use the mean boundary join x coordinate, retaining a centerline knot. Extraction date: 2026-09-15. Estimated digitization uncertainty: ±0.05 dB for simulated curves, ±0.09 dB for measured curves, and ±0.15 nm. These are extraction estimates, not statistical intervals. The original figure and PDF are not bundled.
At the saved meshes listed above, both normalized outputs have been compared with the paper's simulated curves. Overall validation is pending in the verified evidence. The paper also reports a measured 3 dB ± 1 dB split over 88 nm centered at 1565 nm. That measurement is context, not the simulated acceptance target.