Esempio n. 1
0
    def get_scatter_plot_boundaries(self):
        """
        Get the ZPF boundaries to plot from each two phase region.

        Notes
        -----
        For now, we will not support connecting regions with lines, so this
        function returns a tuple of scatter_dict and a tineline_collection.

        Examples
        --------
        >>> scatter_dict, tieline_collection, legend_handles = zpf_boundary_sets.get_scatter_plot_boundaries()  # doctest: +SKIP
        >>> ax.scatter(**scatter_dict)  # doctest: +SKIP
        >>> ax.add_collection(tieline_collection)  # doctest: +SKIP
        >>> ax.legend(handles=legend_handles)  # doctest: +SKIP

        Returns
        -------
        (scatter_dict, tieline_collection, legend_handles)
        """
        all_phases = self.get_phases()
        legend_handles, colors = phase_legend(all_phases)
        scatter_dict = {'x': [], 'y': [], 'c': []}

        tieline_segments = []
        tieline_colors = []
        for tpr in self.two_phase_regions:
            for cs in tpr.compsets:
                # TODO: X=composition, Y=Temperature assumption
                xs = cs.compositions.tolist()
                ys = [cs.temperature, cs.temperature]
                phases = cs.phases

                # prepare scatter dict
                scatter_dict['x'].extend(xs)
                scatter_dict['y'].extend(ys)
                scatter_dict['c'].extend([colors[p] for p in phases])

                # add tieline segment segment
                # a segment is a list of [[x1, y1], [x2, y2]]
                tieline_segments.append(np.array([xs, ys]).T)
                # always a two phase region, green lines
                tieline_colors.append([0, 1, 0, 1])

        tieline_collection = LineCollection(tieline_segments, zorder=1, linewidths=0.5, colors=tieline_colors)
        return scatter_dict, tieline_collection, legend_handles
Esempio n. 2
0
def eqplot(eq,
           ax=None,
           x=None,
           y=None,
           z=None,
           tielines=True,
           resize=False,
           **kwargs):
    """
    Plot the result of an equilibrium calculation.

    The type of plot is controlled by the degrees of freedom in the equilibrium calculation.

    Parameters
    ----------
    eq : xarray.Dataset
        Result of equilibrium calculation.
    ax : matplotlib.Axes
        Default axes used if not specified.
    x : StateVariable, optional
    y : StateVariable, optional
    z : StateVariable, optional
    tielines : bool
        If True, will plot tielines
    kwargs : kwargs
        Passed to `matplotlib.pyplot.scatter`.

    Returns
    -------
    matplotlib AxesSubplot
    """
    # TODO: add kwargs for tie-lines with defaults

    conds = OrderedDict([
        (_map_coord_to_variable(key), unpack_condition(np.asarray(value)))
        for key, value in sorted(eq.coords.items(), key=str)
        if (key in ('T', 'P', 'N')) or (key.startswith('X_'))
    ])
    indep_comps = sorted([
        key for key, value in conds.items()
        if isinstance(key, v.MoleFraction) and len(value) > 1
    ],
                         key=str)
    indep_pots = [
        key for key, value in conds.items()
        if (type(key) is v.StateVariable) and len(value) > 1
    ]

    # we need to wrap the axes handling in these, becase we don't know ahead of time what projection to use.
    # contractually: each inner loops must
    #   1. Define the ax (calling plt.gca() with the correct projection if none are passed)
    #   2. Define legend_handles
    if len(indep_comps) == 1 and len(indep_pots) == 1:
        # binary system with composition and a potential as coordinates
        ax = ax if ax is not None else plt.gca()
        # find x and y, default to x=v.X and y=v.T
        x = x if x is not None else indep_comps[0]
        y = y if y is not None else indep_pots[0]

        # Get all two phase data
        x_2, y_2, labels = get_eq_axis_data(eq, x, y, 2)
        if any(map(is_molar_quantity, (x, y))):
            # The diagram has tie-lines that must be plotted.
            legend_handles, colormap = phase_legend(sorted(np.unique(labels)))
            # plot x vs. y for each phase (phase index 0 and 1)
            kwargs.setdefault('s', 20)
            for phase_idx in range(2):
                # TODO: kwargs.setdefault('c', [colormap[ph] for ph in labels[..., phase_idx]])
                ax.scatter(x_2[..., phase_idx],
                           y_2[..., phase_idx],
                           c=[colormap[ph] for ph in labels[..., phase_idx]],
                           **kwargs)
            if tielines:
                ax.plot(x_2.T, y_2.T, c=[0, 1, 0, 1], linewidth=0.5, zorder=-1)
        else:
            # This diagram does not have tie-lines, we plot x vs. y directly
            legend_handles, colormap = phase_legend(sorted(np.unique(labels)))
            kwargs.setdefault('s', 20)
            # TODO: kwargs colors
            colorlist = [colormap[ph] for ph in labels]
            ax.scatter(x_2, y_2, c=colorlist, **kwargs)

    elif len(indep_comps) == 2 and len(indep_pots) == 0:
        # This is a ternary isothermal, isobaric calculation
        # Default to x and y of mole fractions
        x = x if x is not None else indep_comps[0]
        y = y if y is not None else indep_comps[1]
        # Find two and three phase data
        x2, y2, labels_2 = get_eq_axis_data(eq, x, y, 2)
        x3, y3, labels_3 = get_eq_axis_data(eq, x, y, 3)
        if any(map(is_molar_quantity, (x, y))):
            # The diagram has tie-lines that must be plotted.
            if isinstance(x, v.MoleFraction) and isinstance(x, v.MoleFraction):
                # Both the axes are mole fractions, so the the compositions
                # form a simplex. Use Gibbs "triangular" projection.
                ax = ax if ax is not None else plt.gca(projection='triangular')
                # TODO: code for handling projection specific things here
                ax.yaxis.label.set_rotation(60)
                # Here we adjust the x coordinate of the ylabel.
                # We make it reasonably comparable to the position of the xlabel from the xaxis
                # As the figure size gets very large, the label approaches ~0.55 on the yaxis
                # 0.55*cos(60 deg)=0.275, so that is the xcoord we are approaching.
                ax.yaxis.label.set_va('baseline')
                fig_x_size = ax.figure.get_size_inches()[0]
                y_label_offset = 1 / fig_x_size
                ax.yaxis.set_label_coords(x=(0.275 - y_label_offset), y=0.5)
            else:
                ax = ax if ax is not None else plt.gca()

            # Plot two phase data
            legend_handles, colormap = phase_legend(sorted(
                np.unique(labels_2)))
            kwargs.setdefault('s', 20)
            # TODO: color kwargs
            for phase_idx in range(2):
                ax.scatter(x2[..., phase_idx],
                           y2[..., phase_idx],
                           c=[colormap[ph] for ph in labels_2[..., phase_idx]],
                           **kwargs)
            if tielines:
                # Plot tie-lines between two phases
                ax.plot(x2.T, y2.T, c=[0, 1, 0, 1], linewidth=0.5, zorder=-1)

            # Find and plot three phase tie-triangles
            # plot lines between all three pairs of phases to form tie-triangles
            for phase_idx_pair in ((0, 1), (0, 2), (1, 2)):
                ax.plot(x3[:, phase_idx_pair].T,
                        y3[:, phase_idx_pair].T,
                        c=[1, 0, 0, 1],
                        lw=0.5,
                        zorder=-1)

        else:
            # This diagram does not have tie-lines, we plot x vs. y directly
            ax = ax if ax is not None else plt.gca()
            combined_labels = np.unique(labels_2).tolist() + np.unique(
                labels_3).tolist()
            legend_handles, colormap = phase_legend(sorted(combined_labels))
            kwargs.setdefault('s', 20)
            ax.scatter(x2, y2, c=[colormap[ph] for ph in labels_2], **kwargs)
            ax.scatter(x3, y3, c=[colormap[ph] for ph in labels_3], **kwargs)
    else:
        raise ValueError(
            'The eqplot projection is not defined and cannot be autodetected. There are {} independent compositions and {} indepedent potentials.'
            .format(len(indep_comps), len(indep_pots)))

    # position the phase legend and configure plot
    if resize:
        if not 'Triangular' in str(type(ax)):
            box = ax.get_position()
            ax.set_position([box.x0, box.y0, box.width * 0.8, box.height])
        # TODO: special limits resizing handling for different axes types
