Example

Refer to the following examples to get started with simplex-ui.

Python Script Examples

The following python scripts instruct the usage of the classes and functions in simplex-ui to set the parameters and configurations and how to visualize the results of pre- and post-processing. The sample files are available here.

In the 1st example, SIMPLEX is launched in the interactive mode with the Chrome browser, and an FEL simulation is executed for 10-keV SASE. Then, the simulation results are plotted in term of the gain curve (undulator length vs. pulse energy), and evolution of radiation profiles.

Example 1

  • simulate the SASE FEL of 10-keV radiation

  • plot the gain curve and visualize the growth of the radiation profiles

import sys
isjupyter = "ipykernel" in sys.modules

import simplex

if isjupyter:
    # start with CLI mode
    simplex.Start(mode="c")
    import plotly.io as pio
    pio.renderers.default = "notebook" # to enable exporting as an HTML file
    from IPython.display import display, HTML
else:
    # start with default settings
    # Mode = interactive, Browser = Chrome, Source Files = remote
    simplex.Start()

# open "sample_sase.json" in the current directory
simplex.Open("sample_sase.json")

# export radiation profile data (longer time needed)
simplex.Set("datadump", "temporal", True)
simplex.Set("datadump", "spectral", True)
simplex.Set("datadump", "spatial", True)
simplex.Set("datadump", "angular", True)

# start simulation with an output file of "./output/sample1.json"
simplex.StartSimulation(folder="./output", prefix="sample1", serial=-1)

if isjupyter:
    display(HTML("<h2>Gain Curve of SASE FEL"))
    simplex.PostProcessCLI.Plot(item="Pulse Energy", config={"yscale":"Logarithmic"})

    display(HTML("<h2>Growth of Temporal and Spectral Profiles"))
    simplex.PostProcessCLI.Plot(item=["Temporal Profile", "Spectral Profile"])

    display(HTML("<h2>Growth of Spatial and Angular Profiles"))
    simplex.PostProcessCLI.Plot(item=["Spatial Profile", "Angular Profile"])
else:
    # plot the gain curve (pulse energy vs. undulator length)
    simplex.PostProcess.Plot("Pulse Energy")

    # switch to semi-log scale
    simplex.PostProcess.PlotScale(y="log")

    # plot in a new window
    simplex.PostProcess.DuplicatePlot("Gain Curve: Pulse Energy vs Undulator Length")

    # visualize the growth of temporal and spectral profiles
    simplex.PostProcess.Plot("Temporal Profile")
    simplex.PostProcess.MultiPlot(item="Spectral Profile")
    simplex.PostProcess.MultiPlotCols(2)
    simplex.PostProcess.SetSlide(-1)
    simplex.PostProcess.DuplicatePlot("Growth of Temporal and Spectral Profiles")

    # visualize the growth of spatial and angular profiles
    simplex.PostProcess.Plot("Spatial Profile")
    simplex.PostProcess.MultiPlot(item="Angular Profile")
    simplex.PostProcess.SetSlide(-1)
    simplex.PostProcess.ShowColorScale(False)
    simplex.PostProcess.DuplicatePlot("Growth of Spatial and Angular Profiles")

if not isjupyter:
    # wait for the user's response
    input("Completed. Press enter to exit. ")
    # exit
    simplex.Exit()

Example 2

  • SASE FEL of 10-keV radiation without/with the wakefield and compensation by undulator tapering

  • compare the three results in the post-processor and export an ASCII file

import sys
isjupyter = "ipykernel" in sys.modules

import simplex

if isjupyter:
    simplex.Start(mode="c")
    import plotly.io as pio
    pio.renderers.default = "notebook" # to enable exporting as an HTML file
    from IPython.display import display, HTML
else:
    simplex.Start()

# open "sample.json" in the current directory
simplex.Open("sample_sase.json")

# start simulation with an output file of "./output/sample2-1.json"
simplex.StartSimulation(folder="./output", prefix="sample2", serial=1)

if isjupyter:
    display(HTML("<h2>Gain Curve of SASE FEL"))
    simplex.PostProcessCLI.Plot(item="Pulse Energy", config={"yscale":"Logarithmic"})
else:
    # plot the gain curve (pulse energy vs. undulator length)
    simplex.PostProcess.Plot("Pulse Energy")
    simplex.PostProcess.PlotScale(y="log")

# turn on the wakefield
simplex.Set("wake", "wakeon", True)
simplex.PreProcess.Plot("Wakefield Temporal Profile")
if not isjupyter:
    simplex.PreProcess.DuplicatePlot("Wakefield along the electron bunch")
simplex.StartSimulation()

# compensating the energy loss by undulator tapering (with rough optimization)
simplex.Set("undulator", "taper", "Stair-Like")
simplex.Set("undulator", "opttype", "Compensate Wake")
simplex.StartSimulation()

# check the undulator tapering
if isjupyter:
    display(HTML("<h2>K Value Trend"))
    simplex.PostProcessCLI.Plot(item="K Value Trend")
else:
    simplex.PostProcess.Plot("K Value Trend")
    simplex.PostProcess.DuplicatePlot("K value variation to compensate for the energy loss")

# compare with the former results
if isjupyter:
    display(HTML("<h2>Comparison: 1/2: without/with wake, 3 compensation by tapering"))
    simplex.PostProcessCLI.Plot(data=["sample2-1", "sample2-2", "sample2-3"], item="Pulse Energy")
