magnus.plotting

Pre-packaged figures for Mag \(\nu\) s.

Every figure in notebooks/ used to be built by hand: roughly 25–40 lines of gridspec_kw, tick locators, legend keywords and savefig per plot, copy-pasted and lightly varied. This module collapses that into one call per figure while reproducing the same output, so that switching an existing figure over to it leaves the figure unchanged.

Taking stock of the fifty-odd figures showed that most of them are the same figure with different data. Curves against baseline, curves against energy, curves against a mixing angle, and the convergence studies of the matrix-exponential notebook all share one shape: a set of curves plotted against a swept variable, optionally over a short relative-error subpanel. plot_curves() is that shape, and the plot_probability_vs_* helpers are thin wrappers that only preset labels and limits. plot_curves_stacked() is its small-multiples form: the same plot repeated once per case down a shared abscissa, where the comparison is between panels. The genuinely distinct layouts are the profile-plus-probability stack, the bi-probability plane, and the oscillogram.

API conventions

The functions take named arguments for the quantities every figure has (data, labels, limits, scales, ticks, title, legend placement, output path) and explicit pass-through dictionaries for the long tail of Matplotlib settings: legend_kw, grid_kw, savefig_kw, subplots_kw, and per curve any Line2D keyword.

There is deliberately no bare **kwargs on any of these functions except the three presets, which forward verbatim to plot_curves() under the name **_forbidden, so a typo still raises and names the offending key. A catch-all signature accepts a misspelled keyword in silence, and this project has already paid for that once: oscprob’s keyword chain forwarded unknown names down several layers before failing somewhere unrecognizable, which is why magnus.oscprob.osc_prob() now raises on stray keys. Here every keyword is either named in the signature – so a typo is a TypeError at the call site – or lands in a dictionary destined for one specific Matplotlib call – so a typo is an error from that call, naming the offending key. Nothing is swallowed.

The module brings its own text and tick sizes (HOUSE_RC), so a figure looks the same wherever it is drawn; it reads no matplotlibrc. Font family and LaTeX rendering are left to the caller’s Matplotlib settings.

All functions return (fig, ax) so that the caller can keep customizing: fig for figure-level work and saving, ax for anything Matplotlib exposes on an axes. With return_probability=True, the functions that compute through the Earth wrappers also return what they drew.

Requirements

Matplotlib ships with Magνs, so this module is available in any installation and needs nothing extra.

Added in version 1.0.0.

Attributes

HOUSE_FIGSIZE

Default figure size, in inches, used throughout the notebooks.

HOUSE_RESIDUAL_HEIGHT

Height of the relative-error subpanel, relative to the main panel.

HOUSE_LEGEND_KW

Legend keywords repeated verbatim on essentially every notebook figure.

HOUSE_GRID_KW

Grid keywords used by the notebooks.

HOUSE_SAVEFIG_KW

Default savefig() keywords; figures go to ../fig/ as PDF.

HOUSE_RC

Text and tick sizes every plotting function draws with.

Exceptions

MatplotlibNotFoundError

Raised when magnus.plotting is used without Matplotlib installed.

Functions

prob_label(→ str)

Return the LaTeX label for an oscillation probability.

plot_curves(x, curves, *[, xlabel, ylabel, title, ...])

Plot a set of curves against a swept variable, with an optional error subpanel.

plot_curves_stacked(x, panels, *[, xlabel, ylabel, ...])

Plot small multiples: one panel per case, stacked over a shared abscissa.

plot_probability_vs_baseline(distances, curves, *[, ...])

Plot oscillation probabilities against baseline.

plot_probability_vs_energy(energies, curves, *[, ...])

Plot oscillation probabilities against neutrino energy.

plot_probability_with_profile(x[, profiles, panels, ...])

Stack a matter-density panel above one or more probability panels.

plot_probability_with_average(x, probabilities, ...[, ...])

Overlay phase-averaged probabilities on the oscillating ones.

plot_biprobability([prob_nu, prob_nubar, ...])

Plot neutrino against antineutrino appearance probability.

plot_oscillogram(costhz, log10_energy[, probability, ...])

Plot an oscillogram: probability over zenith angle and energy.

Module Contents

exception magnus.plotting.MatplotlibNotFoundError[source]

Bases: ImportError

Raised when magnus.plotting is used without Matplotlib installed.

Added in version 1.0.0.

magnus.plotting.HOUSE_FIGSIZE: Tuple[float, float] = (18.0, 9.0)[source]

Default figure size, in inches, used throughout the notebooks.

Added in version 1.0.0.

magnus.plotting.HOUSE_RESIDUAL_HEIGHT: float = 0.3[source]

Height of the relative-error subpanel, relative to the main panel.

Added in version 1.0.0.

magnus.plotting.HOUSE_LEGEND_KW: Dict[str, Any][source]

Legend keywords repeated verbatim on essentially every notebook figure.

Added in version 1.0.0.

magnus.plotting.HOUSE_GRID_KW: Dict[str, Any][source]

Grid keywords used by the notebooks.

Added in version 1.0.0.

magnus.plotting.HOUSE_SAVEFIG_KW: Dict[str, Any][source]

Default savefig() keywords; figures go to ../fig/ as PDF.

Added in version 1.0.0.

magnus.plotting.HOUSE_RC: Dict[str, Any][source]

Text and tick sizes every plotting function draws with.

The module’s own defaults, the same values the notebooks use. Before, the module left these to the matplotlibrc of wherever it ran, so outside the notebooks directory (the documentation included) the 18-by-9-inch figures got Matplotlib’s 10-point axis and tick labels. A value the caller has changed from Matplotlib’s default (in rcParams or a style) is left alone.

Added in version 1.2.0.

magnus.plotting.prob_label(nu_i: int, nu_f: int, nubar: bool | None = False) → str[source]

Return the LaTeX label for an oscillation probability.

A prob_label helper was defined separately in several notebooks, with hand-written if/elif chains covering only the three active flavors. This version also covers the sterile states, so the sterile-neutrino notebook can use it too.

Added in version 1.0.0.

Parameters:
  • nu_i (int) – Initial flavor, as one of the magnus.globaldefs constants NUE, NUMU, NUTAU, NUS1, NUS2.

  • nu_f (int) – Final flavor, same encoding.

  • nubar (bool, optional) – If True, label the antineutrino channel. Default is False.

Returns:

A LaTeX string such as '$P_{\\nu_e \\to \\nu_\\mu}$'.

Return type:

str

Raises:

ValueError – If either flavor index is not one of the known values.

Examples

import magnus.globaldefs as gd
from magnus.plotting import prob_label

print(prob_label(gd.NUMU, gd.NUE))
print(prob_label(gd.NUMU, gd.NUE, nubar=True))
$P_{\nu_\mu \to \nu_e}$
$P_{\bar{\nu}_\mu \to \bar{\nu}_e}$
magnus.plotting.plot_curves(x: Sequence[float], curves: Sequence[Sequence[float] | Dict[str, Any]], *, xlabel: str | None = None, ylabel: str | None = None, title: str | None = None, xlim: Tuple[float, float] | None = None, ylim: Tuple[float, float] | None = None, xscale: str = 'linear', yscale: str = 'linear', xmajor: float | None = None, xminor: float | None = None, ymajor: float | None = None, yminor: float | None = None, residual: Sequence[float] | None = None, residual_label: str | None = None, residual_ylim: Tuple[float, float] | None = None, residual_ymajor: float | None = None, residual_yminor: float | None = None, residual_height: float = HOUSE_RESIDUAL_HEIGHT, residual_kw: Dict[str, Any] | None = None, annotations: Sequence[Dict[str, Any]] | None = None, legend: bool = True, legend_title: str | None = None, legend_loc: str | None = None, legend_kw: Dict[str, Any] | None = None, grid: bool = False, grid_kw: Dict[str, Any] | None = None, ylabel_labelpad: float = 25.0, title_fontsize: float = 20.0, figsize: Tuple[float, float] = HOUSE_FIGSIZE, subplots_kw: Dict[str, Any] | None = None, savefig: str | None = None, savefig_kw: Dict[str, Any] | None = None, tight_layout: bool = True)[source]

