Source code for magnopy.io._png_plots

# ================================== LICENSE ===================================
# Magnopy - Python package for magnons.
#
# Copyright (C) 2023 Magnopy Team
#
# e-mail: anry@uv.es, web: magnopy.org
#
# This program is free software: you  can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the  Free Software
# Foundation,  either  version 3  of the License,  or (at your option) any later
# version.
#
# This program is distributed in the  hope  that it will be useful,  but WITHOUT
# ANY WARRANTY;  without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the  GNU General Public License  along with
# this program.  If not, see <https://www.gnu.org/licenses/>.
# ================================ END LICENSE =================================

import warnings
import numpy as np


try:
    import matplotlib.pyplot as plt

    MATPLOTLIB_AVAILABLE = True
except ImportError:
    MATPLOTLIB_AVAILABLE = False
    MATPLOTLIB_WARNING_MESSAGE = "Matplotlib is not available. Please install 'magnopy[visual]' or matplotlib itself."

from magnopy._constants._units import _MAGNON_ENERGY_UNITS


[docs] def plot_dispersion( modes, x_data=None, ticks=None, labels=None, output_filename=None, ylabel="Energy", colors=None, ) -> None: r""" Plot dispersion curves. Input data (``modes``) are expected in meV. meV converted into THz for the twin axis via .. math:: E = h \nu Parameters ---------- modes : (M,) or (N, M) |array-like|_ Dispersion data, Either one or :math:`N` modes with :math:`M` points each. x_data : (M,) |array-like|_, optional Abscissa (x axis) data. If ``None``, then integer indices would be used. ticks : (K,) |array-like|_, optional Positions of high-symmetry points along the x axis. If ``None``, then no high-symmetry points would be marked. coordinates are interpreted together with ``x_data``, whether the latter is provided or not. labels : (K,) list of str, optional Labels of high-symmetry points. output_filename : str, optional Name of the file for saving the image. If ``None``, then the graph would be opened in the interactive matplotlib window. ylabel : str, default "Energy" Label for the ordinate (y axis). Do not include units, units are included automatically - meV for primary axis and THz for twin axis. colors : "random" or (N,) |array-like|_, optional Colors for each mode. If "random", random colors are assigned. If ``None``, then a default color is used for all modes. Raises ------ ValueError If shapes of ``x_data`` and ``modes`` are incompatible, i. e. ``x_data.shape[0] != modes.shape[1]``. ValueError If lengths of ``ticks`` and ``labels`` are incompatible, i. e. ``len(ticks) != len(labels)``. ValueError If length of ``colors`` is incompatible with number of modes, i. e. ``len(colors) != modes.shape[0]``. Notes ----- If ``matplotlib`` is not available, a warning is issued and the function plots nothing. * If ``ticks`` is given and ``labels`` is not, labels are filled as ``K1, K2, ...``. * If ``labels`` is given and ``ticks`` is not, ``labels`` is ignored. * If both ``ticks`` and ``labels`` are not given, no high-symmetry points are marked. """ if not MATPLOTLIB_AVAILABLE: warnings.warn(MATPLOTLIB_WARNING_MESSAGE, RuntimeWarning, stacklevel=2) return modes = np.array(modes) if len(modes.shape) == 1: modes = modes[np.newaxis, :] if x_data is None: x_data = np.arange(modes.shape[1]) else: x_data = np.array(x_data) if x_data.shape[0] != modes.shape[1]: raise ValueError( f"Incompatible shapes between x_data ({x_data.shape}) and modes ({modes.shape}): {x_data.shape[0]} != {modes.shape[1]}." ) if ticks is not None and labels is None: labels = [f"K{i + 1}" for i in range(len(ticks))] if ticks is not None and len(labels) != len(ticks): raise ValueError( f"Incompatible lengths between ticks ({len(ticks)}) and labels ({len(labels)}): {len(ticks)} != {len(labels)}." ) if colors is None: colors = ["#A47864" for _ in range(modes.shape[0])] elif colors == "random": import random colors = [] for _ in range(modes.shape[0]): color = "#" for _ in range(6): color += random.choice("0123456789ABCDEF") colors.append(color) elif len(colors) != modes.shape[0]: raise ValueError( f"Incompatible length of colors ({len(colors)}) and number of modes ({modes.shape[0]}): {len(colors)} != {modes.shape[0]}." ) fig, ax = plt.subplots() for mode, color in zip(modes, colors): ax.plot(x_data, mode, lw=1, color=color) if ticks is not None: ax.set_xticks(ticks=ticks, labels=labels, fontsize=13) ax.vlines( ticks, 0, 1, lw=0.5, color="grey", ls="dashed", zorder=0, transform=ax.get_xaxis_transform(), ) else: ax.set_xticks([], []) ax.set_xlabel("k-points (arbitrary units)", fontsize=15) ax.set_xlim(x_data[0], x_data[-1]) ax.set_ylabel(f"{ylabel}, meV", fontsize=15) ax.hlines( 0, 0, 1, lw=0.5, color="grey", linestyle="dashed", transform=ax.get_yaxis_transform(), ) # Add twin axis ylims = ax.get_ylim() meV_to_THz = _MAGNON_ENERGY_UNITS["mev"] / _MAGNON_ENERGY_UNITS["thz"] twinax = ax.twinx() twinax.set_ylim(meV_to_THz * ylims[0], meV_to_THz * ylims[1]) twinax.set_ylabel(f"{ylabel}, THz", fontsize=15) if output_filename is not None: plt.savefig(output_filename, dpi=400, bbox_inches="tight") else: plt.show() plt.close()