else:
    simplex.PostProcess.SelectData("sample2-1")
    simplex.PostProcess.Plot("Pulse Energy")
    simplex.PostProcess.PlotScale(y="linear")
    simplex.PostProcess.ComparativePlot("sample2-2", "sample2-3")
    simplex.PostProcess.DuplicatePlot("1/2: without/with wake, 3 compensation by tapering")
    # export the comparative plot as an ASCII file
    simplex.PostProcess.Export("./output/comparative.txt")

if not isjupyter:
    input("Completed. Press enter to exit. ")
    simplex.Exit()

Example 3

  • SASE FEL of 10-keV radiation with three different combinations of electron energies and undulator K values

  • compare the three results in the post-processor

import sys
isjupyter = "ipykernel" in sys.modules

import simplex

if isjupyter:
    simplex.Start(mode="c")
    import plotly.io as pio
    pio.renderers.default = "notebook" # to enable exporting as an HTML file
    from IPython.display import display, HTML
else:
    simplex.Start()

# open "sample.json" in the current directory
simplex.Open("sample_sase.json")

EGeV = [6, 8, 10]

for E in EGeV:
    simplex.Set("ebeam", "eenergy", E) # change the electron energy
    simplex.Set("felprm", "e1st", 10000) # set the photon energy; K value is automatically set
    print(f"K@{E}GeV = {simplex.Get("undulator", "K"):.3f}")
    simplex.PreProcess.OptimizeLattice([10,10]) # adjust the beta-matching condition
    simplex.StartSimulation(folder="./output", prefix="sample3", serial=E)

# plot the gain curve (pulse energy vs. undulator length)
if isjupyter:
    display(HTML("<h2>10-keV SASE FEL with different energies and K values"))
    simplex.PostProcessCLI.Plot(data=["sample3-6", "sample3-8", "sample3-10"],
        item="Pulse Energy", config={"yscale":"Logarithmic"})
else:
    simplex.PostProcess.SelectData("sample3-6")
    simplex.PostProcess.Plot("Pulse Energy")
    simplex.PostProcess.ComparativePlot("sample3-8", "sample3-10")
    simplex.PostProcess.PlotScale(y="log")

if not isjupyter:
    input("Completed. Press enter to exit. ")
    simplex.Exit()

Example 4

  • shot-to-shot fluctuation of SASE FEL simulated by varying the seed number for the random number

  • compare the gain curve and spectral/temporal profiles

import sys
isjupyter = "ipykernel" in sys.modules

import simplex

if isjupyter:
    simplex.Start(mode="c")
    import plotly.io as pio
    pio.renderers.default = "notebook" # to enable exporting as an HTML file
    from IPython.display import display, HTML
else:
    simplex.Start()

# open "sample.json" in the current directory
simplex.Open("sample_sase.json")

# export the temporal and spectral proiles
simplex.Set("datadump", "temporal", True)
simplex.Set("datadump", "spectral", True)

# number of shots
Nshots = 5

# scan the random number seed to simulate the shot-to-shot fluctuation
simplex.Scan("condition", "randseed", 1, Nshots, interval=1, folder="./output", prefix="sample4", iniSN=1)

# data names
datanames = []
for n in range(Nshots):
    datanames.append("sample4"+"_"+str(n+1))

if isjupyter:
    display(HTML("<h2>Gain Curve of SASE FEL"))
    simplex.PostProcessCLI.Plot(data=datanames, item="Pulse Energy", config={"yscale":"Logarithmic"})
    display(HTML("<h2>Temporal Profiles"))
    simplex.PostProcessCLI.Plot(data=datanames, item="Temporal Profile")
    display(HTML("<h2>Spectral Profiles"))
    simplex.PostProcessCLI.Plot(data=datanames, item="Temporal Profile")
else:
    simplex.PostProcess.SelectData("sample4_1")
    # gain curves
    simplex.PostProcess.Plot("Pulse Energy")
    simplex.PostProcess.ComparativePlot(*datanames)
    simplex.PostProcess.PlotScale(y="log")
    simplex.PostProcess.DuplicatePlot("Shot-to-shot fluctuation of the gain curve")
    # temporal profiles
    simplex.PostProcess.Plot("Temporal Profile")
    simplex.PostProcess.ComparativePlot(*datanames)
    simplex.PostProcess.SetSlide(-1)
    simplex.PostProcess.DuplicatePlot("Shot-to-shot fluctuation of the temporal profile")
    # temporal profiles
    simplex.PostProcess.Plot("Spectral Profile")
    simplex.PostProcess.ComparativePlot(*datanames)
    simplex.PostProcess.SetSlide(-1)
    simplex.PostProcess.DuplicatePlot("Shot-to-shot fluctuation of the spectral profile")

if not isjupyter:
    input("Completed. Press enter to exit. ")
    simplex.Exit()

Example 5

  • optimization of undulator taper to maximize the pulse energy

  • 2-stage simulation: 1st without taper, 2nd scan taper

import sys
isjupyter = "ipykernel" in sys.modules

import simplex

if isjupyter:
    simplex.Start(mode="c")
    import plotly.io as pio
    pio.renderers.default = "notebook" # to enable exporting as an HTML file
    from IPython.display import display, HTML
else:
    simplex.Start()

# open "sample.json" in the current directory
simplex.Open("sample_sase.json")

