def _get_plot(self, label_stable=True, label_unstable=False): """ Plot convex hull of Pourbaix Diagram entries """ import matplotlib.pyplot as plt import mpl_toolkits.mplot3d.axes3d as p3 from matplotlib.font_manager import FontProperties fig = plt.figure() ax = p3.Axes3D(fig) font = FontProperties() font.set_weight("bold") font.set_size(14) (lines, labels, unstable) = self.pourbaix_hull_plot_data count = 1 newlabels = list() for x, y, z in lines: ax.plot(x, y, z, "bo-", linewidth=3, markeredgecolor="b", markerfacecolor="r", markersize=10) for coords in sorted(labels.keys()): entry = labels[coords] label = self.print_name(entry) if label_stable: ax.text(coords[0], coords[1], coords[2], str(count)) newlabels.append("{} : {}".format( count, latexify_ion(latexify(label)))) count += 1 if label_unstable: for entry in unstable.keys(): label = self.print_name(entry) coords = unstable[entry] ax.plot([coords[0], coords[0]], [coords[1], coords[1]], [coords[2], coords[2]], "bo", markerfacecolor="g", markersize=10) ax.text(coords[0], coords[1], coords[2], str(count)) newlabels.append("{} : {}".format( count, latexify_ion(latexify(label)))) count += 1 plt.figtext(0.01, 0.01, "\n".join(newlabels)) plt.xlabel("pH") plt.ylabel("V") return plt
def print_name(self, entry): """ Print entry name if single, else print multientry """ str_name = "" if isinstance(entry, MultiEntry): if len(entry.entrylist) > 2: return str(self._pd.qhull_entries.index(entry)) for e in entry.entrylist: str_name += latexify_ion(latexify(e.name)) + " + " str_name = str_name[:-3] return str_name else: return latexify_ion(latexify(entry.name))
def plot_planes(self): """ Plot the free energy planes as a function of pH and V """ if self.show_unstable: entries = self._pd._all_entries else: entries = self._pd.stable_entries num_plots = len(entries) import matplotlib.pyplot as plt colormap = plt.cm.gist_ncar fig = plt.figure().gca(projection='3d') color_array = [colormap(i) for i in np.linspace(0, 0.9, num_plots)] labels = [] color_index = -1 for entry in entries: normal = np.array([-PREFAC * entry.npH, -entry.nPhi, +1]) d = entry.g0 color_index += 1 pH, V = np.meshgrid(np.linspace(-10, 28, 100), np.linspace(-3, 3, 100)) g = (-normal[0] * pH - normal[1] * V + d) / normal[2] lbl = latexify_ion( latexify(entry._entry.composition.reduced_formula)) labels.append(lbl) fig.plot_surface(pH, V, g, color=color_array[color_index], label=lbl) plt.legend(labels) plt.xlabel("pH") plt.ylabel("E (V)") plt.show()
def _get_3d_plot(self, label_stable=True): """ Shows the plot using pylab. Usually I won"t do imports in methods, but since plotting is a fairly expensive library to load and not all machines have matplotlib installed, I have done it this way. """ import matplotlib.pyplot as plt import mpl_toolkits.mplot3d.axes3d as p3 from matplotlib.font_manager import FontProperties fig = plt.figure() ax = p3.Axes3D(fig) font = FontProperties() font.set_weight("bold") font.set_size(20) (lines, labels, unstable) = self.pd_plot_data count = 1 newlabels = list() for x, y, z in lines: ax.plot(x, y, z, "bo-", linewidth=3, markeredgecolor="b", markerfacecolor="r", markersize=10) for coords in sorted(labels.keys()): entry = labels[coords] label = entry.name if label_stable: if len(entry.composition.elements) == 1: ax.text(coords[0], coords[1], coords[2], label) else: ax.text(coords[0], coords[1], coords[2], str(count)) newlabels.append("{} : {}".format(count, latexify(label))) count += 1 plt.figtext(0.01, 0.01, "\n".join(newlabels)) ax.axis("off") return plt
def _get_plot(self, label_stable=True, label_unstable=False): """ Plot convex hull of Pourbaix Diagram entries """ import matplotlib.pyplot as plt import mpl_toolkits.mplot3d.axes3d as p3 from matplotlib.font_manager import FontProperties fig = plt.figure() ax = p3.Axes3D(fig) font = FontProperties() font.set_weight("bold") font.set_size(14) (lines, labels, unstable) = self.pourbaix_hull_plot_data count = 1 newlabels = list() for x, y, z in lines: ax.plot(x, y, z, "bo-", linewidth=3, markeredgecolor="b", markerfacecolor="r", markersize=10) for coords in sorted(labels.keys()): entry = labels[coords] label = self.print_name(entry) if label_stable: ax.text(coords[0], coords[1], coords[2], str(count)) newlabels.append("{} : {}".format( count, latexify_ion(latexify(label)))) count += 1 if self.show_unstable: for entry in unstable.keys(): label = self.print_name(entry) coords = unstable[entry] ax.plot([coords[0], coords[0]], [coords[1], coords[1]], [coords[2], coords[2]], "bo", markerfacecolor="g", markersize=10) ax.text(coords[0], coords[1], coords[2], str(count)) newlabels.append("{} : {}".format( count, latexify_ion(latexify(label)))) count += 1 plt.figtext(0.01, 0.01, "\n".join(newlabels)) plt.xlabel("pH") plt.ylabel("V") # plt.tight_layout() return plt
def test_latexify(self): self.assertEqual(latexify("Li3Fe2(PO4)3"), "Li$_{3}$Fe$_{2}$(PO$_{4}$)$_{3}$")
def get_chempot_range_map_plot(self, elements, referenced=True): """ Returns a plot of the chemical potential range _map. Currently works only for 3-component PDs. Args: elements: Sequence of elements to be considered as independent variables. E.g., if you want to show the stability ranges of all Li-Co-O phases wrt to uLi and uO, you will supply [Element("Li"), Element("O")] referenced: if True, gives the results with a reference being the energy of the elemental phase. If False, gives absolute values. Returns: A matplotlib plot object. """ plt = get_publication_quality_plot(12, 8) analyzer = PDAnalyzer(self._pd) chempot_ranges = analyzer.get_chempot_range_map(elements, referenced=referenced) missing_lines = {} excluded_region = [] for entry, lines in chempot_ranges.items(): comp = entry.composition center_x = 0 center_y = 0 coords = [] contain_zero = any( [comp.get_atomic_fraction(el) == 0 for el in elements]) is_boundary = (not contain_zero) and \ sum([comp.get_atomic_fraction(el) for el in elements]) == 1 for line in lines: (x, y) = line.coords.transpose() plt.plot(x, y, "k-") for coord in line.coords: if not in_coord_list(coords, coord): coords.append(coord.tolist()) center_x += coord[0] center_y += coord[1] if is_boundary: excluded_region.extend(line.coords) if coords and contain_zero: missing_lines[entry] = coords else: xy = (center_x / len(coords), center_y / len(coords)) plt.annotate(latexify(entry.name), xy, fontsize=22) ax = plt.gca() xlim = ax.get_xlim() ylim = ax.get_ylim() #Shade the forbidden chemical potential regions. excluded_region.append([xlim[1], ylim[1]]) excluded_region = sorted(excluded_region, key=lambda c: c[0]) (x, y) = np.transpose(excluded_region) plt.fill(x, y, "0.80") #The hull does not generate the missing horizontal and vertical lines. #The following code fixes this. el0 = elements[0] el1 = elements[1] for entry, coords in missing_lines.items(): center_x = sum([c[0] for c in coords]) center_y = sum([c[1] for c in coords]) comp = entry.composition is_x = comp.get_atomic_fraction(el0) < 0.01 is_y = comp.get_atomic_fraction(el1) < 0.01 n = len(coords) if not (is_x and is_y): if is_x: coords = sorted(coords, key=lambda c: c[1]) for i in [0, -1]: x = [min(xlim), coords[i][0]] y = [coords[i][1], coords[i][1]] plt.plot(x, y, "k") center_x += min(xlim) center_y += coords[i][1] elif is_y: coords = sorted(coords, key=lambda c: c[0]) for i in [0, -1]: x = [coords[i][0], coords[i][0]] y = [coords[i][1], min(ylim)] plt.plot(x, y, "k") center_x += coords[i][0] center_y += min(ylim) xy = (center_x / (n + 2), center_y / (n + 2)) else: center_x = sum(coord[0] for coord in coords) + xlim[0] center_y = sum(coord[1] for coord in coords) + ylim[0] xy = (center_x / (n + 1), center_y / (n + 1)) plt.annotate(latexify(entry.name), xy, horizontalalignment="center", verticalalignment="center", fontsize=22) plt.xlabel("$\mu_{{{0}}} - \mu_{{{0}}}^0$ (eV)".format(el0.symbol)) plt.ylabel("$\mu_{{{0}}} - \mu_{{{0}}}^0$ (eV)".format(el1.symbol)) plt.tight_layout() return plt
def _get_2d_plot(self, label_stable=True, label_unstable=True, ordering=None, energy_colormap=None, vmin_mev=-60.0, vmax_mev=60.0, show_colorbar=True, process_attributes=False): """ Shows the plot using pylab. Usually I won't do imports in methods, but since plotting is a fairly expensive library to load and not all machines have matplotlib installed, I have done it this way. """ plt = get_publication_quality_plot(8, 6) from matplotlib.font_manager import FontProperties if ordering is None: (lines, labels, unstable) = self.pd_plot_data else: (_lines, _labels, _unstable) = self.pd_plot_data (lines, labels, unstable) = order_phase_diagram(_lines, _labels, _unstable, ordering) if energy_colormap is None: if process_attributes: for x, y in lines: plt.plot(x, y, "k-", linewidth=3, markeredgecolor="k") # One should think about a clever way to have "complex" # attributes with complex processing options but with a clear # logic. At this moment, I just use the attributes to know # whether an entry is a new compound or an existing (from the # ICSD or from the MP) one. for x, y in labels.keys(): if labels[(x, y)].attribute is None or \ labels[(x, y)].attribute == "existing": plt.plot(x, y, "ko", linewidth=3, markeredgecolor="k", markerfacecolor="b", markersize=12) else: plt.plot(x, y, "k*", linewidth=3, markeredgecolor="k", markerfacecolor="g", markersize=18) else: for x, y in lines: plt.plot(x, y, "ko-", linewidth=3, markeredgecolor="k", markerfacecolor="b", markersize=15) else: from matplotlib.colors import Normalize, LinearSegmentedColormap from matplotlib.cm import ScalarMappable pda = PDAnalyzer(self._pd) for x, y in lines: plt.plot(x, y, "k-", linewidth=3, markeredgecolor="k") vmin = vmin_mev / 1000.0 vmax = vmax_mev / 1000.0 if energy_colormap == 'default': mid = -vmin / (vmax - vmin) cmap = LinearSegmentedColormap.from_list( 'my_colormap', [(0.0, '#005500'), (mid, '#55FF55'), (mid, '#FFAAAA'), (1.0, '#FF0000')]) else: cmap = energy_colormap norm = Normalize(vmin=vmin, vmax=vmax) _map = ScalarMappable(norm=norm, cmap=cmap) _energies = [ pda.get_equilibrium_reaction_energy(entry) for coord, entry in labels.items() ] energies = [en if en < 0.0 else -0.00000001 for en in _energies] vals_stable = _map.to_rgba(energies) ii = 0 if process_attributes: for x, y in labels.keys(): if labels[(x, y)].attribute is None or \ labels[(x, y)].attribute == "existing": plt.plot(x, y, "o", markerfacecolor=vals_stable[ii], markersize=12) else: plt.plot(x, y, "*", markerfacecolor=vals_stable[ii], markersize=18) ii += 1 else: for x, y in labels.keys(): plt.plot(x, y, "o", markerfacecolor=vals_stable[ii], markersize=15) ii += 1 font = FontProperties() font.set_weight("bold") font.set_size(24) # Sets a nice layout depending on the type of PD. Also defines a # "center" for the PD, which then allows the annotations to be spread # out in a nice manner. if len(self._pd.elements) == 3: plt.axis("equal") plt.xlim((-0.1, 1.2)) plt.ylim((-0.1, 1.0)) plt.axis("off") center = (0.5, math.sqrt(3) / 6) else: all_coords = labels.keys() miny = min([c[1] for c in all_coords]) ybuffer = max(abs(miny) * 0.1, 0.1) plt.xlim((-0.1, 1.1)) plt.ylim((miny - ybuffer, ybuffer)) center = (0.5, miny / 2) plt.xlabel("Fraction", fontsize=28, fontweight='bold') plt.ylabel("Formation energy (eV/fu)", fontsize=28, fontweight='bold') for coords in sorted(labels.keys(), key=lambda x: -x[1]): entry = labels[coords] label = entry.name # The follow defines an offset for the annotation text emanating # from the center of the PD. Results in fairly nice layouts for the # most part. vec = (np.array(coords) - center) vec = vec / np.linalg.norm(vec) * 10 if np.linalg.norm(vec) != 0 \ else vec valign = "bottom" if vec[1] > 0 else "top" if vec[0] < -0.01: halign = "right" elif vec[0] > 0.01: halign = "left" else: halign = "center" if label_stable: if process_attributes and entry.attribute == 'new': plt.annotate(latexify(label), coords, xytext=vec, textcoords="offset points", horizontalalignment=halign, verticalalignment=valign, fontproperties=font, color='g') else: plt.annotate(latexify(label), coords, xytext=vec, textcoords="offset points", horizontalalignment=halign, verticalalignment=valign, fontproperties=font) if self.show_unstable: font = FontProperties() font.set_size(16) pda = PDAnalyzer(self._pd) energies_unstable = [ pda.get_e_above_hull(entry) for entry, coord in unstable.items() ] if energy_colormap is not None: energies.extend(energies_unstable) vals_unstable = _map.to_rgba(energies_unstable) ii = 0 for entry, coords in unstable.items(): ehull = pda.get_e_above_hull(entry) if ehull < self.show_unstable: vec = (np.array(coords) - center) vec = vec / np.linalg.norm(vec) * 10 \ if np.linalg.norm(vec) != 0 else vec label = entry.name if energy_colormap is None: plt.plot(coords[0], coords[1], "ks", linewidth=3, markeredgecolor="k", markerfacecolor="r", markersize=8) else: plt.plot(coords[0], coords[1], "s", linewidth=3, markeredgecolor="k", markerfacecolor=vals_unstable[ii], markersize=8) if label_unstable: plt.annotate(latexify(label), coords, xytext=vec, textcoords="offset points", horizontalalignment=halign, color="b", verticalalignment=valign, fontproperties=font) ii += 1 if energy_colormap is not None and show_colorbar: _map.set_array(energies) cbar = plt.colorbar(_map) cbar.set_label( 'Energy [meV/at] above hull (in red)\nInverse energy [' 'meV/at] above hull (in green)', rotation=-90, ha='left', va='center') ticks = cbar.ax.get_yticklabels() cbar.ax.set_yticklabels([ '${v}$'.format(v=float(t.get_text().strip('$')) * 1000.0) for t in ticks ]) f = plt.gcf() f.set_size_inches((8, 6)) plt.subplots_adjust(left=0.09, right=0.98, top=0.98, bottom=0.07) return plt
def get_pourbaix_plot_colorfill_by_element(self, limits=None, title="", label_domains=True, element=None): """ Color domains by element """ from matplotlib.patches import Polygon entry_dict_of_multientries = collections.defaultdict(list) plt = get_publication_quality_plot(16) optim_colors = [ '#0000FF', '#FF0000', '#00FF00', '#FFFF00', '#FF00FF', '#FF8080', '#DCDCDC', '#800000', '#FF8000' ] optim_font_color = [ '#FFFFA0', '#00FFFF', '#FF00FF', '#0000FF', '#00FF00', '#007F7F', '#232323', '#7FFFFF', '#007FFF' ] hatch = ['/', '\\', '|', '-', '+', 'o', '*'] (stable, unstable) = self.pourbaix_plot_data(limits) num_of_overlaps = {key: 0 for key in stable.keys()} for entry in stable: if isinstance(entry, MultiEntry): for e in entry.entrylist: if element in e.composition.elements: entry_dict_of_multientries[e.name].append(entry) num_of_overlaps[entry] += 1 else: entry_dict_of_multientries[entry.name].append(entry) if limits: xlim = limits[0] ylim = limits[1] else: xlim = self._analyzer.chempot_limits[0] ylim = self._analyzer.chempot_limits[1] h_line = np.transpose([[xlim[0], -xlim[0] * PREFAC], [xlim[1], -xlim[1] * PREFAC]]) o_line = np.transpose([[xlim[0], -xlim[0] * PREFAC + 1.23], [xlim[1], -xlim[1] * PREFAC + 1.23]]) neutral_line = np.transpose([[7, ylim[0]], [7, ylim[1]]]) V0_line = np.transpose([[xlim[0], 0], [xlim[1], 0]]) ax = plt.gca() ax.set_xlim(xlim) ax.set_ylim(ylim) from pymatgen import Composition, Element from pymatgen.core.ion import Ion def len_elts(entry): if "(s)" in entry: comp = Composition(entry[:-3]) else: comp = Ion.from_formula(entry) return len([ el for el in comp.elements if el not in [Element("H"), Element("O")] ]) sorted_entry = entry_dict_of_multientries.keys() sorted_entry.sort(key=len_elts) i = -1 label_chr = map(chr, list(range(65, 91))) for entry in sorted_entry: color_indx = 0 x_coord = 0.0 y_coord = 0.0 npts = 0 i += 1 for e in entry_dict_of_multientries[entry]: hc = 0 fc = 0 bc = 0 xy = self.domain_vertices(e) c = self.get_center(stable[e]) x_coord += c[0] y_coord += c[1] npts += 1 color_indx = i if "(s)" in entry: comp = Composition(entry[:-3]) else: comp = Ion.from_formula(entry) if len([ el for el in comp.elements if el not in [Element("H"), Element("O")] ]) == 1: if color_indx >= len(optim_colors): color_indx = color_indx -\ int(color_indx / len(optim_colors)) * len(optim_colors) patch = Polygon(xy, facecolor=optim_colors[color_indx], closed=True, lw=3.0, fill=True) bc = optim_colors[color_indx] else: if color_indx >= len(hatch): color_indx = color_indx - int( color_indx / len(hatch)) * len(hatch) patch = Polygon(xy, hatch=hatch[color_indx], closed=True, lw=3.0, fill=False) hc = hatch[color_indx] ax.add_patch(patch) xy_center = (x_coord / npts, y_coord / npts) if label_domains: if color_indx >= len(optim_colors): color_indx = color_indx -\ int(color_indx / len(optim_colors)) * len(optim_colors) fc = optim_font_color[color_indx] if bc and not hc: bbox = dict(boxstyle="round", fc=fc) if hc and not bc: bc = 'k' fc = 'w' bbox = dict(boxstyle="round", hatch=hc, fill=False) if bc and hc: bbox = dict(boxstyle="round", hatch=hc, fc=fc) # bbox.set_path_effects([PathEffects.withSimplePatchShadow()]) plt.annotate(latexify_ion(latexify(entry)), xy_center, color=bc, fontsize=30, bbox=bbox) # plt.annotate(label_chr[i], xy_center, # color=bc, fontsize=30, bbox=bbox) lw = 3 plt.plot(h_line[0], h_line[1], "r--", linewidth=lw) plt.plot(o_line[0], o_line[1], "r--", linewidth=lw) plt.plot(neutral_line[0], neutral_line[1], "k-.", linewidth=lw) plt.plot(V0_line[0], V0_line[1], "k-.", linewidth=lw) plt.xlabel("pH") plt.ylabel("E (V)") plt.title(title, fontsize=20, fontweight='bold') return plt
def get_pourbaix_plot_colorfill_by_domain_name(self, limits=None, title="", label_domains=True, label_color='k', domain_color=None, domain_fontsize=None, domain_edge_lw=0.5, bold_domains=None, cluster_domains=(), add_h2o_stablity_line=True, add_center_line=False, h2o_lw=0.5): """ Color domains by the colors specific by the domain_color dict Args: limits: 2D list containing limits of the Pourbaix diagram of the form [[xlo, xhi], [ylo, yhi]] lable_domains (Bool): whether add the text lable for domains label_color (str): color of domain lables, defaults to be black domain_color (dict): colors of each domain e.g {"Al(s)": "#FF1100"}. If set to None default color set will be used. domain_fontsize (int): Font size used in domain text labels. domain_edge_lw (int): line width for the boundaries between domains. bold_domains (list): List of domain names to use bold text style for domain lables. cluster_domains (list): List of domain names in cluster phase add_h2o_stablity_line (Bool): whether plot H2O stability line add_center_line (Bool): whether plot lines shows the center coordinate h2o_lw (int): line width for H2O stability line and center lines """ # helper functions def len_elts(entry): comp = Composition( entry[:-3]) if "(s)" in entry else Ion.from_formula(entry) return len(set(comp.elements) - {Element("H"), Element("O")}) def special_lines(xlim, ylim): h_line = np.transpose([[xlim[0], -xlim[0] * PREFAC], [xlim[1], -xlim[1] * PREFAC]]) o_line = np.transpose([[xlim[0], -xlim[0] * PREFAC + 1.23], [xlim[1], -xlim[1] * PREFAC + 1.23]]) neutral_line = np.transpose([[7, ylim[0]], [7, ylim[1]]]) V0_line = np.transpose([[xlim[0], 0], [xlim[1], 0]]) return h_line, o_line, neutral_line, V0_line from matplotlib.patches import Polygon from pymatgen import Composition, Element from pymatgen.core.ion import Ion default_domain_font_size = 12 default_solid_phase_color = '#b8f9e7' # this slighly darker than the MP scheme, to default_cluster_phase_color = '#d0fbef' # avoid making the cluster phase too light plt = get_publication_quality_plot(8, dpi=300) (stable, unstable) = self.pourbaix_plot_data(limits) num_of_overlaps = {key: 0 for key in stable.keys()} entry_dict_of_multientries = collections.defaultdict(list) for entry in stable: if isinstance(entry, MultiEntry): for e in entry.entrylist: entry_dict_of_multientries[e.name].append(entry) num_of_overlaps[entry] += 1 else: entry_dict_of_multientries[entry.name].append(entry) xlim, ylim = limits[:2] if limits else self._analyzer.chempot_limits[:2] h_line, o_line, neutral_line, V0_line = special_lines(xlim, ylim) ax = plt.gca() ax.set_xlim(xlim) ax.set_ylim(ylim) ax.xaxis.set_major_formatter(FormatStrFormatter('%.1f')) ax.yaxis.set_major_formatter(FormatStrFormatter('%.1f')) ax.tick_params(direction='out') ax.xaxis.set_ticks_position('bottom') ax.yaxis.set_ticks_position('left') sorted_entry = list(entry_dict_of_multientries.keys()) sorted_entry.sort(key=len_elts) if domain_fontsize is None: domain_fontsize = { en: default_domain_font_size for en in sorted_entry } if domain_color is None: domain_color = { en: default_solid_phase_color if '(s)' in en else (default_cluster_phase_color if en in cluster_domains else 'w') for i, en in enumerate(sorted_entry) } if bold_domains is None: bold_domains = [en for en in sorted_entry if '(s)' not in en] for entry in sorted_entry: x_coord, y_coord, npts = 0.0, 0.0, 0 for e in entry_dict_of_multientries[entry]: xy = self.domain_vertices(e) if add_h2o_stablity_line: c = self.get_distribution_corrected_center( stable[e], h_line, o_line, 0.3) else: c = self.get_distribution_corrected_center(stable[e]) x_coord += c[0] y_coord += c[1] npts += 1 patch = Polygon(xy, facecolor=domain_color[entry], closed=True, lw=domain_edge_lw, fill=True, antialiased=True) ax.add_patch(patch) xy_center = (x_coord / npts, y_coord / npts) if label_domains: if platform.system() == 'Darwin': # Have to hack to the hard coded font path to get current font On Mac OS X if entry in bold_domains: font = FontProperties( fname='/Library/Fonts/Times New Roman Bold.ttf', size=domain_fontsize[entry]) else: font = FontProperties( fname='/Library/Fonts/Times New Roman.ttf', size=domain_fontsize[entry]) else: if entry in bold_domains: font = FontProperties(family='Times New Roman', weight='bold', size=domain_fontsize[entry]) else: font = FontProperties(family='Times New Roman', weight='regular', size=domain_fontsize[entry]) plt.text(*xy_center, s=latexify_ion(latexify(entry)), fontproperties=font, horizontalalignment="center", verticalalignment="center", multialignment="center", color=label_color) if add_h2o_stablity_line: dashes = (3, 1.5) line, = plt.plot(h_line[0], h_line[1], "k--", linewidth=h2o_lw, antialiased=True) line.set_dashes(dashes) line, = plt.plot(o_line[0], o_line[1], "k--", linewidth=h2o_lw, antialiased=True) line.set_dashes(dashes) if add_center_line: plt.plot(neutral_line[0], neutral_line[1], "k-.", linewidth=h2o_lw, antialiased=False) plt.plot(V0_line[0], V0_line[1], "k-.", linewidth=h2o_lw, antialiased=False) plt.xlabel("pH", fontname="Times New Roman", fontsize=18) plt.ylabel("E (V)", fontname="Times New Roman", fontsize=18) plt.xticks(fontname="Times New Roman", fontsize=16) plt.yticks(fontname="Times New Roman", fontsize=16) plt.title(title, fontsize=20, fontweight='bold', fontname="Times New Roman") return plt
def get_pourbaix_plot_colorfill_by_element(self, limits=None, title="", label_domains=True, element=None): """ Color domains by element """ from matplotlib.patches import Polygon entry_dict_of_multientries = collections.defaultdict(list) plt = get_publication_quality_plot(16) optim_colors = ['#0000FF', '#FF0000', '#00FF00', '#FFFF00', '#FF00FF', '#FF8080', '#DCDCDC', '#800000', '#FF8000'] optim_font_color = ['#FFFFA0', '#00FFFF', '#FF00FF', '#0000FF', '#00FF00', '#007F7F', '#232323', '#7FFFFF', '#007FFF'] hatch = ['/', '\\', '|', '-', '+', 'o', '*'] (stable, unstable) = self.pourbaix_plot_data(limits) num_of_overlaps = {key: 0 for key in stable.keys()} for entry in stable: if isinstance(entry, MultiEntry): for e in entry.entrylist: if element in e.composition.elements: entry_dict_of_multientries[e.name].append(entry) num_of_overlaps[entry] += 1 else: entry_dict_of_multientries[entry.name].append(entry) if limits: xlim = limits[0] ylim = limits[1] else: xlim = self._analyzer.chempot_limits[0] ylim = self._analyzer.chempot_limits[1] h_line = np.transpose([[xlim[0], -xlim[0] * PREFAC], [xlim[1], -xlim[1] * PREFAC]]) o_line = np.transpose([[xlim[0], -xlim[0] * PREFAC + 1.23], [xlim[1], -xlim[1] * PREFAC + 1.23]]) neutral_line = np.transpose([[7, ylim[0]], [7, ylim[1]]]) V0_line = np.transpose([[xlim[0], 0], [xlim[1], 0]]) ax = plt.gca() ax.set_xlim(xlim) ax.set_ylim(ylim) from pymatgen import Composition, Element from pymatgen.core.ion import Ion def len_elts(entry): if "(s)" in entry: comp = Composition(entry[:-3]) else: comp = Ion.from_formula(entry) return len([el for el in comp.elements if el not in [Element("H"), Element("O")]]) sorted_entry = entry_dict_of_multientries.keys() sorted_entry.sort(key=len_elts) i = -1 label_chr = map(chr, list(range(65, 91))) for entry in sorted_entry: color_indx = 0 x_coord = 0.0 y_coord = 0.0 npts = 0 i += 1 for e in entry_dict_of_multientries[entry]: hc = 0 fc = 0 bc = 0 xy = self.domain_vertices(e) c = self.get_center(stable[e]) x_coord += c[0] y_coord += c[1] npts += 1 color_indx = i if "(s)" in entry: comp = Composition(entry[:-3]) else: comp = Ion.from_formula(entry) if len([el for el in comp.elements if el not in [Element("H"), Element("O")]]) == 1: if color_indx >= len(optim_colors): color_indx = color_indx -\ int(color_indx / len(optim_colors)) * len(optim_colors) patch = Polygon(xy, facecolor=optim_colors[color_indx], closed=True, lw=3.0, fill=True) bc = optim_colors[color_indx] else: if color_indx >= len(hatch): color_indx = color_indx - int(color_indx / len(hatch)) * len(hatch) patch = Polygon(xy, hatch=hatch[color_indx], closed=True, lw=3.0, fill=False) hc = hatch[color_indx] ax.add_patch(patch) xy_center = (x_coord / npts, y_coord / npts) if label_domains: if color_indx >= len(optim_colors): color_indx = color_indx -\ int(color_indx / len(optim_colors)) * len(optim_colors) fc = optim_font_color[color_indx] if bc and not hc: bbox = dict(boxstyle="round", fc=fc) if hc and not bc: bc = 'k' fc = 'w' bbox = dict(boxstyle="round", hatch=hc, fill=False) if bc and hc: bbox = dict(boxstyle="round", hatch=hc, fc=fc) # bbox.set_path_effects([PathEffects.withSimplePatchShadow()]) plt.annotate(latexify_ion(latexify(entry)), xy_center, color=bc, fontsize=30, bbox=bbox) # plt.annotate(label_chr[i], xy_center, # color=bc, fontsize=30, bbox=bbox) lw = 3 plt.plot(h_line[0], h_line[1], "r--", linewidth=lw) plt.plot(o_line[0], o_line[1], "r--", linewidth=lw) plt.plot(neutral_line[0], neutral_line[1], "k-.", linewidth=lw) plt.plot(V0_line[0], V0_line[1], "k-.", linewidth=lw) plt.xlabel("pH") plt.ylabel("E (V)") plt.title(title, fontsize=20, fontweight='bold') return plt
def test_latexify(self): self.assertEqual(latexify("Li3Fe2(PO4)3"), "Li$_{3}$Fe$_{2}$(PO$_{4}$)$_{3}$") self.assertEqual(latexify("Li0.2Na0.8Cl"), "Li$_{0.2}$Na$_{0.8}$Cl")
def get_pourbaix_plot_colorfill_by_domain_name(self, limits=None, title="", label_domains=True, label_color='k', domain_color=None, domain_fontsize=None, domain_edge_lw=0.5, bold_domains=None, cluster_domains=(), add_h2o_stablity_line=True, add_center_line=False, h2o_lw=0.5): """ Color domains by the colors specific by the domain_color dict Args: limits: 2D list containing limits of the Pourbaix diagram of the form [[xlo, xhi], [ylo, yhi]] lable_domains (Bool): whether add the text lable for domains label_color (str): color of domain lables, defaults to be black domain_color (dict): colors of each domain e.g {"Al(s)": "#FF1100"}. If set to None default color set will be used. domain_fontsize (int): Font size used in domain text labels. domain_edge_lw (int): line width for the boundaries between domains. bold_domains (list): List of domain names to use bold text style for domain lables. cluster_domains (list): List of domain names in cluster phase add_h2o_stablity_line (Bool): whether plot H2O stability line add_center_line (Bool): whether plot lines shows the center coordinate h2o_lw (int): line width for H2O stability line and center lines """ # helper functions def len_elts(entry): comp = Composition(entry[:-3]) if "(s)" in entry else Ion.from_formula(entry) return len(set(comp.elements) - {Element("H"), Element("O")}) def special_lines(xlim, ylim): h_line = np.transpose([[xlim[0], -xlim[0] * PREFAC], [xlim[1], -xlim[1] * PREFAC]]) o_line = np.transpose([[xlim[0], -xlim[0] * PREFAC + 1.23], [xlim[1], -xlim[1] * PREFAC + 1.23]]) neutral_line = np.transpose([[7, ylim[0]], [7, ylim[1]]]) V0_line = np.transpose([[xlim[0], 0], [xlim[1], 0]]) return h_line, o_line, neutral_line, V0_line from matplotlib.patches import Polygon from pymatgen import Composition, Element from pymatgen.core.ion import Ion default_domain_font_size = 12 default_solid_phase_color = '#b8f9e7' # this slighly darker than the MP scheme, to default_cluster_phase_color = '#d0fbef' # avoid making the cluster phase too light plt = get_publication_quality_plot(8, dpi=300) (stable, unstable) = self.pourbaix_plot_data(limits) num_of_overlaps = {key: 0 for key in stable.keys()} entry_dict_of_multientries = collections.defaultdict(list) for entry in stable: if isinstance(entry, MultiEntry): for e in entry.entrylist: entry_dict_of_multientries[e.name].append(entry) num_of_overlaps[entry] += 1 else: entry_dict_of_multientries[entry.name].append(entry) xlim, ylim = limits[:2] if limits else self._analyzer.chempot_limits[:2] h_line, o_line, neutral_line, V0_line = special_lines(xlim, ylim) ax = plt.gca() ax.set_xlim(xlim) ax.set_ylim(ylim) ax.xaxis.set_major_formatter(FormatStrFormatter('%.1f')) ax.yaxis.set_major_formatter(FormatStrFormatter('%.1f')) ax.tick_params(direction='out') ax.xaxis.set_ticks_position('bottom') ax.yaxis.set_ticks_position('left') sorted_entry = list(entry_dict_of_multientries.keys()) sorted_entry.sort(key=len_elts) if domain_fontsize is None: domain_fontsize = {en: default_domain_font_size for en in sorted_entry} if domain_color is None: domain_color = {en: default_solid_phase_color if '(s)' in en else (default_cluster_phase_color if en in cluster_domains else 'w') for i, en in enumerate(sorted_entry)} if bold_domains is None: bold_domains = [en for en in sorted_entry if '(s)' not in en] for entry in sorted_entry: x_coord, y_coord, npts = 0.0, 0.0, 0 for e in entry_dict_of_multientries[entry]: xy = self.domain_vertices(e) if add_h2o_stablity_line: c = self.get_distribution_corrected_center(stable[e], h_line, o_line, 0.3) else: c = self.get_distribution_corrected_center(stable[e]) x_coord += c[0] y_coord += c[1] npts += 1 patch = Polygon(xy, facecolor=domain_color[entry], closed=True, lw=domain_edge_lw, fill=True, antialiased=True) ax.add_patch(patch) xy_center = (x_coord / npts, y_coord / npts) if label_domains: if platform.system() == 'Darwin': # Have to hack to the hard coded font path to get current font On Mac OS X if entry in bold_domains: font = FontProperties(fname='/Library/Fonts/Times New Roman Bold.ttf', size=domain_fontsize[entry]) else: font = FontProperties(fname='/Library/Fonts/Times New Roman.ttf', size=domain_fontsize[entry]) else: if entry in bold_domains: font = FontProperties(family='Times New Roman', weight='bold', size=domain_fontsize[entry]) else: font = FontProperties(family='Times New Roman', weight='regular', size=domain_fontsize[entry]) plt.text(*xy_center, s=latexify_ion(latexify(entry)), fontproperties=font, horizontalalignment="center", verticalalignment="center", multialignment="center", color=label_color) if add_h2o_stablity_line: dashes = (3, 1.5) line, = plt.plot(h_line[0], h_line[1], "k--", linewidth=h2o_lw, antialiased=True) line.set_dashes(dashes) line, = plt.plot(o_line[0], o_line[1], "k--", linewidth=h2o_lw, antialiased=True) line.set_dashes(dashes) if add_center_line: plt.plot(neutral_line[0], neutral_line[1], "k-.", linewidth=h2o_lw, antialiased=False) plt.plot(V0_line[0], V0_line[1], "k-.", linewidth=h2o_lw, antialiased=False) plt.xlabel("pH", fontname="Times New Roman", fontsize=18) plt.ylabel("E (V)", fontname="Times New Roman", fontsize=18) plt.xticks(fontname="Times New Roman", fontsize=16) plt.yticks(fontname="Times New Roman", fontsize=16) plt.title(title, fontsize=20, fontweight='bold', fontname="Times New Roman") return plt
def get_chempot_range_map_plot(self, elements): """ Returns a plot of the chemical potential range map. Currently works only for 3-component PDs. Args: elements: Sequence of elements to be considered as independent variables. E.g., if you want to show the stability ranges of all Li-Co-O phases wrt to uLi and uO, you will supply [Element("Li"), Element("O")] Returns: A matplotlib plot object. """ plt = get_publication_quality_plot(12, 8) analyzer = PDAnalyzer(self._pd) chempot_ranges = analyzer.get_chempot_range_map(elements) missing_lines = {} excluded_region = [] for entry, lines in chempot_ranges.items(): comp = entry.composition center_x = 0 center_y = 0 coords = [] contain_zero = any([comp.get_atomic_fraction(el) == 0 for el in elements]) is_boundary = (not contain_zero) and sum([comp.get_atomic_fraction(el) for el in elements]) == 1 for line in lines: (x, y) = line.coords.transpose() plt.plot(x, y, "k-") for coord in line.coords: if not in_coord_list(coords, coord): coords.append(coord.tolist()) center_x += coord[0] center_y += coord[1] if is_boundary: excluded_region.extend(line.coords) if coords and contain_zero: missing_lines[entry] = coords else: xy = (center_x / len(coords), center_y / len(coords)) plt.annotate(latexify(entry.name), xy, fontsize=22) ax = plt.gca() xlim = ax.get_xlim() ylim = ax.get_ylim() # Shade the forbidden chemical potential regions. excluded_region.append([xlim[1], ylim[1]]) excluded_region = sorted(excluded_region, key=lambda c: c[0]) (x, y) = np.transpose(excluded_region) plt.fill(x, y, "0.80") # The hull does not generate the missing horizontal and vertical lines. # The following code fixes this. el0 = elements[0] el1 = elements[1] for entry, coords in missing_lines.items(): center_x = sum([c[0] for c in coords]) center_y = sum([c[1] for c in coords]) comp = entry.composition is_x = comp.get_atomic_fraction(el0) < 0.01 is_y = comp.get_atomic_fraction(el1) < 0.01 n = len(coords) if not (is_x and is_y): if is_x: coords = sorted(coords, key=lambda c: c[1]) for i in [0, -1]: x = [min(xlim), coords[i][0]] y = [coords[i][1], coords[i][1]] plt.plot(x, y, "k") center_x += min(xlim) center_y += coords[i][1] elif is_y: coords = sorted(coords, key=lambda c: c[0]) for i in [0, -1]: x = [coords[i][0], coords[i][0]] y = [coords[i][1], min(ylim)] plt.plot(x, y, "k") center_x += coords[i][0] center_y += min(ylim) xy = (center_x / (n + 2), center_y / (n + 2)) else: center_x = sum(coord[0] for coord in coords) + xlim[0] center_y = sum(coord[1] for coord in coords) + ylim[0] xy = (center_x / (n + 1), center_y / (n + 1)) plt.annotate( latexify(entry.name), xy, horizontalalignment="center", verticalalignment="center", fontsize=22 ) plt.xlabel("$\mu_{{{0}}} - \mu_{{{0}}}^0$ (eV)".format(el0.symbol)) plt.ylabel("$\mu_{{{0}}} - \mu_{{{0}}}^0$ (eV)".format(el1.symbol)) plt.tight_layout() return plt
def _get_2d_plot(self, label_stable=True, label_unstable=True): """ Shows the plot using pylab. Usually I won"t do imports in methods, but since plotting is a fairly expensive library to load and not all machines have matplotlib installed, I have done it this way. """ plt = get_publication_quality_plot(8, 6) from matplotlib.font_manager import FontProperties (lines, labels, unstable) = self.pd_plot_data for x, y in lines: plt.plot(x, y, "ko-", linewidth=3, markeredgecolor="k", markerfacecolor="b", markersize=15) font = FontProperties() font.set_weight("bold") font.set_size(24) # Sets a nice layout depending on the type of PD. Also defines a # "center" for the PD, which then allows the annotations to be spread # out in a nice manner. if len(self._pd.elements) == 3: plt.axis("equal") plt.xlim((-0.1, 1.2)) plt.ylim((-0.1, 1.0)) plt.axis("off") center = (0.5, math.sqrt(3) / 6) else: all_coords = labels.keys() miny = min([c[1] for c in all_coords]) ybuffer = max(abs(miny) * 0.1, 0.1) plt.xlim((-0.1, 1.1)) plt.ylim((miny - ybuffer, ybuffer)) center = (0.5, miny / 2) plt.xlabel("Fraction", fontsize=28, fontweight='bold') plt.ylabel("Formation energy (eV/fu)", fontsize=28, fontweight='bold') for coords in sorted(labels.keys(), key=lambda x: -x[1]): entry = labels[coords] label = entry.name # The follow defines an offset for the annotation text emanating # from the center of the PD. Results in fairly nice layouts for the # most part. vec = (np.array(coords) - center) vec = vec / np.linalg.norm(vec) * 10 if np.linalg.norm(vec) != 0 \ else vec valign = "bottom" if vec[1] > 0 else "top" if vec[0] < -0.01: halign = "right" elif vec[0] > 0.01: halign = "left" else: halign = "center" if label_stable: plt.annotate(latexify(label), coords, xytext=vec, textcoords="offset points", horizontalalignment=halign, verticalalignment=valign, fontproperties=font) if self.show_unstable: font = FontProperties() font.set_size(16) for entry, coords in unstable.items(): vec = (np.array(coords) - center) vec = vec / np.linalg.norm(vec) * 10 label = entry.name plt.plot(coords[0], coords[1], "ks", linewidth=3, markeredgecolor="k", markerfacecolor="r", markersize=8) if label_unstable: plt.annotate(latexify(label), coords, xytext=vec, textcoords="offset points", horizontalalignment=halign, color="b", verticalalignment=valign, fontproperties=font) F = plt.gcf() F.set_size_inches((8, 6)) plt.subplots_adjust(left=0.09, right=0.98, top=0.98, bottom=0.07) return plt
def _get_2d_plot(self, label_stable=True, label_unstable=True): """ Shows the plot using pylab. Usually I won"t do imports in methods, but since plotting is a fairly expensive library to load and not all machines have matplotlib installed, I have done it this way. """ plt = get_publication_quality_plot(8, 6) from matplotlib.font_manager import FontProperties (lines, labels, unstable) = self.pd_plot_data for x, y in lines: plt.plot(x, y, "ko-", linewidth=3, markeredgecolor="k", markerfacecolor="b", markersize=15) font = FontProperties() font.set_weight("bold") font.set_size(24) # Sets a nice layout depending on the type of PD. Also defines a # "center" for the PD, which then allows the annotations to be spread # out in a nice manner. if len(self._pd.elements) == 3: plt.axis("equal") plt.xlim((-0.1, 1.2)) plt.ylim((-0.1, 1.0)) plt.axis("off") center = (0.5, math.sqrt(3) / 6) else: all_coords = labels.keys() miny = min([c[1] for c in all_coords]) ybuffer = max(abs(miny) * 0.1, 0.1) plt.xlim((-0.1, 1.1)) plt.ylim((miny - ybuffer, ybuffer)) center = (0.5, miny / 2) plt.xlabel("Fraction", fontsize=28, fontweight="bold") plt.ylabel("Formation energy (eV/fu)", fontsize=28, fontweight="bold") for coords in sorted(labels.keys(), key=lambda x: -x[1]): entry = labels[coords] label = entry.name # The follow defines an offset for the annotation text emanating # from the center of the PD. Results in fairly nice layouts for the # most part. vec = np.array(coords) - center vec = vec / np.linalg.norm(vec) * 10 if np.linalg.norm(vec) != 0 else vec valign = "bottom" if vec[1] > 0 else "top" if vec[0] < -0.01: halign = "right" elif vec[0] > 0.01: halign = "left" else: halign = "center" if label_stable: plt.annotate( latexify(label), coords, xytext=vec, textcoords="offset points", horizontalalignment=halign, verticalalignment=valign, fontproperties=font, ) if self.show_unstable: font = FontProperties() font.set_size(16) for entry, coords in unstable.items(): vec = np.array(coords) - center vec = vec / np.linalg.norm(vec) * 10 label = entry.name plt.plot( coords[0], coords[1], "ks", linewidth=3, markeredgecolor="k", markerfacecolor="r", markersize=8 ) if label_unstable: plt.annotate( latexify(label), coords, xytext=vec, textcoords="offset points", horizontalalignment=halign, color="b", verticalalignment=valign, fontproperties=font, ) F = plt.gcf() F.set_size_inches((8, 6)) plt.subplots_adjust(left=0.09, right=0.98, top=0.98, bottom=0.07) return plt
def _get_2d_plot(self, label_stable=True, label_unstable=True, ordering=None, energy_colormap=None, vmin_mev=-60.0, vmax_mev=60.0, show_colorbar=True, process_attributes=False): """ Shows the plot using pylab. Usually I won't do imports in methods, but since plotting is a fairly expensive library to load and not all machines have matplotlib installed, I have done it this way. """ plt = get_publication_quality_plot(8, 6) from matplotlib.font_manager import FontProperties if ordering is None: (lines, labels, unstable) = self.pd_plot_data else: (_lines, _labels, _unstable) = self.pd_plot_data (lines, labels, unstable) = order_phase_diagram( _lines, _labels, _unstable, ordering) if energy_colormap is None: if process_attributes: for x, y in lines: plt.plot(x, y, "k-", linewidth=3, markeredgecolor="k") # One should think about a clever way to have "complex" # attributes with complex processing options but with a clear # logic. At this moment, I just use the attributes to know # whether an entry is a new compound or an existing (from the # ICSD or from the MP) one. for x, y in labels.keys(): if labels[(x, y)].attribute is None or \ labels[(x, y)].attribute == "existing": plt.plot(x, y, "ko", linewidth=3, markeredgecolor="k", markerfacecolor="b", markersize=12) else: plt.plot(x, y, "k*", linewidth=3, markeredgecolor="k", markerfacecolor="g", markersize=18) else: for x, y in lines: plt.plot(x, y, "ko-", linewidth=3, markeredgecolor="k", markerfacecolor="b", markersize=15) else: from matplotlib.colors import Normalize, LinearSegmentedColormap from matplotlib.cm import ScalarMappable pda = PDAnalyzer(self._pd) for x, y in lines: plt.plot(x, y, "k-", linewidth=3, markeredgecolor="k") vmin = vmin_mev / 1000.0 vmax = vmax_mev / 1000.0 if energy_colormap == 'default': mid = - vmin / (vmax - vmin) cmap = LinearSegmentedColormap.from_list( 'my_colormap', [(0.0, '#005500'), (mid, '#55FF55'), (mid, '#FFAAAA'), (1.0, '#FF0000')]) else: cmap = energy_colormap norm = Normalize(vmin=vmin, vmax=vmax) _map = ScalarMappable(norm=norm, cmap=cmap) _energies = [pda.get_equilibrium_reaction_energy(entry) for coord, entry in labels.items()] energies = [en if en < 0.0 else -0.00000001 for en in _energies] vals_stable = _map.to_rgba(energies) ii = 0 if process_attributes: for x, y in labels.keys(): if labels[(x, y)].attribute is None or \ labels[(x, y)].attribute == "existing": plt.plot(x, y, "o", markerfacecolor=vals_stable[ii], markersize=12) else: plt.plot(x, y, "*", markerfacecolor=vals_stable[ii], markersize=18) ii += 1 else: for x, y in labels.keys(): plt.plot(x, y, "o", markerfacecolor=vals_stable[ii], markersize=15) ii += 1 font = FontProperties() font.set_weight("bold") font.set_size(24) # Sets a nice layout depending on the type of PD. Also defines a # "center" for the PD, which then allows the annotations to be spread # out in a nice manner. if len(self._pd.elements) == 3: plt.axis("equal") plt.xlim((-0.1, 1.2)) plt.ylim((-0.1, 1.0)) plt.axis("off") center = (0.5, math.sqrt(3) / 6) else: all_coords = labels.keys() miny = min([c[1] for c in all_coords]) ybuffer = max(abs(miny) * 0.1, 0.1) plt.xlim((-0.1, 1.1)) plt.ylim((miny - ybuffer, ybuffer)) center = (0.5, miny / 2) plt.xlabel("Fraction", fontsize=28, fontweight='bold') plt.ylabel("Formation energy (eV/fu)", fontsize=28, fontweight='bold') for coords in sorted(labels.keys(), key=lambda x: -x[1]): entry = labels[coords] label = entry.name # The follow defines an offset for the annotation text emanating # from the center of the PD. Results in fairly nice layouts for the # most part. vec = (np.array(coords) - center) vec = vec / np.linalg.norm(vec) * 10 if np.linalg.norm(vec) != 0 \ else vec valign = "bottom" if vec[1] > 0 else "top" if vec[0] < -0.01: halign = "right" elif vec[0] > 0.01: halign = "left" else: halign = "center" if label_stable: if process_attributes and entry.attribute == 'new': plt.annotate(latexify(label), coords, xytext=vec, textcoords="offset points", horizontalalignment=halign, verticalalignment=valign, fontproperties=font, color='g') else: plt.annotate(latexify(label), coords, xytext=vec, textcoords="offset points", horizontalalignment=halign, verticalalignment=valign, fontproperties=font) if self.show_unstable: font = FontProperties() font.set_size(16) pda = PDAnalyzer(self._pd) energies_unstable = [pda.get_e_above_hull(entry) for entry, coord in unstable.items()] if energy_colormap is not None: energies.extend(energies_unstable) vals_unstable = _map.to_rgba(energies_unstable) ii = 0 for entry, coords in unstable.items(): vec = (np.array(coords) - center) vec = vec / np.linalg.norm(vec) * 10 \ if np.linalg.norm(vec) != 0 else vec label = entry.name if energy_colormap is None: plt.plot(coords[0], coords[1], "ks", linewidth=3, markeredgecolor="k", markerfacecolor="r", markersize=8) else: plt.plot(coords[0], coords[1], "s", linewidth=3, markeredgecolor="k", markerfacecolor=vals_unstable[ii], markersize=8) if label_unstable: plt.annotate(latexify(label), coords, xytext=vec, textcoords="offset points", horizontalalignment=halign, color="b", verticalalignment=valign, fontproperties=font) ii += 1 if energy_colormap is not None and show_colorbar: _map.set_array(energies) cbar = plt.colorbar(_map) cbar.set_label( 'Energy [meV/at] above hull (in red)\nInverse energy [' 'meV/at] above hull (in green)', rotation=-90, ha='left', va='center') ticks = cbar.ax.get_yticklabels() cbar.ax.set_yticklabels(['${v}$'.format( v=float(t.get_text().strip('$'))*1000.0) for t in ticks]) f = plt.gcf() f.set_size_inches((8, 6)) plt.subplots_adjust(left=0.09, right=0.98, top=0.98, bottom=0.07) return plt