#         ax.set_xlim([np.min(conds[x]) - 1e-2, np.max(conds[x]) + 1e-2])
#         ax.set_ylim([np.min(conds[y]), np.max(conds[y])])
        ax.tick_params(axis='both', which='major', labelsize=12)

    ax.legend(handles=legend_handles,
              loc='center left',
              bbox_to_anchor=(1, 0.5))
    comps = eq.component.values.tolist()
    plot_title = '-'.join([
        component.title() for component in sorted(comps) if component != 'VA'
    ])
    ax.set_title(plot_title, fontsize=20)
    ax.set_xlabel(_axis_label(x), labelpad=15, fontsize=20)
    ax.set_ylabel(_axis_label(y), fontsize=20)

    return ax
Esempio n. 3
0
def dataplot(comps,
             phases,
             conds,
             datasets,
             ax=None,
             plot_kwargs=None,
             tieline_plot_kwargs=None):
    """
    Plot datapoints corresponding to the components, phases, and conditions.


    Parameters
    ----------
    comps : list
        Names of components to consider in the calculation.
    phases : []
        Names of phases to consider in the calculation.
    conds : dict
        Maps StateVariables to values and/or iterables of values.
    datasets : PickleableTinyDB
    ax : matplotlib.Axes
        Default axes used if not specified.
    plot_kwargs : dict
        Additional keyword arguments to pass to the matplotlib plot function for points
    tieline_plot_kwargs : dict
        Additional keyword arguments to pass to the matplotlib plot function for tielines

    Returns
    -------
    matplotlib.Axes
        A plot of phase equilibria points as a figure

    Examples
    --------

    >>> from espei.datasets import load_datasets, recursive_glob
    >>> from espei.plot import dataplot
    >>> datasets = load_datasets(recursive_glob('.', '*.json'))
    >>> my_phases = ['BCC_A2', 'CUMG2', 'FCC_A1', 'LAVES_C15', 'LIQUID']
    >>> my_components = ['CU', 'MG' 'VA']
    >>> conditions = {v.P: 101325, v.T: (500, 1000, 10), v.X('MG'): (0, 1, 0.01)}
    >>> dataplot(my_components, my_phases, conditions, datasets)

    """
    indep_comps = [
        key for key, value in conds.items()
        if isinstance(key, v.Composition) and len(np.atleast_1d(value)) > 1
    ]
    indep_pots = [
        key for key, value in conds.items()
        if ((key == v.T) or (key == v.P)) and len(np.atleast_1d(value)) > 1
    ]
    plot_kwargs = plot_kwargs or {}
    phases = sorted(phases)

    # determine what the type of plot will be
    if len(indep_comps) == 1 and len(indep_pots) == 1:
        projection = None
    elif len(indep_comps) == 2 and len(indep_pots) == 0:
        projection = 'triangular'
    else:
        raise ValueError(
            'The eqplot projection is not defined and cannot be autodetected. There are {} independent compositions and {} indepedent potentials.'
            .format(len(indep_comps), len(indep_pots)))

    if projection is None:
        x = indep_comps[0].species
        y = indep_pots[0]
    elif projection == 'triangular':
        x = indep_comps[0].species
        y = indep_comps[1].species

    # set up plot if not done already
    if ax is None:
        ax = plt.gca(projection=projection)
        box = ax.get_position()
        ax.set_position([box.x0, box.y0, box.width * 0.8, box.height])
        ax.tick_params(axis='both', which='major', labelsize=14)
        ax.grid(True)
        plot_title = '-'.join([
            component.title() for component in sorted(comps)
            if component != 'VA'
        ])
        ax.set_title(plot_title, fontsize=20)
        ax.set_xlabel('X({})'.format(x), labelpad=15, fontsize=20)
        ax.set_xlim((0, 1))
        if projection is None:
            ax.set_ylabel(plot_mapping.get(str(y), y), fontsize=20)
        elif projection == 'triangular':
            ax.set_ylabel('X({})'.format(y), labelpad=15, fontsize=20)
            ax.set_ylim((0, 1))
            ax.yaxis.label.set_rotation(60)
            # Here we adjust the x coordinate of the ylabel.
            # We make it reasonably comparable to the position of the xlabel from the xaxis
            # As the figure size gets very large, the label approaches ~0.55 on the yaxis
            # 0.55*cos(60 deg)=0.275, so that is the xcoord we are approaching.
            ax.yaxis.label.set_va('baseline')
            fig_x_size = ax.figure.get_size_inches()[0]
            y_label_offset = 1 / fig_x_size
            ax.yaxis.set_label_coords(x=(0.275 - y_label_offset), y=0.5)

    output = 'ZPF'
    # TODO: used to include VA. Should this be added by default. Can't determine presence of VA in eq.
    # Techincally, VA should not be present in any phase equilibria.
    desired_data = datasets.search(
        (tinydb.where('output') == output) & (tinydb.where('components').test(
            lambda x: set(x).issubset(comps + ['VA'])))
        & (tinydb.where('phases').test(
            lambda x: len(set(phases).intersection(x)) > 0)))

    # get all the possible references from the data and create the bibliography map
    bib_reference_keys = sorted(
        list({entry['reference']
              for entry in desired_data}))
    symbol_map = bib_marker_map(bib_reference_keys)

    # The above handled the phases as in the equilibrium, but there may be
    # phases that are in the datasets but not in the equilibrium diagram that
    # we would like to plot point for (they need color maps).
    # To keep consistent colors with the equilibrium diagram, we will append
    # the new phases from the datasets to the existing phases in the equilibrium
    # calculation.
    data_phases = set()
    for entry in desired_data:
        data_phases.update(set(entry['phases']))
    new_phases = sorted(list(data_phases.difference(set(phases))))
    phases.extend(new_phases)
    legend_handles, phase_color_map = phase_legend(phases)

    if projection is None:
        # TODO: There are lot of ways this could break in multi-component situations

        # plot x vs. T
        y = 'T'

        # handle plotting kwargs
        scatter_kwargs = {'markersize': 6, 'markeredgewidth': 1}
        # raise warnings if any of the aliased versions of the default values are used
        possible_aliases = [('markersize', 'ms'), ('markeredgewidth', 'mew')]
        for actual_arg, aliased_arg in possible_aliases:
            if aliased_arg in plot_kwargs:
                warnings.warn(
                    "'{0}' passed as plotting keyword argument to dataplot, but the alias '{1}' is already set to '{2}'. Use the full version of the keyword argument '{1}' to override the default."
                    .format(aliased_arg, actual_arg,
                            scatter_kwargs.get(actual_arg)))
        scatter_kwargs.update(plot_kwargs)

        eq_dict = ravel_zpf_values(desired_data, [x])

        # two phase
        updated_tieline_plot_kwargs = {'linewidth': 1, 'color': 'k'}
        if tieline_plot_kwargs is not None:
            updated_tieline_plot_kwargs.update(tieline_plot_kwargs)
        for eq in eq_dict.get(2, []):  # list of things in equilibrium
            # plot the scatter points for the right phases
            x_points, y_points = [], []
            for phase_name, comp_dict, ref_key in eq:
                sym_ref = symbol_map[ref_key]
                x_val, y_val = comp_dict[x], comp_dict[y]
                if x_val is not None and y_val is not None:
                    ax.plot(x_val,
                            y_val,
                            label=sym_ref['formatted'],
                            fillstyle=sym_ref['markers']['fillstyle'],
                            marker=sym_ref['markers']['marker'],
                            linestyle='',
                            color=phase_color_map[phase_name],
                            **scatter_kwargs)
                x_points.append(x_val)
                y_points.append(y_val)

            # plot the tielines
            if all([
                    xx is not None and yy is not None
                    for xx, yy in zip(x_points, y_points)
            ]):
                ax.plot(x_points, y_points, **updated_tieline_plot_kwargs)

    elif projection == 'triangular':
        scatter_kwargs = {'markersize': 4, 'markeredgewidth': 0.4}
        # raise warnings if any of the aliased versions of the default values are used
        possible_aliases = [('markersize', 'ms'), ('markeredgewidth', 'mew')]
        for actual_arg, aliased_arg in possible_aliases:
            if aliased_arg in plot_kwargs:
                warnings.warn(
                    "'{0}' passed as plotting keyword argument to dataplot, but the alias '{1}' is already set to '{2}'. Use the full version of the keyword argument '{1}' to override the default."
                    .format(aliased_arg, actual_arg,
                            scatter_kwargs.get(actual_arg)))
        scatter_kwargs.update(plot_kwargs)

        eq_dict = ravel_zpf_values(desired_data, [x, y], {'T': conds[v.T]})

        # two phase
        updated_tieline_plot_kwargs = {'linewidth': 1, 'color': 'k'}
        if tieline_plot_kwargs is not None:
            updated_tieline_plot_kwargs.update(tieline_plot_kwargs)
        for eq in eq_dict.get(2, []):  # list of things in equilibrium
            # plot the scatter points for the right phases
            x_points, y_points = [], []
            for phase_name, comp_dict, ref_key in eq:
                sym_ref = symbol_map[ref_key]
                x_val, y_val = comp_dict[x], comp_dict[y]
                if x_val is not None and y_val is not None:
                    ax.plot(x_val,
                            y_val,
                            label=sym_ref['formatted'],
                            fillstyle=sym_ref['markers']['fillstyle'],
                            marker=sym_ref['markers']['marker'],
                            linestyle='',
                            color=phase_color_map[phase_name],
                            **scatter_kwargs)
                x_points.append(x_val)
                y_points.append(y_val)

            # plot the tielines
            if all([
                    xx is not None and yy is not None
                    for xx, yy in zip(x_points, y_points)
            ]):
                ax.plot(x_points, y_points, **updated_tieline_plot_kwargs)

        # three phase
        updated_tieline_plot_kwargs = {'linewidth': 1, 'color': 'r'}
        if tieline_plot_kwargs is not None:
            updated_tieline_plot_kwargs.update(tieline_plot_kwargs)
        for eq in eq_dict.get(3, []):  # list of things in equilibrium
            # plot the scatter points for the right phases
            x_points, y_points = [], []
            for phase_name, comp_dict, ref_key in eq:
                x_val, y_val = comp_dict[x], comp_dict[y]
                x_points.append(x_val)
                y_points.append(y_val)
            # Make sure the triangle completes
            x_points.append(x_points[0])
            y_points.append(y_points[0])
            # plot
            # check for None values
            if all([
                    xx is not None and yy is not None
                    for xx, yy in zip(x_points, y_points)
            ]):
                ax.plot(x_points, y_points, **updated_tieline_plot_kwargs)

    # now we will add the symbols for the references to the legend handles
    for ref_key in bib_reference_keys:
        mark = symbol_map[ref_key]['markers']
        # The legend marker edge width appears smaller than in the plot.
        # We will add this small hack to increase the width in the legend only.
        legend_kwargs = scatter_kwargs.copy()
        legend_kwargs['markeredgewidth'] = 1
        legend_kwargs['markersize'] = 6
        legend_handles.append(
            mlines.Line2D([], [],
                          linestyle='',
                          color='black',
                          markeredgecolor='black',
                          label=symbol_map[ref_key]['formatted'],
                          fillstyle=mark['fillstyle'],
                          marker=mark['marker'],
                          **legend_kwargs))

    # finally, add the completed legend
    ax.legend(handles=legend_handles,
              loc='center left',
              bbox_to_anchor=(1, 0.5))

    return ax