# stop at the 6th segment and export the raw data
simplex.Set("datadump", "particle", True)
simplex.Set("datadump", "radiation", True)
simplex.Set("datadump", "expstep", "All Segments")

# start simulation with an output file of "./output/sample5-1.json"
simplex.StartSimulation(folder="./output", prefix="sample5", serial=1)

# import the above simulation result
simplex.Set("ebeam", "bmprofile", "SIMPLEX Output")
simplex.Set("seed", "seedprofile", "SIMPLEX Output")
simplex.Set("spxout", "spxfile", "./output/sample5-1.json")

# matching distance = 6.15 (segment interval) - 270 (#periods) x 0.018 (period) = 1.29 m
simplex.Set("spxout", "matching", 1.29)

# kill the raw data export
simplex.Set("datadump", "particle", False)
simplex.Set("datadump", "radiation", False)

# assume stair-like taper from the 1st segment
simplex.Set("undulator", "taper", "Stair-Like")
simplex.Set("undulator", "opttype", "N.A.")
simplex.Set("undulator", "initial", 1)

taperinit = [5, 6] # optimize taper starting after the 5-th or 6-th segment
Mtotal = 12 # total segments
Nt = 4 # taper scan points

datanames = []
for k in taperinit:
    # use the result at the exit of the k-th segment
    simplex.Set("spxout", "spxstep", k-Mtotal)

    # number of segments in the taper
    simplex.Set("undulator", "segments", Mtotal-k)

    # data names in this run
    for n in range(Nt):
        datanames.append("sample5-"+str(k)+"_"+str(n))

    if k%2 == 0: # starting from an odd-number segment
        simplex.Set("lattice", "ltype", "FUDU (QF-U-QD-U)")
    else: # starting from an even-number segment
        simplex.Set("lattice", "ltype", "DUFU (QD-U-QF-U)")
    simplex.PreProcess.OptimizeLattice([10,10])

    # scan the taper value
    simplex.Scan("undulator", "base", 0, -0.0012, Nt, folder="./output", serial=k, iniSN=0)

# check the result; compare with the former result
if isjupyter:
    display(HTML("<h2>Taper optimization starting from 5/6-th segment"))
    simplex.PostProcessCLI.Plot(data=["sample5-1", *datanames], item="Pulse Energy")
else:
    simplex.PostProcess.SelectData("sample5-1")
    simplex.PostProcess.Plot("Pulse Energy")
    simplex.PostProcess.ComparativePlot(*datanames)
    simplex.PostProcess.PlotScale(y="linear")
    simplex.PostProcess.DuplicatePlot("Taper optimization starting from 5/6-th segment")

if not isjupyter:
    input("Completed. Press enter to exit.")
    simplex.Exit()

Example 6

  • double-color SASE FEL by applying different K values in the first and second sections

  • visualize the variation of spectral profile

import sys
isjupyter = "ipykernel" in sys.modules

import simplex

if isjupyter:
    simplex.Start(mode="c")
    import plotly.io as pio
    pio.renderers.default = "notebook" # to enable exporting as an HTML file
    from IPython.display import display, HTML
else:
    simplex.Start()

# open "sample.json" in the current directory
simplex.Open("sample_sase.json")

simplex.Set("undulator", "taper", "Custom")
simplex.Set("datadump", "spectral", True)

taper = []
for n in range(12):
    taper.append(["0.02" if n >= 5 else "-0.02", "0"])
    # first 5 segments with lower K, last 7 segments with higher K
simplex.Set("undulator", "tapercustom", taper)

# start simulation with an output file of "./output/sample6.json"
simplex.StartSimulation(folder="./output", prefix="sample6", serial=-1)

if isjupyter:
    display(HTML("<h2>Example of double-color SASE FEL"))
    simplex.PostProcessCLI.Plot(item="Spectral Profile")
else:
    simplex.PostProcess.Plot("Spectral Profile")
    simplex.PostProcess.StartAnimation()

if not isjupyter:
    input("Completed. Press enter to exit. ")
    simplex.Exit()

Example 7

  • double-pulse SASE FEL by inserting a chicane to tune the delay in between

  • visualize the growth of temporal profile

import sys
isjupyter = "ipykernel" in sys.modules

import simplex

if isjupyter:
    simplex.Start(mode="c")
    import plotly.io as pio
    pio.renderers.default = "notebook" # to enable exporting as an HTML file
    from IPython.display import display, HTML
else:
    simplex.Start()

# open "sample.json" in the current directory
simplex.Open("sample_sase.json")

# setup chicane parameters
simplex.Set("chicane", "chicaneon", True)
simplex.Set("chicane", "delay", 10)
simplex.Set("chicane", "chpos", 5)

# expand the temporal window
simplex.Set("condition", "simrange", [-1e-6, 5e-6])
simplex.StartSimulation(folder="./output", prefix="sample7", serial=1)

# rearrange the macroparticle distribution to be consistent with the extinction of microbunching
simplex.Set("chicane", "rearrange", True)
simplex.StartSimulation(folder="./output", prefix="sample7", serial=2)

if isjupyter:
    display(HTML("<h2>Example of double-pulse SASE FEL"))
    simplex.PostProcessCLI.Plot(data=["sample7-1", "sample7-2"], item="Temporal Profile")
else:
    # compare the two schemes
    simplex.PostProcess.SelectData("sample7-1")
    simplex.PostProcess.Plot("Temporal Profile")
    simplex.PostProcess.ComparativePlot("sample7-2")
    simplex.PostProcess.SetSlide(-1)

