Skip to content

plot_phase_space

Classes¤

PlotPhaseSpace ¤

PlotPhaseSpace(
    run_number,
    z_pos="end",
    working_dir="",
    distribution_file="generator.in",
    run_file="photo_track.in",
    log_dir="log",
    log_file_name="plots_log.log",
    console_log=True,
    plots_dir="plots",
)

Bases: PlotCore

Source code in plot_analysis/plot_phase_space.py
def __init__(self, run_number, z_pos="end", working_dir="", distribution_file="generator.in", run_file="photo_track.in", log_dir="log", log_file_name="plots_log.log", console_log=True, plots_dir="plots") -> None:
    super().__init__(working_dir, distribution_file, run_file, log_dir, log_file_name, console_log, plots_dir)

    self.plots_dir = self.plots_dir.joinpath(f"RUN_{run_number}_phase_space")
    self.plots_dir.mkdir(exist_ok=True, parents=True)

    self.run_number = run_number
    self.z_pos = z_pos

    self.data = self.prepare_data(run_number, z_pos)

    self.number_of_events = self.data.shape[0]

Functions¤

_charge_density_plot ¤
_charge_density_plot(p, z_pos)

Prepares the charge density plot.

Parameters:

Name Type Description Default
p str

name of the axis (or time), along which to plot the density

required
z_pos int

z position, at which to plot

required

Returns:

Type Description
figure

plt.figure: Chare density figure

Source code in plot_analysis/plot_phase_space.py
def _charge_density_plot(self, p:str, z_pos:int) -> plt.figure:
    """Prepares the charge density plot.

    Args:
        p (str): name of the axis (or time), along which to plot the density
        z_pos (int): z position, at which to plot

    Returns:
        plt.figure: Chare density figure
    """
    df = self.data.loc[self.data["z_pos"] == z_pos]

    fig, ax = plt.subplots(figsize=(6,5))
    (counts, bins) = np.histogram(df[p], bins=50)
    one_particle_charge = df["charge"].mean()
    bin_length = (bins[-1]-bins[0])/(len(bins)-1)
    ax.hist(bins[:-1], bins=bins, weights=counts*abs(one_particle_charge)/bin_length)

    if p == "t":
        plt.xlabel("$t$ [ps]")
        plt.ylabel("Linear charge density [nC/ps]")
    else:
        plt.xlabel(f"$\Delta {p}$ [mm]")
        plt.ylabel("Linear charge density [nC/mm]")

    return fig
_density_plot ¤
_density_plot(p, z_pos)

Prepares the macroparticle density figure.

Parameters:

Name Type Description Default
p str

name of the axis, along which to plot the density

required
z_pos int

z position, at which to plot

required

Returns:

Type Description
figure

plt.figure: Macroparticle density figure

Source code in plot_analysis/plot_phase_space.py
def _density_plot(self, p:str, z_pos) -> plt.figure:
    """Prepares the macroparticle density figure.

    Args:
        p (str): name of the axis, along which to plot the density
        z_pos (int): z position, at which to plot

    Returns:
        plt.figure: Macroparticle density figure
    """
    df = self.data.loc[self.data["z_pos"] == z_pos]

    fig, ax = plt.subplots(figsize=(6,5))

    (counts, bins) = np.histogram(df[p], bins=100)
    ax.hist(bins[:-1], bins=bins, weights = counts, density=True)

    plt.xlabel(f"${p}$ [mm]")
    plt.ylabel("Normalised particle density [mm${}^{-1}$]")

    return fig 
_relative_momentum_plot ¤
_relative_momentum_plot(p, z_pos)

Prepares the relative momentum figure.

Parameters:

Name Type Description Default
p str

the component of the momentum

required
z_pos int

z position, at which to plot

required

Returns:

Type Description
figure

plt.figure: Relative momentum figure

