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 } ;
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 ) ;
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