if not isjupyter:
    input("Completed. Press enter to exit. ")
    simplex.Exit()

Example 8

  • evaluate the effects of the trajectory errors on the FEL gain

  • errors caused by the BPM misalignment and injection error

import sys
isjupyter = "ipykernel" in sys.modules

import simplex

if isjupyter:
    simplex.Start(mode="c")
    import plotly.io as pio
    pio.renderers.default = "notebook" # to enable exporting as an HTML file
    from IPython.display import display, HTML
else:
    simplex.Start()

# open "sample.json" in the current directory
simplex.Open("sample_sase.json")

# ideal trajectory
simplex.StartSimulation(folder="./output", prefix="sample8", serial=0)

# trajectory error due to BPM misalignment
Ncases = 4
simplex.Set("alignment", "BPMalign", "Specify Tolerance")
# BPM alignment tolerance scanned from 5um to 20um
simplex.ScanXY("alignment", "xytol", 5e-3, 0.02, Ncases, link=True, serial=1, iniSN=1)

# data names
datanames = []
for n in range(Ncases):
    datanames.append("sample8-1"+"_"+str(n+1))

# plot and compare the results
if isjupyter:
    display(HTML("<h2>Gain reduction by the BPM misalignment"))
    simplex.PostProcessCLI.Plot(data=["sample8-0", *datanames],
        item="Pulse Energy", config={"yscale":"Logarithmic"})
else:
    simplex.PostProcess.Plot("Pulse Energy")
    simplex.PostProcess.PlotScale(y="log")
    simplex.PostProcess.SelectData("sample8-0")
    simplex.PostProcess.ComparativePlot(*datanames)
    simplex.PostProcess.DuplicatePlot("Gain reduction by the BPM misalignment")

# betatron oscillation due to injection error
simplex.Set("alignment", "BPMalign", "Ideal")
simplex.Set("dispersion", "einjec", True)
# injection error scanned from 1urad to 4urad
simplex.ScanXY("dispersion", "exyp", 1e-3, 4e-3, Ncases, link=True, serial=2, iniSN=1)

# data names
datanames = []
for n in range(Ncases):
    datanames.append("sample8-2"+"_"+str(n+1))

# plot and compare the results
if isjupyter:
    display(HTML("<h2>Gain reduction by the injection error"))
    simplex.PostProcessCLI.Plot(data=["sample8-0", *datanames],
        item="Pulse Energy", config={"yscale":"Logarithmic"})
else:
    simplex.PostProcess.Plot("Pulse Energy")
    simplex.PostProcess.PlotScale(y="log")
    simplex.PostProcess.SelectData("sample8-0")
    simplex.PostProcess.ComparativePlot(*datanames)
    simplex.PostProcess.DuplicatePlot("Gain reduction by the injection error")

if not isjupyter:
    input("Completed. Press enter to exit. ")
    simplex.Exit()

Example 9

  • evaluate the effects due to misalignment in the undulator line

  • discrepancy in K values / phase mismatch in the drift section

import sys
isjupyter = "ipykernel" in sys.modules

import simplex

if isjupyter:
    simplex.Start(mode="c")
    import plotly.io as pio
    pio.renderers.default = "notebook" # to enable exporting as an HTML file
    from IPython.display import display, HTML
else:
    simplex.Start()

# open "sample.json" in the current directory
simplex.Open("sample_sase.json")

# ideal alignment
simplex.StartSimulation(folder="./output", prefix="sample9", serial=0)

Ncases = 4
simplex.Set("alignment", "ualign", "Specify Tolerance")
# K-value tolerance scanned from 2e-3 to 8e-3
simplex.Scan("alignment", "Ktol", 2e-3, 8e-3, Ncases, link=True, serial=1, iniSN=1)

# data names
datanames = []
for n in range(Ncases):
    datanames.append("sample9-1"+"_"+str(n+1))

# plot and compare the results
if isjupyter:
    display(HTML("<h2>Gain reduction by the K value discrepancy between segments"))
    simplex.PostProcessCLI.Plot(data=["sample9-0", *datanames], item="Pulse Energy", config={"yscale":"Logarithmic"})
else:
    simplex.PostProcess.Plot("Pulse Energy")
    simplex.PostProcess.PlotScale(y="log")
    simplex.PostProcess.SelectData("sample9-0")
    simplex.PostProcess.ComparativePlot(*datanames)
    simplex.PostProcess.DuplicatePlot("Gain reduction by the K value discrepancy between segments")

# slippage tolerance scanned from 30 deg. to 120 deg.
simplex.Scan("alignment", "sliptol", 30, 120, Ncases, link=True, serial=2, iniSN=1)

# data names
datanames = []
for n in range(Ncases):
    datanames.append("sample9-2"+"_"+str(n+1))

if isjupyter:
    display(HTML("<h2>Gain reduction by the mismatched phase between segments"))
    simplex.PostProcessCLI.Plot(data=["sample9-0", *datanames], item="Pulse Energy", config={"yscale":"Logarithmic"})
else:
    simplex.PostProcess.Plot("Pulse Energy")
    simplex.PostProcess.PlotScale(y="log")
    simplex.PostProcess.SelectData("sample9-0")
    simplex.PostProcess.ComparativePlot(*datanames)
    simplex.PostProcess.DuplicatePlot("Gain reduction by the mismatched phase between segments")

