.. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. .. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: .. "auto_examples/waveguides/plot_02_tapered_capillary.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_examples_waveguides_plot_02_tapered_capillary.py: A tapered capillary: compression, a dispersive wave and mode conversion ======================================================================= A capillary whose bore narrows along its length is a second handle on soliton dynamics beside the fill. Narrowing the core raises the intensity for the same energy, which accelerates self-compression, and strengthens the waveguide's anomalous dispersion, which moves the zero-dispersion wavelength -- and with it the resonant dispersive wave -- towards shorter wavelengths. A taper also does something a straight bore cannot: it **converts** between modes. The field is written on the modes of the *local* bore, and those modes change shape as the bore narrows, so propagating in them costs a term, the connection ``C_mn = (a'/a) 2 s_m s_n u_m u_n / (u_m^2 - u_n^2)`` (:doc:`/guide/waveguides/taper-coupling`). ``prop_capillary`` carries it by default on every multimode taper, with or without ``wall=CoherentWall()``; the wall adds its own coherent loss on top. ``taper_coupling=False`` is the **adiabatic** approximation, each mode following its local shape without linear taper conversion, which this example runs beside the coupled taper for comparison. The fibre here is a metre of helium-filled capillary: 50 cm of 100 um core radius, a linear taper to 60 um over the next 20 cm, and 30 cm at 60 um. The pump is a 10 fs, 150 uJ pulse at 800 nm. All runs carry four ``HE_1m`` modes. First, at a nanojoule, where nonlinear transfer is negligible for this comparison, the taper three times: with the coherent wall, without it (the connection alone) and adiabatically; this isolates the linear conversion and separates the connection's share from the wall's. Then at full energy, three times: the taper with the coherent wall, the same taper adiabatically, with each mode's own Marcatili loss and no wall, and the straight 100 um bore with the wall. **What to look for.** In the linear runs the coherent wall alone holds a few parts in a million of the launch in ``HE_12`` to ``HE_14`` along the straight bore -- the coherent exchange a lossy boundary makes between modes -- and ``HE_12`` then jumps by a factor of twenty or more *at the start of the transition*, where the connection switches on; the taper without the wall makes the same jump there, having held nothing in the straight bore, so the jump is the connection's, and the adiabatic run leaves every higher mode below 1e-13. At full energy the pulse compresses and emits its dispersive wave in the narrow section, earlier than in the straight bore and further into the ultraviolet; there the Kerr overlap between the modes moves far more energy into the higher modes than the taper does, from the first centimetre, so the connection shows only in the weakest channel. **What is approximate.** The taper connection is derived for the Dirichlet ``J_0`` basis the capillary modes are built on, and it is exact for that basis; the coherent wall is the condensed paraxial Robin condition, and its validity ratios are printed from the run's record (the run warns when the quasi-static one comes within a factor ten of its limit, as it does in the 60 um section at the longest wavelengths). The gas index is optmat's helium fit, qualified over the whole band (from 60 nm). .. GENERATED FROM PYTHON SOURCE LINES 60-65 The set-up ---------- The taper is a :class:`~solprop.profiles.PiecewiseLinear` profile of the core radius. Its corners are where the linear operator's intervals begin and end, and the adaptive stepper lands on each of them. .. GENERATED FROM PYTHON SOURCE LINES 65-152 .. code-block:: Python import time import warnings import matplotlib.pyplot as plt import numpy as np from solprop.driver import prop_capillary from solprop.exceptions import ( AdiabaticTaperWarning, GrazingIncidenceWarning, SolpropWarning, ) from solprop.modes import CoherentWall from solprop.plotting import prop_2d from solprop.processing import ( bandpass, fwhm_t, get_spectral_density, grid_of, ) from solprop.processing import energy as energy_of from solprop.processing import peak_power as peak_power_of from solprop.profiles import PiecewiseLinear LENGTH = 1.0 """Capillary length, m.""" CORNERS = (0.0, 0.5, 0.7, LENGTH) """Where the straight, tapering and straight sections meet, m.""" TAPER = PiecewiseLinear.from_stages(CORNERS, [100e-6, 100e-6, 60e-6, 60e-6]) """Core radius along z, m.""" PRESSURE = 1.5 """Helium pressure, bar.""" PUMP = {"lambda0": 800e-9, "fwhm": 10e-15, "energy": 150e-6} """The pump: wavelength (m), duration (s) and energy (J).""" UV = (100e-9, 300e-9) """The band the dispersive wave is counted in, m.""" started = time.perf_counter() def propagate(radius, wall, backend="auto", energy=PUMP["energy"], taper_coupling=True): """Propagate the pump along `radius`, with or without the wall and the coupling.""" with warnings.catch_warnings(): # The wall's quasi-static ratio approaches its limit at the longest # wavelengths of the narrow section: the approximation named above, which # each run would repeat. An AdiabaticTaperWarning would name the comparison # this example draws on purpose, so it is silenced too. The coherent # wall's generator is resolved along z to a Chebyshev tail at the round-off # floor of its samples, which it reports as a SolpropWarning. for category in ( GrazingIncidenceWarning, AdiabaticTaperWarning, SolpropWarning, ): warnings.simplefilter("ignore", category) return prop_capillary( radius, LENGTH, "He", PRESSURE, wavelength_limits=(90e-9, 3e-6), trange=250e-15, modes=4, wall=wall, taper_coupling=taper_coupling, dz_max=2e-3, n_save=200, progress="none", backend=backend, lambda0=PUMP["lambda0"], fwhm=PUMP["fwhm"], energy=energy, ) def shares(run): """Return the positions and each mode's energy over the launched energy.""" per_mode = np.asarray(energy_of(run)) return np.asarray(run["z"]), per_mode / per_mode[0].sum() .. GENERATED FROM PYTHON SOURCE LINES 153-161 The linear conversion --------------------- One nanojoule, so that the Kerr effect is ten orders of magnitude below everything else. With the coupling declined, the adiabatic taper's multimode set on a radius profile with corners is carried by the linear operator's general z table, which every CPU backend runs (the native backend evaluates it in its own kernels at each step's positions), so the default backend runs it too. The coupled runs, with or without the wall, carry the taper in the coupled flow instead. .. GENERATED FROM PYTHON SOURCE LINES 161-217 .. code-block:: Python linear = { "coherent wall": propagate(TAPER, CoherentWall(), energy=1e-9), "no wall (coupled)": propagate(TAPER, None, energy=1e-9), "no wall (adiabatic)": propagate(TAPER, None, energy=1e-9, taper_coupling=False), } fig, ax = plt.subplots(figsize=(6.4, 4.0)) for (name, run), style in zip(linear.items(), ("-", "--", ":"), strict=True): z, fraction = shares(run) for mode in range(1, 4): ax.semilogy( z, np.maximum(fraction[:, mode], 1e-30), style, color=f"C{mode}", label=f"HE_1{mode + 1}, {name}", ) for corner in CORNERS[1:3]: ax.axvline(corner, color="0.5", linewidth=0.8, linestyle="--") ax.set_ylim(1e-14, 1e-1) ax.set_xlabel("z (m)") ax.set_ylabel("fraction of the launched energy") ax.set_title("1 nJ: the conversion is the taper's") ax.legend(frameon=False, fontsize="x-small", ncol=3) fig.tight_layout() # **Check the weak channel near the taper corner.** A spurious spike in a # nearly empty mode can be hidden by good conservation of total energy. The coupled # flow applies its explicit residual only on the requested band; the spectral collar # around it is carried by each section's frozen block alone. Were the residual # applied in the collar too, the plane at z = 0.6985 m, just before the transition # ends, would show HE14 a thousand times above its neighbours, a numerical artefact # rather than a conversion. Compare # that plane's HE14 share with its neighbours'. Smoothness is a useful diagnostic, # but does not replace step, band and mode-count refinement; see # :doc:`/guide/numerics/accuracy`. for name, run in linear.items(): z, fraction = shares(run) near = int(np.argmin(np.abs(z - 0.6985))) around = ", ".join( f"z = {z[index]:.4f} m {fraction[index, 3]:.2e}" for index in (near - 5, near, near + 5) ) print(f"{name:20s} HE_14 near the corner: {around}") for name, run in linear.items(): z, fraction = shares(run) before = int(np.argmin(np.abs(z - CORNERS[1]))) after = int(np.argmin(np.abs(z - (CORNERS[2] + 0.01)))) for label, index in (("z = 0.5 m", before), ("z = 0.71 m", after)): text = ", ".join( f"HE_1{m + 2} {share:.2e}" for m, share in enumerate(fraction[index, 1:]) ) print(f"{name:20s} {label:9s} {text}") .. image-sg:: /auto_examples/waveguides/images/sphx_glr_plot_02_tapered_capillary_001.png :alt: 1 nJ: the conversion is the taper's :srcset: /auto_examples/waveguides/images/sphx_glr_plot_02_tapered_capillary_001.png :class: sphx-glr-single-img .. rst-class:: sphx-glr-script-out .. code-block:: none coherent wall HE_14 near the corner: z = 0.6734 m 3.60e-07, z = 0.6985 m 4.59e-07, z = 0.7236 m 1.52e-06 no wall (coupled) HE_14 near the corner: z = 0.6734 m 4.40e-07, z = 0.6985 m 3.35e-07, z = 0.7236 m 1.58e-07 no wall (adiabatic) HE_14 near the corner: z = 0.6734 m 9.60e-20, z = 0.6985 m 8.80e-20, z = 0.7236 m 7.31e-20 coherent wall z = 0.5 m HE_12 2.67e-05, HE_13 5.56e-06, HE_14 1.13e-06 coherent wall z = 0.71 m HE_12 1.71e-04, HE_13 7.74e-06, HE_14 1.49e-06 no wall (coupled) z = 0.5 m HE_12 2.50e-05, HE_13 7.25e-06, HE_14 2.77e-06 no wall (coupled) z = 0.71 m HE_12 1.28e-04, HE_13 3.01e-06, HE_14 2.54e-07 no wall (adiabatic) z = 0.5 m HE_12 4.05e-14, HE_13 2.75e-15, HE_14 1.40e-19 no wall (adiabatic) z = 0.71 m HE_12 1.54e-14, HE_13 1.71e-15, HE_14 7.48e-20 .. GENERATED FROM PYTHON SOURCE LINES 218-221 The nonlinear runs ------------------ The same fibre at 150 uJ, with and without the wall, and the straight bore. .. GENERATED FROM PYTHON SOURCE LINES 221-229 .. code-block:: Python runs = { "taper, coherent wall": propagate(TAPER, CoherentWall()), "taper, no wall (adiabatic)": propagate(TAPER, None, taper_coupling=False), "straight 100 um, coherent wall": propagate(100e-6, CoherentWall()), } walled = runs["taper, coherent wall"] .. GENERATED FROM PYTHON SOURCE LINES 230-234 What the record says about the flow: how many frozen sections the run was cut into, the Chebyshev degree along z the wall's generator was resolved at, whether the connection was carried (it is, because the radius varies), the advisory step, and the worst of the three validity ratios over the whole run. .. GENERATED FROM PYTHON SOURCE LINES 234-240 .. code-block:: Python flow = walled.metadata.modal["flow"] for key in ("model", "n_sections", "n_deg", "connection", "advisory_step"): print(f"{key:14s} {flow[key]}") print(f"{'quasi_static':14s} {flow['quasi_static']:.3g}") .. rst-class:: sphx-glr-script-out .. code-block:: none model mean impedance coherent wall n_sections 4 n_deg 17 connection True advisory_step 0.000167777 quasi_static 0.0952 .. GENERATED FROM PYTHON SOURCE LINES 241-244 The walled taper's propagation map, summed over the modes. The spectrum broadens slowly along the straight section and explodes at the end of the taper, where the pulse compresses and the dispersive wave appears near 130 nm. .. GENERATED FROM PYTHON SOURCE LINES 244-256 .. code-block:: Python figures = prop_2d( walled, "wavelength", wavelength_range=(90e-9, 1.4e-6), trange=(-30e-15, 30e-15), db_min=-40, modes="sum", ) figures[-1].set_size_inches(9.5, 3.6) figures[-1].tight_layout() .. image-sg:: /auto_examples/waveguides/images/sphx_glr_plot_02_tapered_capillary_002.png :alt: Propagation (sum) :srcset: /auto_examples/waveguides/images/sphx_glr_plot_02_tapered_capillary_002.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 257-261 Compression along the fibre --------------------------- The peak of the total power: the taper compresses the pulse earlier than the straight bore does, because it squeezes the same energy into a smaller area. .. GENERATED FROM PYTHON SOURCE LINES 261-281 .. code-block:: Python fig, ax = plt.subplots(figsize=(6.4, 3.6)) for name, run in runs.items(): ax.plot(np.asarray(run["z"]), peak_power_of(run, modes="sum") * 1e-9, label=name) for corner in CORNERS[1:3]: ax.axvline(corner, color="0.5", linewidth=0.8, linestyle="--") ax.set_xlabel("z (m)") ax.set_ylabel("peak power (GW)") ax.legend(frameon=False, fontsize="small") fig.tight_layout() for name, run in runs.items(): power = peak_power_of(run, modes="sum") best = int(np.argmax(power)) print( f"{name:31s} peak {power[best] * 1e-9:6.1f} GW at z = " f"{np.asarray(run['z'])[best]:.3f} m, " f"{fwhm_t(run, plane=best, modes='sum') * 1e15:.2f} fs there" ) .. image-sg:: /auto_examples/waveguides/images/sphx_glr_plot_02_tapered_capillary_003.png :alt: plot 02 tapered capillary :srcset: /auto_examples/waveguides/images/sphx_glr_plot_02_tapered_capillary_003.png :class: sphx-glr-single-img .. rst-class:: sphx-glr-script-out .. code-block:: none taper, coherent wall peak 32.9 GW at z = 0.673 m, 1.79 fs there taper, no wall (adiabatic) peak 32.7 GW at z = 0.673 m, 1.80 fs there straight 100 um, coherent wall peak 41.5 GW at z = 0.834 m, 1.62 fs there .. GENERATED FROM PYTHON SOURCE LINES 282-290 Mode conversion at full energy ------------------------------ The energy in each mode along the fibre, as a fraction of the launched energy. The Kerr overlap between the modes fills ``HE_12`` and ``HE_13`` to 1e-3 within the first centimetre, in both runs, and the compression in the narrow section moves more; against that, the taper's own conversion -- the linear figure above -- is visible only in ``HE_14``, which the wall and the connection hold an order of magnitude above the adiabatic run before the taper and through it. .. GENERATED FROM PYTHON SOURCE LINES 290-313 .. code-block:: Python fig, ax = plt.subplots(figsize=(6.4, 4.0)) for name, style in (("taper, coherent wall", "-"), ("taper, no wall (adiabatic)", ":")): run = runs[name] z = np.asarray(run["z"]) per_mode = np.asarray(energy_of(run)) launched = per_mode[0].sum() for mode in range(1, 4): ax.semilogy( z, np.maximum(per_mode[:, mode] / launched, 1e-20), style, color=f"C{mode}", label=f"HE_1{mode + 1}, {name.split(', ')[1]}", ) for corner in CORNERS[1:3]: ax.axvline(corner, color="0.5", linewidth=0.8, linestyle="--") ax.set_ylim(1e-12, 1e-1) ax.set_xlabel("z (m)") ax.set_ylabel("fraction of the launched energy") ax.legend(frameon=False, fontsize="small", ncol=2) fig.tight_layout() .. image-sg:: /auto_examples/waveguides/images/sphx_glr_plot_02_tapered_capillary_004.png :alt: plot 02 tapered capillary :srcset: /auto_examples/waveguides/images/sphx_glr_plot_02_tapered_capillary_004.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 314-317 The same numbers at two places: just after the transition (z = 0.71 m), before the pulse has compressed, where the difference is the connection alone, and at the exit. .. GENERATED FROM PYTHON SOURCE LINES 317-328 .. code-block:: Python for name in ("taper, coherent wall", "taper, no wall (adiabatic)"): run = runs[name] per_mode = np.asarray(energy_of(run)) z = np.asarray(run["z"]) end_of_taper = int(np.argmin(np.abs(z - (CORNERS[2] + 0.01)))) for label, index in (("z = 0.71 m", end_of_taper), ("exit", -1)): shares = per_mode[index] / per_mode[0].sum() text = ", ".join(f"HE_1{m + 1} {share:.2e}" for m, share in enumerate(shares)) print(f"{name:27s} {label:9s} {text}") .. rst-class:: sphx-glr-script-out .. code-block:: none taper, coherent wall z = 0.71 m HE_11 5.86e-01, HE_12 2.66e-03, HE_13 1.29e-03, HE_14 1.06e-04 taper, coherent wall exit HE_11 2.68e-01, HE_12 8.63e-03, HE_13 1.76e-03, HE_14 2.07e-04 taper, no wall (adiabatic) z = 0.71 m HE_11 5.89e-01, HE_12 2.30e-03, HE_13 1.22e-03, HE_14 9.58e-05 taper, no wall (adiabatic) exit HE_11 2.67e-01, HE_12 8.68e-03, HE_13 1.70e-03, HE_14 1.94e-04 .. GENERATED FROM PYTHON SOURCE LINES 329-334 The dispersive wave ------------------- The exit spectra, summed over the modes. The narrower core pushes the dispersive wave further into the ultraviolet than the straight bore does, and the energy in the ultraviolet band is printed below. .. GENERATED FROM PYTHON SOURCE LINES 334-356 .. code-block:: Python fig, ax = plt.subplots(figsize=(6.4, 3.6)) for name, run in runs.items(): wavelength, density = get_spectral_density(run, "wavelength", plane=-1, modes="sum") order = np.argsort(wavelength) ax.semilogy(wavelength[order] * 1e9, density[order], label=name, linewidth=1.0) ax.set_xlim(100.0, 1200.0) ax.set_ylim(1e-4 * np.max(density), 3.0 * np.max(density)) ax.set_xlabel("wavelength (nm)") ax.set_ylabel("spectral energy density (J/m)") ax.legend(frameon=False, fontsize="small") fig.tight_layout() for name, run in runs.items(): launched = float(np.sum(energy_of(run, plane=0))) uv = float(np.sum(energy_of(run, plane=-1, bandpass=bandpass(grid_of(run), UV)))) left = float(np.sum(energy_of(run, plane=-1))) print( f"{name:31s} transmitted {left / launched:.3f}; " f"100-300 nm {uv * 1e6:.2f} uJ ({uv / launched:.2%})" ) .. image-sg:: /auto_examples/waveguides/images/sphx_glr_plot_02_tapered_capillary_005.png :alt: plot 02 tapered capillary :srcset: /auto_examples/waveguides/images/sphx_glr_plot_02_tapered_capillary_005.png :class: sphx-glr-single-img .. rst-class:: sphx-glr-script-out .. code-block:: none taper, coherent wall transmitted 0.279; 100-300 nm 14.01 uJ (9.34%) taper, no wall (adiabatic) transmitted 0.278; 100-300 nm 13.72 uJ (9.14%) straight 100 um, coherent wall transmitted 0.705; 100-300 nm 16.79 uJ (11.19%) .. GENERATED FROM PYTHON SOURCE LINES 357-360 Runtime and reproducibility --------------------------- Six complete one-metre runs of four coupled modes: three linear, three nonlinear. .. GENERATED FROM PYTHON SOURCE LINES 360-367 .. code-block:: Python settings = walled.metadata.backend.settings print( f"{time.perf_counter() - started:.0f} s; backend {walled.metadata.backend.name}, " f"{settings.get('thread_count', 'default')} threads" ) .. rst-class:: sphx-glr-script-out .. code-block:: none 55 s; backend native, 18 threads .. GENERATED FROM PYTHON SOURCE LINES 368-375 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 55.152 seconds) .. _sphx_glr_download_auto_examples_waveguides_plot_02_tapered_capillary.py: .. only:: html .. container:: sphx-glr-footer sphx-glr-footer-example .. container:: sphx-glr-download sphx-glr-download-jupyter :download:`Download Jupyter notebook: plot_02_tapered_capillary.ipynb ` .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: plot_02_tapered_capillary.py ` .. container:: sphx-glr-download sphx-glr-download-zip :download:`Download zipped: plot_02_tapered_capillary.zip `