Esempio n. 4
0
    def get_line_plot_boundaries(self, close_miscibility_gaps=0.05):
        """
        Get the ZPF boundaries to plot from each two phase region.

        Parameters
        ----------
        close_miscibility_gaps : float, optional
            If a float is passed, add a line segment between compsets at the top
             or bottom of a two phase region if the discrepancy is below a
             tolerance. If `None` is passed, do not close the gap.
        Notes
        -----
        For now, we will not support connecting regions with lines, so this
        function returns a tuple of scatter_dict and a tineline_collection.

        Examples
        --------
        >>> boundary_collection, tieline_collection, legend_handles = zpf_boundary_sets.get_line_plot_boundaries()  # doctest: +SKIP
        >>> ax.add_collection(boundary_collection)  # doctest: +SKIP
        >>> ax.add_collection(tieline_collection)  # doctest: +SKIP
        >>> ax.legend(handles=legend_handles)  # doctest: +SKIP

        Returns
        -------
        (line_collections, tieline_collection, legend_handles)
        """
        # TODO: add some tracking of the endpoints/startpoints and join them with
        #       a new line segment if they are close.
        all_phases = self.get_phases()
        legend_handles, colors = phase_legend(all_phases)
        tieline_segments = []
        tieline_colors = []
        boundary_segments = []
        boundary_colors = []
        for tpr in self.two_phase_regions:
            # each two phase region contributes two line collections, one for
            # each ZPF line
            a_path = []
            b_path = []
            for cs in tpr.compsets:
                # TODO: X=composition, Y=Temperature assumption
                xs = cs.compositions.tolist()
                ys = [cs.temperature, cs.temperature]
                a_path.append([xs[0], ys[0]])
                b_path.append([xs[1], ys[1]])
                # add tieline segment segment
                # a segment is a list of [[x1, y1], [x2, y2]]
                tieline_segments.append(np.array([xs, ys]).T)
                # always a two phase region, green lines
                tieline_colors.append([0, 1, 0, 1])

            # build the line collections for each two phase region
            ordered_phases = tpr.compsets[0].phases
            a_color = to_rgba(colors[ordered_phases[0]])
            b_color = to_rgba(colors[ordered_phases[1]])

            # close miscibility gaps, both top and bottom
            if close_miscibility_gaps is not None and len(tpr.phases) == 1:
                bottom_cs = tpr.compsets[0]
                if bottom_cs.xdiscrepancy() < close_miscibility_gaps:
                    boundary_segments.append(np.array([bottom_cs.compositions, [bottom_cs.temperature, bottom_cs.temperature]]).T)
                    boundary_colors.append(colors[ordered_phases[0]])  # colors are the same
                top_cs = tpr.compsets[-1]
                if top_cs.xdiscrepancy() < close_miscibility_gaps:
                    boundary_segments.append(np.array([top_cs.compositions, [top_cs.temperature, top_cs.temperature]]).T)
                    boundary_colors.append(colors[ordered_phases[0]])  # colors are the same

            boundary_segments.append(a_path)
            boundary_segments.append(b_path)
            boundary_colors.append(a_color)
            boundary_colors.append(b_color)
        boundary_collection = LineCollection(boundary_segments, colors=boundary_colors)
        tieline_collection = LineCollection(tieline_segments, zorder=1, linewidths=0.5, colors=tieline_colors)
        return boundary_collection, tieline_collection, legend_handles