if not isjupyter:
    input("Completed. Press enter to exit. ")
    simplex.Exit()

Example 10

  • SASE FEL of 10-keV radiation using particle distribution data saved in an ASCII file

  • adjust the injection condition to eliminate positional and angular errors at the lasing slice

import sys
isjupyter = "ipykernel" in sys.modules

import simplex

if isjupyter:
    simplex.Start(mode="c")
    import plotly.io as pio
    pio.renderers.default = "notebook" # to enable exporting as an HTML file
    from IPython.display import display, HTML
else:
    simplex.Start()

# open "sample.json" in the current directory
simplex.Open("sample_sase.json")

# change "Bunch Profile" configuration
simplex.Set("ebeam", "bmprofile", "Particle Distribution")
simplex.Set("ebeam", "partfile", "./particles.dat")

# configure the format of the data file
simplex.PreProcess.LoadParticle()
simplex.PreProcess.ParticleDataFormat(unitE="gamma", pcharge=1e-15, bins=25)
    # energy unit = gamma, charge/particle = 2fC, #bins/RMS bunch length = 25
if isjupyter:
    display(HTML("<h2>E-t Phase Space"))
simplex.PreProcess.PlotParticles(x="s (m)", y="Energy (GeV)", max=50000)
if not isjupyter:
    simplex.PreProcess.DuplicatePlot("E-t Phase Space")
if isjupyter:
    display(HTML("<h2>Expected Saturation Power"))
simplex.PreProcess.PlotSliceParameter("Saturation Power")
if not isjupyter:
    simplex.PreProcess.DuplicatePlot("Expected Saturation Power")

# export spatial profile of radiation
simplex.Set("datadump", "spatial", True)
simplex.Set("condition", "simrange", [-2e-6,3e-6])
simplex.Set("condition", "beamlets", 2e6)

# start simulation without change
simplex.StartSimulation(folder="./output", prefix="sample10", serial=1)
if isjupyter:
    display(HTML("<h2>Temporal Profile"))
    simplex.PostProcessCLI.Plot(item="Temporal Profile")
else:
    simplex.PostProcess.Plot("Temporal Profile")
    simplex.PostProcess.SetSlide(-1)
    simplex.PostProcess.DuplicatePlot()

# adjust the injection condition; roughly evaluated by the particles distribution
simplex.Set("dispersion", "einjec", True)
simplex.Set("dispersion", "exy", [-3e-2, 0]) # horizozntal position offset 30um
simplex.Set("dispersion", "exyp", [3e-3, 0]) # horizozntal angular offset -3urad
simplex.StartSimulation(folder="./output", prefix="sample10", serial=2)

# compare the two results: gain curve
if isjupyter:
    display(HTML("<h2>Gain Curve"))
    simplex.PostProcessCLI.Plot(data=["sample10-1", "sample10-2"], item="Pulse Energy")
else:
    simplex.PostProcess.SelectData("sample10-1")
    simplex.PostProcess.Plot("Pulse Energy")
    simplex.PostProcess.PlotScale(y="log")
    simplex.PostProcess.ComparativePlot("sample10-2")
    simplex.PostProcess.DuplicatePlot()

# compare the two results: growth of the spatial profile
if isjupyter:
    display(HTML("<h2>Spatial Profile"))
    simplex.PostProcessCLI.Plot(data=["sample10-1", "sample10-2"], item="Spatial Profile")
else:
    simplex.PostProcess.Plot("Spatial Profile")
    simplex.PostProcess.PlotScale(y="log")
    simplex.PostProcess.ComparativePlot("sample10-2")
    simplex.PostProcess.ComparativePlotCols(2)
    simplex.PostProcess.StartAnimation()

if not isjupyter:
    input("Completed. Press enter to exit. ")
    simplex.Exit()

Example 11

  • HGHG (high gain harmonic generation) FEL

  • 2-stage simulation: 1st modulator, 2nd radiator

import sys
isjupyter = "ipykernel" in sys.modules

import simplex

if isjupyter:
    simplex.Start(mode="c")
    import plotly.io as pio
    pio.renderers.default = "notebook" # to enable exporting as an HTML file
    from IPython.display import display, HTML
else:
    simplex.Start()

# open "sample_mod.json" to simulate the particle motion in the modulator
simplex.Open("sample_mod.json")

# seed wavelength: 266 nm
simplex.Set("felprm", "l1st", 266)

# 2(h+2) = 44 particles needed to simulate up to 20th harmonic (h=20)
simplex.Set("condition", "particles", 44)

# dump the particle data at the final step
simplex.Set("datadump", "particle", True)
simplex.Set("datadump", "expstep", "Final Step")

# start simulation in the modulator
simplex.StartSimulation(folder="./output", prefix="sample11", serial=1)

# verify the energy modulation
if isjupyter:
    display(HTML("<h2>Particle distribution in E-t space with 10um R<sub>56</sub>"))
    simplex.PostProcessCLI.RunDataProcessing(\
        data="sample11-1", item="Particle Motion", timerange="Set Window", timewindow=[-1, 1], r56pp=1e-5)
