Skip to content

Commit b8b238a

Browse files
fixed beam behavior inside the slab
1 parent 62d55cc commit b8b238a

1 file changed

Lines changed: 68 additions & 41 deletions

File tree

script.js

Lines changed: 68 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -200,17 +200,15 @@
200200
const thinLensMatrix = (f_m) => f_m === 0 ? identityMatrix() : [[1, 0], [-1 / f_m, 1]];
201201
const sphericalMirrorMatrix = (R_m) => R_m === 0 ? identityMatrix() : [[1, 0], [-2.0 / R_m, 1]];
202202
const flatMirrorMatrix = () => identityMatrix();
203+
const flatInterfaceMatrix = (n_from, n_to) => [[1, 0], [0, n_from / n_to]]; // ** NEW **
203204
const dielectricSlabMatrix = (n1_base, n2_over_n1_ratio, width_m) => {
204-
// The q-parameter propagation already accounts for base refractive index n1_base.
205-
// The matrix for a slab embedded in a medium n1, with slab index n2, and thickness W is:
206-
// A=1, B=W/n_ratio_slab_to_embedding = W / (n2/n1) , C=0, D=1
207-
// Here, n2_over_n1_ratio is n_slab / n_base_medium.
205+
// This function is no longer used by the main simulation loop for slabs,
206+
// but is kept for potential other uses or reference. The main loop
207+
// now uses flatInterfaceMatrix and free-space propagation.
208208
if (n2_over_n1_ratio <= 0) {
209209
console.warn("Invalid n ratio for dielectric slab:", n2_over_n1_ratio);
210210
return identityMatrix();
211211
}
212-
// The effective B for ABCD matrix operating on q (which is defined using lambda_vac/n_base)
213-
// is physical_width / (n_slab/n_base)
214212
const B_eff_m = width_m / n2_over_n1_ratio;
215213
return [[1, B_eff_m], [0, 1]];
216214
};
@@ -222,8 +220,7 @@
222220
case 'lens': return thinLensMatrix(props.f_mm / 1000.0);
223221
case 'mirror_spherical': return sphericalMirrorMatrix(props.R_mm / 1000.0);
224222
case 'mirror_flat': return flatMirrorMatrix();
225-
// Pass n1_base to slab_dielectric for clarity, though it might not directly use it if n_ratio is n_slab/n_base
226-
case 'slab_dielectric': return dielectricSlabMatrix(n1_base, props.n_ratio, props.width_mm / 1000.0);
223+
case 'slab_dielectric': return identityMatrix(); // Handled separately in the main loop
227224
case 'abcd_generic': return genericABCDMatrix(props.A, props.B_mm / 1000.0, props.C_perm * 1000.0, props.D);
228225
default:
229226
console.warn("Unknown element type:", element.type);
@@ -288,15 +285,10 @@
288285
if (opticalElements.length > 0 && !isNaN(opticalElements[0].position_mm)) {
289286
min_element_pos_m = Math.min(min_element_pos_m, opticalElements[0].position_mm / 1000.0);
290287
}
291-
// Ensure simulation starts reasonably before the initial waist or first element
292-
293-
const typical_zR_display = Math.max(initial_zR_M2_m, 0.01); // Avoid zero or negative zR for range calc
294-
// If user provided z_min_mm, use it; otherwise, fall back to heuristic
295-
const user_start_m = (typeof beamParams.z_min_mm === 'number' && isFinite(beamParams.z_min_mm)) ? (beamParams.z_min_mm / 1000.0) : null;
296-
const simulation_start_z_m = (user_start_m !== null) ? user_start_m : Math.min(0, z0_m - typical_zR_display * 2, min_element_pos_m - typical_zR_display * 0.5);
297-
const simulation_end_z_m = plot_end_z_m;
298-
299-
288+
const typical_zR_display = Math.max(initial_zR_M2_m, 0.01); // Avoid zero or negative zR for range calc
289+
const user_start_m = (typeof beamParams.z_min_mm === 'number' && isFinite(beamParams.z_min_mm)) ? (beamParams.z_min_mm / 1000.0) : null;
290+
const simulation_start_z_m = (user_start_m !== null) ? user_start_m : Math.min(0, z0_m - typical_zR_display * 2, min_element_pos_m - typical_zR_display * 0.5);
291+
const simulation_end_z_m = plot_end_z_m;
300292

301293
// 5. Calculate Initial q
302294
const dist_from_waist_to_start = simulation_start_z_m - z0_m;
@@ -309,13 +301,11 @@ const simulation_end_z_m = plot_end_z_m;
309301
const N_POINTS_PER_SEGMENT = 100;
310302
let last_z_plotted_mm = simulation_start_z_m * 1000 - 1;
311303

