示例#1
0
    def _plot_observer_spectrum(self, label_lines=False):
        """Plot the spectrum components and observer spectra on a 1x2 panel
        plot. The left panel has the components, whilst the right panel has the
        observer spectrum.

        Parameters
        ----------
        label_lines: bool
            Plot line IDs.
        """
        name = self._get_spec_key("spec")
        if name not in self.available:
            raise IOError(
                "A .spec/.log_spec file was not read in, cannot use this function"
            )

        fig, ax = plt.subplots(1, 2, figsize=(12, 5), sharey="row")

        # Plot the components of the observer spectrum, i.e Emitted, Created,
        # Disc, etc.

        for component in self.columns[:-self.n_inclinations]:
            if component in ["Lambda", "Freq."]:
                continue
            ax[0] = self._plot_thing(component,
                                     name,
                                     label_lines=label_lines,
                                     ax_update=ax[0])

        for line in ax[0].get_lines(
        ):  # Set the different spectra to have a transparency
            line.set_alpha(0.7)
        ax[0].legend(ncol=2, loc="upper right").set_zorder(0)

        # Now plot the observer spectra

        for inclination in self.inclinations:
            ax[1] = self._plot_thing(inclination,
                                     name,
                                     label_lines=label_lines,
                                     ax_update=ax[1])

        for label, line in zip(self.inclinations, ax[1].get_lines()):
            line.set_alpha(0.7)
            line.set_label(str(label) + r"$^{\circ}$")
        ax[1].set_ylabel("")
        ax[1].legend(ncol=2, loc="upper right").set_zorder(0)

        # Final clean up to make a nice spectrum

        ax[0].set_title("Components")
        ax[1].set_title("Observer spectra")
        fig = finish_figure(fig, wspace=0)

        return fig, ax
示例#2
0
def components(spectrum,
               xmin=None,
               xmax=None,
               scale="loglog",
               alpha=0.65,
               multiply_by_spatial_units=False,
               display=False):
    """Plot the different components of the spectrum.

    The components are the columns labelled with words, rather than inclination
    angles. These columns are not supposed to add together, i.e. all of them
    together shouldn't equal the Emitted spectrum, which is the angle
    averaged escaping flux/luminosity.

    Parameters
    ----------
    spectrum: pypython.Spectrum
        The spectrum object.
    xmin: float [optional]
        The minimum x boundary to plot.
    xmax: float [optional]
        The maximum x boundary to plot.
    scale: str [optional]
        The scaling of the axes.
    alpha: float [optional]
        The line transparency on the plot.
    multiply_by_spatial_units: bool [optional]
        Plot in flux or nu Lnu instead of flux density or luminosity.
    display: bool [optional]
        Display the object once plotted.

    Returns
    -------
    fig: plt.Figure
        matplotlib Figure object.
    ax: plt.Axes
        matplotlib Axes object.
    """
    fig, ax = plt.subplots(2, 1, figsize=(12, 10))

    ax[0] = _plot_subplot(ax[0], spectrum, ["CenSrc", "Disk", "WCreated"], xmin, xmax, alpha, scale,
                          multiply_by_spatial_units)
    ax[1] = _plot_subplot(ax[1], spectrum, ["Created", "Emitted", "Wind", "HitSurf"], xmin, xmax, alpha, scale,
                          multiply_by_spatial_units)

    fig = finish_figure(fig)
    fig.savefig(f"{spectrum.fp}/{spectrum.root}_{spectrum.current}_components.png")

    if display:
        plt.show()

    return fig, ax
