"""Various utility functions for creating interactive plots, including functions for generating plot objects from
contributions and perturbations. These functions are used in the interactive plotting functions in the ``viz`` module."""
import socket
from tsunami_ip_utils._utils import _filter_redundant_reactions, _isotope_reaction_list_to_nested_dict
import numpy as np
from typing import Dict, List, Tuple
from uncertainties import unumpy
from pathlib import Path
from uncertainties import ufloat
import tsunami_ip_utils
[docs]
def _find_free_port() -> int:
"""Finds a free port on localhost for running a Flask/Dash server. This is done by creating a socket and binding it
to an available port. The socket is then closed and the port number is returned.
Returns
-------
A free port number."""
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(("", 0)) # Let the OS pick an available port
port = s.getsockname()[1]
s.close()
return port
[docs]
def _determine_plot_type(contributions: List[ dict ], plot_redundant_reactions: bool) -> Tuple[dict, bool]:
"""Determines whether the contributions are nuclide-wise or nuclide-reaction-wise and whether to plot redundant
reactions or not. Then converts the contributions (list of dictionaries) to a dictionary of contributions keyed by
isotope then by reaction type if necessary.
Parameters
----------
contributions
List of dictionaries containing the contributions to the similarity parameter for each
nuclide or nuclide-reaction pair.
plot_redundant_reactions
Whether to plot redundant reactions (or irrelevant reactions) when considering
nuclide-reaction-wise contributions.
Returns
-------
- contributions
Contributions to the similarity parameter keyed by isotope then by reaction type (if necessary).
- nested_plot
Whether the plot is nested by nuclide then by reaction type."""
if 'reaction_type' in contributions[0]: # Nuclide-reaction-wise contributions
nested_plot = True # Nested plot by nuclide then by reaction type
# Create a dictionary of contributions keyed by isotope then by reaction type
contributions = _isotope_reaction_list_to_nested_dict(contributions, 'contribution')
# If viewing nuclide-reaction wise contributions, it's important (at least for the visualizations in this function)
# that if viewing the true contributions to the nuclide total, that redundant interactions (e.g. capture and fission
# + (n, g)) and irrelevant interactions (e.g. chi and nubar) are not plotted.
if not plot_redundant_reactions:
# Remove redundant interactions
contributions = _filter_redundant_reactions(contributions)
else: # Nuclide-wise contributions
nested_plot = False
contributions = { contribution['isotope']: contribution['contribution'] for contribution in contributions }
return contributions, nested_plot
[docs]
def generate_plot_objects_array_from_contributions(contributions: Dict[ str, List[ unumpy.uarray ] ], integral_index_name: str,
**kwargs: dict) -> np.ndarray:
"""Generate a matrix of plot objects (for creating a matrix plot) for the given contributions to an arbitrary integral index.
This is valid for plots of :math:`\\Delta k/k` contributions, :math:`E` contributions, :math:`c_k` contributions, etc..
Parameters
----------
contributions
Dictionary of a list of contributions to the integral index keyed by application or experiment.
integral_index_name
Name of the integral index being plotted.
kwargs
Additional keyword arguments. The following are supported:
- diagonal_type (str)
Type of plot to create on the diagonal. Default is ``'interactive_pie'`` which creates an interactive
pie chart.
- interactive_contribution_legend (bool)
Whether to make the legend interactive for the contribution plots. Default is ``True``.
- interactive_correlation_legend (bool)
Whether to make the legend interactive for the correlation plots. Default is ``True``.
Returns
-------
2D numpy array of plot objects to be plotted with the :func:`tsunami_ip_utils.viz.viz.matrix_plot` function."""
from tsunami_ip_utils.viz import contribution_plot, correlation_plot # Import here to avoid circular import
# Get options for legend interactivity and the diagonal plot type if supplied
diagonal_type = kwargs.get('diagonal_type', 'interactive_pie')
interactive_correlation_legend = kwargs.get('interactive_correlation_legend', True)
interactive_contribution_legend = kwargs.get('interactive_contribution_legend', True)
num_applications = len(contributions['application'])
num_experiments = len(contributions['experiment'])
# Construct plot matrix
plot_objects_array = np.empty( ( num_applications, num_experiments ), dtype=object )
for application_index in range(num_applications):
for experiment_index in range(num_experiments):
if experiment_index == application_index:
# On the diagonal, make a contribution plot, as a correlation plot is not useful when comparing the same
# application and experiment
plot_objects_array[application_index, experiment_index] = \
contribution_plot(
contributions['application'][application_index],
plot_type=diagonal_type,
integral_index_name=integral_index_name,
interactive_legend=interactive_contribution_legend,
)
else:
plot_objects_array[application_index, experiment_index] = \
correlation_plot(
contributions['application'][application_index],
contributions['experiment'][experiment_index],
plot_type='interactive_scatter',
integral_index_name=integral_index_name,
plot_redundant_reactions=True,
interactive_legend=interactive_correlation_legend
)
return plot_objects_array
[docs]
def generate_plot_objects_array_from_perturbations(points_array: np.ndarray) -> np.ndarray:
"""Generate a matrix of plot objects (for creating a matrix plot) from a numpy array of perturbation points. This is
used for a matrix of perturbation plots only.
Parameters
----------
points_array
2D numpy array of points generated from the perturbation test. Shape ``(num_applications, num_experiments)``.
Returns
-------
2D numpy array of plot objects to be plotted with the :func:`tsunami_ip_utils.viz.viz.matrix_plot` function."""
from tsunami_ip_utils.viz import perturbation_plot # Import here to avoid circular import
# Construct plot matrix
num_applications, num_experiments, _, _ = np.shape(points_array)
plot_objects_array = np.empty( ( num_applications, num_experiments ), dtype=object)
for i, row in enumerate(points_array):
for j, _ in enumerate(row):
fig = perturbation_plot(points_array[i, j])
plot_objects_array[i, j] = fig
return plot_objects_array