Plot a set of curves against a swept variable, with an optional error subpanel.

This is the workhorse: most notebook figures are an instance of it. The plot_probability_vs_baseline and plot_probability_vs_energy wrappers differ from it only in their preset labels and limits, and the convergence studies of the matrix-exponential notebook use it directly with a slab count or grid size on the abscissa.

Added in version 1.0.0.

Parameters:
  • x (sequence of float) – Abscissa, shared by every curve and by the residual panel.

  • curves (sequence) – One entry per curve. An entry is either a bare ordinate array, or a dict carrying the ordinate under 'y' plus any Line2D keyword (label, color, ls, lw, …). Entries without an explicit color take the 'C0', 'C1', … cycle in order.

  • xlabel (str, optional) – Axis labels and title. ylabel goes on the main panel.

  • ylabel (str, optional) – Axis labels and title. ylabel goes on the main panel.

  • title (str, optional) – Axis labels and title. ylabel goes on the main panel.

  • xlim (tuple of float, optional) – Axis limits. xlim is applied to the residual panel too, so the two panels stay aligned.

  • ylim (tuple of float, optional) – Axis limits. xlim is applied to the residual panel too, so the two panels stay aligned.

  • xscale (str, optional) – Matplotlib axis scales, e.g. 'log'. Default is 'linear'. xscale is applied to the residual panel as well.

  • yscale (str, optional) – Matplotlib axis scales, e.g. 'log'. Default is 'linear'. xscale is applied to the residual panel as well.

  • xmajor (float, optional) – Major/minor tick spacings for the main panel.

  • xminor (float, optional) – Major/minor tick spacings for the main panel.

  • ymajor (float, optional) – Major/minor tick spacings for the main panel.

  • yminor (float, optional) – Major/minor tick spacings for the main panel.

  • residual (sequence of float, optional) – If given, a short subpanel is added below the main panel and this is plotted in it – typically a relative error against a reference curve. The main panel’s tick labels are then suppressed, as in the notebooks.

  • residual_label (str, optional) – Ordinate label for the residual subpanel.

  • residual_ylim (tuple of float, optional) – Ordinate limits for the residual subpanel.

  • residual_ymajor (float, optional) – Tick spacings for the residual subpanel.

  • residual_yminor (float, optional) – Tick spacings for the residual subpanel.

  • residual_height (float, optional) – Height of the residual subpanel relative to the main panel. Default is HOUSE_RESIDUAL_HEIGHT.

  • residual_kw (dict, optional) – Extra Line2D keywords for the residual curve. Defaults to a thin black solid line.

  • annotations (sequence of dict, optional) – Text placed on the main panel. Each entry needs 'text' and 'xy' (axes fractions by default) and may carry any other annotate() keyword. Used by the BSM notebooks to record the parameter values a figure was made with.

  • legend (bool, optional) – Whether to draw a legend. Default is True; it is drawn only if at least one curve carries a label.

  • legend_title (str, optional) – Legend title.

  • legend_loc (str, optional) – Legend location.

  • legend_kw (dict, optional) – Extra keywords merged over HOUSE_LEGEND_KW and forwarded to legend().

  • grid (bool, optional) – Whether to draw a grid. Default is False.

  • grid_kw (dict, optional) – Extra keywords merged over HOUSE_GRID_KW.

  • ylabel_labelpad (float, optional) – Padding of the main ordinate label. Default is 25.0.

  • title_fontsize (float, optional) – Title font size. Default is 20.0.

  • figsize (tuple of float, optional) – Figure size in inches. Default is HOUSE_FIGSIZE.

  • subplots_kw (dict, optional) – Extra keywords for subplots().

  • savefig (str, optional) – If given, the figure is written here.

  • savefig_kw (dict, optional) – Extra keywords merged over HOUSE_SAVEFIG_KW.

  • tight_layout (bool, optional) – Whether to call tight_layout(). Default is True.

Returns:

  • fig (matplotlib.figure.Figure) – The figure, ready for further customization or saving.

  • ax (matplotlib.axes.Axes or numpy.ndarray of Axes) – A single axes when there is no residual panel; an array of two (main, residual) when there is.

Examples

import matplotlib
matplotlib.use('Agg')
import numpy as np
from magnus.plotting import plot_curves

# starts away from zero: the reference appears in a denominator below,
# and sin(0)**2 is exactly 0
L = np.linspace(50.0, 1000.0, 200)
exact = np.sin(L / 200.0) ** 2
approx = exact + 1e-3 * np.cos(L / 50.0)

fig, ax = plot_curves(
    L,
    [dict(y=approx, label='Magnus expansion', color='C1'),
     dict(y=exact, label='Standard formula', color='k', ls='--')],
    xlabel=r'Baseline, $L$ [km]', ylabel='Probability',
    ylim=(0, 1), residual=(approx - exact) / exact,
    residual_label=r'$\epsilon_{\rm rel}$', legend_title='Method',
)
print(len(ax), ax[0].get_ylim())
2 (np.float64(0.0), np.float64(1.0))
magnus.plotting.plot_curves_stacked(x: Sequence[float], panels: Sequence[Sequence[Sequence[float] | Dict[str, Any]]], *, xlabel: str | None = None, ylabel: str | None = None, title: str | None = None, xlim: Tuple[float, float] | None = None, ylim: Tuple[float, float] | None = None, xscale: str = 'linear', yscale: str = 'linear', xmajor: float | None = None, xminor: float | None = None, ymajor: float | None = None, yminor: float | None = None, panel_labels: Sequence[str] | None = None, panel_label_xy: Tuple[float, float] = (0.02, 0.1), panel_label_kw: Dict[str, Any] | None = None, annotations: Sequence[Dict[str, Any]] | None = None, legend: bool = True, legend_panel: int = 0, legend_proxies: Sequence[Dict[str, Any]] | None = None, legend_title: str | None = None, legend_loc: str | None = None, legend_kw: Dict[str, Any] | None = None, grid: bool = False, grid_kw: Dict[str, Any] | None = None, ylabel_kw: Dict[str, Any] | None = None, title_fontsize: float = 23.0, figsize: Tuple[float, float] | None = None, height_ratios: Sequence[float] | None = None, subplots_kw: Dict[str, Any] | None = None, savefig: str | None = None, savefig_kw: Dict[str, Any] | None = None, tight_layout: bool = True)[source]

Plot small multiples: one panel per case, stacked over a shared abscissa.

The layout for “the same quantity, once per configuration” – one panel per detector, per baseline, per zenith angle – where the comparison the reader makes is between panels, so every panel must share limits, scales and tick spacings exactly. Only the bottom panel keeps its tick labels and abscissa label, and the ordinate label is a single figure-level label spanning the stack.

This differs from plot_probability_with_profile(), whose panels show different quantities (a density profile above a probability), and from plot_curves(), whose optional second panel is a relative error rather than another instance of the same plot.

Added in version 1.0.0.