示例#3
0
def _wind1d(m_points, parameter_points, distance_units, scale="logx", fig=None, ax=None, i=0, j=0):
    """Plot a 1D wind.

    Parameters
    ----------
    m_points: np.ndarray
        The 1st axis points, which are the r bins.
    parameter_points: np.ndarray
        The wind parameter to be plotted, in the same shape as the n_points and
        m_points arrays.
    distance_units: pypython.wind.WindDistanceUnits
        The units of the distance axis.
    scale: str [optional]
        The scaling of the axes: [logx, logy, loglog, linlin]
    fig: plt.Figure [optional]
        A Figure object to update, otherwise a new one will be created.
    ax: plt.Axes [optional]
        An axes array to update, otherwise a new one will be created.
    i: int [optional]
        The i index for the sub panel to plot onto.
    j: int [optional]
        The j index for the sub panel to plot onto.

    Returns
    -------
    fig: plt.Figure
        The (updated) Figure object for the plot.
    ax: plt.Axes
        The (updated) axes array for the plot.
    """
    if fig is None or ax is None:
        fig, ax = plt.subplots(figsize=(8, 6), squeeze=False)

    ax[i, j].plot(m_points, parameter_points)

    if distance_units == WindDistanceUnits.cm:
        ax[i, j].set_xlabel(r"$R$ [cm]")
    else:
        ax[i, j].set_xlabel(r"$r / R_{g}$")
    # ax[i, j].set_xlim(np.min(m_points[m_points > 0]), np.max(m_points))
    ax[i, j] = set_axes_scales(ax[i, j], scale)
    fig = finish_figure(fig)

    return fig, ax
示例#4
0
    def _plot_thing(self,
                    thing,
                    spec_type,
                    scale="loglog",
                    label_lines=False,
                    ax_update=None):
        """Plot a specific column in a spectrum file.

        Parameters
        ----------
        thing: str
            The name of the thing to be plotted.
        scale: str
            The scale of the axes, i.e. loglog, logx, logy, linlin.
        label_lines: bool
            Plot line IDs.
        ax_update: plt.Axes
            An plt.Axes object to update, i.e. to plot on.
        """
        if ax_update:
            ax = ax_update
        else:
            fig, ax = plt.subplots(figsize=(9, 5))

        if spec_type:
            key = spec_type
        else:
            key = self.current

        units = self.spectra[key].units
        distance = self.spectra[key].distance
        ax = set_axes_scales(ax, scale)
        ax = plot.set_axes_labels(ax, units=units, distance=distance)

        # How things are plotted depends on the units of the spectrum

        if units == SpectrumUnits.f_lm or units == SpectrumUnits.l_lm:
            x_thing = "Lambda"
        else:
            x_thing = "Freq."

        label = thing
        if thing.isdigit():
            label += r"$^{\circ}$"

        ax.plot(self.spectra[key][x_thing],
                self.spectra[key][thing],
                label=label,
                zorder=0)

        if label_lines:
            ax = plot.add_line_ids(ax,
                                   plot.common_lines(units),
                                   linestyle="none",
                                   fontsize=10)

        if ax_update:
            return ax
        else:
            fig = finish_figure(fig)
            return fig, ax