Source code in plot_analysis/plot_phase_space.py
def _relative_momentum_plot(self, p:str, z_pos:int) -> plt.figure:
    """Prepares the relative momentum figure.

    Args:
        p (str): the component of the momentum
        z_pos (int): z position, at which to plot

    Returns:
        plt.figure: Relative momentum figure
    """
    df = self.data.loc[self.data["z_pos"] == z_pos]

    horizontal = df[p] if p != "z" else df["delta_z"]
    vertical = df[f"p{p}/pz"] if p != "z" else df["delta_pz/pz"]

    # remove values far away from median
    # h_quantile_index = (abs(horizontal) < horizontal.quantile(.99)).replace({True: 1,  False: 0})
    # if p == "z":
    #     v_quantile_index = (abs(vertical) < vertical.quantile(.9)).replace({True: 1,  False: 0})
    # else:
    #     v_quantile_index = (abs(vertical) < 2).replace({True: 1,  False: 0})
    # index = (h_quantile_index + v_quantile_index) == 2
    # horizontal = horizontal[index]
    # vertical = vertical[index]
    # print(len(vertical))

    fig = plt.figure(figsize=(6, 6))
    gs = fig.add_gridspec(2, 2,  width_ratios=(3, 1), height_ratios=(1, 3),
                  left=0.1, right=0.9, bottom=0.1, top=0.9,
                  wspace=0.05, hspace=0.05)

    ax = fig.add_subplot(gs[1, 0])
    additionalHgap = (horizontal.max() - horizontal.min())*0.05/0.9
    additionalVgap = (vertical.max() - vertical.min())*0.05/0.9
    hm_bins = [np.linspace(horizontal.min()-additionalHgap, horizontal.max()+additionalHgap, 110),
            np.linspace(vertical.min()-additionalVgap, vertical.max()+additionalVgap, 110)]

    hm = ax.hist2d(horizontal, vertical, bins=hm_bins, cmin=0.0001, cmap=plt.cm.jet)
    # plt.colorbar(hm[3], ax=ax, label="Number of particles")

    ax_histx = fig.add_subplot(gs[0, 0], sharex=ax)
    ax_histy = fig.add_subplot(gs[1, 1], sharey=ax)
    ax_histx.tick_params(axis="x", labelbottom=False)
    ax_histy.tick_params(axis="y", labelleft=False)

    (v_counts, v_bins) = np.histogram(vertical, bins=50)
    (h_counts, h_bins) = np.histogram(horizontal, bins=50)
    ax_histx.hist(h_bins[:-1], bins=h_bins, weights = h_counts)
    ax_histy.hist(v_bins[:-1], bins=v_bins, weights = v_counts, orientation='horizontal')

    # make info label
    ax.plot([], [], " ", label=f"Total particles: {self.number_of_events}", c="white")
    ax.legend(loc="lower right")

    ax.set_xlabel((f"${p}$" if p != "z" else "$\Delta z$") + " [mm]")
    ax.set_ylabel((f"$p_{p}/p_z$ " if p != "z" else "$\Delta p_z/p_z$") + " [mrad]")

    ax_histy.set_xlabel("N")
    ax_histx.set_ylabel("N")

    return fig
_transverse_particle_density_plot ¤
_transverse_particle_density_plot(z_pos)

Function to build the particle density plot (heatmap) and side histograms.

Parameters:

Name Type Description Default
z_pos int

z position, at which to plot

required
Source code in plot_analysis/plot_phase_space.py
def _transverse_particle_density_plot(self, z_pos:int):
    """Function to build the particle density plot (heatmap) and side histograms.

    Args:
        z_pos (int): z position, at which to plot
    """
    df = self.data.loc[self.data["z_pos"] == z_pos]

    horizontal = df["x"]
    vertical = df[f"y"]

    fig = plt.figure(figsize=(6, 6))
    gs = fig.add_gridspec(2, 2,  width_ratios=(3, 1), height_ratios=(1, 3),
                  left=0.1, right=0.9, bottom=0.1, top=0.9,
                  wspace=0.05, hspace=0.05)

    ax = fig.add_subplot(gs[1, 0])
    # create heatmap bins
    hm_bins = [np.linspace(horizontal.min(), horizontal.max(), 50),
            np.linspace(vertical.min(), vertical.max(), 50)]

    hm = ax.hist2d(horizontal, vertical, bins=hm_bins, cmin=0.0001, cmap=plt.cm.jet)
    # plt.colorbar(hm[3], ax=ax, label="Number of particles")

    ax_histx = fig.add_subplot(gs[0, 0], sharex=ax)
    ax_histy = fig.add_subplot(gs[1, 1], sharey=ax)
    ax_histx.tick_params(axis="x", labelbottom=False)
    ax_histy.tick_params(axis="y", labelleft=False)

    (v_counts, v_bins) = np.histogram(vertical, bins=50)
    (h_counts, h_bins) = np.histogram(horizontal, bins=50)
    ax_histx.hist(h_bins[:-1], bins=h_bins, weights = h_counts)
    ax_histy.hist(v_bins[:-1], bins=v_bins, weights = v_counts, orientation='horizontal')

    # make info label
    ax.plot([], [], " ", label=f"Total particles: {self.number_of_events}", c="white")
    ax.legend()

    ax.set_xlabel("$x$ [mm]")
    ax.set_ylabel("$y$ [mm]")

    ax_histy.set_xlabel("N")
    ax_histx.set_ylabel("N")

    return fig, gs, ax, ax_histy, ax_histx
plot ¤
plot()

Plots the relative momentum, macroparticle particle densities and charge densities. Main function to use.

Source code in plot_analysis/plot_phase_space.py
def plot(self) -> None:
    """Plots the relative momentum, macroparticle particle densities and charge densities. Main function to use.
    """
    self.logger.info("Starting plotting phase space plots.")
    self.plot_relative_momentum()
    self.plot_densities()
    self.plot_charge_density()
    self.plot_transverse_particle_density()
    self.logger.info(f"Phase space plots done. They can be found in {self.plots_dir}")

    self.save_data()
plot_charge_density ¤
plot_charge_density()

Plots the charge density along x,y,z axis and along time.

