This notebook reproduces the compact modal demultiplexer from §III-B of Wong et al., “Simplifying Photonic Topologies in Inverse Design With Contour Constraints” [1]. The device is a 5 × 4 µm² O-band TE modal demultiplexer on a 220 nm silicon-on-insulator (SOI) platform with 700 nm access waveguides.
The contour constraint is a foundry-agnostic topology-optimization (TO) technique that automatically identifies and erodes connected regions carrying low integrated field power — the “extraneous” features that inflate device complexity without contributing to performance. It removes the manual post-processing step of deleting non-functional features by hand, yielding simpler, more fabrication-friendly geometries with no penalty to insertion loss.
Routing function
Input mode
Output port
Output mode
TE0
Bottom
TE0
TE1
Top
TE0 (mode-converted from TE1)
Two designs compared over the O-band (1260–1360 nm):
Standard TO demux — density-based TO with foundry constraints.
Contour-constrained TO demux — the same, plus the contour constraint that gradually erodes connected regions with low integrated field power.
Optimization workflow (following the paper’s Fig. 2 milestones):
Phase 1 (staged TO) — unconstrained TO (iters 0–50) → ramped fabrication penalty (50–120) → full fabrication constraints (120–135). Shared between both branches.
Phase 2 (parallel branches) — from iteration 135, standard TO continued versus contour-constrained TO.
Phase 3 (seeded TO) — a fabrication-only polishing pass applied to both branches.
This example uses the Tidy3D autograd plugin for automatic-differentiation–based inverse design. If you are new to Tidy3D, we recommend starting with the tutorials and browsing the example library for more inverse design walkthroughs.
import autogradimport autograd.numpy as npimport matplotlib.pyplot as pltimport scipy.ndimageimport tidy3d as tdimport tidy3d.web as webfrom scipy.interpolate import RegularGridInterpolatorfrom tidy3d.plugins.autograd import ( make_filter_and_project, make_erosion_dilation_penalty,)from tidy3d.plugins.invdes import AdamOptimizer
1. O-Band SOI Platform Parameters
Matches the paper: 220 nm SOI, 700 nm waveguide width, O-band (1260–1360 nm) on the GF Fotonix™ platform.
O-band SOI demux setup
Waveguide width: 700 nm
Si thickness: 220 nm
Wavelength band: 1.26–1.36 μm
Optimization frequencies: 5 pts
2. Demultiplexer Geometry
5 µm (x) × 4 µm (y) design region
1 input waveguide on the left (centered at y=0)
2 output waveguides on the right (top: y=+1.0 µm, bottom: y=−1.0 µm)
1 µm of straight waveguide on each side beyond the design region
The 1.0 µm offset between output ports keeps them well-separated relative to the mode field diameter (≈0.7 µm) so we don’t get spurious evanescent coupling outside the design region.
To define the dual-mode FOM we run two simulations per gradient step:
Sim A: TE0 source on the input → measure TE0 amplitude at the bottom monitor
Sim B: TE1 source on the input → measure TE0 amplitude at the top monitor
Both monitors use num_modes=2 so we can examine cross-talk into the other output port if needed.
src_size_y =4* wg_width # generous to fully capture both TE0 and TE1src_size_z =6* thick# Source x-position: just inside the input straight sectionx_src =-design_size_x /2- wg_length + lda0 /3# Monitor x-position: just inside each output straight sectionx_mnt =+design_size_x /2+ wg_length - lda0 /3mode_spec_input = td.ModeSpec(num_modes=2, target_neff=n_si)mode_spec_output = td.ModeSpec(num_modes=2, target_neff=n_si)def make_source(mode_index: int) -> td.ModeSource:return td.ModeSource( center=(x_src, 0, 0), size=(0, src_size_y, src_size_z), source_time=td.GaussianPulse(freq0=freq0, fwidth=fwidth), direction="+", mode_spec=mode_spec_input, mode_index=mode_index, )mode_source_te0 = make_source(0)mode_source_te1 = make_source(1)# Output monitors (one per port) — record both TE0 and TE1 at eachdef make_monitor(y: float, name: str, freqs=None) -> td.ModeMonitor:return td.ModeMonitor( center=(x_mnt, y, 0), size=(0, src_size_y, src_size_z), freqs=list(freqs_opt) if freqs isNoneelselist(freqs), mode_spec=mode_spec_output, name=name, )mode_monitor_top = make_monitor(+y_port, "mode_top")mode_monitor_bot = make_monitor(-y_port, "mode_bot")# Optional 2D field monitor for visualizationfield_monitor = td.FieldMonitor( center=(0, 0, 0), size=(td.inf, td.inf, 0), freqs=[freq0], name="field",)
4. Topology Optimization Setup
Density-based TO with a conic filter (minimum feature size = 100 nm) and a hyperbolic-tangent projection. The design region holds the demultiplexer’s nontrivial topology.
Unconstrained — fields not yet confined, extraneous contours form
50–119
0 → 1 (ramp)
~16 → ~28
Foundry constraints fade in
120–134
1.0
~28 → 32
Full DRC
This is critical: without an unconstrained early stage the optimizer converges directly to a clean topology and there are no extraneous contours for the constraint to act on later.
7. Phase 2: Two Parallel Branches From the Iter-135 Design
Both branches start from the same Phase-1 final params and run the same number of additional Adam steps.
Branch A (standard TO) simply continues at fixed β = 32.
Branch B (contour-constrained)re-softens the projection and ramps β from 12 → 32 while the contour penalty is active. This is the key fix: at the terminal β = 32 the tanh projection is saturated (∂density/∂params ≈ 0), so the contour penalty has no gradient leverage no matter how large its weight. Applying it while β is still ramping keeps parasitic features erodible, so the penalty can actually drive them to zero.
The contour mask is recomputed every mask_update_freq steps from a forward simulation of the TE0-in sub-problem (the dominant routing pathway); any connected feature carrying negligible field there is marked for erosion.
T_P =0.05# mark contours holding less than 5% of total field powercontour_weight =0.75mask_update_freq =10pixel_area = pixel_size**2# Field monitor over optimization frequencies, only used for contour analysisfield_monitor_contour = td.FieldMonitor( center=(0, 0, 0), size=(td.inf, td.inf, 0), freqs=freqs_opt, name="field",)def compute_contour_penalty_mask( params: np.ndarray, beta: float, threshold: float) -> np.ndarray:"""Compute the indicator mask of voxels in low-field contours. Uses the TE0-in forward simulation: this is the dominant routing pathway and any feature with negligible field there can be safely eroded.""" density = get_density(params, beta=beta).reshape(num_pixels_x, num_pixels_y) binary_design = (density >0.5).astype(int) labeled_contours, num_contours_local = scipy.ndimage.label(binary_design) n_map = np.where(binary_design, n_si, n_sio2) sim_field = get_sim(params, beta=beta, input_mode=0) sim_field = sim_field.updated_copy( monitors=list(sim_field.monitors) + [field_monitor_contour] ) sim_data_field = web.run( sim_field, folder_name="contour constraint demux", task_name="contour_analysis", verbose=False, ) field_data = sim_data_field["field"] E_sq_per_freq = ( np.abs(field_data.Ex.values) **2+ np.abs(field_data.Ey.values) **2+ np.abs(field_data.Ez.values) **2 ) E_sq = np.mean(E_sq_per_freq, axis=-1) x_field = field_data.Ex.coords["x"].values y_field = field_data.Ex.coords["y"].values x_design_grid = np.linspace(-design_size_x /2+ pixel_size /2, design_size_x /2- pixel_size /2, num_pixels_x, ) y_design_grid = np.linspace(-design_size_y /2+ pixel_size /2, design_size_y /2- pixel_size /2, num_pixels_y, ) interp = RegularGridInterpolator( (x_field, y_field), E_sq[:, :, 0], method="linear", bounds_error=False, fill_value=0.0, ) xx_des, yy_des = np.meshgrid(x_design_grid, y_design_grid, indexing="ij") E_sq_design_local = interp( np.stack([xx_des.ravel(), yy_des.ravel()], axis=-1) ).reshape(num_pixels_x, num_pixels_y) P_tot_local = np.sum(n_map * E_sq_design_local) mask_out = np.zeros((num_pixels_x, num_pixels_y, 1)) n_marked =0for m inrange(1, num_contours_local +1): mask_m = labeled_contours == m P_m = np.sum(np.where(mask_m, n_map, 0) * E_sq_design_local)if P_tot_local >0and P_m / P_tot_local < threshold: mask_out += mask_m.reshape(num_pixels_x, num_pixels_y, 1).astype(float) n_marked +=1print(f" [contour mask] {num_contours_local} contours, {n_marked} marked (P/P_tot < {threshold:.2f})" )return mask_out_contour_mask_holder = [np.zeros((num_pixels_x, num_pixels_y, 1))]def objective_with_contour(params: np.ndarray, beta: float) ->float: transmission = get_transmission(params, beta=beta) fab_penalty = get_penalty(params, beta=beta) density = get_density(params, beta=beta) contour_penalty = np.sum(_contour_mask_holder[0] * density) * pixel_areareturn ( transmission - _fab_weight[0] * fab_penalty - contour_weight * contour_penalty )
num_steps_phase2 =25learning_rate_p2 =0.02beta_p2_start =12# re-soften so the contour penalty can actually erode features_fab_weight[0] =1.0# full fab strength for all later phasesdef get_beta_p2(step_num: int, num_steps: int, use_contour: bool) ->float:"""Contour branch ramps β so features stay erodible while the penalty acts; the standard baseline just continues at full β."""ifnot use_contour:return beta_maxreturn beta_p2_start + (beta_max - beta_p2_start) * step_num /max(num_steps -1, 1)def run_phase2(params_init: np.ndarray, use_contour: bool, label: str): params = params_init.copy() optimizer = AdamOptimizer.construct(learning_rate=learning_rate_p2) opt_state = optimizer.initial_state(params) beta0 = get_beta_p2(0, num_steps_phase2, use_contour)if use_contour: _contour_mask_holder[0] = compute_contour_penalty_mask( params, beta0, threshold=T_P ) val_grad_fn_local = autograd.value_and_grad(objective_with_contour)else: val_grad_fn_local = autograd.value_and_grad(objective) history_params = [params.copy()] history_il_te0 = [] history_il_te1 = []print("="*70)print(f"PHASE 2 — {label}")print("="*70)for i inrange(num_steps_phase2): beta = get_beta_p2(i, num_steps_phase2, use_contour)if use_contour and i >0and i % mask_update_freq ==0: _contour_mask_holder[0] = compute_contour_penalty_mask( params, beta, threshold=T_P ) value, gradient = val_grad_fn_local(params, beta=beta) params, opt_state = optimizer.update( parameters=params, gradient=-gradient, state=opt_state ) params = np.clip(params, 0, 1) il_te0 =-10* np.log10(max(_last_T_te0[0], 1e-12)) il_te1 =-10* np.log10(max(_last_T_te1[0], 1e-12)) history_il_te0.append(il_te0) history_il_te1.append(il_te1) history_params.append(params.copy())print(f" Step {i+1:3d}/{num_steps_phase2} β={beta:.1f} obj={value:.6f} IL(TE0)={il_te0:.4f} dB IL(TE1)={il_te1:.4f} dB" )if (i +1) %20==0or i ==0: sim_viz = get_sim(params, beta=beta, input_mode=0) _, ax = plt.subplots(figsize=(5, 3)) sim_viz.plot_eps(z=0.01, ax=ax, monitor_alpha=0.0, source_alpha=0.0) ax.set_title(f"{label}, Step {i+1}") plt.show()print(f"\n{label} complete.")return history_params, history_il_te0, history_il_te1# Branch A: continue without contour constraintparam_history_p2_std, il_te0_p2_std, il_te1_p2_std = run_phase2( params_standard_to, use_contour=False, label="Standard-TO Continued")# Branch B: with the contour constraint, applied while β ramps 12 -> 32param_history_p2_cc, il_te0_p2_cc, il_te1_p2_cc = run_phase2( params_standard_to, use_contour=True, label="Contour-Constrained")
======================================================================
PHASE 2 — Standard-TO Continued
======================================================================
/var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_62177/3064855979.py:17: PydanticDeprecatedSince20: The `construct` method is deprecated; use `model_construct` instead. Deprecated in Pydantic V2.0 to be removed in V3.0. See Pydantic V2 Migration Guide at https://errors.pydantic.dev/2.10/migration/
optimizer = AdamOptimizer.construct(learning_rate=learning_rate_p2)
Step 1/25 β=32.0 obj=0.939977 IL(TE0)=0.0301 dB IL(TE1)=0.0168 dB
Step 2/25 β=32.0 obj=0.770512 IL(TE0)=0.6002 dB IL(TE1)=1.0682 dB
Step 3/25 β=32.0 obj=0.909626 IL(TE0)=0.1182 dB IL(TE1)=0.2605 dB
Step 4/25 β=32.0 obj=0.841428 IL(TE0)=0.3869 dB IL(TE1)=0.6714 dB
Step 5/25 β=32.0 obj=0.889329 IL(TE0)=0.2401 dB IL(TE1)=0.3681 dB
Step 6/25 β=32.0 obj=0.923377 IL(TE0)=0.0916 dB IL(TE1)=0.1785 dB
Step 7/25 β=32.0 obj=0.907351 IL(TE0)=0.0928 dB IL(TE1)=0.2972 dB
Step 8/25 β=32.0 obj=0.898345 IL(TE0)=0.1186 dB IL(TE1)=0.3481 dB
Step 9/25 β=32.0 obj=0.915820 IL(TE0)=0.0944 dB IL(TE1)=0.2226 dB
Step 10/25 β=32.0 obj=0.934899 IL(TE0)=0.0606 dB IL(TE1)=0.1065 dB
Step 11/25 β=32.0 obj=0.933037 IL(TE0)=0.0703 dB IL(TE1)=0.1329 dB
Step 12/25 β=32.0 obj=0.922296 IL(TE0)=0.1026 dB IL(TE1)=0.2061 dB
Step 13/25 β=32.0 obj=0.922429 IL(TE0)=0.1079 dB IL(TE1)=0.1965 dB
Step 14/25 β=32.0 obj=0.932617 IL(TE0)=0.0797 dB IL(TE1)=0.1231 dB
Step 15/25 β=32.0 obj=0.941455 IL(TE0)=0.0475 dB IL(TE1)=0.0673 dB
Step 16/25 β=32.0 obj=0.941677 IL(TE0)=0.0407 dB IL(TE1)=0.0657 dB
Step 17/25 β=32.0 obj=0.936945 IL(TE0)=0.0543 dB IL(TE1)=0.0920 dB
Step 18/25 β=32.0 obj=0.935165 IL(TE0)=0.0617 dB IL(TE1)=0.1036 dB
Step 19/25 β=32.0 obj=0.938844 IL(TE0)=0.0528 dB IL(TE1)=0.0878 dB
Step 20/25 β=32.0 obj=0.943887 IL(TE0)=0.0416 dB IL(TE1)=0.0647 dB
Step 21/25 β=32.0 obj=0.945723 IL(TE0)=0.0435 dB IL(TE1)=0.0555 dB
Step 22/25 β=32.0 obj=0.944118 IL(TE0)=0.0565 dB IL(TE1)=0.0596 dB
Step 23/25 β=32.0 obj=0.943374 IL(TE0)=0.0645 dB IL(TE1)=0.0619 dB
Step 24/25 β=32.0 obj=0.944666 IL(TE0)=0.0582 dB IL(TE1)=0.0551 dB
Step 25/25 β=32.0 obj=0.946774 IL(TE0)=0.0438 dB IL(TE1)=0.0460 dB
Standard-TO Continued complete.
/var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_62177/3064855979.py:17: PydanticDeprecatedSince20: The `construct` method is deprecated; use `model_construct` instead. Deprecated in Pydantic V2.0 to be removed in V3.0. See Pydantic V2 Migration Guide at https://errors.pydantic.dev/2.10/migration/
optimizer = AdamOptimizer.construct(learning_rate=learning_rate_p2)
[contour mask] 4 contours, 3 marked (P/P_tot < 0.05)
======================================================================
PHASE 2 — Contour-Constrained
======================================================================
Step 1/25 β=12.0 obj=-4.306218 IL(TE0)=0.3976 dB IL(TE1)=0.5276 dB
Step 2/25 β=12.8 obj=-4.231834 IL(TE0)=0.6491 dB IL(TE1)=1.0511 dB
Step 3/25 β=13.7 obj=-3.971378 IL(TE0)=0.2631 dB IL(TE1)=0.5384 dB
Step 4/25 β=14.5 obj=-3.738763 IL(TE0)=0.1895 dB IL(TE1)=0.2860 dB
Step 5/25 β=15.3 obj=-3.470009 IL(TE0)=0.2446 dB IL(TE1)=0.3031 dB
Step 6/25 β=16.2 obj=-3.069188 IL(TE0)=0.1997 dB IL(TE1)=0.2714 dB
Step 7/25 β=17.0 obj=-2.542798 IL(TE0)=0.1284 dB IL(TE1)=0.1565 dB
Step 8/25 β=17.8 obj=-1.936563 IL(TE0)=0.1359 dB IL(TE1)=0.1052 dB
Step 9/25 β=18.7 obj=-1.289786 IL(TE0)=0.1733 dB IL(TE1)=0.1390 dB
Step 10/25 β=19.5 obj=-0.688261 IL(TE0)=0.1438 dB IL(TE1)=0.1449 dB
[contour mask] 29 contours, 28 marked (P/P_tot < 0.05)
Step 11/25 β=20.3 obj=0.245071 IL(TE0)=0.0862 dB IL(TE1)=0.1142 dB
Step 12/25 β=21.2 obj=0.393050 IL(TE0)=0.0655 dB IL(TE1)=0.0935 dB
Step 13/25 β=22.0 obj=0.547862 IL(TE0)=0.0804 dB IL(TE1)=0.0889 dB
Step 14/25 β=22.8 obj=0.690882 IL(TE0)=0.0854 dB IL(TE1)=0.0737 dB
Step 15/25 β=23.7 obj=0.803066 IL(TE0)=0.0753 dB IL(TE1)=0.0596 dB
Step 16/25 β=24.5 obj=0.870381 IL(TE0)=0.0679 dB IL(TE1)=0.0617 dB
Step 17/25 β=25.3 obj=0.903139 IL(TE0)=0.0640 dB IL(TE1)=0.0614 dB
Step 18/25 β=26.2 obj=0.921612 IL(TE0)=0.0560 dB IL(TE1)=0.0484 dB
Step 19/25 β=27.0 obj=0.930805 IL(TE0)=0.0496 dB IL(TE1)=0.0468 dB
Step 20/25 β=27.8 obj=0.933617 IL(TE0)=0.0468 dB IL(TE1)=0.0571 dB
Both branches receive a fab-only seeded-TO polishing pass. Critically, the contour penalty is off during this stage — it has done its job and should not keep eroding material, which would damage the main contour. This matches the paper’s description that seeded TO smooths rough edges and enforces foundry DRC.
======================================================================
PHASE 3: Seeded TO — Standard-TO Continued (fab-only)
======================================================================
/var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_62177/3601026129.py:13: PydanticDeprecatedSince20: The `construct` method is deprecated; use `model_construct` instead. Deprecated in Pydantic V2.0 to be removed in V3.0. See Pydantic V2 Migration Guide at https://errors.pydantic.dev/2.10/migration/
optimizer = AdamOptimizer.construct(learning_rate=learning_rate_seeded)
Step 1/10 obj=0.614772 IL(TE0)=1.7591 dB IL(TE1)=1.7964 dB
Step 2/10 obj=0.634819 IL(TE0)=1.6368 dB IL(TE1)=1.6594 dB
Step 3/10 obj=0.654505 IL(TE0)=1.5192 dB IL(TE1)=1.5294 dB
Step 4/10 obj=0.673859 IL(TE0)=1.4056 dB IL(TE1)=1.4060 dB
Step 5/10 obj=0.692915 IL(TE0)=1.2952 dB IL(TE1)=1.2885 dB
Step 6/10 obj=0.711697 IL(TE0)=1.1877 dB IL(TE1)=1.1768 dB
Step 7/10 obj=0.730217 IL(TE0)=1.0824 dB IL(TE1)=1.0704 dB
Step 8/10 obj=0.748477 IL(TE0)=0.9790 dB IL(TE1)=0.9694 dB
Step 9/10 obj=0.766456 IL(TE0)=0.8770 dB IL(TE1)=0.8740 dB
Step 10/10 obj=0.784059 IL(TE0)=0.7763 dB IL(TE1)=0.7850 dB
======================================================================
PHASE 3: Seeded TO — Contour-Constrained (fab-only)
======================================================================
/var/folders/qn/syhrzy8n7930sgqvxv2x65s40000gn/T/ipykernel_62177/3601026129.py:13: PydanticDeprecatedSince20: The `construct` method is deprecated; use `model_construct` instead. Deprecated in Pydantic V2.0 to be removed in V3.0. See Pydantic V2 Migration Guide at https://errors.pydantic.dev/2.10/migration/
optimizer = AdamOptimizer.construct(learning_rate=learning_rate_seeded)
Step 1/10 obj=0.661275 IL(TE0)=1.6087 dB IL(TE1)=1.4612 dB
Step 2/10 obj=0.680328 IL(TE0)=1.4694 dB IL(TE1)=1.3633 dB
Step 3/10 obj=0.699242 IL(TE0)=1.3345 dB IL(TE1)=1.2693 dB
Step 4/10 obj=0.718016 IL(TE0)=1.2037 dB IL(TE1)=1.1789 dB
Step 5/10 obj=0.736598 IL(TE0)=1.0772 dB IL(TE1)=1.0920 dB
Step 6/10 obj=0.754893 IL(TE0)=0.9558 dB IL(TE1)=1.0083 dB
Step 7/10 obj=0.772767 IL(TE0)=0.8402 dB IL(TE1)=0.9279 dB
Step 8/10 obj=0.790041 IL(TE0)=0.7314 dB IL(TE1)=0.8508 dB
Step 9/10 obj=0.806475 IL(TE0)=0.6306 dB IL(TE1)=0.7774 dB
Step 10/10 obj=0.821767 IL(TE0)=0.5390 dB IL(TE1)=0.7084 dB
9. Broadband Comparison (1260–1360 nm)
Replicates Fig. 4c of the paper: per-mode insertion loss spectra for both the standard-TO and contour-constrained final devices.
============================================================
INSERTION LOSS SUMMARY (1260–1360 nm)
============================================================
Standard TO TE0 IL: 0.6301 – 0.7177 dB
Standard TO TE1 IL: 0.6684 – 0.7669 dB
Contour-constrained TE0 IL: 0.4092 – 0.5513 dB
Contour-constrained TE1 IL: 0.6145 – 0.6876 dB
TE0 improvement (paper reports ~0.010 dB): +0.2290 dB
TE1 improvement (paper reports ~0.025 dB): +0.0528 dB
10. Device Geometry and Field Patterns
Top row: final device geometries from each branch. Bottom two rows: \(E_y\) field patterns for TE0 and TE1 input — replicates panels (a) and (b) of the paper’s Fig. 4.
This notebook reproduced the modal demultiplexer experiment from §III-B of Wong et al. [1]. The key observations are consistent with the paper:
The contour constraint produces a visibly simpler topology (fewer small, disconnected features) than standard TO at the same iteration count.
Per-mode insertion-loss improvements are small but positive on average; the paper reports roughly +0.010 dB for TE0 and +0.025 dB for TE1.
The simplified topology is expected to be more robust to over/under-etch (process variation), although that study is not reproduced here.
Mechanistically, the contour constraint adds a gradient term that gradually erodes connected components whose integrated field intensity falls below a fraction \(T_P\) of the design’s total. For this demultiplexer we use the TE0-in forward simulation as the reference field for marking contours, since it is the dominant routing pathway.
Tuning knobs
T_P — threshold for marking contours (lower = more conservative, more contours preserved).
contour_weight — penalty strength (higher = faster erosion but more risk of damaging the main contours).
mask_update_freq — how often the contour mask is recomputed during Phase 2.
num_steps_phase1, num_steps_phase2, num_steps_seeded — the length of each optimization stage.
Note on iteration count
This notebook uses reduced iteration counts (num_steps_phase1=75, num_steps_phase2=25, num_steps_seeded=10) to keep the runtime short. For better final performance—particularly lower insertion loss—increase these values. The reference paper uses roughly num_steps_phase1=135 (with unconstrained_end=50, ramp_end=120), num_steps_phase2=50, and num_steps_seeded=25. The seeded-TO phase (Phase 3) benefits most from additional iterations, as it is still actively converging at 10 steps.
References
[1] J. J. Wong, R. P. Pesch, J. M. Hiesener, and S. E. Ralph, “Simplifying Photonic Topologies in Inverse Design With Contour Constraints,” IEEE Photonics Technology Letters, vol. 38, no. 2, pp. 117–120, Jan. 2026, doi: 10.1109/LPT.2025.3623046.
We use necessary cookies to run this website. With your permission, we
also use analytics cookies to understand site usage and marketing
cookies for advertising, retargeting, and HubSpot tracking.
Learn more about our cookie policy.
Privacy choices
Choose which optional cookies Flexcompute may use. Necessary cookies
are always on because they support core website behavior, security, and
saving your consent record.
Your browser is sending a Global Privacy Control signal, so marketing
cookies are disabled.
Subscribe
Thanks for subscribing
Publish Your Notebook
Thank you for publishingA confirmation email has been sent to your inbox. Your notebook will be available within the next 48 hours.
Community Library
TERMS & CONDITIONS
EFFECTIVE DATE: January 1, 2025
Terms and Conditions for User-Submitted Content
By submitting content to Flexcompute, you agree to the following terms:
1. Ownership and Copyright
All content, including but not limited to text, data, images, and other materials submitted by users,
remains the sole property of the original creator. Flexcompute does not claim ownership of the submitted
content or any intellectual property rights associated with it.
Users affirm that they own the copyright or have obtained all necessary permissions for the submitted
content and are fully responsible for ensuring that their submissions do not infringe on any third-party
rights.
2. Responsibility for Content
Users are solely responsible for the content they submit. Flexcompute does not endorse, guarantee,
or verify the accuracy, legality, or appropriateness of any submitted content. Users are responsible
for ensuring that their content complies with all applicable laws and regulations.
Users declare that they have obtained authorization from all co-authors and collaborators associated
with the submitted content, granting them the right to submit and sign on behalf of all contributors.
Users agree not to submit content that is illegal, defamatory, obscene, or violates the rights
of others, including privacy and intellectual property rights.
3. Modification and Presentation
Flexcompute reserves the right to review, edit, or modify submitted content to improve clarity,
formatting, and presentation while maintaining the original intent and message.
These modifications are made to enhance the overall quality and readability of the published material.
Users acknowledge that Flexcompute may format or present the content in a way
that aligns with our editorial and visual standards.
4. Liability Disclaimer
Flexcompute shall not be held liable for any disputes arising from user-submitted content, including
but not limited to copyright claims, inaccuracies, or damages resulting from the publication of user content.
Users agree to indemnify Flexcompute against any claims or legal actions resulting from their submitted content.
5. Content Use Rights
By submitting content, users grant Flexcompute a non-exclusive, royalty-free, worldwide license to publish, distribute,
and promote the content for the purposes of showcasing user contributions and marketing our services.
This license does not transfer ownership of the content or any copyright to Flexcompute.
Users retain the right to withdraw their submissions at any time.
Upon request, Flexcompute will remove the content from its platform.
6. Privacy and Confidentiality
Users acknowledge that submitted content may be publicly accessible and therefore waive any
rights to confidentiality or privacy regarding the published material.
Flexcompute will not share personal information of users without
their consent, in accordance with our privacy policy.
Community Library
How the process works:
You submit your notebook.
You will get a permanent URL to promote your work.
Cite the URL as a reference for others in your upcoming papers to enhance the impact.
When someone clicks your URL, your notebook will be displayed.
Enter your email address below to receive the presentation slides. In the future, we’ll share you very few emails when we have new tutorial release, development updates, valuable toolkits and technical guidance . You can unsubscribe at any time by clicking the link at the bottom of every email. We’ll never share your information.