312-
// Add initial beam parameters to table
313304
tableData.push({
314305
opticType: "Input Beam", position_mm: beamParams.z0_mm, rel_pos_mm: null,
315-
waist_um: beamParams.w0_um, waist_pos_mm: beamParams.z0_mm, // Displaying input physical waist
306+
waist_um: beamParams.w0_um, waist_pos_mm: beamParams.z0_mm,
316307
zR_mm: initial_zR_M2_m * 1000.0, theta_mrad: initial_theta_M2_rad * 1000.0, id: 'initial'
317308
});
318-
// For waist marker, w is physical waist radius in meters
319309
plotData.waistMarkers.push({ z: z0_m, w: w0_input_m, label: `Waist 0 (${(z0_m * 1000).toFixed(1)}mm)` });
320310

321311

@@ -333,21 +323,20 @@ const simulation_end_z_m = plot_end_z_m;
333323
const dist_to_element_m = element_pos_m - z_current_m;
334324

335325
// A. Propagate free space *before* the element
336-
if (dist_to_element_m > 1e-12) { // Check for significant positive distance
326+
if (dist_to_element_m > 1e-12) {
337327
for (let i = 1; i <= N_POINTS_PER_SEGMENT; i++) {
338328
const z_step_rel = dist_to_element_m * (i / N_POINTS_PER_SEGMENT);
339329
const q_step = complexAdd(q_current, complex(z_step_rel, 0));
340330

341-
// calculateWR returns w_calc = w_actual_at_z / sqrt(M2_factor)
342331
const { w_m: w_calc, R_m } = calculateWR(q_step, lambda_in_base_medium_m);
343-
const w_actual_at_z_m = w_calc * Math.sqrt(M2_factor); // Correct to physical radius
332+
const w_actual_at_z_m = w_calc * Math.sqrt(M2_factor);
344333

345334
const z_abs_m = z_current_m + z_step_rel;
346335
const z_abs_mm = z_abs_m * 1000.0;
347336

348337
if (z_abs_mm > last_z_plotted_mm + 1e-9 && z_abs_m <= simulation_end_z_m + 1e-9) {
349338
plotData.z.push(z_abs_mm);
350-
plotData.w.push(w_actual_at_z_m * 1e6); // Store physical radius in um
339+
plotData.w.push(w_actual_at_z_m * 1e6);
351340
plotData.R.push(isFinite(R_m) ? R_m * 1000.0 : (R_m > 0 ? Infinity : -Infinity));
352341
last_z_plotted_mm = z_abs_mm;
353342
}
@@ -362,20 +351,61 @@ const simulation_end_z_m = plot_end_z_m;
362351
}
363352

364353
// B. Apply the element's transformation
365-
// n1_base_medium_idx is passed for context, e.g. for dielectric slab effective B calculation
366-
const M_element = getElementMatrix(element, n1_base_medium_idx);
367-
368-
q_current = transformQ(q_current, M_element);
369-
370-
371-
// If this is a slab, advance the physical position by its actual thickness.
372354
if (element.type === 'slab_dielectric') {
373-
const W_m = (element.property && typeof element.property.width_mm === 'number') ? (element.property.width_mm / 1000.0) : 0.0;
374-
if (W_m > 0) {
355+
// ** NEW: DETAILED SLAB HANDLING **
356+
const props = element.property;
357+
const W_m = (props && typeof props.width_mm === 'number') ? (props.width_mm / 1000.0) : 0.0;
358+
const n_ratio = (props && typeof props.n_ratio === 'number') ? props.n_ratio : 1.0;
359+
360+
if (W_m > 0 && n_ratio > 0) {
361+
const n2_medium_idx = n1_base_medium_idx * n_ratio;
362+
const lambda_in_slab_medium_m = lambda_vac_m / n2_medium_idx;
363+
364+
// 1. Enter the slab interface
365+
const M_enter = flatInterfaceMatrix(n1_base_medium_idx, n2_medium_idx);
366+
q_current = transformQ(q_current, M_enter);
367+
plotData.elementMarkers.push({ z: z_current_m, label: `Slab ${index + 1} Start` });
368+
369+
// 2. Propagate *through* the slab, plotting points inside
370+
for (let i = 1; i <= N_POINTS_PER_SEGMENT; i++) {
371+
const z_step_rel = W_m * (i / N_POINTS_PER_SEGMENT);
372+
const q_step = complexAdd(q_current, complex(z_step_rel, 0));
373+
374+
// Use the SLAB's internal wavelength for this calculation
375+
const { w_m: w_calc, R_m } = calculateWR(q_step, lambda_in_slab_medium_m);
376+
const w_actual_at_z_m = w_calc * Math.sqrt(M2_factor);
377+
378+
const z_abs_m = z_current_m + z_step_rel;
379+
const z_abs_mm = z_abs_m * 1000.0;
380+
381+
if (z_abs_mm > last_z_plotted_mm + 1e-9 && z_abs_m <= simulation_end_z_m + 1e-9) {
382+
plotData.z.push(z_abs_mm);
383+
plotData.w.push(w_actual_at_z_m * 1e6);
384+
plotData.R.push(isFinite(R_m) ? R_m * 1000.0 : (R_m > 0 ? Infinity : -Infinity));
385+
last_z_plotted_mm = z_abs_mm;
386+
}
387+
}
388+
// Update q and z to be at the exit face of the slab
389+
q_current = complexAdd(q_current, complex(W_m, 0));
375390
z_current_m += W_m;
391+
392+
// 3. Exit the slab interface
393+
const M_exit = flatInterfaceMatrix(n2_medium_idx, n1_base_medium_idx);
394+
q_current = transformQ(q_current, M_exit);
395+
plotData.elementMarkers.push({ z: z_current_m, label: `Slab ${index + 1} End` });
396+
397+
} else {
398+
// If slab has zero width or invalid ratio, treat as identity.
399+
plotData.elementMarkers.push({ z: z_current_m, label: `${formatElementType(element.type)} ${index + 1}` });
376400
}
401+
} else {
402+
// ** ORIGINAL HANDLING FOR OTHER ELEMENTS **
403+
const M_element = getElementMatrix(element, n1_base_medium_idx);
404+
q_current = transformQ(q_current, M_element);
405+
plotData.elementMarkers.push({ z: element_pos_m, label: `${formatElementType(element.type)} ${index + 1}` });
377406
}
378-
// C. Calculate output beam parameters (new physical waist, new zR_M2, new theta_M2)
407+
408+
// C. Calculate output beam parameters after the element interaction
379409
const { w0_m: w0_actual_new_m, z_waist_rel_m: z_waist_rel_new_m, zR_m: zR_M2_new_m, theta_rad: theta_M2_new_rad } = findWaistFromQ(q_current, lambda_vac_m, n1_base_medium_idx, M2_factor);
380410
const waist_abs_pos_m = z_current_m + z_waist_rel_new_m;
381411

@@ -384,16 +414,14 @@ q_current = transformQ(q_current, M_element);
384414
tableData.push({
385415
opticType: formatElementType(element.type), position_mm: element.position_mm, rel_pos_mm: rel_pos_mm,
386416
properties: formatElementProperties(element),
387-
waist_um: w0_actual_new_m * 1e6, // Store physical waist in um
417+
waist_um: w0_actual_new_m * 1e6,
388418
waist_pos_mm: waist_abs_pos_m * 1000.0,
389419
zR_mm: zR_M2_new_m * 1000.0, theta_mrad: theta_M2_new_rad * 1000.0, id: element.id
390420
});
391421

392-
// E. Add markers for plots (element z in m, waist w in m)
393-
plotData.elementMarkers.push({ z: element_pos_m, label: `${formatElementType(element.type)} ${index + 1}` });
422+
// E. Add waist markers for plots
394423
plotData.waistMarkers.push({ z: waist_abs_pos_m, w: w0_actual_new_m, label: `Waist ${index + 1}` });
395424

396-
397425
// F. Update position tracker for next relative calculation
398426
previous_element_pos_m = element_pos_m;
399427
});
@@ -405,16 +433,15 @@ q_current = transformQ(q_current, M_element);
405433
const z_step_rel = final_dist_m * (i / N_POINTS_PER_SEGMENT);
406434
const q_step = complexAdd(q_current, complex(z_step_rel, 0));
407435

408-
// calculateWR returns w_calc = w_actual_at_z / sqrt(M2_factor)
409436
const { w_m: w_calc, R_m } = calculateWR(q_step, lambda_in_base_medium_m);
410-
const w_actual_at_z_m = w_calc * Math.sqrt(M2_factor); // Correct to physical radius
437+
const w_actual_at_z_m = w_calc * Math.sqrt(M2_factor);
411438

412439
const z_abs_m = z_current_m + z_step_rel;
413440
const z_abs_mm = z_abs_m * 1000.0;
414441

415442
if (z_abs_mm > last_z_plotted_mm + 1e-9 && z_abs_m <= simulation_end_z_m + 1e-9) {
416443
plotData.z.push(z_abs_mm);
417-
plotData.w.push(w_actual_at_z_m * 1e6); // Store physical radius in um
444+
plotData.w.push(w_actual_at_z_m * 1e6);
418445
plotData.R.push(isFinite(R_m) ? R_m * 1000.0 : (R_m > 0 ? Infinity : -Infinity));
419446
last_z_plotted_mm = z_abs_mm;
420447
}

0 commit comments

Comments
 (0)