Source code in plot_analysis/plot_phase_space.py
def plot_charge_density(self) -> None:
    """Plots the charge density along x,y,z axis and along time.
    """
    for p in ["x", "y", "z", "t"]:
        self.logger.info(f"Ploting longitudial charge density dependance on {p}.")
        fig = self._charge_density_plot(p, self.z_pos)               

        fig_name = self.plots_dir.joinpath(f"charge_density_{p}.png")
        fig.savefig(fig_name, dpi=300, bbox_inches="tight")
        plt.close()
plot_densities ¤
plot_densities()

Plots the macroparticle densities along the axes x,y,z.

Source code in plot_analysis/plot_phase_space.py
def plot_densities(self) -> None:
    """Plots the macroparticle densities along the axes x,y,z.
    """
    for p in ["x", "y", "z"]:
        self.logger.info(f"Ploting particle densities for {p} coordinate.")
        fig = self._density_plot(p, self.z_pos)
        fig_name = self.plots_dir.joinpath(f"density_{p}.png")
        fig.savefig(fig_name, dpi=300, bbox_inches="tight")
        plt.close()
plot_relative_momentum ¤
plot_relative_momentum()

Plots the relative momentum in the axes x,y,z.

Source code in plot_analysis/plot_phase_space.py
def plot_relative_momentum(self) -> None:
    """Plots the relative momentum in the axes x,y,z.
    """
    if self.z_pos == 0:
        self.logger.warning("Skipping plots of relative momentum due to z position set to 0 (momentum plots do not make sense at this position).")
        return
    for p in ["x", "y", "z"]:
        try:
            self.logger.info(f"Ploting relative momentum for {p} coordinate.")
            fig = self._relative_momentum_plot(p, self.z_pos)

            fig_name = self.plots_dir.joinpath(f"relative_momentum_{p}.png")
            fig.savefig(fig_name, dpi=300, bbox_inches="tight")
            plt.close()
        except ValueError:
            self.logger.warning(f"Cant plot relative momentum for {p}, skipping.")
plot_transverse_particle_density ¤
plot_transverse_particle_density()

Plots the transverse particle density at self.z_pos.

Source code in plot_analysis/plot_phase_space.py
def plot_transverse_particle_density(self) -> None:
    """Plots the transverse particle density at self.z_pos.
    """
    fig, gs, ax, ax_histy, ax_histx = self._transverse_particle_density_plot(self.z_pos)

    fig_name = self.plots_dir.joinpath(f"transverse_particle_density.png")
    fig.savefig(fig_name, dpi=300, bbox_inches="tight")
    plt.close()
prepare_data ¤
prepare_data(run_number, z_pos)

Method to prepare the data for plotting.

Parameters:

Name Type Description Default
run_number int

number of the run, from which to take the data

required
z_pos in

z position, from which to take the data

required

Returns:

Type Description
DataFrame

pd.DataFrame: dataframe containing the prepared data

Source code in plot_analysis/plot_phase_space.py
def prepare_data(self, run_number, z_pos) -> pd.DataFrame:
    """Method to prepare the data for plotting.

    Args:
        run_number (int): number of the run, from which to take the data
        z_pos (in): z position, from which to take the data

    Returns:
        pd.DataFrame: dataframe containing the prepared data
    """
    # load data
    df = self.get_at_zpos(run_number, z_pos)

    if df.loc[df["flag"] < 0].shape[0] != 0:
        self.logger.warning("There were particles lost in this run!")
        self.logger.warning("Removing lost particles from the plots.")
        df = df.loc[df["flag"] < 0]

    for p in ["x", "y", "z", "t"]:
        df[p] = df[p]*1000 # to mm and ps respectively
    # first particle is reference -> shows absolute position, the other show relative pos to this one
    absolute_pz = df.loc[0, "pz"]
    absolute_t = df.loc[0, "t"]
    # set to 0 so everything is relative
    df.loc[0, "z"] = 0
    df.loc[0, "t"] = 0 
    df.loc[0, "pz"] = 0 

    df["px/pz"] = df["px"]/(df["pz"]+absolute_pz)*1000 # to mrad
    df["py/pz"] = df["py"]/(df["pz"]+absolute_pz)*1000 # to mrad
    df["delta_z"] = df["z"]
    df["delta_pz/pz"] = df["pz"]/(df["pz"]+absolute_pz) # to mrad

    df["z_pos"] = z_pos

    return df
save_data ¤
save_data()

Saves the data to an excel file.

Source code in plot_analysis/plot_phase_space.py
def save_data(self):
    """Saves the data to an excel file.
    """
    self.logger.info(f"Saving data to an excel file: {self.plots_dir.joinpath('data.xlsx')}.")
    self.data.to_excel(self.plots_dir.joinpath("data.xlsx"))     
start_3D_plot ¤
start_3D_plot()

Method to start the 3D visualisation in dash.

Source code in plot_analysis/plot_phase_space.py
def start_3D_plot(self):
    """Method to start the 3D visualisation in dash.
    """
    self.logger.info("Starting the 3D bunch visualisation.")
    plotter3D = phase_space_3D.Plot3DPhaseSpace(self.run_number, working_dir=self.working_dir)
    plotter3D.start_dash_app()      

Last update: October 31, 2023
Created: October 31, 2023