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
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
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
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
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))
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