Esempio n. 5
0
def eqplot(eq, ax=None, x=None, y=None, z=None, tielines=True, **kwargs):
    """
    Plot the result of an equilibrium calculation.

    The type of plot is controlled by the degrees of freedom in the equilibrium calculation.

    Parameters
    ----------
    eq : xarray.Dataset
        Result of equilibrium calculation.
    ax : matplotlib.Axes
        Default axes used if not specified.
    x : StateVariable, optional
    y : StateVariable, optional
    z : StateVariable, optional
    tielines : bool
        If True, will plot tielines
    kwargs : kwargs
        Passed to `matplotlib.pyplot.scatter`.

    Returns
    -------
    matplotlib AxesSubplot
    """
    conds = OrderedDict([(_map_coord_to_variable(key), unpack_condition(np.asarray(value)))
                         for key, value in sorted(eq.coords.items(), key=str)
                         if (key == 'T') or (key == 'P') or (key.startswith('X_'))])
    indep_comps = sorted([key for key, value in conds.items() if isinstance(key, v.Composition) and len(value) > 1], key=str)
    indep_pots = [key for key, value in conds.items() if ((key == v.T) or (key == v.P)) and len(value) > 1]

    # determine what the type of plot will be
    if len(indep_comps) == 1 and len(indep_pots) == 1:
        projection = None
    elif len(indep_comps) == 2 and len(indep_pots) == 0:
        projection = 'triangular'
    else:
        raise ValueError('The eqplot projection is not defined and cannot be autodetected. There are {} independent compositions and {} indepedent potentials.'.format(len(indep_comps), len(indep_pots)))
    if z is not None:
        raise NotImplementedError('3D plotting is not yet implemented')
    if ax is None:
        fig = plt.figure()
        ax = fig.gca(projection=projection)
    ax = plt.gca(projection=projection) if ax is None else ax

    # Handle cases for different plot types
    if projection is None:
        x = indep_comps[0] if x is None else x
        y = indep_pots[0] if y is None else y
        # plot settings
        ax.set_xlim([np.min(conds[x]) - 1e-2, np.max(conds[x]) + 1e-2])
        ax.set_ylim([np.min(conds[y]), np.max(conds[y])])
    elif projection == 'triangular':
        x = indep_comps[0] if x is None else x
        y = indep_comps[1] if y is None else y
        # plot settings
        ax.yaxis.label.set_rotation(60)
        # Here we adjust the x coordinate of the ylabel.
        # We make it reasonably comparable to the position of the xlabel from the xaxis
        # As the figure size gets very large, the label approaches ~0.55 on the yaxis
        # 0.55*cos(60 deg)=0.275, so that is the xcoord we are approaching.
        ax.yaxis.label.set_va('baseline')
        fig_x_size = ax.figure.get_size_inches()[0]
        y_label_offset = 1 / fig_x_size
        ax.yaxis.set_label_coords(x=(0.275 - y_label_offset), y=0.5)

    # get the active phases and support loading netcdf files from disk
    phases = map(str, sorted(set(np.array(eq.Phase.values.ravel(), dtype='U')) - {''}, key=str))
    comps = map(str, sorted(np.array(eq.coords['component'].values, dtype='U'), key=str))
    eq['component'] = np.array(eq['component'], dtype='U')
    eq['Phase'].values = np.array(eq['Phase'].values, dtype='U')

    # Select all two- and three-phase regions
    three_phase_idx = np.nonzero(np.sum(eq.Phase.values != '', axis=-1, dtype=np.int) == 3)
    two_phase_idx = np.nonzero(np.sum(eq.Phase.values != '', axis=-1, dtype=np.int) == 2)

    legend_handles, colorlist = phase_legend(phases)

    # For both two and three phase, cast the tuple of indices to an array and flatten
    # If we found two phase regions:
    if two_phase_idx[0].size > 0:
        found_two_phase = eq.Phase.values[two_phase_idx][..., :2]
        # get tieline endpoint compositions
        two_phase_x = eq.X.sel(component=x.species.name).values[two_phase_idx][..., :2]
        # handle special case for potential
        if isinstance(y, v.Composition):
            two_phase_y = eq.X.sel(component=y.species.name).values[two_phase_idx][..., :2]
        else:
            # it's a StateVariable. This must be True
            two_phase_y = np.take(eq[str(y)].values, two_phase_idx[list(str(i) for i in conds.keys()).index(str(y))])
            # because the above gave us a shape of (n,) instead of (n,2) we are going to create it ourselves
            two_phase_y = np.array([two_phase_y, two_phase_y]).swapaxes(0, 1)

        # plot two phase points
        two_phase_plotcolors = np.array(list(map(lambda x: [colorlist[x[0]], colorlist[x[1]]], found_two_phase)), dtype='U') # from pycalphad
        ax.scatter(two_phase_x[..., 0], two_phase_y[..., 0], s=3, c=two_phase_plotcolors[:,0], edgecolors='None', zorder=2, **kwargs)
        ax.scatter(two_phase_x[..., 1], two_phase_y[..., 1], s=3, c=two_phase_plotcolors[:,1], edgecolors='None', zorder=2, **kwargs)

        if tielines:
            # construct and plot tielines
            two_phase_tielines = np.array([np.concatenate((two_phase_x[..., 0][..., np.newaxis], two_phase_y[..., 0][..., np.newaxis]), axis=-1),
                                           np.concatenate((two_phase_x[..., 1][..., np.newaxis], two_phase_y[..., 1][..., np.newaxis]), axis=-1)])
            two_phase_tielines = np.rollaxis(two_phase_tielines, 1)
            lc = mc.LineCollection(two_phase_tielines, zorder=1, colors=[0,1,0,1], linewidths=[0.5, 0.5])
            ax.add_collection(lc)

    # If we found three phase regions:
    if three_phase_idx[0].size > 0:
        found_three_phase = eq.Phase.values[three_phase_idx][..., :3]
        # get tieline endpoints
        three_phase_x = eq.X.sel(component=x.species.name).values[three_phase_idx][..., :3]
        three_phase_y = eq.X.sel(component=y.species.name).values[three_phase_idx][..., :3]
        # three phase tielines, these are tie triangles and we always plot them
        three_phase_tielines = np.array([np.concatenate((three_phase_x[..., 0][..., np.newaxis], three_phase_y[..., 0][..., np.newaxis]), axis=-1),
                                     np.concatenate((three_phase_x[..., 1][..., np.newaxis], three_phase_y[..., 1][..., np.newaxis]), axis=-1),
                                     np.concatenate((three_phase_x[..., 2][..., np.newaxis], three_phase_y[..., 2][..., np.newaxis]), axis=-1)])
        three_phase_tielines = np.rollaxis(three_phase_tielines,1)
        three_lc = mc.LineCollection(three_phase_tielines, zorder=1, colors=[1,0,0,1], linewidths=[0.5, 0.5])
        # plot three phase points and tielines
        three_phase_plotcolors = np.array(list(map(lambda x: [colorlist[x[0]], colorlist[x[1]], colorlist[x[2]]], found_three_phase)), dtype='U') # from pycalphad
        ax.scatter(three_phase_x[..., 0], three_phase_y[..., 0], s=3, c=three_phase_plotcolors[:, 0], edgecolors='None', zorder=2, **kwargs)
        ax.scatter(three_phase_x[..., 1], three_phase_y[..., 1], s=3, c=three_phase_plotcolors[:, 1], edgecolors='None', zorder=2, **kwargs)
        ax.scatter(three_phase_x[..., 2], three_phase_y[..., 2], s=3, c=three_phase_plotcolors[:, 2], edgecolors='None', zorder=2, **kwargs)
        ax.add_collection(three_lc)

    # position the phase legend and configure plot
    box = ax.get_position()
    ax.set_position([box.x0, box.y0, box.width * 0.8, box.height])
    ax.legend(handles=legend_handles, loc='center left', bbox_to_anchor=(1, 0.5))
    ax.tick_params(axis='both', which='major', labelsize=14)
    ax.grid(True)
    plot_title = '-'.join([component.title() for component in sorted(comps) if component != 'VA'])
    ax.set_title(plot_title, fontsize=20)
    ax.set_xlabel(_axis_label(x), labelpad=15, fontsize=20)
    ax.set_ylabel(_axis_label(y), fontsize=20)

    return ax
    binplot(db_cuni, ['CU', 'NI', 'VA'],
            my_phases_cuni, {
                v.X('NI'): (0, 1, 0.01),
                v.T: (1300, 1800, 5),
                v.P: 101325
            },
            ax=fig.gca())
    plt.savefig('CuNi.png', dpi=400, bbox_inches='tight')
    plt.close()

