def test_in_coord_list(self): coords = [[0, 0, 0], [0.5, 0.5, 0.5]] test_coord = [0.1, 0.1, 0.1] self.assertFalse(coord.in_coord_list(coords, test_coord)) self.assertTrue(coord.in_coord_list(coords, test_coord, atol=0.15)) self.assertFalse( coord.in_coord_list([0.99, 0.99, 0.99], test_coord, atol=0.15))
def test_generate_adsorption_structures(self): co = Molecule("CO", [[0, 0, 0], [0, 0, 1.23]]) structures = self.asf_111.generate_adsorption_structures( co, repeat=[2, 2, 1]) self.assertEqual(len(structures), 4) sites = self.asf_111.find_adsorption_sites() # Check repeat functionality self.assertEqual( len([ site for site in structures[0] if site.properties['surface_properties'] != 'adsorbate' ]), 4 * len(self.asf_111.slab)) for n, structure in enumerate(structures): self.assertArrayAlmostEqual(structure[-2].coords, sites['all'][n]) find_args = {"positions": ["hollow"]} structures_hollow = self.asf_111.\ generate_adsorption_structures(co, find_args=find_args) self.assertEqual(len(structures_hollow), len(sites['hollow'])) for n, structure in enumerate(structures_hollow): self.assertTrue( in_coord_list(sites['hollow'], structure[-2].coords)) # checks if top_surface boolean will properly # adsorb at the bottom surface when False o = Molecule("O", [[0, 0, 0]]) adslabs = self.asf_111_bottom.generate_adsorption_structures(o) for adslab in adslabs: sites = sorted(adslab, key=lambda site: site.frac_coords[2]) self.assertTrue(sites[0].species_string == "O")
def test_generate_adsorption_structures(self): co = Molecule("CO", [[0, 0, 0], [0, 0, 1.23]]) structures = self.asf_111.generate_adsorption_structures(co, repeat=[2, 2, 1]) self.assertEqual(len(structures), 4) sites = self.asf_111.find_adsorption_sites() # Check repeat functionality self.assertEqual(len([site for site in structures[0] if site.properties['surface_properties'] != 'adsorbate']), 4*len(self.asf_111.slab)) for n, structure in enumerate(structures): self.assertArrayAlmostEqual(structure[-2].coords, sites['all'][n]) find_args = {"positions":["hollow"]} structures_hollow = self.asf_111.\ generate_adsorption_structures(co, find_args=find_args) self.assertEqual(len(structures_hollow), len(sites['hollow'])) for n, structure in enumerate(structures_hollow): self.assertTrue(in_coord_list(sites['hollow'], structure[-2].coords)) # checks if top_surface boolean will properly # adsorb at the bottom surface when False o = Molecule("O", [[0, 0, 0]]) adslabs = self.asf_111_bottom.generate_adsorption_structures(o) for adslab in adslabs: sites = sorted(adslab, key=lambda site: site.frac_coords[2]) self.assertTrue(sites[0].species_string == "O")
def get_center(self, lines): """ Returns coordinates of center of a domain. Useful for labeling a Pourbaix plot. Args: lines: Lines corresponding to a domain limits: Limits of Pourbaix diagram Returns: center_x, center_y: x,y coordinate of center of domain. If domain lies outside limits, center will lie on the boundary. """ center_x = 0.0 center_y = 0.0 coords = [] count_center = 0.0 for line in lines: for coord in np.array(line).T: if not in_coord_list(coords, coord): coords.append(coord.tolist()) cx = coord[0] cy = coord[1] center_x += cx center_y += cy count_center += 1.0 if count_center == 0.0: count_center = 1.0 center_x /= count_center center_y /= count_center return center_x, center_y
def get_center(self, lines): """ Returns coordinates of center of a domain. Useful for labeling a Pourbaix plot. Args: lines: Lines corresponding to a domain limits: Limits of Pourbaix diagram Returns: center_x, center_y: x,y coordinate of center of domain. If domain lies outside limits, center will lie on the boundary. """ center_x = 0.0 center_y = 0.0 coords = [] count_center = 0.0 for line in lines: for coord in np.array(line).T: if not in_coord_list(coords, coord): coords.append(coord.tolist()) cx = coord[0] cy = coord[1] center_x += cx center_y += cy count_center += 1.0 if count_center == 0.0: count_center = 1.0 center_x /= count_center center_y /= count_center return center_x, center_y
def test_generate_adsorption_structures(self): co = Molecule("CO", [[0, 0, 0], [0, 0, 1.23]]) structures = self.asf_111.generate_adsorption_structures(co, repeat=[2, 2, 1]) self.assertEqual(len(structures), 4) sites = self.asf_111.find_adsorption_sites() # Check repeat functionality self.assertEqual( len([site for site in structures[0] if site.properties["surface_properties"] != "adsorbate"]), 4 * len(self.asf_111.slab), ) for n, structure in enumerate(structures): self.assertArrayAlmostEqual(structure[-2].coords, sites["all"][n]) find_args = {"positions": ["hollow"]} structures_hollow = self.asf_111.generate_adsorption_structures(co, find_args=find_args) self.assertEqual(len(structures_hollow), len(sites["hollow"])) for n, structure in enumerate(structures_hollow): self.assertTrue(in_coord_list(sites["hollow"], structure[-2].coords, 1e-4)) # Check molecule not changed after rotation when added to surface co = Molecule("CO", [[1.0, -0.5, 3], [0.8, 0.46, 3.75]]) structures = self.asf_211.generate_adsorption_structures(co) self.assertEqual(co, Molecule("CO", [[1.0, -0.5, 3], [0.8, 0.46, 3.75]])) # Check translation sites = self.asf_211.find_adsorption_sites() ads_site_coords = sites["all"][0] c_site = structures[0].sites[-2] self.assertEqual(str(c_site.specie), "C") self.assertArrayAlmostEqual(c_site.coords, sites["all"][0]) # Check no translation structures = self.asf_111.generate_adsorption_structures(co, translate=False) self.assertEqual(co, Molecule("CO", [[1.0, -0.5, 3], [0.8, 0.46, 3.75]])) sites = self.asf_111.find_adsorption_sites() ads_site_coords = sites["all"][0] c_site = structures[0].sites[-2] self.assertArrayAlmostEqual(c_site.coords, ads_site_coords + np.array([1.0, -0.5, 3]))
def set_miller_family(self): """ get all miller indices for the given maximum index get the list of indices that correspond to the given family of indices """ recp_structure = Structure(self.recp_lattice, ["H"], [[0, 0, 0]]) analyzer = SpacegroupAnalyzer(recp_structure, symprec=0.001) symm_ops = analyzer.get_symmetry_operations() max_index = max(max(m) for m in self.hkl_family) r = list(range(-max_index, max_index + 1)) r.reverse() miller_indices = [] self.all_equiv_millers = [] self.all_surface_energies = [] for miller in itertools.product(r, r, r): if any([i != 0 for i in miller]): d = abs(reduce(gcd, miller)) miller_index = tuple([int(i / d) for i in miller]) for op in symm_ops: for i, u_miller in enumerate(self.hkl_family): if in_coord_list(u_miller, op.operate(miller_index)): self.all_equiv_millers.append(miller_index) self.all_surface_energies.append( self.surface_energies[i])
def symm_check(self, ucell, wulff_vertices): """ # Checks if the point group of the Wulff shape matches # the point group of its conventional unit cell Args: ucell (string): Unit cell that the Wulff shape is based on. wulff_vertices (list): List of all vertices on the Wulff shape. Use wulff.wulff_pt_list to obtain the list (see wulff_generator.py). return (bool) """ space_group_analyzer = SpacegroupAnalyzer(ucell) symm_ops = space_group_analyzer.get_point_group_operations( cartesian=True) for point in wulff_vertices: for op in symm_ops: symm_point = op.operate(point) if in_coord_list(wulff_vertices, symm_point): continue else: return False return True
def symm_check(self, ucell, wulff_vertices): """ # Checks if the point group of the Wulff shape matches # the point group of its conventional unit cell Args: ucell (string): Unit cell that the Wulff shape is based on. wulff_vertices (list): List of all vertices on the Wulff shape. Use wulff.wulff_pt_list to obtain the list (see wulff_generator.py). return (bool) """ space_group_analyzer = SpacegroupAnalyzer(ucell) symm_ops = space_group_analyzer.get_point_group_operations( cartesian=True) for point in wulff_vertices: for op in symm_ops: symm_point = op.operate(point) if in_coord_list(wulff_vertices, symm_point): continue else: return False return True
def test_generate_adsorption_structures(self): co = Molecule("CO", [[0, 0, 0], [0, 0, 1.23]]) structures = self.asf_111.generate_adsorption_structures(co, repeat=[2, 2, 1]) self.assertEqual(len(structures), 4) sites = self.asf_111.find_adsorption_sites() # Check repeat functionality self.assertEqual(len([site for site in structures[0] if site.properties['surface_properties'] != 'adsorbate']), 4*len(self.asf_111.slab)) for n, structure in enumerate(structures): self.assertArrayAlmostEqual(structure[-2].coords, sites['all'][n]) find_args = {"positions":["hollow"]} structures_hollow = self.asf_111.\ generate_adsorption_structures(co, find_args=find_args) self.assertEqual(len(structures_hollow), len(sites['hollow'])) for n, structure in enumerate(structures_hollow): self.assertTrue(in_coord_list(sites['hollow'], structure[-2].coords))
def test_generate_adsorption_structures(self): co = Molecule("CO", [[0, 0, 0], [0, 0, 1.23]]) structures = self.asf_111.generate_adsorption_structures( co, repeat=[2, 2, 1]) self.assertEqual(len(structures), 4) sites = self.asf_111.find_adsorption_sites() # Check repeat functionality self.assertEqual( len([ site for site in structures[0] if site.properties['surface_properties'] != 'adsorbate' ]), 4 * len(self.asf_111.slab)) for n, structure in enumerate(structures): self.assertArrayAlmostEqual(structure[-2].coords, sites['all'][n]) find_args = {"positions": ["hollow"]} structures_hollow = self.asf_111.\ generate_adsorption_structures(co, find_args=find_args) self.assertEqual(len(structures_hollow), len(sites['hollow'])) for n, structure in enumerate(structures_hollow): self.assertTrue( in_coord_list(sites['hollow'], structure[-2].coords))
def pourbaix_data(element1, element2, mat_co_1, limits): """ Get data required to plot Pourbaix diagram. Args: limits: 2D list containing limits of the Pourbaix diagram of the form [[xlo, xhi], [ylo, yhi]] Returns: """ (stable_pb, unstable_pb) = pourbaix_plot_data(element1, element2, mat_co_1, limits) if limits: xlim = limits[0] ylim = limits[1] else: analyzer = PourbaixAnalyzer(pd) xlim = analyzer.chempot_limits[0] ylim = analyzer.chempot_limits[1] # h_line[0]: x_axis: [pH_h_lmin, pH_h_max] # h_line[1]: y_axis: [U_h_lmin, U_h_max] 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]]) labels_name = [] labels_loc_x = [] labels_loc_y = [] species_lines = [] for entry, lines in stable_pb.items(): center_x = 0.0 center_y = 0.0 coords = [] count_center = 0.0 for line in lines: (x, y) = line species_lines.append(line) ## print(x,y) # plt.plot(x,y,"k-",linewidth = 3) for coord in np.array(line).T: if not in_coord_list(coords, coord): coords.append(coord.tolist()) cx = coord[0] cy = coord[1] center_x += cx center_y += cy count_center += 1.0 if count_center == 0.0: count_center = 1.0 center_x /= count_center center_y /= count_center if ((center_x <= xlim[0]) | (center_x >= xlim[1]) | (center_y <= ylim[0]) | (center_y >= ylim[1])): continue # xy = (center_x, center_y) labels_name.append(print_name(element1, element2, mat_co_1, entry)) # labels_name.append(entry) labels_loc_x.append(center_x) labels_loc_y.append(center_y) return labels_name, labels_loc_x, labels_loc_y, species_lines, h_line, o_line, neutral_line, V0_line
def set_structure(self, structure, reset_camera=True, to_unit_cell=True): """ Add a structure to the visualizer. Args: structure: structure to visualize reset_camera: Set to True to reset the camera to a default determined based on the structure. to_unit_cell: Whether or not to fall back sites into the unit cell. """ self.ren.RemoveAllViewProps() has_lattice = hasattr(structure, "lattice") if has_lattice: s = Structure.from_sites(structure, to_unit_cell=to_unit_cell) s.make_supercell(self.supercell, to_unit_cell=to_unit_cell) else: s = structure inc_coords = [] for site in s: self.add_site(site) inc_coords.append(site.coords) count = 0 labels = ["a", "b", "c"] colors = [(1, 0, 0), (0, 1, 0), (0, 0, 1)] if has_lattice: matrix = s.lattice.matrix if self.show_unit_cell and has_lattice: # matrix = s.lattice.matrix self.add_text([0, 0, 0], "o") for vec in matrix: self.add_line((0, 0, 0), vec, colors[count]) self.add_text(vec, labels[count], colors[count]) count += 1 for (vec1, vec2) in itertools.permutations(matrix, 2): self.add_line(vec1, vec1 + vec2) for (vec1, vec2, vec3) in itertools.permutations(matrix, 3): self.add_line(vec1 + vec2, vec1 + vec2 + vec3) if self.show_bonds or self.show_polyhedron: elements = sorted(s.composition.elements, key=lambda a: a.X) anion = elements[-1] def contains_anion(site): for sp in site.species.keys(): if sp.symbol == anion.symbol: return True return False anion_radius = anion.average_ionic_radius for site in s: exclude = False max_radius = 0 color = np.array([0, 0, 0]) for sp, occu in site.species.items(): if sp.symbol in self.excluded_bonding_elements or sp == anion: exclude = True break max_radius = max(max_radius, sp.average_ionic_radius) color = color + occu * np.array( self.el_color_mapping.get(sp.symbol, [0, 0, 0])) if not exclude: max_radius = (1 + self.poly_radii_tol_factor) * ( max_radius + anion_radius) nn = structure.get_neighbors(site, float(max_radius)) nn_sites = [] for neighbor in nn: if contains_anion(neighbor): nn_sites.append(neighbor) if not in_coord_list(inc_coords, neighbor.coords): self.add_site(neighbor) if self.show_bonds: self.add_bonds(nn_sites, site) if self.show_polyhedron: color = [i / 255 for i in color] self.add_polyhedron(nn_sites, site, color) if self.show_help: self.helptxt_actor = vtk.vtkActor2D() self.helptxt_actor.VisibilityOn() self.helptxt_actor.SetMapper(self.helptxt_mapper) self.ren.AddActor(self.helptxt_actor) self.display_help() camera = self.ren.GetActiveCamera() if reset_camera: if has_lattice: # Adjust the camera for best viewing lengths = s.lattice.abc pos = (matrix[1] + matrix[2] ) * 0.5 + matrix[0] * max(lengths) / lengths[0] * 3.5 camera.SetPosition(pos) camera.SetViewUp(matrix[2]) camera.SetFocalPoint((matrix[0] + matrix[1] + matrix[2]) * 0.5) else: origin = s.center_of_mass max_site = max( s, key=lambda site: site.distance_from_point(origin)) camera.SetPosition(origin + 5 * (max_site.coords - origin)) camera.SetFocalPoint(s.center_of_mass) self.structure = structure self.title = s.composition.formula
def is_already_analyzed(miller_index): for op in symm_ops: if in_coord_list(unique_millers, op.operate(miller_index)): return True return False
def get_pourbaix_plot(self, limits=None, title="", label_domains=True): """ Plot Pourbaix diagram. Args: limits: 2D list containing limits of the Pourbaix diagram of the form [[xlo, xhi], [ylo, yhi]] Returns: plt: matplotlib plot object """ # plt = pretty_plot(24, 14.4) plt = pretty_plot(16) (stable, unstable) = self.pourbaix_plot_data(limits) 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) 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) for entry, lines in stable.items(): center_x = 0.0 center_y = 0.0 coords = [] count_center = 0.0 for line in lines: (x, y) = line plt.plot(x, y, "k-", linewidth=lw) for coord in np.array(line).T: if not in_coord_list(coords, coord): coords.append(coord.tolist()) cx = coord[0] cy = coord[1] center_x += cx center_y += cy count_center += 1.0 if count_center == 0.0: count_center = 1.0 center_x /= count_center center_y /= count_center if ((center_x <= xlim[0]) | (center_x >= xlim[1]) | (center_y <= ylim[0]) | (center_y >= ylim[1])): continue xy = (center_x, center_y) if label_domains: plt.annotate(self.print_name(entry), xy, fontsize=20, color="b") plt.xlabel("pH") plt.ylabel("E (V)") plt.title(title, fontsize=20, fontweight='bold') return plt
def get_distribution_corrected_center(self, lines, h2o_h_line=None, h2o_o_line=None, radius=None): """ Returns coordinates of distribution corrected center of a domain. Similar to get_center(), but considers the distance to the surronding lines that mostly affects the feeling of "center". This function will also try avoid overalapping the text babel with H2O stability line if H2O stability line is provided. Useful for labeling a Pourbaix plot. Args: lines: Lines corresponding to a domain limits: Limits of Pourbaix diagram h2o_h_line: Hydrogen line of H2O stability h2o_o_line: Oxygen line of H2O stablity radius: Half height of the text label. Returns: center_x, center_y: x,y coordinate of center of domain. If domain lies outside limits, center will lie on the boundary. """ coords = [] pts_x = [] pts_y = [] for line in lines: for coord in np.array(line).T: if not in_coord_list(coords, coord): coords.append(coord.tolist()) cx = coord[0] cy = coord[1] pts_x.append(cx) pts_y.append(cy) if len(pts_x) < 1: return 0.0, 0.0 cx_1 = (max(pts_x) + min(pts_x)) / 2.0 cy_1 = (max(pts_y) + min(pts_y)) / 2.0 mid_x_list = [] mid_y_list = [] # move the center to the center of surrounding lines for line in lines: (x1, y1), (x2, y2) = np.array(line).T if (x1 - cx_1) * (x2 - cx_1) <= 0.0: # horizontal line mid_y = ((y2 - y1) / (x2 - x1)) * (cx_1 - x1) + y1 assert (y2 - mid_y) * (y1 - mid_y) <= 0.0 mid_y_list.append(mid_y) if (y1 - cy_1) * (y2 - cy_1) <= 0.0: # vertical line mid_x = ((x2 - x1) / (y2 - y1)) * (cy_1 - y1) + x1 assert (x2 - mid_x) * (x1 - mid_x) <= 0.0 mid_x_list.append(mid_x) upper_y = sorted([y for y in mid_y_list if y >= cy_1] + [cy_1])[0] lower_y = sorted([y for y in mid_y_list if y < cy_1] + [cy_1])[-1] left_x = sorted([x for x in mid_x_list if x <= cx_1] + [cx_1])[-1] right_x = sorted([x for x in mid_x_list if x > cx_1] + [cx_1])[0] center_x = (left_x + right_x) / 2.0 center_y = (upper_y + lower_y) / 2.0 if h2o_h_line is not None: (h2o_h_x1, h2o_h_y1), (h2o_h_x2, h2o_h_y2) = h2o_h_line.T h_slope = (h2o_h_y2 - h2o_h_y1) / (h2o_h_x2 - h2o_h_x1) (h2o_o_x1, h2o_o_y1), (h2o_o_x2, h2o_o_y2) = h2o_o_line.T o_slope = (h2o_o_y2 - h2o_o_y1) / (h2o_o_x2 - h2o_o_x1) h_y = h_slope * (cx_1 - h2o_h_x1) + h2o_h_y1 o_y = o_slope * (cx_1 - h2o_o_x1) + h2o_o_y1 h2o_y = None if abs(center_y - h_y) < radius: h2o_y = h_y elif abs(center_y - o_y) < radius: h2o_y = o_y if h2o_y is not None: if (upper_y - lower_y) / 2.0 > radius * 2.0: # The space can hold the whole text (radius * 2.0) if h2o_y > center_y: center_y = h2o_y - radius else: center_y = h2o_y + radius return center_x, center_y
def set_structure(self, structure, reset_camera=True, to_unit_cell=True): """ Add a structure to the visualizer. Args: structure: structure to visualize reset_camera: Set to True to reset the camera to a default determined based on the structure. to_unit_cell: Whether or not to fall back sites into the unit cell. """ self.ren.RemoveAllViewProps() has_lattice = hasattr(structure, "lattice") if has_lattice: s = Structure.from_sites(structure, to_unit_cell=to_unit_cell) s.make_supercell(self.supercell, to_unit_cell=to_unit_cell) else: s = structure inc_coords = [] for site in s: self.add_site(site) inc_coords.append(site.coords) count = 0 labels = ["a", "b", "c"] colors = [(1, 0, 0), (0, 1, 0), (0, 0, 1)] if has_lattice: matrix = s.lattice.matrix if self.show_unit_cell and has_lattice: #matrix = s.lattice.matrix self.add_text([0, 0, 0], "o") for vec in matrix: self.add_line((0, 0, 0), vec, colors[count]) self.add_text(vec, labels[count], colors[count]) count += 1 for (vec1, vec2) in itertools.permutations(matrix, 2): self.add_line(vec1, vec1 + vec2) for (vec1, vec2, vec3) in itertools.permutations(matrix, 3): self.add_line(vec1 + vec2, vec1 + vec2 + vec3) if self.show_bonds or self.show_polyhedron: elements = sorted(s.composition.elements, key=lambda a: a.X) anion = elements[-1] def contains_anion(site): for sp in site.species.keys(): if sp.symbol == anion.symbol: return True return False anion_radius = anion.average_ionic_radius for site in s: exclude = False max_radius = 0 color = np.array([0, 0, 0]) for sp, occu in site.species.items(): if sp.symbol in self.excluded_bonding_elements \ or sp == anion: exclude = True break max_radius = max(max_radius, sp.average_ionic_radius) color = color + \ occu * np.array(self.el_color_mapping.get(sp.symbol, [0, 0, 0])) if not exclude: max_radius = (1 + self.poly_radii_tol_factor) * \ (max_radius + anion_radius) nn = structure.get_neighbors(site, float(max_radius)) nn_sites = [] for nnsite, dist in nn: if contains_anion(nnsite): nn_sites.append(nnsite) if not in_coord_list(inc_coords, nnsite.coords): self.add_site(nnsite) if self.show_bonds: self.add_bonds(nn_sites, site) if self.show_polyhedron: color = [i / 255 for i in color] self.add_polyhedron(nn_sites, site, color) if self.show_help: self.helptxt_actor = vtk.vtkActor2D() self.helptxt_actor.VisibilityOn() self.helptxt_actor.SetMapper(self.helptxt_mapper) self.ren.AddActor(self.helptxt_actor) self.display_help() camera = self.ren.GetActiveCamera() if reset_camera: if has_lattice: #Adjust the camera for best viewing lengths = s.lattice.abc pos = (matrix[1] + matrix[2]) * 0.5 + \ matrix[0] * max(lengths) / lengths[0] * 3.5 camera.SetPosition(pos) camera.SetViewUp(matrix[2]) camera.SetFocalPoint((matrix[0] + matrix[1] + matrix[2]) * 0.5) else: origin = s.center_of_mass max_site = max( s, key=lambda site: site.distance_from_point(origin)) camera.SetPosition(origin + 5 * (max_site.coords - origin)) camera.SetFocalPoint(s.center_of_mass) self.structure = structure self.title = s.composition.formula
def get_pourbaix_plot(self, limits=[[-2, 14], [-3, 3]], title="", label_domains=True): """ Plot Pourbaix diagram. Args: limits: 2D list containing limits of the Pourbaix diagram of the form [[xlo, xhi], [ylo, yhi]] Returns: plt: matplotlib plot object """ # plt = pretty_plot(24, 14.4) plt = pretty_plot(16) (stable, unstable) = self.pourbaix_plot_data(limits) 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) 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) for entry, lines in stable.items(): center_x = 0.0 center_y = 0.0 coords = [] count_center = 0.0 for line in lines: (x, y) = line plt.plot(x, y, "k-", linewidth=lw) for coord in np.array(line).T: if not in_coord_list(coords, coord): coords.append(coord.tolist()) cx = coord[0] cy = coord[1] center_x += cx center_y += cy count_center += 1.0 if count_center == 0.0: count_center = 1.0 center_x /= count_center center_y /= count_center if ((center_x <= xlim[0]) | (center_x >= xlim[1]) | (center_y <= ylim[0]) | (center_y >= ylim[1])): continue xy = (center_x, center_y) if label_domains: plt.annotate(self.print_name(entry), xy, ha='center', va='center', fontsize=20, color="b") plt.xlabel("pH") plt.ylabel("E (V)") plt.title(title, fontsize=20, fontweight='bold') return plt
def get_distribution_corrected_center(self, lines, h2o_h_line=None, h2o_o_line=None, radius=None): """ Returns coordinates of distribution corrected center of a domain. Similar to get_center(), but considers the distance to the surronding lines that mostly affects the feeling of "center". This function will also try avoid overalapping the text babel with H2O stability line if H2O stability line is provided. Useful for labeling a Pourbaix plot. Args: lines: Lines corresponding to a domain limits: Limits of Pourbaix diagram h2o_h_line: Hydrogen line of H2O stability h2o_o_line: Oxygen line of H2O stablity radius: Half height of the text label. Returns: center_x, center_y: x,y coordinate of center of domain. If domain lies outside limits, center will lie on the boundary. """ coords = [] pts_x = [] pts_y = [] for line in lines: for coord in np.array(line).T: if not in_coord_list(coords, coord): coords.append(coord.tolist()) cx = coord[0] cy = coord[1] pts_x.append(cx) pts_y.append(cy) if len(pts_x) < 1: return 0.0, 0.0 cx_1 = (max(pts_x) + min(pts_x)) / 2.0 cy_1 = (max(pts_y) + min(pts_y)) / 2.0 mid_x_list = [] mid_y_list = [] # move the center to the center of surrounding lines for line in lines: (x1, y1), (x2, y2) = np.array(line).T if (x1 - cx_1) * (x2 - cx_1) <= 0.0: # horizontal line mid_y = ((y2 - y1) / (x2 - x1)) * (cx_1 - x1) + y1 assert (y2 - mid_y) * (y1 - mid_y) <= 0.0 mid_y_list.append(mid_y) if (y1 - cy_1) * (y2 - cy_1) <= 0.0: # vertical line mid_x = ((x2 - x1) / (y2 - y1)) * (cy_1 - y1) + x1 assert (x2 - mid_x) * (x1 - mid_x) <= 0.0 mid_x_list.append(mid_x) upper_y = sorted([y for y in mid_y_list if y >= cy_1] + [cy_1])[0] lower_y = sorted([y for y in mid_y_list if y < cy_1] + [cy_1])[-1] left_x = sorted([x for x in mid_x_list if x <= cx_1] + [cx_1])[-1] right_x = sorted([x for x in mid_x_list if x > cx_1] + [cx_1])[0] center_x = (left_x + right_x) / 2.0 center_y = (upper_y + lower_y) / 2.0 if h2o_h_line is not None: (h2o_h_x1, h2o_h_y1), (h2o_h_x2, h2o_h_y2) = h2o_h_line.T h_slope = (h2o_h_y2 - h2o_h_y1) / (h2o_h_x2 - h2o_h_x1) (h2o_o_x1, h2o_o_y1), (h2o_o_x2, h2o_o_y2) = h2o_o_line.T o_slope = (h2o_o_y2 - h2o_o_y1) / (h2o_o_x2 - h2o_o_x1) h_y = h_slope * (cx_1 - h2o_h_x1) + h2o_h_y1 o_y = o_slope * (cx_1 - h2o_o_x1) + h2o_o_y1 h2o_y = None if abs(center_y - h_y) < radius: h2o_y = h_y elif abs(center_y - o_y) < radius: h2o_y = o_y if h2o_y is not None: if (upper_y - lower_y) / 2.0 > radius * 2.0: # The space can hold the whole text (radius * 2.0) if h2o_y > center_y: center_y = h2o_y - radius else: center_y = h2o_y + radius return center_x, center_y