示例#5
0
def _wind2d(m_points,
            n_points,
            parameter_points,
            distance_units,
            coordinate_system,
            inclinations_to_plot=None,
            scale="loglog",
            vmin=None,
            vmax=None,
            fig=None,
            ax=None,
            i=0,
            j=0):
    """Plot a 2D wind using a contour plot.

    Parameters
    ----------
    m_points: np.ndarray
        The 1st axis points, either x or angular (in degrees) bins.
    n_points: np.ndarray
        The 2nd axis points, either z or radial bins.
    parameter_points: np.ndarray
        The wind parameter to be plotted, in the same shape as the n_points and
        m_points arrays.
    distance_units: pypython.wind.WindDistanceUnits
        The units of the distance axes.
    coordinate_system: pypython.wind.WindCoordSystem
        The coordinate system in use, either rectilinear or polar.
    inclinations_to_plot: List[str] [optional]
        A list of inclination angles to plot onto the ax[0, 0] sub panel. Must
        be strings and 0 < inclination < 90.
    scale: str [optional]
        The scaling of the axes: [logx, logy, loglog, linlin]
    vmin: float [optional]
        The minimum value to plot.
    vmax: float [optional]
        The maximum value to plot.
    fig: plt.Figure [optional]
        A Figure object to update, otherwise a new one will be created.
    ax: plt.Axes [optional]
        An axes array to update, otherwise a new one will be created.
    i: int [optional]
        The i index for the sub panel to plot onto.
    j: int [optional]
        The j index for the sub panel to plot onto.

    Returns
    -------
    fig: plt.Figure
        The (updated) Figure object for the plot.
    ax: plt.Axes
        The (updated) axes array for the plot.
    """
    if fig is None or ax is None:
        if coordinate_system == WindCoordSystem.cylindrical:
            fig, ax = plt.subplots(figsize=(8, 6), squeeze=False)
        elif coordinate_system == WindCoordSystem.polar:
            fig, ax = plt.subplots(figsize=(8, 6), squeeze=False, subplot_kw={"projection": "polar"})
        else:
            raise ValueError(f"Unknown projection, expected {WindCoordSystem.cylindrical} or {WindCoordSystem.polar}")

    im = ax[i, j].pcolormesh(m_points,
                             n_points,
                             parameter_points,
                             shading="auto",
                             vmin=vmin,
                             vmax=vmax,
                             linewidth=0,
                             rasterized=True)

    fig.colorbar(im, ax=ax[i, j])

    # this plots lines representing sight lines for different observers of
    # different inclinations

    if inclinations_to_plot:
        n_coords = np.unique(m_points)
        for inclination in inclinations_to_plot:
            if coordinate_system == WindCoordSystem.cylindrical:
                m_coords = n_coords * np.tan(0.5 * PI - np.deg2rad(float(inclination)))
            else:
                x_coords = np.logspace(np.log10(0), np.max(n_points))
                m_coords = x_coords * np.tan(0.5 * PI - np.deg2rad(90 - float(inclination)))
                m_coords = np.sqrt(x_coords**2 + m_coords**2)

            ax[0, 0].plot(n_coords, m_coords, label=inclination + r"$^{\circ}$")

        ax[0, 0].legend(loc="lower left")

    # Clean up the axes with labs and set up scales, limits etc

    if coordinate_system == WindCoordSystem.cylindrical:
        if distance_units == WindDistanceUnits.cm:
            ax[i, j].set_xlabel(r"$x$ [cm]")
            ax[i, j].set_ylabel(r"$z$ [cm]")
        else:
            ax[i, j].set_xlabel(r"$x / R_{g}$")
            ax[i, j].set_ylabel(r"$z / R_{g}$")
        ax[i, j].set_xlim(np.min(m_points[m_points > 0]), np.max(m_points))
        ax[i, j] = set_axes_scales(ax[i, j], scale)
    else:
        ax[i, j].set_theta_zero_location("N")
        ax[i, j].set_theta_direction(-1)
        ax[i, j].set_thetamin(0)
        ax[i, j].set_thetamax(90)
        ax[i, j].set_rlabel_position(90)
        if distance_units == WindDistanceUnits.cm:
            ax[i, j].set_ylabel(r"$\log_{10}(r)$ [cm]")
        else:
            ax[i, j].set_ylabel(r"$\log_{10}(r / R_{g})$")

    ax[i, j].set_ylim(np.min(n_points[n_points > 0]), np.max(n_points))
    fig = finish_figure(fig)

    return fig, ax
