.. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. .. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: .. "auto_tutorials/capillaries/plot_08_coherent_wall_taper.py" .. LINE NUMBERS ARE GIVEN BELOW. .. only:: html .. note:: :class: sphx-glr-download-link-note :ref:`Go to the end ` to download the full example code. .. rst-class:: sphx-glr-example-title .. _sphx_glr_auto_tutorials_capillaries_plot_08_coherent_wall_taper.py: Mode conversion and loss in a taper with a coherent wall ============================================================= A capillary whose bore varies along z is written in a basis attached to the local radius, and a moving basis costs a term: the **connection** ``C_mn = (a'/a) 2 s_m s_n u_m u_n / (u_m^2 - u_n^2)``, ``C_nn = 0``, real and antisymmetric in the true-power amplitude, so on its own it moves no power out of the retained set -- it moves it **between** the channels, and the channels it fills are the lossier ones (:doc:`/guide/waveguides/taper-coupling`). ``prop_capillary(taper, ..., wall=CoherentWall())`` carries it, and so does a multimode taper without a wall, by default, beside each channel's own Marcatili loss (the *taper coupling*, :doc:`/guide/waveguides/capillaries`); ``taper_coupling=False`` is the adiabatic route, on which every channel follows its local mode and linear taper conversion is omitted -- the comparison drawn here. The run is cut into sections on the linear operator's own intervals; inside each, the complete wall generator is **frozen** at the midpoint and integrated exactly, and everything the freezing leaves out -- the generator's variation and the connection -- is put back as an explicit term in the Runge-Kutta source. This example runs a 50 to 40 um transition over a fifth of a millimetre, launched into ``HE_11`` alone, and checks three numbers: * the walled taper keeps ``0.9287`` of the arriving energy in ``HE_11`` and puts ``0.038`` in ``HE_12`` and ``0.033`` in ``HE_13``, where the adiabatic route leaves ``5e-18`` there; * it therefore loses ``1.87e-3`` of the launched energy, more than either of the two straight bores it runs between, where the adiabatic taper loses ``6.4e-4``; * freezing is a rearrangement and not an approximation: one frozen section per interval and four of them agree to ``1.9e-10`` of the state. **Every channel keeps its own normalisation.** The cumulative operator beside a coupled block must act on every channel alike, so it renormalises a vacuum amplitude along the varying index by the lowest channel's effective index; the flow's own diagonal carries the rest of each channel's, ``-(1/2) d ln(w_n/w_1)/dz``, so the upper channels' amplitudes are good to the step error. .. GENERATED FROM PYTHON SOURCE LINES 47-60 .. code-block:: Python import warnings import matplotlib.pyplot as plt import numpy as np from solprop.boundaries import absorber from solprop.driver import prop_capillary from solprop.grid import FieldGrid from solprop.modes import CoherentWall from solprop.profiles import PiecewiseLinear from solprop.stepper import FixedSchedule .. GENERATED FROM PYTHON SOURCE LINES 61-66 The taper: straight, a linear transition, straight -- the shape of a 125 to 62.5 um capillary taper, scaled to the radius and length a ten-step run allows. The corners of the profile are where the sections start and end, and the fixed schedule lands on every one of them (a schedule that comes near a corner without landing on it is refused by name). .. GENERATED FROM PYTHON SOURCE LINES 66-132 .. code-block:: Python length, steps = 2e-4, 10 taper = PiecewiseLinear.from_stages( (0.0, 6e-5, 1.4e-4, 2e-4), [50e-6, 50e-6, 40e-6, 40e-6] ) grid = FieldGrid.from_limits(length, 800e-9, (400e-9, 1.6e-6), 300e-15) def run(wall, radius=taper, taper_coupling=True): """Propagate three channels ten steps along `radius`, with or without a wall.""" # The band runs past helium's and silica's fitted ranges and the geometry is a # marginal one on purpose, so that a fifth of a millimetre shows the wall: both # warn, and both are silenced here, as is the advisory step of the coupled flow. with warnings.catch_warnings(): warnings.simplefilter("ignore") return prop_capillary( radius, length, "He", 1.0, lambda0=800e-9, grid=grid, absorber=absorber(grid, boundary_length=1e3), fwhm=30e-15, energy=1e-9, modes=3, n_save=11, backend="numpy_ref", wall=wall, taper_coupling=taper_coupling, schedule=FixedSchedule(np.full(steps, length / steps)), dz_max=length / steps, dz_init=length / steps, plasma=False, raman=False, progress="none", ) def channel_energy(output): """Return the positions and the energy in each channel at every save plane.""" planes = output.planes() energy = np.array( [np.asarray(plane.stats["energy"]).reshape(3, -1)[:, 0] for plane in planes] ) return np.array([plane.z for plane in planes]), energy walled, plain = run(CoherentWall()), run(None, taper_coupling=False) coupled = run(None) straight = {radius: run(CoherentWall(), radius) for radius in (50e-6, 40e-6)} for name, output in ( ("taper, CoherentWall()", walled), ("taper, no wall", coupled), ("taper, adiabatic", plain), ("50 um straight, wall", straight[50e-6]), ("40 um straight, wall", straight[40e-6]), ): _, energy = channel_energy(output) lost = 1.0 - energy[-1].sum() / energy[0].sum() shares = energy[-1] / energy[-1].sum() print( f"{name:22s} lost {lost:.3e}; of what arrives: HE_11 {shares[0]:.5f}, " f"HE_12 {shares[1]:.2e}, HE_13 {shares[2]:.2e}" ) .. rst-class:: sphx-glr-script-out .. code-block:: none taper, CoherentWall() lost 1.667e-03; of what arrives: HE_11 0.92811, HE_12 3.86e-02, HE_13 3.33e-02 taper, no wall lost 6.265e-04; of what arrives: HE_11 0.92733, HE_12 3.88e-02, HE_13 3.38e-02 taper, adiabatic lost 6.423e-04; of what arrives: HE_11 1.00000, HE_12 4.85e-18, HE_13 1.02e-20 50 um straight, wall lost 4.409e-04; of what arrives: HE_11 1.00000, HE_12 2.57e-07, HE_13 6.27e-07 40 um straight, wall lost 8.575e-04; of what arrives: HE_11 1.00000, HE_12 9.76e-07, HE_13 2.34e-06 .. GENERATED FROM PYTHON SOURCE LINES 133-137 What the record says about the sectioning: how many frozen sections the run was cut into, the Chebyshev degree along z the generator was resolved at, whether a connection was carried, and the advisory step a declared ``dz_max`` is warned against. Ten steps of 20 um sit inside it. .. GENERATED FROM PYTHON SOURCE LINES 137-143 .. code-block:: Python flow = walled.metadata.modal["flow"] print( f"{flow['n_sections']} sections, n_deg {flow['n_deg']}, " f"connection {flow['connection']}, advisory step {flow['advisory_step']:.3e} m" ) .. rst-class:: sphx-glr-script-out .. code-block:: none 4 sections, n_deg 17, connection True, advisory step 1.053e-04 m .. GENERATED FROM PYTHON SOURCE LINES 144-149 Freezing is a rearrangement. Four frozen sections per interval freeze four different blocks and carry four different residuals, and the answer does not move: the residual is evaluated exactly, so what ``sections`` buys is a smaller explicit term, not a new physical model. At finite step size, changing this split can still change the numerical error, so section and step refinement are distinct checks. .. GENERATED FROM PYTHON SOURCE LINES 149-182 .. code-block:: Python finer = run(CoherentWall(sections=4)) coarse_field = walled.planes()[-1].field fine_field = finer.planes()[-1].field departure = np.max(np.abs(fine_field - coarse_field)) / np.max(np.abs(coarse_field)) print(f"one section per interval against four: {departure:.2e} of the state") fig, ax = plt.subplots(figsize=(6.4, 3.8), constrained_layout=True) for output, style, label in ( (plain, ":", "adiabatic"), (walled, "-", "CoherentWall()"), ): z, energy = channel_energy(output) shares = energy / energy[0, 0] shares = np.where(shares > 0.0, shares, np.nan) for channel in range(3): ax.semilogy( z * 1e6, shares[:, channel], style, color=f"C{channel}", label=f"HE_1{channel + 1}, {label}", ) for corner in (60.0, 140.0): ax.axvline(corner, color="grey", linewidth=0.6, linestyle="--") ax.set( xlabel="z (um); the taper runs between the dashed lines", ylabel="Fraction of the launched energy", title="A 50 to 40 um taper converts HE_11 into the lossier orders", ylim=(1e-22, 3.0), ) ax.legend(frameon=False, fontsize="small", ncol=2) plt.show() .. image-sg:: /auto_tutorials/capillaries/images/sphx_glr_plot_08_coherent_wall_taper_001.png :alt: A 50 to 40 um taper converts HE_11 into the lossier orders :srcset: /auto_tutorials/capillaries/images/sphx_glr_plot_08_coherent_wall_taper_001.png :class: sphx-glr-single-img .. rst-class:: sphx-glr-script-out .. code-block:: none one section per interval against four: 3.42e-05 of the state .. GENERATED FROM PYTHON SOURCE LINES 183-190 Related reading --------------- Continue with :doc:`/guide/waveguides/taper-coupling` for the model, options and limits, and :doc:`/guide/numerics/accuracy` for convergence checks. For a paced introduction to propagation, start with the :doc:`/introduction/index`. .. rst-class:: sphx-glr-timing **Total running time of the script:** (0 minutes 7.225 seconds) .. _sphx_glr_download_auto_tutorials_capillaries_plot_08_coherent_wall_taper.py: .. only:: html .. container:: sphx-glr-footer sphx-glr-footer-example .. container:: sphx-glr-download sphx-glr-download-jupyter :download:`Download Jupyter notebook: plot_08_coherent_wall_taper.ipynb ` .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: plot_08_coherent_wall_taper.py ` .. container:: sphx-glr-download sphx-glr-download-zip :download:`Download zipped: plot_08_coherent_wall_taper.zip `