Changed in version 1.2.0: Each panel’s curves are checked as plot_curves() checks its curves: 1-D, finite, as long as x, and on a log y axis with at least one entry above 0 (issue #160 §12).

Parameters:
  • x (sequence of float) – Abscissa, shared by every panel.

  • panels (sequence of sequence) – One entry per panel, each a sequence of curves in the form plot_curves() accepts: a bare ordinate array, or a dict carrying the ordinate under 'y' plus any Line2D keyword. Curves without an explicit color take the 'C0', 'C1', … cycle within their panel, so the n-th curve of every panel matches by default.

  • xlabel (str, optional) – Abscissa label, placed on the bottom panel only.

  • ylabel (str, optional) – Ordinate label. Drawn once for the whole stack with supylabel(), since every panel shows the same quantity. Being figure-level, it takes no labelpad; use ylabel_kw for its placement.

  • title (str, optional) – Title, placed above the top panel.

  • xlim (tuple of float, optional) – Axis limits, applied to every panel.

  • ylim (tuple of float, optional) – Axis limits, applied to every panel.

  • xscale (str, optional) – Matplotlib axis scales, applied to every panel. Default 'linear'.

  • yscale (str, optional) – Matplotlib axis scales, applied to every panel. Default 'linear'.

  • xmajor (float, optional) – Major/minor tick spacings, applied to every panel.

  • xminor (float, optional) – Major/minor tick spacings, applied to every panel.

  • ymajor (float, optional) – Major/minor tick spacings, applied to every panel.

  • yminor (float, optional) – Major/minor tick spacings, applied to every panel.

  • panel_labels (sequence of str, optional) – One caption per panel, annotated inside it – the usual way of saying which case a panel is. Must match the number of panels.

  • panel_label_xy (tuple of float, optional) – Position of those captions, in axes fractions. Default (0.02, 0.10).

  • panel_label_kw (dict, optional) – Extra annotate() keywords for them.

  • annotations (sequence of dict, optional) – Free-form text. Each entry needs 'text' and 'xy', may name a 'panel' (index, default 0), and may carry any other annotate() keyword.

  • legend (bool, optional) – Whether to draw a legend. Default True; drawn only if there is something to put in it.

  • legend_panel (int, optional) – Which panel carries the legend. Default 0.

  • legend_proxies (sequence of dict, optional) – Legend entries that describe a style shared across panels rather than any one curve – e.g. “solid: 3+1, dashed: standard” when the color varies from panel to panel. Each entry is a set of Line2D keywords including label, drawn as an empty proxy artist. When given, these replace the labels picked up from the curves themselves. This exists because the alternative, and what the notebooks did, is plotting dummy points outside the axis limits to manufacture legend handles.

  • legend_title (str, optional) – Legend title and location.

  • legend_loc (str, optional) – Legend title and location.

  • legend_kw (dict, optional) – Extra keywords merged over HOUSE_LEGEND_KW.

  • grid (bool, optional) – Whether to draw a grid on every panel. Default False.

  • grid_kw (dict, optional) – Extra keywords merged over HOUSE_GRID_KW.

  • ylabel_kw (dict, optional) – Extra keywords for supylabel().

  • title_fontsize (float, optional) – Title font size. Default 23.0.

  • figsize (tuple of float, optional) – Figure size in inches. Defaults to HOUSE_FIGSIZE’s width and half its height per panel, which reproduces the notebooks’ proportions.

  • height_ratios (sequence of float, optional) – Relative panel heights. Default: equal.

  • subplots_kw (dict, optional) – Extra keywords for subplots().

  • savefig (str, optional) – If given, the figure is written here.

  • savefig_kw (dict, optional) – Extra keywords merged over HOUSE_SAVEFIG_KW.

  • tight_layout (bool, optional) – Whether to call tight_layout(). Default True.

Returns:

  • fig (matplotlib.figure.Figure) – The figure, ready for further customization or saving.

  • ax (numpy.ndarray of Axes) – One axes per panel, top to bottom. Always an array, including for a single panel, so that indexing does not depend on the panel count.

Examples

import matplotlib
matplotlib.use('Agg')
import numpy as np
from magnus.plotting import plot_curves_stacked

E = np.linspace(1.0, 40.0, 200)
cases = [0.5, 1.0, 2.0]
panels = [
    [dict(y=np.sin(k*E/8.0)**2, color=f'C{i}'),
     dict(y=np.sin(k*E/8.0)**2*0.8, color='0.7', ls='--')]
    for i, k in enumerate(cases)
]

fig, ax = plot_curves_stacked(
    E, panels,
    xlabel=r'Neutrino energy, $E_\nu$ [GeV]', ylabel='Probability',
    ylim=(0, 1), xlim=(1.0, 40.0),
    panel_labels=[f'baseline {k:.1f} kton-yr' for k in cases],
    legend_proxies=[dict(label='3+1', color='k', ls='-'),
                    dict(label=r'standard', color='k', ls='--')],
)
print(ax.shape, ax[0].get_xticklabels()[0].get_text() == '')
(3,) True
magnus.plotting.plot_probability_vs_baseline(distances: Sequence[float], curves: Sequence[Sequence[float] | Dict[str, Any]], *, nu_i: int | None = None, nu_f: int | None = None, nubar: bool | None = None, num_flavors: int | None = None, xlabel: str = 'Baseline, $L$ [km]', ylabel: str | None = None, ylim: Tuple[float, float] = (0.0, 1.0), xscale: str = 'log', ymajor: float | None = 0.1, yminor: float | None = 0.02, **_forbidden: Any)[source]

Plot oscillation probabilities against baseline.

A thin preset over plot_curves(): log abscissa, ordinate on \([0, 1]\) with the notebooks’ tick spacings, and an ordinate label built from the flavor pair.

Added in version 1.0.0.

Changed in version 1.2.0: Takes nubar, for the default ordinate label (issue #145 §2).

Parameters:
  • distances (sequence of float) – Baselines [km].

  • curves (sequence) – As in plot_curves().

  • nu_i (int, optional) – Flavor pair, used to build the ordinate label via prob_label() when ylabel is not given.

  • nu_f (int, optional) – Flavor pair, used to build the ordinate label via prob_label() when ylabel is not given.

  • num_flavors (int, optional) – If given, prefixes the ordinate label with 'Two-', 'Three-', 'Four-' or 'Five-neutrino probability'.

  • nubar (bool, optional) – True if the curves are antineutrino probabilities, for the default ordinate label. Default: None, read as False.

  • xlabel (str, optional) – Abscissa label.

  • ylabel (str, optional) – Ordinate label; overrides the one built from the flavor pair.

  • ylim (tuple of float, optional) – Ordinate limits. Default is (0.0, 1.0).

  • xscale (str, optional) – Abscissa scale. Default is 'log'.

  • ymajor (float, optional) – Ordinate tick spacings. Pass None to hand the axis back to Matplotlib’s own locator. Default: 0.10 and 0.02.

  • yminor (float, optional) – Ordinate tick spacings. Pass None to hand the axis back to Matplotlib’s own locator. Default: 0.10 and 0.02.

  • **_forbidden – Every remaining keyword of plot_curves() is accepted and forwarded unchanged; unknown names raise TypeError there.

Returns:

  • fig (matplotlib.figure.Figure)

  • ax (matplotlib.axes.Axes or numpy.ndarray of Axes)

Examples

import matplotlib
matplotlib.use('Agg')
import numpy as np
import magnus.globaldefs as gd
from magnus.plotting import plot_probability_vs_baseline

L = np.logspace(1, 5, 200)
P = np.sin(L / 3000.0) ** 2
fig, ax = plot_probability_vs_baseline(
    L, [dict(y=P, label='Magnus expansion')],
    nu_i=gd.NUE, nu_f=gd.NUE, num_flavors=2, xlim=(L[0], L[-1]),
)
print(ax.get_xlabel())
Baseline, $L$ [km]
magnus.plotting.plot_probability_vs_energy(energies: Sequence[float], curves: Sequence[Sequence[float] | Dict[str, Any]], *, nu_i: int | None = None, nu_f: int | None = None, nubar: bool | None = None, num_flavors: int | None = None, energy_unit: str = 'GeV', xlabel: str | None = None, ylabel: str | None = None, ylim: Tuple[float, float] = (0.0, 1.0), xscale: str = 'log', ymajor: float | None = 0.1, yminor: float | None = 0.02, **_forbidden: Any)[source]

Plot oscillation probabilities against neutrino energy.

The energy counterpart of plot_probability_vs_baseline().

Added in version 1.0.0.

Changed in version 1.2.0: Takes nubar, for the default ordinate label (issue #145 §2); energies that look like eV under a larger unit raise EnergyUnitWarning (issue #160 §12).

Parameters:
  • energies (sequence of float) – Neutrino energies, in the unit named by energy_unit.

  • curves (sequence) – As in plot_curves().

  • nu_i (int, optional) – Flavor pair for the ordinate label.

  • nu_f (int, optional) – Flavor pair for the ordinate label.

  • num_flavors (int, optional) – Flavor count, for the ordinate label prefix.

  • nubar (bool, optional) – True if the curves are antineutrino probabilities, for the default ordinate label. Default: None, read as False.

  • energy_unit (str, optional) – Unit shown in the abscissa label. Default is 'GeV'.

  • xlabel (str, optional) – Abscissa label; overrides the one built from energy_unit.

  • ylabel (str, optional) – Ordinate label.

  • ylim (tuple of float, optional) – Ordinate limits. Default is (0.0, 1.0).

  • xscale (str, optional) – Abscissa scale. Default is 'log'.

  • ymajor (float, optional) – Ordinate tick spacings. Pass None to hand the axis back to Matplotlib’s own locator. Default: 0.10 and 0.02.

  • yminor (float, optional) – Ordinate tick spacings. Pass None to hand the axis back to Matplotlib’s own locator. Default: 0.10 and 0.02.

  • **_forbidden – Forwarded to plot_curves().

Returns:

  • fig (matplotlib.figure.Figure)

  • ax (matplotlib.axes.Axes or numpy.ndarray of Axes)

Examples

import matplotlib
matplotlib.use('Agg')
import numpy as np
import magnus.globaldefs as gd
from magnus.plotting import plot_probability_vs_energy

E = np.logspace(-1, 1, 200)
P = np.cos(1.0 / E) ** 2
fig, ax = plot_probability_vs_energy(
    E, [dict(y=P, label='Magnus expansion')],
    nu_i=gd.NUMU, nu_f=gd.NUE, xlim=(E[0], E[-1]),
)
print(ax.get_xlabel())
Neutrino energy, $E_\nu$ [GeV]
magnus.plotting.plot_probability_with_profile(x: Sequence[float], profiles: Sequence[Sequence[float] | Dict[str, Any]] | None = None, panels: Sequence[Sequence[Sequence[float] | Dict[str, Any]]] | None = None, *, trajectories: Sequence[Dict[str, Any]] | None = None, x_axis: str | None = None, x_unit: float | None = None, energy: float | None = None, nu_i: int | None = None, nu_f: int | None = None, nubar: bool | None = None, num_flavors: int | None = None, osc_params: Dict[str, float] | None = None, electron_fraction: float | None = None, electron_fraction_core: float | None = None, electron_fraction_mantle: float | None = None, electron_fraction_crust: float | None = None, electron_fraction_ocean: float | None = None, ratio_number_neutrons_to_protons: float | None = None, wrapper_kw: Dict[str, Any] | None = None, show_profile: bool | None = None, panel_per_trajectory: bool | None = None, return_probability: bool = False, xlabel: str | None = None, profile_ylabel: str = '$\\frac{N_e}{N_{\\rm Av}}$ [cm$^{-3}$]', panel_ylabels: Sequence[str | None] | None = None, panel_annotations: Sequence[str | None] | None = None, panel_annotation_xy: Tuple[float, float] = (0.02, 0.88), panel_annotation_fontsize: float = 23.0, shared_ylabel: str | None = None, shared_ylabel_labelpad: float = 20.0, title: str | None = None, title_fontsize: float = 23.0, xlim: Tuple[float, float] | None = None, xscale: str = 'log', xmajor: float | None = None, xminor: float | None = None, profile_ylim: Tuple[float, float] | None = None, profile_ymajor: float | None = None, profile_yminor: float | None = None, profile_height: float = 0.4, panel_ylim: Tuple[float, float] | None = (0.0, 1.0), panel_yscale: str = 'linear', panel_ymajor: float | None = 0.1, panel_yminor: float | None = 0.02, legend: bool = True, legend_title: str | None = None, legend_loc: str | None = None, legend_kw: Dict[str, Any] | None = None, legend_on_panel: int = 0, grid: bool = True, grid_kw: Dict[str, Any] | None = None, ylabel_labelpad: float = 25.0, figsize: Tuple[float, float] | None = None, subplots_kw: Dict[str, Any] | None = None, savefig: str | None = None, savefig_kw: Dict[str, Any] | None = None, tight_layout: bool = False)[source]

Stack a matter-density panel above one or more probability panels.

This is the layout of the long-baseline notebook: the electron-density profile along the trajectory on top, then one probability panel per detector or per profile, sharing the abscissa. With a single probability panel it is the profile-plus-probability figure of the introduction and the two-flavor notebooks.

It draws curves you computed, or, given trajectories instead of profiles and panels, computes them through the Earth wrappers (magnus.oscprob.osc_prob_3nu_earth() and its two-, four- and five-flavor siblings), which declare the PREM layer boundaries as slab edges and use a layered electron fraction unless told otherwise. The density panel then shows the electron density those wrappers integrate along each trajectory.

Added in version 1.0.0.

Changed in version 1.2.0: Computes the probabilities through the Earth wrappers when given trajectories; profiles and panels default to None.

Changed in version 1.2.0: Takes nubar (issue #145 §2), labels the computed probability panel (issue #146 §2), and computes NSI and LIV through the Earth wrappers (issue #146 §1).

Parameters:
  • x (sequence of float) – Shared abscissa, or, when the panels have different abscissae, the one used by the profile panel. Individual curves may carry their own x. When computing, baselines measured from where the neutrino enters the Earth (x_axis='baseline') or energies (x_axis='energy'), in units of x_unit.

  • profiles (sequence or None) – Curves for the density panel, in the form plot_curves() takes. A curve may add its own abscissa under 'x'. Pass None (or an empty sequence) to omit the density panel entirely and get a plain stack of probability panels sharing an abscissa – the layout the long-baseline notebook uses for a probability above its energy-smoothed version. Leave it None when computing.

  • panels (sequence of sequence) – One entry per probability panel; each entry is a sequence of curves. Required unless computing, and then left None.

  • trajectories (sequence of dict, optional) – Compute instead of draw: one curve per entry, each a path through the Earth given as 'costhz' (the cosine of the zenith angle) or as 'loc_ini' and 'loc_fin' (two named locations of magnus.earth.loc_coords_dms, or their coordinates, joined by the chord between them). An entry may carry its own abscissa under 'x' – a baseline grid ending at its own chord length, say – and any Line2D keyword (color, ls, label…) for its curve.

  • x_axis (str, optional) – What the abscissa is when computing, 'baseline' or 'energy'. 'baseline' (the default): one wrapper call per trajectory over all its baselines, at energy; a baseline longer than the trajectory’s chord is an error. 'energy': one call per trajectory over all its energies, at the full chord.

  • x_unit (float, optional) – What one unit of the abscissa is, in the wrappers’ units: a length in \(\text{eV}^{-1}\) or an energy in eV. Default magnus.globaldefs.UNIT_KM for baselines (x in km) and magnus.globaldefs.UNIT_GEV for energies (x in GeV).

  • energy (float, optional) – Neutrino energy [eV], required on the baseline axis and refused on the energy axis.

  • nu_i (int, optional) – Initial and final flavors when computing, e.g. magnus.globaldefs.NUMU and magnus.globaldefs.NUE.

  • nu_f (int, optional) – Initial and final flavors when computing, e.g. magnus.globaldefs.NUMU and magnus.globaldefs.NUE.

  • num_flavors (int, optional) – 2, 3, 4 or 5: which Earth wrapper computes the probabilities.

  • nubar (bool, optional) – True for antineutrinos. In compute mode it is also passed to the wrapper, so the curves and their label agree; a value contradicting wrapper_kw['nubar'] is refused. Default: None, which reads wrapper_kw['nubar'] in compute mode and False otherwise.

  • osc_params (dict, optional) – Mixing parameters, passed to the wrapper as keywords: sth and Dm2 (required) at two flavors; at three to five flavors any of the wrapper’s own (s12, …, D41…), the rest taking the wrapper’s defaults. magnus.globaldefs.load_nufit_params() returns such a dict.

  • electron_fraction (float, optional) – The Earth’s composition, as the wrappers take it: one \(Y_e\) for the whole Earth, or per PREM layer. Default: None, the layered values. The density panel follows them too.

  • electron_fraction_core (float, optional) – The Earth’s composition, as the wrappers take it: one \(Y_e\) for the whole Earth, or per PREM layer. Default: None, the layered values. The density panel follows them too.

  • electron_fraction_mantle (float, optional) – The Earth’s composition, as the wrappers take it: one \(Y_e\) for the whole Earth, or per PREM layer. Default: None, the layered values. The density panel follows them too.

  • electron_fraction_crust (float, optional) – The Earth’s composition, as the wrappers take it: one \(Y_e\) for the whole Earth, or per PREM layer. Default: None, the layered values. The density panel follows them too.

  • electron_fraction_ocean (float, optional) – The Earth’s composition, as the wrappers take it: one \(Y_e\) for the whole Earth, or per PREM layer. Default: None, the layered values. The density panel follows them too.

  • ratio_number_neutrons_to_protons (float, optional) – Passed to the wrapper; matters from four flavors up. Default: None, derived from the same \(Y_e\).

  • wrapper_kw (dict, optional) – Any other wrapper keyword, e.g. rtol or nubar.

  • show_profile (bool, optional) – Whether to draw the density panel when computing. Default: yes on the baseline axis, no on the energy axis, where the abscissa is not a position along the trajectory (so True there is an error).

  • panel_per_trajectory (bool, optional) – When computing, one probability panel per trajectory (the default) or all curves in one panel.

  • return_probability (bool, optional) – When computing, also return the probabilities drawn. Default False.

  • xlabel (str, optional) – Abscissa label, placed under the bottom panel. Default 'Baseline, $L$ [km]', or, when computing on the energy axis, a neutrino-energy label in GeV or MeV (for those two x_unit values).

  • profile_ylabel (str, optional) – Ordinate label of the density panel.

  • panel_ylabels (sequence of str, optional) –

    Ordinate labels for the probability panels. Entries may be None. In compute mode, when neither this nor shared_ylabel is given, every probability panel is labelled with the channel of nu_i and nu_f (and nubar).

    Changed in version 1.2.0: That label is the probability symbol alone: the longer “Three-neutrino probability, …” ran into the density label at the house text size.

  • panel_annotations (sequence, optional) – Text placed inside each probability panel, one entry per panel, at panel_annotation_xy in axes coordinates. An entry is a string, or a dict with 'text' plus any other annotate() keyword – a bbox, for instance, when the text would otherwise sit over dense curves. Entries may be None. The three-flavor notebook uses this to name the channel each panel shows, rather than repeating it in the ordinate label.

  • panel_annotation_xy (tuple of float, optional) – Position of those annotations, in axes fractions. Default (0.02, 0.88).

  • panel_annotation_fontsize (float, optional) – Their font size. Default is 23.0.

  • shared_ylabel (str, optional) – A single ordinate label spanning the whole stack, drawn on a frameless overlay axes. Use it instead of panel_ylabels when every panel shows the same quantity.

  • shared_ylabel_labelpad (float, optional) – Padding of that shared label. Default is 20.0.

  • title (str, optional) – Title, placed above the top panel, which is the density panel only when profiles is given.

  • title_fontsize (float, optional) – Title font size. Default is 23.0.

  • xlim (tuple of float, optional) – Shared abscissa limits.

  • xscale (str, optional) – Shared abscissa scale. Default is 'log'.

  • xmajor (float, optional) – Major/minor tick spacings on the shared abscissa. Only meaningful on a linear scale.

  • xminor (float, optional) – Major/minor tick spacings on the shared abscissa. Only meaningful on a linear scale.

  • profile_ylim (tuple of float, optional) – Ordinate limits of the density panel.

  • profile_ymajor (float, optional) – Tick spacings for the density panel.

  • profile_yminor (float, optional) – Tick spacings for the density panel.

  • profile_height (float, optional) – Height of the density panel relative to a probability panel. Default is 0.4.

  • panel_ylim (tuple of float, optional) – Ordinate limits shared by the probability panels. None autoscales.

  • panel_yscale (str, optional) – Ordinate scale shared by the panels, e.g. 'log' when they carry something other than a probability. Default is 'linear'.

  • panel_ymajor (float, optional) – Tick spacings for the probability panels.

  • panel_yminor (float, optional) – Tick spacings for the probability panels.

  • legend (bool, optional) – Whether to draw a legend.

  • legend_title (str, optional) – Legend title.

  • legend_loc (str, optional) – Legend location.

  • legend_kw (dict, optional) – Extra keywords merged over HOUSE_LEGEND_KW.

  • legend_on_panel (int, optional) – Index of the probability panel carrying the legend, or -1 to give every panel its own. Default is 0.

  • grid (bool, optional) – Whether to draw grids. Default is True.

  • grid_kw (dict, optional) – Extra keywords merged over HOUSE_GRID_KW.

  • ylabel_labelpad (float, optional) – Padding of the density panel’s ordinate label. The probability panels use a fixed padding of 15 and do not read this. Default: 25.0.

  • figsize (tuple of float, optional) – Figure size. Defaults to (18, 9) for one probability panel, growing by 4.5 inches per extra panel.

  • subplots_kw (dict, optional) – Extra keywords for subplots().

  • savefig (str, optional) – If given, the figure is written here.

  • savefig_kw (dict, optional) – Extra keywords merged over HOUSE_SAVEFIG_KW.

  • tight_layout (bool, optional) – Whether to call tight_layout. Default is False, matching the notebooks, whose explicit subplots_adjust this would override.

Returns:

  • fig (matplotlib.figure.Figure)

  • ax (numpy.ndarray of Axes) – Length 1 + len(panels) with a density panel, which comes first; length len(panels) without one.

  • probability (list of numpy.ndarray) – Only with return_probability=True: one array per trajectory, over its abscissa.

Raises:

ValueError – If panels is missing or empty when drawing; if trajectories is given together with profiles or panels, or any computing argument without trajectories; if a trajectory names neither or both of costhz and the two locations; if x_axis is unknown, energy is missing on the baseline axis or given on the energy axis, or show_profile=True on the energy axis; if trajectories is empty, or wrapper_kw holds source_depth or detector_depth; and for the channel, flavor-count and duplicate-keyword checks of plot_oscillogram().

Examples

import matplotlib
matplotlib.use('Agg')
import numpy as np
from magnus.plotting import plot_probability_with_profile

L = np.logspace(2, 4, 300)
n_e = 5.0 * np.exp(-L / 5000.0)
P = np.sin(L / 900.0) ** 2

fig, ax = plot_probability_with_profile(
    L, [dict(y=n_e, color='C0')], [[dict(y=P, label='PREM')]],
    xlim=(L[0], L[-1]), profile_ylim=(0, 6),
)
print(len(ax))
2

Computed through the Earth wrappers instead, along a chord that crosses the core, with the electron density it samples on top:

import matplotlib
matplotlib.use('Agg')
import numpy as np
import magnus.globaldefs as gd
from magnus.plotting import plot_probability_with_profile

L = np.linspace(100.0, 11000.0, 200)    # [km]; the chord is 11 467 km long
fig, ax, P = plot_probability_with_profile(
    L, trajectories=[dict(costhz=-0.9, label=r'$\cos\theta_z = -0.9$')],
    energy=5.0*gd.UNIT_GEV, nu_i=gd.NUMU, nu_f=gd.NUE, num_flavors=3,
    xscale='linear', return_probability=True)
print(len(ax), P[0].shape)
2
 (200,)
magnus.plotting.plot_probability_with_average(x: Sequence[float], probabilities: Sequence[float] | Sequence[Sequence[float]], averages: float | Sequence[float] | Sequence[Sequence[float]], *, labels: Sequence[str] | None = None, colors: Sequence[str] | None = None, average_label: str = 'Phase-averaged', oscillating_kw: Dict[str, Any] | None = None, average_kw: Dict[str, Any] | None = None, **_forbidden: Any)[source]

Overlay phase-averaged probabilities on the oscillating ones.

The figure of the averaged-probability notebook: rapidly oscillating curves, each with its phase-averaged value drawn through it as a dashed line of the same color – for instance what magnus.oscprob.osc_prob() returns with average=True (the average over average_spread).

Several channels are usually shown at once, so the legend carries one entry per channel plus a single entry explaining the dashed style, rather than repeating “averaged” once per curve.

Added in version 1.0.0.

Parameters:
  • x (sequence of float) – Abscissa, typically baseline [km].

  • probabilities (sequence of float or sequence of sequence of float) – One oscillating probability, or several.

  • averages (float or sequence) – The corresponding phase-averaged values: a scalar per curve (broadcast across x), or a full curve each. Must match probabilities in number.

  • labels (sequence of str, optional) – Legend label per channel.

  • colors (sequence of str, optional) – Color per channel. Defaults to the 'C0', 'C1', … cycle; each average takes its channel’s color.

  • average_label (str, optional) – Text of the single legend entry explaining the dashed lines.

  • oscillating_kw (dict, optional) – Extra Line2D keywords applied to every oscillating or every averaged curve.

  • average_kw (dict, optional) – Extra Line2D keywords applied to every oscillating or every averaged curve.

  • **_forbidden – Forwarded to plot_probability_vs_baseline().

Returns:

  • fig (matplotlib.figure.Figure)

  • ax (matplotlib.axes.Axes or numpy.ndarray of Axes)

Raises:

ValueError – If the number of averages does not match the number of probabilities.

Examples

import matplotlib
matplotlib.use('Agg')
import numpy as np
from magnus.plotting import plot_probability_with_average

L = np.linspace(1.0, 1000.0, 500)
P = np.sin(L / 7.0) ** 2
fig, ax = plot_probability_with_average(L, P, 0.5, xscale='linear')
print(len(ax.get_lines()))
2
magnus.plotting.plot_biprobability(prob_nu: Sequence[Sequence[float]] | None = None, prob_nubar: Sequence[Sequence[float]] | None = None, *, configurations: Sequence[Dict[str, Any]] | None = None, dcp: Sequence[float] | None = None, energy: float | None = None, nu_i: int | None = None, nu_f: int | None = None, num_flavors: int | None = None, osc_params: Dict[str, float] | None = None, electron_fraction: float | None = None, electron_fraction_core: float | None = None, electron_fraction_mantle: float | None = None, electron_fraction_crust: float | None = None, electron_fraction_ocean: float | None = None, ratio_number_neutrons_to_protons: float | None = None, wrapper_kw: Dict[str, Any] | None = None, return_probability: bool = False, labels: Sequence[str] | None = None, curve_kw: Sequence[Dict[str, Any]] | None = None, markers: Sequence[Dict[str, Any]] | None = None, xlabel: str | None = None, ylabel: str | None = None, title: str | None = None, title_fontsize: float = 20.0, xlim: Tuple[float, float] | None = None, ylim: Tuple[float, float] | None = None, xmajor: float | None = None, xminor: float | None = None, ymajor: float | None = None, yminor: float | None = None, annotations: Sequence[Dict[str, Any]] | None = None, legend: bool = True, legend_title: str = '$\\delta_{\\rm CP}$', legend_loc: str | None = None, legend_kw: Dict[str, Any] | None = None, figsize: Tuple[float, float] = (9.0, 9.0), subplots_kw: Dict[str, Any] | None = None, savefig: str | None = None, savefig_kw: Dict[str, Any] | None = None, tight_layout: bool = True)[source]

Plot neutrino against antineutrino appearance probability.

The bi-probability plane: for each configuration, the locus traced out as \(\delta_{\rm CP}\) runs over \([-\pi, \pi]\), with optional markers at selected phases.

It draws probabilities you computed, or, given configurations instead, computes them through the Earth wrappers (magnus.oscprob.osc_prob_3nu_earth() and its four- and five-flavor siblings), which declare the PREM layer boundaries as slab edges and use a layered electron fraction unless told otherwise.

Added in version 1.0.0.

Changed in version 1.2.0: Computes the probabilities through the Earth wrappers when given configurations; markers may name a phase ('dcp'); nu_i and nu_f set the default axis labels.

Parameters:
  • prob_nu (sequence of sequence of float) – One entry per curve, each a sequence of probabilities over the same grid of \(\delta_{\rm CP}\) values. Required unless computing, and then left None.

  • prob_nubar (sequence of sequence of float) – One entry per curve, each a sequence of probabilities over the same grid of \(\delta_{\rm CP}\) values. Required unless computing, and then left None.

  • configurations (sequence of dict, optional) – Compute instead of draw: one curve per entry, each a path through the Earth – 'costhz', with 'L' [\(\text{eV}^{-1}\)] or, by default, the full chord; or 'loc_ini' and 'loc_fin', two locations joined by the chord between them – plus, optionally, its own 'energy' and any other wrapper keyword for that curve alone (the inverted-ordering parameters for an inverted-ordering curve, say). A configuration’s entry overrides the shared energy, osc_params, composition or wrapper_kw value for its curve. Style the curves with labels and curve_kw, as when drawing.

  • dcp (sequence of float, optional) – The phases the curves run over, in the wrapper’s convention (radians, or degrees under angles='deg'). Default: 100 points from \(-\pi\) to \(\pi\).

  • energy (float, optional) – Neutrino energy [eV] shared by the configurations that give none.

  • nu_i (int, optional) – The channel: the default axis labels, and, when computing, what is computed. Default \(\nu_\mu \to \nu_e\). Give both or neither.

  • nu_f (int, optional) – The channel: the default axis labels, and, when computing, what is computed. Default \(\nu_\mu \to \nu_e\). Give both or neither.

  • num_flavors (int, optional) – 3, 4 or 5: which Earth wrapper computes the probabilities (two flavors have no CP phase).

  • osc_params (dict, optional) – Mixing parameters shared by all curves, as the wrapper takes them. A dCP in it, or in a configuration, is replaced by the phases in dcp (and a marker’s), so a set from magnus.globaldefs.load_nufit_params() can be passed whole.

  • electron_fraction (float, optional) – The Earth’s composition, as the wrappers take it. Default: None, the layered values.

  • electron_fraction_core (float, optional) – The Earth’s composition, as the wrappers take it. Default: None, the layered values.

  • electron_fraction_mantle (float, optional) – The Earth’s composition, as the wrappers take it. Default: None, the layered values.

  • electron_fraction_crust (float, optional) – The Earth’s composition, as the wrappers take it. Default: None, the layered values.

  • electron_fraction_ocean (float, optional) – The Earth’s composition, as the wrappers take it. Default: None, the layered values.

  • ratio_number_neutrons_to_protons (float, optional) – Passed to the wrapper; matters from four flavors up.

  • wrapper_kw (dict, optional) – Any other wrapper keyword, e.g. rtol.

  • return_probability (bool, optional) – Also return the probabilities drawn. Default False.

  • labels (sequence of str, optional) – Legend label per curve.

  • curve_kw (sequence of dict, optional) – Per-curve Line2D keywords.

  • markers (sequence of dict, optional) – Markers at selected phases. Each entry gives its position either as 'index' (a position along the curve) or as 'xy' (an explicit coordinate pair, which is what you have when the marked phases were computed separately from the curve) or, when computing, as 'dcp' (a phase, computed for each curve it marks). Optionally 'marker', 'label', 'filled' and 'curve' (which curve it belongs to, default all).

  • xlabel (str, optional) – Axis labels. Default to the nu_i, nu_f pair.

  • ylabel (str, optional) – Axis labels. Default to the nu_i, nu_f pair.

  • title (str, optional) – Title.

  • title_fontsize (float, optional) – Title font size.

  • xlim (tuple of float, optional) – Axis limits.

  • ylim (tuple of float, optional) – Axis limits.

  • xmajor (float, optional) – Tick spacings.

  • xminor (float, optional) – Tick spacings.

  • ymajor (float, optional) – Tick spacings.

  • yminor (float, optional) – Tick spacings.

  • annotations (sequence of dict, optional) – Passed to annotate(); each entry needs 'text' and 'xy', and may carry any other annotate keyword. Coordinates are axes fractions.

  • legend (bool, optional) – Whether to draw a legend.

  • legend_title (str, optional) – Legend title. Default is '$\\delta_{\\rm CP}$'.

  • legend_loc (str, optional) – Legend location.

  • legend_kw (dict, optional) – Extra keywords merged over HOUSE_LEGEND_KW.

  • figsize (tuple of float, optional) – Figure size. Default is (9.0, 9.0), the square panel this plot uses.

  • subplots_kw (dict, optional) – Extra keywords for subplots().

  • savefig (str, optional) – If given, the figure is written here.

  • savefig_kw (dict, optional) – Extra keywords merged over HOUSE_SAVEFIG_KW.

  • tight_layout (bool, optional) –

    Whether to call tight_layout. Default is True.

    Changed in version 1.2.0: Default True: at the house text size the ordinate label was cut off.

Returns:

  • fig (matplotlib.figure.Figure)

  • ax (matplotlib.axes.Axes)

  • prob_nu, prob_nubar (numpy.ndarray or sequence) – Only with return_probability=True: the probabilities drawn, arrays of shape (len(configurations), len(dcp)) when computed, the inputs as given otherwise.

Raises:

ValueError – If the two probabilities have different numbers of curves, or one is missing when drawing; if configurations is given together with them, or a computing argument without configurations; if a marker gives none or more than one of index, xy and dcp, or dcp when drawing; if a configuration has no energy, no geometry or both geometries, L with two locations, or a per-point keyword (nubar, nu_i, nu_f); if only one of nu_i, nu_f is given; and for the flavor-count and duplicate-keyword checks of plot_oscillogram(), here with num_flavors 3, 4 or 5.

Examples

import matplotlib
matplotlib.use('Agg')
import numpy as np
from magnus.plotting import plot_biprobability

d = np.linspace(-np.pi, np.pi, 100)
P_nu = 0.05 + 0.02 * np.sin(d)
P_nubar = 0.04 + 0.02 * np.sin(d + 0.4)

fig, ax = plot_biprobability([P_nu], [P_nubar], labels=['NO'])
print(ax.get_xlabel())
$P_{\nu_\mu \to \nu_e}$

Computed through the Earth wrappers instead, from Fermilab to Homestake at 2 GeV, both orderings, with the phase \(\delta_{\rm CP} = 0\) marked:

import matplotlib
matplotlib.use('Agg')
import numpy as np
import magnus.globaldefs as gd
from magnus.plotting import plot_biprobability

where = dict(loc_ini='fermilab', loc_fin='homestake')
fig, ax, P_nu, P_nubar = plot_biprobability(
    configurations=[dict(where, **gd.load_nufit_params('NuFIT 6.1', 'NO')),
                    dict(where, **gd.load_nufit_params('NuFIT 6.1', 'IO'))],
    dcp=np.linspace(-np.pi, np.pi, 25), energy=2.0*gd.UNIT_GEV, num_flavors=3,
    labels=['NO', 'IO'], markers=[dict(dcp=0.0, marker='*', label='0')],
    return_probability=True)
print(P_nu.shape)
(2, 25)
magnus.plotting.plot_oscillogram(costhz: Sequence[float], log10_energy: Sequence[float], probability: Sequence[Sequence[float]] | None = None, *, nu_i: int | None = None, nu_f: int | None = None, nubar: bool | None = None, num_flavors: int | None = None, osc_params: Dict[str, float] | None = None, electron_fraction: float | None = None, electron_fraction_core: float | None = None, electron_fraction_mantle: float | None = None, electron_fraction_crust: float | None = None, electron_fraction_ocean: float | None = None, ratio_number_neutrons_to_protons: float | None = None, wrapper_kw: Dict[str, Any] | None = None, return_probability: bool = False, levels: int = 120, cmap: str = 'plasma', xlabel: str = 'Zenith angle, $\\cos(\\theta_z)$', ylabel: str = 'Neutrino energy, $\\log_{10}(E_\\nu/{\\rm GeV})$', cbar_label: str | None = None, cbar_label_prefix: str = '', cbar_fontsize: float = 25.0, cbar_labelsize: float = 25.0, annotation: str | None = None, annotation_fontsize: float = 23.0, xlim: Tuple[float, float] | None = None, ylim: Tuple[float, float] | None = None, xmajor: float | None = 0.2, xminor: float | None = 0.02, ymajor: float | None = 0.1, yminor: float | None = 0.02, figsize: Tuple[float, float] = (9.0, 9.0), contourf_kw: Dict[str, Any] | None = None, subplots_kw: Dict[str, Any] | None = None, savefig: str | None = None, savefig_kw: Dict[str, Any] | None = None, tight_layout: bool = False)[source]

Plot an oscillogram: probability over zenith angle and energy.

A filled contour map of the oscillation probability in the plane of \(\cos\theta_z\) (equivalently, baseline through the Earth) and \(\log_{10} E_\nu\), with a color bar and the channel annotated in the corner over a white stroke so it stays legible against the color map.

Given a probability array, it draws it. Given none, it computes it first through the Earth wrappers (magnus.oscprob.osc_prob_3nu_earth() and its two-, four- and five-flavor siblings), one call per zenith angle over the whole energy array, so each is an energy scan the energy-batched engine answers. The wrappers declare the PREM layer boundaries as slab edges and use a layered electron fraction unless it is overridden here.

Added in version 1.0.0.

Changed in version 1.2.0: probability is optional: without it, the oscillogram is computed through the Earth wrappers. Adds num_flavors, osc_params, the electron-fraction keywords, wrapper_kw and return_probability.

Changed in version 1.2.0: Takes nubar and labels an antineutrino oscillogram as such (issue #145), computes NSI and LIV through the Earth wrappers (issue #146 §1), and refuses log10_energy above 19 in compute mode (issue #160 §12).

Parameters:
  • costhz (sequence of float) – Zenith-angle cosines, the abscissa. The physical range is \([-1, 0]\); at \(\cos\theta_z \geq 0\) the path inside the Earth has zero length and the computed probability is the identity.

  • log10_energy (sequence of float) – \(\log_{10}\) of the energy in GeV, the ordinate.

  • probability (sequence of sequence of float, optional) – Probability with shape (len(log10_energy), len(costhz)). If None (default), it is computed through the Earth wrappers, which needs nu_i, nu_f and num_flavors. Give either this or the arguments that compute it, not both.

  • nu_i (int, optional) – Flavor pair, used for the color-bar label and the annotation when those are not given explicitly, and as the channel when the probability is computed. For two flavors they index the wrapper’s \(2 \times 2\) matrix (0 or 1).

  • nu_f (int, optional) – Flavor pair, used for the color-bar label and the annotation when those are not given explicitly, and as the channel when the probability is computed. For two flavors they index the wrapper’s \(2 \times 2\) matrix (0 or 1).

  • num_flavors (int, optional) – 2, 3, 4 or 5: which Earth wrapper computes the probability.

  • nubar (bool, optional) – True for antineutrinos. In compute mode it is also passed to the wrapper, so the grid and its label agree; a value contradicting wrapper_kw['nubar'] is refused. Default: None, which reads wrapper_kw['nubar'] in compute mode and False otherwise.

  • osc_params (dict, optional) – Mixing parameters, passed to the wrapper by name (sth and Dm2 at two flavors, where they are required; s12, …, D31 and the sterile ones at three to five flavors, where the wrapper’s default set applies to any left out).

  • electron_fraction (float, optional) – Electron fraction, everywhere or per PREM layer. None (default) keeps the wrappers’ layered values. Passed to the wrapper unchanged, with the wrapper’s meaning and validation.

  • electron_fraction_core (float, optional) – Electron fraction, everywhere or per PREM layer. None (default) keeps the wrappers’ layered values. Passed to the wrapper unchanged, with the wrapper’s meaning and validation.

  • electron_fraction_mantle (float, optional) – Electron fraction, everywhere or per PREM layer. None (default) keeps the wrappers’ layered values. Passed to the wrapper unchanged, with the wrapper’s meaning and validation.

  • electron_fraction_crust (float, optional) – Electron fraction, everywhere or per PREM layer. None (default) keeps the wrappers’ layered values. Passed to the wrapper unchanged, with the wrapper’s meaning and validation.

  • electron_fraction_ocean (float, optional) – Electron fraction, everywhere or per PREM layer. None (default) keeps the wrappers’ layered values. Passed to the wrapper unchanged, with the wrapper’s meaning and validation.

  • ratio_number_neutrons_to_protons (float, optional) – Passed to the wrapper unchanged; None keeps its default.

  • wrapper_kw (dict, optional) – Any other keyword the Earth wrapper accepts (rtol, atol, nubar, integration_method, magnus_exp_order, …). The channel, energy, costhz, L and the composition keywords above cannot be given here too. loc_ini, loc_fin and the depths are not refused but do not belong here: each column of the oscillogram is one zenith angle, from surface to surface.

  • return_probability (bool, optional) – If True, also return the probability drawn. Default is False.

  • levels (int, optional) – Number of filled contour levels. Default is 120.

  • cmap (str, optional) – Color map. Default is 'plasma'.

  • xlabel (str, optional) – Axis labels.

  • ylabel (str, optional) – Axis labels.

  • cbar_label (str, optional) – Color-bar label; overrides the one built from the flavor pair.

  • cbar_label_prefix (str, optional) – Text placed before the probability label on the color bar.

  • cbar_fontsize (float, optional) – Color-bar label and tick-label sizes.

  • cbar_labelsize (float, optional) – Color-bar label and tick-label sizes.

  • annotation (str, optional) – Corner annotation. Defaults to the probability label when the flavor pair is given; pass '' to suppress it.

  • annotation_fontsize (float, optional) – Corner annotation size.

  • xlim (tuple of float, optional) – Axis limits. Default to the data range.

  • ylim (tuple of float, optional) – Axis limits. Default to the data range.

  • xmajor (float, optional) – Tick spacings. Pass None to hand an axis back to Matplotlib’s own locator. Default: 0.2, 0.02, 0.1 and 0.02.

  • xminor (float, optional) – Tick spacings. Pass None to hand an axis back to Matplotlib’s own locator. Default: 0.2, 0.02, 0.1 and 0.02.

  • ymajor (float, optional) – Tick spacings. Pass None to hand an axis back to Matplotlib’s own locator. Default: 0.2, 0.02, 0.1 and 0.02.

  • yminor (float, optional) – Tick spacings. Pass None to hand an axis back to Matplotlib’s own locator. Default: 0.2, 0.02, 0.1 and 0.02.

  • figsize (tuple of float, optional) – Figure size. Default is (9.0, 9.0).

  • contourf_kw (dict, optional) – Extra keywords for contourf().

  • subplots_kw (dict, optional) – Extra keywords for subplots().

  • savefig (str, optional) – If given, the figure is written here.

  • savefig_kw (dict, optional) – Extra keywords merged over HOUSE_SAVEFIG_KW.

  • tight_layout (bool, optional) – Whether to call tight_layout. Default is False.

Returns:

  • fig (matplotlib.figure.Figure)

  • ax (matplotlib.axes.Axes)

  • probability (np.ndarray) – Only with return_probability=True: the probability drawn, shape (len(log10_energy), len(costhz)).

Raises:

ValueError – If the probability has the wrong shape; if it is given together with any argument that would compute it; if it is to be computed without nu_i, nu_f or a valid num_flavors, or at two flavors without osc_params; or if a keyword arrives both explicitly and in wrapper_kw.

Examples

A precomputed probability is drawn as given:

import matplotlib
matplotlib.use('Agg')
import numpy as np
import magnus.globaldefs as gd
from magnus.plotting import plot_oscillogram

c = np.linspace(-1.0, 0.0, 40)
lE = np.linspace(-1.0, 1.0, 30)
P = np.sin(np.outer(10 ** lE, 1.0 + c)) ** 2

fig, ax = plot_oscillogram(c, lE, P, nu_i=gd.NUMU, nu_f=gd.NUMU)
print(ax.get_xlabel())
Zenith angle, $\cos(\theta_z)$

Without one, it is computed through the Earth wrappers, here at three flavors with the default mixing parameters and the layered electron fraction:

import matplotlib
matplotlib.use('Agg')
import numpy as np
import magnus.globaldefs as gd
from magnus.plotting import plot_oscillogram

import warnings
from magnus.magnus import MagnusConvergenceWarning

c = np.linspace(-1.0, -0.1, 25)
lE = np.linspace(0.0, 1.0, 20)
# A few of these chords need the adaptive refinement to widen its first grids;
# this is the expected, informational MagnusConvergenceWarning discussed in the
# package README.
with warnings.catch_warnings():
    warnings.simplefilter('ignore', MagnusConvergenceWarning)
    fig, ax, P = plot_oscillogram(c, lE, nu_i=gd.NUMU, nu_f=gd.NUE, num_flavors=3,
                                  return_probability=True)
print(P.shape, round(float(P.max()), 3))
(20, 25) 0.492