else:
    simplex.PostProcess.SetDataProcessing("item", "Particle Motion")
    simplex.PostProcess.SetDataProcessing("timerange", "Set Window")
    simplex.PostProcess.SetDataProcessing("timewindow", [-1, 1])
    simplex.PostProcess.SetDataProcessing("r56pp", 1e-5)
    simplex.PostProcess.RunDataProcessing()
    simplex.PostProcess.SymbolPlot(1)
    simplex.PostProcess.DuplicatePlot("Particle distribution in E-t space with 10um R<sub>56</sub>")

# open "sample_rad.json" to simulate the harmonic generation in the radiator
simplex.Open("sample_rad.json")

# import the simulation result in the modulator
simplex.Set("ebeam", "bmprofile", "SIMPLEX Output")
simplex.Set("spxout", "spxfile", "./output/sample11-1.json")

harmonics = [10, 15, 20] # 10th, 15th and 20th harmonics
nr = 5 # number of R56 scan for each harmonic
r56 = [0, 2e-5] # scan R56 from 0 to 20um
fr = 0.01  # spectral range (+-1%)  to show

for harmonic in harmonics:
    simplex.Set("felprm", "l1st", 266/harmonic)
    simplex.Scan("ebeam", "r56", r56[0], r56[1], nr, folder="./output", prefix="sample11", serial=harmonic)
    datanames = []
    enh = 1240/266*harmonic # central photon energy
    for sn in range(1, nr):
        datanames.append("sample11-"+str(harmonic)+"_"+str(sn))
    if isjupyter:
        display(HTML("<h2>Spectrum of "+str(harmonic)+"th-harmonic radiation"))
        simplex.PostProcessCLI.Plot(data=["sample11-"+str(harmonic)+"_0", *datanames],
            item="Spectral Profile", config={"xrange":[enh*(1-fr),enh*(1+fr)]})
    else:
        simplex.PostProcess.SelectData("sample11-"+str(harmonic)+"_0")
        simplex.PostProcess.Plot("Spectral Profile")
        simplex.PostProcess.ComparativePlot(*datanames)
        simplex.PostProcess.PlotRange(x=[enh*(1-fr),enh*(1+fr)])
        simplex.PostProcess.SetSlide(-1)
        simplex.PostProcess.DuplicatePlot("Spectrum of "+str(harmonic)+"th-harmonic radiation")

if not isjupyter:
    input("Completed. Press enter to exit.")
    simplex.Exit()

Example 12

  • EEHG (echo enhanced harmonic generation) FEL

  • 3-stage simulation: 1st/2nd modulator, 3rd radiator

import sys
isjupyter = "ipykernel" in sys.modules

import simplex

if isjupyter:
    simplex.Start(mode="c")
    import plotly.io as pio
    pio.renderers.default = "notebook" # to enable exporting as an HTML file
    from IPython.display import display, HTML
else:
    simplex.Start()

# open "sample_mod.json" to simulate the particle motion in the modulator 1&2
simplex.Open("sample_mod.json")

# reduce the bunch charge and length
simplex.Set("ebeam", "bunchleng", 5e-6)
simplex.Set("ebeam", "bunchcharge", 0.05)

# reduce the seed power
simplex.Set("seed", "pkpower", 5e7)

# increase particles/beamlet to cover up to 50th harmonic
simplex.Set("condition", "particles", 102)

# shrink the simulation temporal window
simplex.Set("condition", "simrange", [-12e-6,12e-6])

# dump the particle data at the final step
simplex.Set("datadump", "particle", True)
simplex.Set("datadump", "expstep", "Final Step")

# start simulation in the 1st modulator
simplex.StartSimulation(folder="./output", prefix="sample12", serial=1)

# verify the energy modulation
if isjupyter:
    display(HTML("<h2>Particle distribution at the 1st modulator exit with R<sub>56</sub>=5mm"))
    simplex.PostProcessCLI.RunDataProcessing(
        item="Particle Motion", timerange="Set Window", timewindow=[0, 0], r56pp=8.4e-3,
        config={"type":"Symbol", "size":1})
else:
    simplex.PostProcess.SetDataProcessing("item", "Particle Motion")
    simplex.PostProcess.SetDataProcessing("timerange", "Set Window")
    simplex.PostProcess.SetDataProcessing("timewindow", [0, 0])
    simplex.PostProcess.SetDataProcessing("r56pp", 8.4e-3)
    simplex.PostProcess.RunDataProcessing()
    simplex.PostProcess.SymbolPlot(1)
    simplex.PostProcess.DuplicatePlot("Particle distribution at the 1st modulator exit with R<sub>56</sub>=5mm")

# 2nd modulator
# import the particle data at the 1st modulator exit with R56=8.4mm
simplex.Set("condition", "simoption", "N.A.") # disable any option to enable importing SIMPLEX output (next command)
simplex.Set("ebeam", "bmprofile", "SIMPLEX Output")
simplex.Set("ebeam", "r56", 8.4e-3)
simplex.Set("spxout", "spxfile", "./output/sample12-1.json")
simplex.Set("seed", "pkpower", 5e6)
simplex.Set("seed", "timing", -4)

# start simulation in the 2nd modulator
simplex.StartSimulation(folder="./output", prefix="sample12", serial=2)

# verify the energy modulation
if isjupyter:
    display(HTML("<h2>Particle distribution at the 2nd modulator exit with R<sub>56</sub>=0.24mm"))
    simplex.PostProcessCLI.RunDataProcessing(r56pp=3.6e-4, config={"type":"Symbol", "size":1})