示例#6
0
def reprocessing(spectrum, xmin=None, xmax=None, scale="loglog", label_edges=True, alpha=0.75, display=False):
    """Create a plot to show the amount of reprocessing in the model.

    Parameters
    ----------

    Returns
    -------
    fig: plt.Figure
        matplotlib Figure object.
    ax: plt.Axes
        matplotlib Axes object.
    """
    if "spec_tau" not in spectrum.available:
        raise ValueError("There is no spec_tau spectrum so cannot create this plot")
    if "spec" not in spectrum.available and "log_spec" not in spectrum.available:
        raise ValueError("There is no observer spectrum so cannot create this plot")

    fig, ax = plt.subplots(figsize=(12, 7))
    ax2 = ax.twinx()

    ax = set_axes_scales(ax, scale)
    ax2 = set_axes_scales(ax2, scale)

    # Plot the optical depth

    spectrum.set("spec_tau")

    for n, inclination in enumerate(spectrum.inclinations):
        y = spectrum[inclination]
        if np.count_nonzero == 0:
            continue
        ax.plot(spectrum["Freq."], y, label=str(inclination) + r"$^{\circ}$", alpha=alpha)

    ax.legend(loc="upper left")
    ax.set_xlim(xmin, xmax)
    ax.set_xlabel("Rest-frame Frequency [Hz]")
    ax.set_ylabel("Continuum Optical Depth")

    if label_edges:
        ax = add_line_ids(ax, photoionization_edges(spectrum=spectrum), "none")

    ax.set_zorder(ax2.get_zorder() + 1)
    ax.patch.set_visible(False)

    # Plot the emitted and created spectrum

    spectrum.set("spec")

    for thing in ["Created", "Emitted"]:
        x, y = get_xy_subset(spectrum["Freq."], spectrum[thing], xmin, xmax)

        if spectrum.units == SpectrumUnits.f_lm:
            y *= spectrum["Lambda"]
        else:
            y *= spectrum["Freq."]

        ax2.plot(x, y, label=thing, alpha=alpha)

    ax2.legend(loc="upper right")
    ax2 = set_axes_labels(ax2, spectrum)

    fig = finish_figure(fig)
    fig.savefig(f"{spectrum.fp}/{spectrum.root}_reprocessing.png")

    if display:
        plt.show()

    return fig, ax
示例#7
0
def optical_depth(spectrum,
                  inclinations="all",
                  xmin=None,
                  xmax=None,
                  scale="loglog",
                  label_edges=True,
                  frequency_space=True,
                  display=False):
    """Plot the continuum optical depth spectrum.

    Create a plot of the continuum optical depth against either frequency or
    wavelength. Frequency space is the default and preferred. This function
    returns the Figure and Axes object.

    Parameters
    ----------
    spectrum: pypython.Spectrum
        The spectrum object.
    inclinations: str or list or tuple [optional]
        A list of inclination angles to plot, but all is also an acceptable
        choice if all inclinations are to be plotted.
    xmin: float [optional]
        The lower x boundary for the figure
    xmax: float [optional]
        The upper x boundary for the figure
    scale: str [optional]
        The scale of the axes for the plot.
    label_edges: bool [optional]
        Label common absorption edges of interest onto the figure
    frequency_space: bool [optional]
        Create the figure in frequency space instead of wavelength space
    display: bool [optional]
        Display the final plot if True.

    Returns
    -------
    fig: plt.Figure
        matplotlib Figure object.
    ax: plt.Axes
        matplotlib Axes object.
    """
    fig, ax = plt.subplots(1, 1, figsize=(12, 7))
    current = spectrum.current
    spectrum.set("spec_tau")

    # Determine if we're plotting in frequency or wavelength space and then
    # determine the inclinations we want to plot

    if frequency_space:
        xlabel = "Freq."
        units = SpectrumUnits.f_nu
    else:
        xlabel = "Lambda"
        units = SpectrumUnits.f_lm

    if not xmin:
        xmin = np.min(spectrum[xlabel])
    if not xmax:
        xmax = np.max(spectrum[xlabel])

    inclinations = _get_inclinations(spectrum, inclinations)

    # Now loop over the inclinations and plot each one, skipping ones which are
    # completely empty

    for inclination in inclinations:
        if inclination != "all":
            if inclination not in spectrum.inclinations:  # Skip inclinations which don't exist
                continue

        x, y = get_xy_subset(spectrum[xlabel], spectrum[inclination], xmin, xmax)
        if np.count_nonzero(y) == 0:  # skip arrays which are all zeros
            continue

        ax.plot(x, y, label=f"{inclination}" + r"$^{\circ}$")

    ax = set_axes_scales(ax, scale)
    ax.set_ylabel(r"Continuum Optical Depth")

    if frequency_space:
        ax.set_xlabel(r"Rest-frame Frequency [Hz]")
    else:
        ax.set_xlabel(r"Rest-frame Wavelength [$\AA$]")

    ax.legend(loc="upper left")

    if label_edges:
        ax = add_line_ids(ax, photoionization_edges(units=units), linestyle="none", offset=0)

    spectrum.set(current)
    fig = finish_figure(fig)
    fig.savefig(f"{spectrum.fp}/{spectrum.root}_spec_optical_depth.png")

    if display:
        plt.show()

    return fig, ax