# It is very common in CALPHAD modeling to directly examine the Gibbs energy surface of all the constituent phases in a system.
# Below we show how the Gibbs energy of all phases may be calculated as a function of composition at a given temperature (1550 K).

# Calculate Energy Surfaces of Binary Systems
if not os.path.isfile('CuNi_energy.png'):
    legend_handles, colorlist = phase_legend(my_phases_cuni)
    fig = plt.figure(figsize=(9, 6))
    ax = fig.gca()
    xref = np.linspace(-0.1, 1.1, 150)
    for name in my_phases_cuni:
        result = calculate(db_cuni, ['CU', 'NI', 'VA'],
                           name,
                           T=1550,
                           output='GM')
        x = np.ravel(result.X.sel(component='NI'))
        y = np.ravel(7.124e-4 * result.GM)
        ax.scatter(x, y, marker='.', s=5, color=colorlist[name.upper()])
        popt, pcov = curve_fit(func, x, y)
        print name, popt
        ax.plot(xref,
                func(xref, popt[0], popt[1], popt[2], popt[3], popt[4],
# Calculate Isobaric Binary Phase Diagram
if not os.path.isfile('CuNi.png'):
    fig = plt.figure(figsize=(9,6))
    binplot(db_cuni, ['CU', 'NI', 'VA'] , my_phases_cuni, {v.X('NI'):(0,1,0.01),  v.T: (1300, 1800, 5), v.P:101325},  ax=fig.gca())
    plt.savefig('CuNi.png', dpi=400, bbox_inches='tight')
    plt.close()


# It is very common in CALPHAD modeling to directly examine the Gibbs energy surface of all the constituent phases in a system.
# Below we show how the Gibbs energy of all phases may be calculated as a function of composition at a given temperature (1550 K).


# Calculate Energy Surfaces of Binary Systems
if not os.path.isfile('CuNi_energy.png'):
    legend_handles, colorlist = phase_legend(my_phases_cuni)
    fig = plt.figure(figsize=(9,6))
    ax = fig.gca()
    xref = np.linspace(-0.1,1.1,150)
    for name in my_phases_cuni:
        result = calculate(db_cuni, ['CU', 'NI', 'VA'], name, T=1550, output='GM')
        x = np.ravel(result.X.sel(component='NI'))
        y = np.ravel(7.124e-4*result.GM)
        ax.scatter(x, y, marker='.', s=5, color=colorlist[name.upper()])
        popt, pcov = curve_fit(func, x, y)
        print name, popt
        ax.plot(xref, func(xref,popt[0],popt[1],popt[2],popt[3],popt[4],popt[5],popt[6],popt[7],popt[8],popt[9],popt[10]), '-', color=colorlist[name.upper()])
        roo = newton(fprime, 0.5, args=(popt[0],popt[1],popt[2],popt[3],popt[4],popt[5],popt[6],popt[7],popt[8],popt[9]),maxiter=1000)
        print name, "Equilibrium x_Ni near ", roo
    ax.set_xlim((-0.1, 1.1))
    ax.legend(handles=legend_handles, loc='center left', bbox_to_anchor=(1, 0.6))
Esempio n. 8
0
def dataplot(comps, phases, conds, datasets, ax=None, plot_kwargs=None):
    """
    Plot datapoints corresponding to the components, phases, and conditions.


    Parameters
    ----------
    comps : list
        Names of components to consider in the calculation.
    phases : []
        Names of phases to consider in the calculation.
    conds : dict
        Maps StateVariables to values and/or iterables of values.
    datasets : PickleableTinyDB
    ax : matplotlib.Axes
        Default axes used if not specified.
    plot_kwargs : dict
        Additional keyword arguments to pass to the matplotlib plot function

    Returns
    -------
    matplotlib.Axes
        A plot of phase equilibria points as a figure

    Examples
    --------

    >>> from espei.datasets import load_datasets, recursive_glob
    >>> from espei.plot import dataplot
    >>> datasets = load_datasets(recursive_glob('.', '*.json'))
    >>> my_phases = ['BCC_A2', 'CUMG2', 'FCC_A1', 'LAVES_C15', 'LIQUID']
    >>> my_components = ['CU', 'MG' 'VA']
    >>> conditions = {v.P: 101325, v.T: 1000, v.X('MG'): (0, 1, 0.01)}
    >>> dataplot(my_components, my_phases, conditions, datasets)

    """
    indep_comps = [
        key for key, value in conds.items()
        if isinstance(key, v.Composition) and len(np.atleast_1d(value)) > 1
    ]
    indep_pots = [
        key for key, value in conds.items()
        if ((key == v.T) or (key == v.P)) and len(np.atleast_1d(value)) > 1
    ]
    plot_kwargs = plot_kwargs or {}
    phases = sorted(phases)

    # determine what the type of plot will be
    if len(indep_comps) == 1 and len(indep_pots) == 1:
        projection = None
    elif len(indep_comps) == 2 and len(indep_pots) == 0:
        # TODO: support isotherm plotting
        raise NotImplementedError('Triangular plotting is not yet implemented')
        projection = 'triangular'
    else:
        raise ValueError(
            'The eqplot projection is not defined and cannot be autodetected. There are {} independent compositions and {} indepedent potentials.'
            .format(len(indep_comps), len(indep_pots)))

    if projection is None:
        x = indep_comps[0].species
        y = indep_pots[0]

    # set up plot if not done already
    if ax is None:
        ax = plt.gca(projection=projection)
        box = ax.get_position()
        ax.set_position([box.x0, box.y0, box.width * 0.8, box.height])
        ax.tick_params(axis='both', which='major', labelsize=14)
        ax.grid(True)
        plot_title = '-'.join([
            component.title() for component in sorted(comps)
            if component != 'VA'
        ])
        ax.set_title(plot_title, fontsize=20)
        ax.set_xlabel('X({})'.format(x), labelpad=15, fontsize=20)
        ax.set_ylabel(plot_mapping.get(str(y), y), fontsize=20)
        ax.set_xlim((0, 1))

    # handle plotting kwargs
    scatter_kwargs = {'markersize': 6, 'markeredgewidth': 0.2}
    # raise warnings if any of the aliased versions of the default values are used
    possible_aliases = [('markersize', 'ms'), ('markeredgewidth', 'mew')]
    for actual_arg, aliased_arg in possible_aliases:
        if aliased_arg in plot_kwargs:
            warnings.warn(
                "'{0}' passed as plotting keyword argument to dataplot, but the alias '{1}' is already set to '{2}'. Use the full version of the keyword argument '{1}' to override the default."
                .format(aliased_arg, actual_arg,
                        scatter_kwargs.get(actual_arg)))
    scatter_kwargs.update(plot_kwargs)

    plots = [('ZPF', 'T')]
    for output, y in plots:
        # TODO: used to include VA. Should this be added by default. Can't determine presence of VA in eq.
        # Techincally, VA should not be present in any phase equilibria.
        desired_data = datasets.search(
            (tinydb.where('output') == output) & (tinydb.where(
                'components').test(lambda x: set(x).issubset(comps + ['VA'])))
            & (tinydb.where('phases').test(
                lambda x: len(set(phases).intersection(x)) > 0)))

        # get all the possible references from the data and create the bibliography map
        # TODO: explore building tielines from multiphase equilibria. Should we do this?
        bib_reference_keys = sorted(
            list({entry['reference']
                  for entry in desired_data}))
        symbol_map = bib_marker_map(bib_reference_keys)

        # The above handled the phases as in the equilibrium, but there may be
        # phases that are in the datasets but not in the equilibrium diagram that
        # we would like to plot point for (they need color maps).
        # To keep consistent colors with the equilibrium diagram, we will append
        # the new phases from the datasets to the existing phases in the equilibrium
        # calculation.
        data_phases = set()
        for entry in desired_data:
            data_phases.update(set(entry['phases']))
        new_phases = sorted(list(data_phases.difference(set(phases))))
        phases.extend(new_phases)
        legend_handles, phase_color_map = phase_legend(phases)

        # now we will add the symbols for the references to the legend handles
        for ref_key in bib_reference_keys:
            mark = symbol_map[ref_key]['markers']
            # The legend marker edge width appears smaller than in the plot.
            # We will add this small hack to increase the width in the legend only.
            legend_kwargs = scatter_kwargs.copy()
            legend_kwargs['markeredgewidth'] *= 5
            legend_handles.append(
                mlines.Line2D([], [],
                              linestyle='',
                              color='black',
                              markeredgecolor='black',
                              label=symbol_map[ref_key]['formatted'],
                              fillstyle=mark['fillstyle'],
                              marker=mark['marker'],
                              **legend_kwargs))

        # finally, add the completed legend
        ax.legend(handles=legend_handles,
                  loc='center left',
                  bbox_to_anchor=(1, 0.5))

        # TODO: There are lot of ways this could break in multi-component situations

        for data in desired_data:
            payload = data['values']
            # TODO: Add broadcast_conditions support
            # Repeat the temperature (or whatever variable) vector to align with the unraveled data
            temp_repeats = np.zeros(len(np.atleast_1d(data['conditions'][y])),
                                    dtype=np.int)
            for idx, p in enumerate(payload):
                temp_repeats[idx] = len(p)
            temps_ravelled = np.repeat(data['conditions'][y], temp_repeats)
            payload_ravelled = []
            phases_ravelled = []
            comps_ravelled = []
            symbols_ravelled = []
            # TODO: Fix to only include equilibria listed in 'phases'
            for p in payload:
                markers = symbol_map[data['reference']]['markers']
                fill_sym_tuple = (markers['fillstyle'], markers['marker'])
                symbols_ravelled.extend([fill_sym_tuple] * len(p))
                payload_ravelled.extend(p)
            for rp in payload_ravelled:
                phases_ravelled.append(rp[0])
                comp_dict = dict(zip([x.upper() for x in rp[1]], rp[2]))
                dependent_comp = list(
                    set(comps) - set(comp_dict.keys()) - set(['VA']))
                if len(dependent_comp) > 1:
                    raise ValueError('Dependent components greater than one')
                elif len(dependent_comp) == 1:
                    dependent_comp = dependent_comp[0]
                    # TODO: Assuming N=1
                    comp_dict[dependent_comp] = 1 - sum(
                        np.array(list(comp_dict.values()), dtype=np.float))
                chosen_comp_value = comp_dict[x]
                comps_ravelled.append(chosen_comp_value)
            symbols_ravelled = np.array(symbols_ravelled)
            comps_ravelled = np.array(comps_ravelled)
            temps_ravelled = np.array(temps_ravelled)
            phases_ravelled = np.array(phases_ravelled)
            # We can't pass an array of markers to scatter, sadly
            for fill_sym in symbols_ravelled:
                # fill_sym is a tuple of ('fillstyle', 'marker')
                selected = np.all(symbols_ravelled == fill_sym, axis=1)
                # ax.plot does not seem to be able to take a list of hex values
                # for colors in the same was as ax.scatter. To get around this,
                # we'll use the set_prop_cycler for each plot cycle
                phase_colors = [
                    phase_color_map[x] for x in phases_ravelled[selected]
                ]
                ax.set_prop_cycle(cycler('color', phase_colors))
                ax.plot(comps_ravelled[selected],
                        temps_ravelled[selected],
                        fillstyle=fill_sym[0],
                        marker=fill_sym[1],
                        linestyle='',
                        **scatter_kwargs)
    return ax