else:
    simplex.PostProcess.SetDataProcessing("r56pp", 3.6e-4)
    simplex.PostProcess.RunDataProcessing()
    simplex.PostProcess.SymbolPlot(1)
    simplex.PostProcess.DuplicatePlot("Particle distribution at the 2nd modulator exit with R<sub>56</sub>=0.24mm")

# open "sample_rad.json" to simulate the harmonic generation in the radiator
simplex.Open("sample_rad.json")

# import the simulation result at the 2nd modulator exit
simplex.Set("ebeam", "bmprofile", "SIMPLEX Output")
simplex.Set("spxout", "spxfile", "./output/sample12-2.json")

# shorte period and more segments for higher harmonics
simplex.Set("undulator", "lu", 30)
simplex.Set("undulator", "segments", 8)

# enable "Higher Precision" to evaluate the bunch factor more precisely
# needed because the beamlet expands across a large number of slices
simplex.Set("condition", "hpcond", True)
simplex.Set("condition", "step", 10)

# shrink the simulation temporal window
simplex.Set("condition", "simrange", [-5e-6,5e-6])

harmonics = [24, 48] # evaluate the 25th and 50th harmonics
nr = 5 # number of R56 scan for each harmonics
r56 = [3.3e-4, 3.7e-4] # scan R56 from 330um to 370um
fr = 0.01  # spectral range (+-1%)  to show

for harmonic in harmonics:
    simplex.Set("felprm", "l1st", 266/harmonic)
    simplex.Scan("ebeam", "r56", r56[0], r56[1], nr, folder="./output", prefix="sample12", serial=harmonic)
    datanames = []
    enh = 1240/266*harmonic # central photon energy
    for sn in range(1, nr):
        datanames.append("sample12-"+str(harmonic)+"_"+str(sn))
    if isjupyter:
        display(HTML("<h2>Spectrum of "+str(harmonic)+"th-harmonic radiation"))
        simplex.PostProcessCLI.Plot(data=["sample12-"+str(harmonic)+"_0", *datanames], item="Spectral Profile",
            config={"xrange":[enh*(1-fr),enh*(1+fr)]})
    else:
        simplex.PostProcess.SelectData("sample12-"+str(harmonic)+"_0")
        simplex.PostProcess.Plot("Spectral Profile")
        simplex.PostProcess.ComparativePlot(*datanames)
        simplex.PostProcess.PlotRange(x=[enh*(1-fr),enh*(1+fr)])
        simplex.PostProcess.SetSlide(-1)
        simplex.PostProcess.DuplicatePlot("Spectrum of "+str(harmonic)+"th-harmonic radiation")

if not isjupyter:
    input("Completed. Press enter to exit. ")
    simplex.Exit()

Example 13

  • oscillator FEL at 13.3 nm

  • perform simulation for a single turn, and use the radiation data for the next turn

import sys
isjupyter = "ipykernel" in sys.modules

import simplex

if isjupyter:
    simplex.Start(mode="c")
    import plotly.io as pio
    pio.renderers.default = "notebook" # to enable exporting as an HTML file
    from IPython.display import display, HTML
else:
    simplex.Start()

import numpy as np
import json
import math

# constants
Wavelen1eV = 1.23984247e-6
CC = 2.9979246e+8

# keys for radiation profiles
profkeys = ["Gain Curve", "Characteristics", "Temporal Profile", "Spectral Profile", "Spatial Profile", "Angular Profile"]

# compute the phase factor & mirror aperture
def SetApt(Apt, Ld, apt, wavel):
    bdr = math.sqrt(dDx*dDy)
    aptin = apt-bdr
    for m in range(M):
        mf = m if m <= M/2 else m-M
        qx = mf*dDx
        for n in range(N):
            nf = n if n <= N/2 else n-N
            qy = nf*dDy
            q = (math.hypot(qx, qy)-aptin)/bdr
            q = min(q, 1)
            if q >= 0:
                phase = -np.pi/wavel*Ld*(qx**2+qy**2) # phase advance in the distance of Ld
                Apt[m][2*n] = q*math.cos(phase)
                Apt[m][2*n+1] = q*math.sin(phase)

# transfer from the undulator exit to the entrance with outcoupling
def Transfer(Apt, E):
    for m in range(M):
        for n in range(N):
            dummy = E[m][2*n]
            E[m][2*n  ] = E[m][2*n]*Apt[m][2*n  ]-E[m][2*n+1]*Apt[m][2*n+1]
            E[m][2*n+1] =     dummy*Apt[m][2*n+1]+E[m][2*n+1]*Apt[m][2*n  ]

# export spatial profile (for debugging)
def Export(E, isfar, suf):
    Mh = M>>1
    Nh = N>>1
    dx = dDx if isfar else Dx
    dy = dDy if isfar else Dy
    with open("./temp/exy"+suf+".dat", "w") as f:
        for n in range(-Nh, Nh+1):
            nf = n if n >= 0 else n+N
            for m in range(-Mh, Mh+1):
                mf = m if m >= 0 else m+M
                P = E[mf][2*nf]**2+E[mf][2*nf+1]**2
                f.write(f'{m*dx*1000}\t{n*dy*1000}\t{E[mf][2*nf]}\t{E[mf][2*nf+1]}\t{P}\n')