示例#8
0
def observer(spectrum,
             inclinations,
             xmin=None,
             xmax=None,
             scale="logy",
             multiply_by_spatial_units=False,
             label_lines=True,
             display=False):
    """Plot the request observer spectrum.

    If all is passed to inclinations, then all the observer angles will be
    plotted on a single figure. This function will only be available if there
    is a .spec or .log_spec file available in the passed spectrum, it does not
    work for spec_tot, etc. For these spectra, plot.spectrum.spectrum_components
    should be used instead, or plot.plot for something finer tuned.

    Parameters
    ----------
    spectrum: pypython.Spectrum
        The spectrum being plotted, "spec" or "log_spec" should be the active
        spectrum.
    inclinations: list or str
        The inclination angles to plot.
    xmin: float [optional]
        The lower x boundary of the plot.
    xmax: float [optional]
        The upper x boundary of the plot.
    scale: str [optional]
        The scale of the axes.
    multiply_by_spatial_units: bool [optional]
        Plot the flux instead of flux density.
    label_lines: bool [optional]
        Label common spectrum lines.
    display: bool [optional]
        Display the figure once plotted.

    Returns
    -------
    fig: plt.Figure
        matplotlib Figure object.
    ax: plt.Axes
        matplotlib Axes object.
    """

    if spectrum.current not in ["spec", "log_spec"]:
        spectrum.set("spec")

    fig, ax = plt.subplots(1, 1, figsize=(12, 5))

    inclinations = _get_inclinations(spectrum, inclinations)

    wrong_input = []
    for inclination in inclinations:
        if inclination not in spectrum.inclinations:
            wrong_input.append(inclination)

    if len(wrong_input) > 0:
        print(f"The following inclinations provided are not in the spectrum inclinations and will be skipped:"
              f" {', '.join(wrong_input)}")
        for to_remove in wrong_input:
            inclinations.remove(to_remove)

    if len(inclinations) == 0:
        print(f"Returning empty figure without creating plot as there is nothing to plot")
        return fig, ax

    ax = _plot_subplot(ax, spectrum, inclinations, xmin, xmax, 1.0, scale, multiply_by_spatial_units)

    if label_lines:
        ax = add_line_ids(ax, common_lines(spectrum=spectrum), "none")

    fig = finish_figure(fig)
    fig.savefig(f"{spectrum.fp}/{spectrum.root}_{spectrum.current}.png")

    if display:
        plt.show()

    return fig, ax
