Source code for pygaps.graphing.calc_graphs

"""Functions for plotting calculation-specific graphs."""

import typing as t

import matplotlib as mpl
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker

from pygaps.graphing.labels import label_units_dict
from pygaps.graphing.mpl_styles import BASE_STYLE
from pygaps.graphing.mpl_styles import LINE_ERROR
from pygaps.graphing.mpl_styles import LINE_FIT
from pygaps.graphing.mpl_styles import POINTS_HIGHLIGHTED
from pygaps.graphing.mpl_styles import POINTS_IMPORTANT
from pygaps.graphing.mpl_styles import POINTS_MUTED


[docs]@mpl.rc_context(BASE_STYLE) def roq_plot( pressure: t.Iterable[float], roq_points: t.Iterable[float], minimum: int, maximum: int, p_monolayer: float, roq_monolayer: float, ax=None, ): """ Draw a Rouquerol plot. Parameters ---------- pressure : array Pressure points which will make up the x axis. roq_points : array Rouquerol-transformed points which will make up the y axis. minimum : int Lower bound of the selected points. maximum : int Higher bound of the selected points. p_monolayer : float Pressure at which statistical monolayer is achieved. rol_monolayer : float Rouquerol transform of the point at which statistical monolayer is achieved. ax : matplotlib axes object, default None The axes object where to plot the graph if a new figure is not desired. Returns ------- matplotlib.axes Matplotlib axes of the graph generated. The user can then apply their own styling if desired. """ # Generate the figure if needed if ax is None: _, ax = plt.subplots(figsize=(6, 4)) with mpl.rc_context(POINTS_MUTED): ax.plot( pressure, roq_points, label='all points', ) with mpl.rc_context(POINTS_HIGHLIGHTED): ax.plot( pressure[minimum:maximum + 1], roq_points[minimum:maximum + 1], label='chosen points', ) with mpl.rc_context(POINTS_IMPORTANT): ax.plot( p_monolayer, roq_monolayer, label='monolayer point', ) ax.set_title("Rouquerol plot") ax.set_xlabel('p/p°') ax.set_ylabel('$n ( 1 - p/p°)$') ax.legend(loc='best') return ax
[docs]@mpl.rc_context(BASE_STYLE) def bet_plot( pressure: t.Iterable[float], bet_points: t.Iterable[float], minimum: int, maximum: int, slope: float, intercept: float, p_monolayer: float, bet_monolayer: float, ax=None, ): """ Draw a BET plot. Parameters ---------- pressure : array Pressure points which will make up the x axis. bet_points : array BET-transformed points which will make up the y axis. minimum : int Lower bound of the selected points. maximum : int Higher bound of the selected points. slope : float Slope of the chosen linear region. intercept : float Intercept of the chosen linear region. p_monolayer : float Pressure at which statistical monolayer is achieved. rol_monolayer : float BET transform of the point at which statistical monolayer is achieved. ax : matplotlib axes object, default None The axes object where to plot the graph if a new figure is not desired. Returns ------- matplotlib.axes Matplotlib axes of the graph generated. The user can then apply their own styling if desired. """ # Generate the figure if needed if ax is None: _, ax = plt.subplots(figsize=(6, 4)) with mpl.rc_context(POINTS_MUTED): ax.plot( pressure, bet_points, label='all points', ) with mpl.rc_context(POINTS_HIGHLIGHTED): ax.plot( pressure[minimum:maximum + 1], bet_points[minimum:maximum + 1], label='chosen points', ) x_lim = [0, pressure[maximum]] y_lim = [slope * x_lim[0] + intercept, slope * x_lim[1] + intercept] with mpl.rc_context(LINE_FIT): ax.plot( x_lim, y_lim, color='black', label='model fit', ) with mpl.rc_context(POINTS_IMPORTANT): ax.plot( p_monolayer, bet_monolayer, label='monolayer point', ) ax.set_ylim(bottom=0, top=bet_points[maximum] * 1.2) ax.set_xlim(left=0, right=pressure[maximum] * 1.2) ax.set_title("BET plot") ax.set_xlabel('p/p°') ax.set_ylabel('$\\frac{p/p°}{n ( 1- p/p°)}$') ax.legend(loc='best') return ax
[docs]@mpl.rc_context(BASE_STYLE) def langmuir_plot( pressure: t.Iterable[float], langmuir_points: t.Iterable[float], minimum: int, maximum: int, slope: float, intercept: float, ax=None, ): """ Draw a Langmuir plot. Parameters ---------- pressure : array Pressure points which will make up the x axix. langmuir_points : array Langmuir-transformed points which will make up the y axis. minimum : int Lower bound of the selected points. maximum : int Higher bound of the selected points. slope : float Slope of the chosen linear region. intercept : float Intercept of the chosen linear region. ax : matplotlib axes object, default None The axes object where to plot the graph if a new figure is not desired. Returns ------- matplotlib.axes Matplotlib axes of the graph generated. The user can then apply their own styling if desired. """ # Generate the figure if needed if ax is None: _, ax = plt.subplots(figsize=(6, 4)) with mpl.rc_context(POINTS_MUTED): ax.plot( pressure, langmuir_points, label='all points', ) with mpl.rc_context(POINTS_HIGHLIGHTED): ax.plot( pressure[minimum:maximum], langmuir_points[minimum:maximum], label='chosen points', ) x_lim = [0, pressure[maximum - 1]] y_lim = [slope * x_lim[0] + intercept, slope * x_lim[1] + intercept] with mpl.rc_context(LINE_FIT): ax.plot( x_lim, y_lim, linestyle='--', color='black', label='model fit', ) ax.set_ylim(bottom=0, top=langmuir_points[maximum - 1] * 1.2) ax.set_xlim(left=0, right=pressure[maximum - 1] * 1.2) ax.set_title("Langmuir plot") ax.set_xlabel('p/p°') ax.set_ylabel('(p/p°)/n') ax.legend(loc='best') return ax
[docs]@mpl.rc_context(BASE_STYLE) def tp_plot( thickness_curve: t.Iterable[float], loading: t.Iterable[float], results: dict, units: dict, alpha_s: bool = False, alpha_reducing_p: float = None, ax=None, ): """ Draw a t-plot (also used for the alpha-s plot). Parameters ---------- thickness_curve : array Thickness of the adsorbed layer at selected points. In the case of alpha_s plot, it is the alpha_s transform of the reference isotherm. loading : array Loading of the isotherm to plot. results : dict Dictionary of linear regions selected with the members: - ``section`` (array) : the points of the plot chosen for the line - ``area`` (float) : calculated surface area, from the section parameters - ``adsorbed_volume`` (float) : the amount adsorbed in the pores as calculated per section - ``slope`` (float) : slope of the straight trendline fixed through the region - ``intercept`` (float) : intercept of the straight trendline through the region - ``corr_coef`` (float) : correlation coefficient of the linear region units : dict A unit dictionary to fill in labels. alpha_s : bool Whether the function is used for alpha_s display. alpha_reducing_p : float The reducing pressure used for alpha_s. ax : matplotlib axes object, default None The axes object where to plot the graph if a new figure is not desired. Returns ------- matplotlib.axes Matplotlib axes of the graph generated. The user can then apply their own styling if desired. """ # Generate the figure if needed if ax is None: _, ax = plt.subplots() if alpha_s: label_points = '$\\alpha_s$ transform' label_x = '$\\alpha_s (V/V_{' + str(alpha_reducing_p) + '})$' label_title = '$\\alpha_s$ method' else: label_points = 't transform' label_x = 'Layer thickness [nm]' label_title = 't-plot method' ax.plot( thickness_curve, loading, label=label_points, color="k", ) for index, result in enumerate(results): # plot chosen points with mpl.rc_context(POINTS_HIGHLIGHTED): ax.plot( thickness_curve[result.get('section')], loading[result.get('section')], color=f"C{index}", ) # plot line min_lim = 0 max_lim = max(thickness_curve[result.get('section')]) * 1.2 x_lim = [min_lim, max_lim] y_lim = [ result.get('slope') * min_lim + result.get('intercept'), result.get('slope') * max_lim + result.get('intercept') ] with mpl.rc_context(LINE_FIT): ax.plot( x_lim, y_lim, color=f"C{index}", label=f'linear {index+1}', ) ax.set_title(label_title) ax.set_xlim(left=0) ax.set_ylim(bottom=0) ax.set_xlabel(label_x) ax.set_ylabel(label_units_dict('loading', units)) ax.legend(loc='best') return ax
[docs]@mpl.rc_context(BASE_STYLE) def psd_plot( pore_widths: t.Iterable[float], pore_dist: t.Iterable[float], pore_vol_cum: t.Iterable[float] = None, method: str = None, labeldiff: str = 'distribution', labelcum: str = 'cumulative', log: bool = True, right: int = None, left: int = None, ax=None, ): """ Draw a pore size distribution plot. Parameters ---------- pore_widths : array Array of the pore radii which will become the x axis. pore_dist : array Contribution of each pore radius which will make up the y axis. pore_vol_cum : array Pre-calculated cumulative value. method : str The method used. Will be a string part of the title. labeldiff : str The label for the plotted data, which will appear in the legend. labelcum : str, optional The label for the cumulative data, which will appear in the legend. Set to None to remove cumulative distribution log : bool Whether to display a logarithmic graph. right : int Higher bound of the selected pore widths. right : int Lower bound of the selected pore widths. ax : matplotlib axes object, default None The axes object where to plot the graph if a new figure is not desired. Returns ------- matplotlib.axes Matplotlib axes of the graph generated. The user can then apply their own styling if desired. """ # Generate the figure if needed if ax is None: fig = plt.figure(figsize=(15, 5)) ax = fig.add_subplot(111) l1 = ax.plot( pore_widths, pore_dist, label=labeldiff, color='k', ) if labelcum: ax2 = ax.twinx() l2 = ax2.plot( pore_widths, pore_vol_cum, color='r', label=labelcum, ) def formatter(x, pos): """Forces matplotlib to display whole numbers""" return f"{x:g}" if log: ax.set_xscale('log') ax.xaxis.set_minor_formatter(ticker.FuncFormatter(formatter)) ax.xaxis.set_major_formatter(ticker.FuncFormatter(formatter)) ax.xaxis.set_major_locator(ticker.LogLocator(base=10.0, numticks=15, numdecs=20)) ax.tick_params(axis='x', which='minor', width=0.75, length=2.5) ax.tick_params(axis='x', which='major', width=2, length=10) ax.set_xlim(left=left, right=right) else: if not left: left = 0 ax.set_xlim(left=left, right=right) ax.tick_params(axis='both', which='major') ax.set_title("PSD plot " + str(method)) ax.set_xlabel('Pore width [nm]') ax.set_ylabel('Distribution, dV/dw [$cm^3 g^{-1} nm^{-1}$]') if labelcum: ax2.set_ylabel('Cumulative volume [$cm^3 g^{-1}$]') lns = l1 if labelcum: lns = l1 + l2 ax.legend(lns, [lbl.get_label() for lbl in lns], loc='lower right') ax.set_ylim(bottom=0) if labelcum: ax2.set_ylim(bottom=0) ax.grid(True) return ax
[docs]@mpl.rc_context(BASE_STYLE) def isosteric_enthalpy_plot( loading: t.Iterable[float], isosteric_enthalpy: t.Iterable[float], std_err: t.Iterable[float], units: dict, log: bool = False, ax=None, ): """ Draws the isosteric enthalpy plot. Parameters ---------- loading : array Loadings for which the isosteric enthalpy was calculated. isosteric_enthalpy : array The isosteric enthalpy corresponding to each loading. std_err : array Standard error for each point. units : dict A unit dictionary to fill in labels. log : int Whether to display a logarithmic graph. ax : matplotlib axes object, default None The axes object where to plot the graph if a new figure is not desired. Returns ------- matplotlib.axes Matplotlib axes of the graph generated. The user can then apply their own styling if desired. """ # Generate the figure if needed if ax is None: _, ax = plt.subplots() with mpl.rc_context(LINE_ERROR): ax.errorbar( loading, isosteric_enthalpy, yerr=std_err, color='r', label=r'$\Delta h_{st}$', ) if log: ax.set_xscale('log') ax.xaxis.set_major_locator(ticker.LogLocator(base=10.0, numticks=15, numdecs=20)) ax.set_xlabel(label_units_dict('loading', units)) ax.set_ylabel(r'Isosteric enthalpy [$kJ\/mol^{-1}$]') ax.legend(loc='best') ax.tick_params(axis='both', which='major') ax.set_xlim(left=0) ax.set_ylim(bottom=0) return ax
[docs]@mpl.rc_context(BASE_STYLE) def initial_enthalpy_plot( loading: t.Iterable[float], enthalpy: t.Iterable[float], fitted_enthalpy: t.Iterable[float], log: bool = False, title: str = None, extras=None, # TODO what is extra used for? ax=None, ): """ Draws the initial enthalpy calculation plot. Parameters ---------- loading : array Loadings for which the initial enthalpy was calculated. enthalpy : array The enthalpy corresponding to each loading. fitted_enthalpy : array The predicted enthalpy corresponding to each loading. log : int Whether to display a logarithmic graph title : str Name of the material to put in the title. ax : matplotlib axes object, default None The axes object where to plot the graph if a new figure is not desired. Returns ------- matplotlib.axes Matplotlib axes of the graph generated. The user can then apply their own styling if desired. """ # Generate the figure if needed if ax is None: _, ax = plt.subplots() with mpl.rc_context(POINTS_MUTED): ax.plot( loading, enthalpy, label='original', ) ax.plot( loading, fitted_enthalpy, color='r', label='fitted', ) if extras is not None: with mpl.rc_context(LINE_FIT): for param in extras: ax.plot( param[0], param[1], label=param[2], linestyle='--', ) if log: ax.set_xscale('log') ax.xaxis.set_major_locator(plt.ticker.LogLocator(base=10.0, numticks=15, numdecs=20)) ax.set_title(title + " initial enthalpy fit") ax.set_xlabel('Loading') ax.set_ylabel('Enthalpy') ax.legend(loc='best') ax.set_ylim(bottom=0, top=(max(enthalpy) * 1.2)) ax.set_xlim(left=0) ax.tick_params(axis='both', which='major') return ax
[docs]@mpl.rc_context(BASE_STYLE) def dra_plot( logv: t.Iterable[float], log_n_p0p: t.Iterable[float], minimum: int, maximum: int, slope: float, intercept: float, exp: float, ax=None, ): """ Draw a Dubinin plot. Parameters ---------- logv : array Logarithm of volume adsorbed. log_n_p0p : array Logarithm of pressure term. minimum : int Lower index of the selected points. maximum : int Higher index of the selected points. slope : float Slope of the fitted line. intercept : float Intercept of the fitted line. exp : float The exponent of the DA/DR graph. ax : matplotlib axes object, default None The axes object where to plot the graph if a new figure is not desired. Returns ------- matplotlib.axes Matplotlib axes of the graph generated. The user can then apply their own styling if desired. """ # Generate the figure if needed if ax is None: _, ax = plt.subplots() if exp != 2: exp = 'n' with mpl.rc_context(POINTS_MUTED): ax.plot( log_n_p0p, logv, label='all points', ) with mpl.rc_context(POINTS_HIGHLIGHTED): ax.plot( log_n_p0p[minimum:maximum + 1], logv[minimum:maximum + 1], label='chosen points', ) with mpl.rc_context(LINE_FIT): ax.plot( log_n_p0p, slope * log_n_p0p + intercept, color='black', label='model fit', ) ax.set_xlabel(fr'$ln^{exp}\ p^0/p$') ax.set_ylabel(r'$ln\ V/V_0$') ax.legend() return ax
[docs]@mpl.rc_context(BASE_STYLE) def virial_plot( loading: t.Iterable[float], ln_p_over_n: t.Iterable[float], n_load: t.Iterable[float], p_load: t.Iterable[float], added_point: bool, ax=None, ): """ Draw a Virial plot. """ # Generate the figure if needed if ax is None: _, ax = plt.subplots() ax.plot(loading, ln_p_over_n) ax.plot(n_load, p_load, '-') if added_point: ax.plot(1e-1, ln_p_over_n[0], 'or') ax.set_title("Virial fit") ax.set_xlabel("Loading") ax.set_ylabel("ln(p/n)") ax.tick_params(axis='both', which='major') return ax