# retrieve and store the result after a single turn
def RetrieveData(turn, fpath, profiles):
    with open(fpath, "r") as f:
        obj = json.load(f)
        for prof in profkeys:
            if prof in obj:
                dim = obj[prof]["dimension"]
                profiles[prof]["data"][dim-1].append(turn)
                ndata = len(profiles[prof]["data"])
                for n in range(dim, ndata):
                    data = obj[prof]["data"][n]
                    if dim == 1:
                        profiles[prof]["data"][n].append(data[-1])
                    else:
                        ndata = len(data)
                        nz = len(obj[prof]["data"][dim-1])
                        nslice = ndata//nz
                        sdata = np.array(data).reshape([nz, nslice])
                        profiles[prof]["data"][n] += sdata[-1].tolist()

# open "sample.json" in the current directory
simplex.Open("sample_felo.json")

# undulator specs.
Nu = simplex.Get("undulator", "periods")
lu = simplex.Get("undulator", "lu")
Lu = Nu*lu*1e-3

# start simulation with an output file of "./output/sample1.json"
simplex.StartSimulation(folder="./output", prefix="sample13", serial=0)

# get parameters used for the simulation
with open("./output/sample13-0.json", "r") as f:
    obj = json.load(f)
    config = obj["Raw Data Export"]
    K = len(config["Steps (m)"]) # steps exported
    L = len(config["Slices (m)"]) # total slices
    MN = config["Grid Points"]
    M = MN[0] # x grids
    N = MN[1] # y grids
    Nb = config["Beamlets"] # beamlets
    Np = config["Particles/Beamlet"] # particles/beamlet
    Dxy = config["Grid Intervals (m,rad)"]
    Dx = Dxy[0]
    Dy = Dxy[1]
    dDx = Dxy[2]
    dDy = Dxy[3]
    wavel = Wavelen1eV/config["Central Photon Energy (eV)"]

# adjust cavity length
sdif = -wavel*Nu/2
tdif = sdif/CC*1e15 # should be give in "fs"

# modify relevant parameters for the 1st and later turns
simplex.Set("seed", "seedprofile", "SIMPLEX Output")
simplex.Set("seed", "timing", tdif)
simplex.Set("spxout", "matching", 0)

# outcoupling: aperture with a radius of 25urad
apt = 25e-6
Apt = np.full((M, 2*N), 0.0)
SetApt(Apt, -Lu, apt, wavel)

# generate an object to store data at each turn
profiles = json.loads(json.dumps(obj))
for datakey in profkeys:
    if datakey in profiles:
        ndata = len(profiles[datakey]["titles"])
        dim = profiles[datakey]["dimension"]
        profiles[datakey]["titles"][dim-1] = "Turns"
        profiles[datakey]["units"][dim-1] = "-"
        for n in range(dim-1, ndata):
            profiles[datakey]["data"][n] = []

# save the 0th turn
RetrieveData(0, f"./output/sample13-0.json", profiles)

# data counts per step
nc = 4*2*M*N*L # bytes = 4(byte)*2(re,im)*M(x)*N(y)*L(slice)

# number of turns
turns = 30

for nt in range(turns):
    print(f"-- {nt+1} turn started --")
    fdata = f"./output/sample13-{nt%2}"

    # retrive the radiation data from the binary file "*-1.fld"
    x = np.fromfile(fdata+"-1.fld", dtype=np.float32, count=nc)
    E = x.reshape([L, M, 2*N]) # E[l][m][2*n] real part, [2*n+1] imaginary part

    # transfer the radiation field to the entrance of the undulator
    for l in range(L):
        print(f"Transferring wavefront: {l+1}/{L} slice", end="\r")
        Transfer(Apt, E[l])

    # save the data in the binary file
    x = E.reshape(2*L*M*N)
    x.tofile(fdata+"-1.fld")

    # modify the input SIMPLEX file
    simplex.Set("spxout", "spxfile", fdata+".json")

    # change the shot noise condition
    simplex.Set("condition", "randseed", nt+1)

    # start simulation for the (nt+1)-th turn
    simplex.StartSimulation(folder="./output", prefix="sample13", serial=(nt+1)%2)

    # save the (nt+1)-th turn result
    RetrieveData(nt+1, f"./output/sample13-{(nt+1)%2}.json", profiles)

# export the bundled data (growth of radiation vs. number of turns) and export in the post-processor
with open("./output/sample13.json", "w") as f:
    json.dump(profiles, f, indent=4, sort_keys=True, separators=(',', ': '))
if isjupyter:
    simplex.PostProcessCLI.Import("./output/sample13.json")
    display(HTML("<h2>Turn-by-turn growth of the pulse energy"))
    simplex.PostProcessCLI.Plot(item="Pulse Energy", config={"yscale":"Logarithmic"})
    display(HTML("<h2>Turn-by-turn evolution of the spectrum"))
    simplex.PostProcessCLI.Plot(item="Spectral Profile")
else:
    simplex.PostProcess.Import("./output/sample13.json")
    simplex.PostProcess.Plot("Pulse Energy")
    simplex.PostProcess.PlotScale(y="log")
    simplex.PostProcess.DuplicatePlot("Turn-by-turn growth of the pulse energy")
    simplex.PostProcess.Plot("Spectral Profile")
    simplex.PostProcess.DuplicatePlot("Turn-by-turn evolution of the spectrum")

if not isjupyter:
    input("Completed. Press enter to exit. ")
    simplex.Exit()

Jupyter Output Examples

The above python scripts can be executed in the Jupyter notebook as well; for reference, Jupyter notebook outputs for respective examples are exported as HTML files and are shown below.