示例#9
0
def multiple_models(output_name,
                    spectra,
                    spectrum_type,
                    things_to_plot,
                    xmin=None,
                    xmax=None,
                    multiply_by_spatial_units=False,
                    alpha=0.7,
                    scale="loglog",
                    label_lines=True,
                    log_spec=False,
                    smooth=None,
                    distance=None,
                    display=False):
    """Plot multiple spectra, from multiple models, given in the list of
    spectra provided.

    Spectrum file paths are passed and then each spectrum is loaded in as a
    Spectrum object. Each spectrum must have the same units and are also assumed
    to be at the same distance.

    In this function, it is possible to compare Emitted or Created spectra. It
    is agnostic to the type of spectrum file being plotted, unlike
    spectrum_observer.

    todo: label absorption edges if spec_tau is selected

    Parameters
    ----------
    output_name: str
        The name of the output .png file.
    spectra: str
        The file paths of the spectra to plot.
    spectrum_type: str
        The type of spectrum to plot, i.e. spec or spec_tot.
    things_to_plot: str or list of str or tuple of str
        The things which will be plotted, i.e. '45' or ['Created', '45', '60']
    xmin: float [optional]
        The lower x boundary of the plot
    xmax: float [optional]
        The upper x boundary for the plot
    multiply_by_spatial_units: bool [optional]
        Plot in flux units, instead of flux density.
    alpha: float [optional]
        The transparency of the plotted spectra.
    scale: str [optional]
        The scaling of the axes.
    label_lines: bool [optional]
        Label common emission and absorption features, will not work with
        spec_tau.
    log_spec: bool [optional]
        Use either the linear or logarithmically spaced spectra.
    smooth: int [optional]
        The amount of smoothing to apply to the spectra.
    distance: float [optional]
        The distance to scale the spectra to in parsecs.
    display: bool [optional]
        Display the figure after plotting, or don't.

    Returns
    -------
    fig: plt.Figure
        matplotlib Figure object.
    ax: plt.Axes
        matplotlib Axes object.
    """
    if type(spectra) is str:
        spectra = list(spectra)

    spectra_to_plot = []

    for spectrum in spectra:
        if type(spectrum) is not Spectrum:
            root, fp = get_root_name(spectrum)
            spectra_to_plot.append(Spectrum(root, fp, log_spec, smooth, distance, spectrum_type))
        else:
            spectra_to_plot.append(spectrum)

    units = list(dict.fromkeys([spectrum.units for spectrum in spectra_to_plot]))

    if len(units) > 1:
        msg = ""
        for spectrum in spectra_to_plot:
            msg += f"{spectrum.units} : {spectrum.fp}{spectrum.root}.{spectrum.current}\n"
        raise ValueError(f"Some of the spectra have different units, unable to plot:\n{msg}")

    # Now, this is some convoluted code to get the inclination angles
    # get only the unique, sorted, values if inclination == "all"

    if things_to_plot == "all":
        things_to_plot = ()
        for spectrum in spectra_to_plot:  # have to do it like this, as spectrum.inclinations is a tuple
            things_to_plot += spectrum.inclinations
        if len(things_to_plot) == 0:
            raise ValueError("\"all\" does not seem to have worked, try specifying what to plot instead")
        things_to_plot = tuple(sorted(list(dict.fromkeys(things_to_plot))))  # Gets sorted, unique values from tuple
    else:
        things_to_plot = things_to_plot.split(",")
        things_to_plot = tuple(things_to_plot)

    n_to_plot = len(things_to_plot)
    n_rows, n_cols = subplot_dims(n_to_plot)

    if n_rows > 1:
        figsize = (12, 12)
    else:
        figsize = (12, 5)

    fig, ax = plt.subplots(n_rows, n_cols, figsize=figsize, squeeze=False)
    fig, ax = remove_extra_axes(fig, ax, n_to_plot, n_rows * n_cols)
    ax = ax.flatten()

    for n, thing in enumerate(things_to_plot):
        for spectrum in spectra_to_plot:
            try:
                y = spectrum[thing]
            except KeyError:
                continue  # We will skip key errors, as models may have different inclinations

            if np.count_nonzero(y) == 0:  # skip arrays which are all zeros
                continue

            if multiply_by_spatial_units:
                if spectrum.units == SpectrumUnits.f_lm:
                    y *= spectrum["Lambda"]
                else:
                    y *= spectrum["Freq."]

            if spectrum.units == SpectrumUnits.f_lm:
                x = spectrum["Lambda"]
            else:
                x = spectrum["Freq."]

            x, y = get_xy_subset(x, y, xmin, xmax)
            label = spectrum.fp.replace("_", r"\_") + spectrum.root.replace("_", r"\_")
            ax[n].plot(x, y, label=label, alpha=alpha)

        ax[n] = set_axes_scales(ax[n], scale)
        ax[n] = set_axes_labels(ax[n], spectra_to_plot[0], multiply_by_spatial_units=multiply_by_spatial_units)

        if thing.isdigit():
            ax[n].set_title(f"{thing}" + r"$^{\circ}$")
        else:
            ax[n].set_title(f"{thing}")

        if label_lines:
            ax[n] = add_line_ids(ax[n], common_lines(spectrum=spectra_to_plot[0]), "none")

    ax[0].legend(fontsize=10).set_zorder(0)
    fig = finish_figure(fig)
    fig.savefig(f"{output_name}.png")

    if display:
        plt.show()

    return fig, ax