def setUp(self): self.maxDiff = None # trivial example, simple square lattice for testing structure = Structure(Lattice.tetragonal(5.0, 50.0), ["H"], [[0, 0, 0]]) self.square_sg = StructureGraph.with_empty_graph(structure, edge_weight_name="", edge_weight_units="") self.square_sg.add_edge(0, 0, from_jimage=(0, 0, 0), to_jimage=(1, 0, 0)) self.square_sg.add_edge(0, 0, from_jimage=(0, 0, 0), to_jimage=(-1, 0, 0)) self.square_sg.add_edge(0, 0, from_jimage=(0, 0, 0), to_jimage=(0, 1, 0)) self.square_sg.add_edge(0, 0, from_jimage=(0, 0, 0), to_jimage=(0, -1, 0)) # TODO: decorating still fails because the structure graph gives a CN of 8 for this square lattice # self.square_sg.decorate_structure_with_ce_info() # body-centered square lattice for testing structure = Structure(Lattice.tetragonal(5.0, 50.0), ["H", "He"], [[0, 0, 0], [0.5, 0.5, 0.5]]) self.bc_square_sg = StructureGraph.with_empty_graph(structure, edge_weight_name="", edge_weight_units="") self.bc_square_sg.add_edge(0, 0, from_jimage=(0, 0, 0), to_jimage=(1, 0, 0)) self.bc_square_sg.add_edge(0, 0, from_jimage=(0, 0, 0), to_jimage=(-1, 0, 0)) self.bc_square_sg.add_edge(0, 0, from_jimage=(0, 0, 0), to_jimage=(0, 1, 0)) self.bc_square_sg.add_edge(0, 0, from_jimage=(0, 0, 0), to_jimage=(0, -1, 0)) self.bc_square_sg.add_edge(0, 1, from_jimage=(0, 0, 0), to_jimage=(0, 0, 0)) self.bc_square_sg.add_edge(0, 1, from_jimage=(0, 0, 0), to_jimage=(-1, 0, 0)) self.bc_square_sg.add_edge(0, 1, from_jimage=(0, 0, 0), to_jimage=(-1, -1, 0)) self.bc_square_sg.add_edge(0, 1, from_jimage=(0, 0, 0), to_jimage=(0, -1, 0)) # body-centered square lattice for testing # directions reversed, should be equivalent to bc_square structure = Structure(Lattice.tetragonal(5.0, 50.0), ["H", "He"], [[0, 0, 0], [0.5, 0.5, 0.5]]) self.bc_square_sg_r = StructureGraph.with_empty_graph(structure, edge_weight_name="", edge_weight_units="") self.bc_square_sg_r.add_edge(0, 0, from_jimage=(0, 0, 0), to_jimage=(1, 0, 0)) self.bc_square_sg_r.add_edge(0, 0, from_jimage=(0, 0, 0), to_jimage=(-1, 0, 0)) self.bc_square_sg_r.add_edge(0, 0, from_jimage=(0, 0, 0), to_jimage=(0, 1, 0)) self.bc_square_sg_r.add_edge(0, 0, from_jimage=(0, 0, 0), to_jimage=(0, -1, 0)) self.bc_square_sg_r.add_edge(0, 1, from_jimage=(0, 0, 0), to_jimage=(0, 0, 0)) self.bc_square_sg_r.add_edge(1, 0, from_jimage=(-1, 0, 0), to_jimage=(0, 0, 0)) self.bc_square_sg_r.add_edge(1, 0, from_jimage=(-1, -1, 0), to_jimage=(0, 0, 0)) self.bc_square_sg_r.add_edge(1, 0, from_jimage=(0, -1, 0), to_jimage=(0, 0, 0)) # MoS2 example, structure graph obtained from critic2 # (not ground state, from mp-1023924, single layer) stdout_file = os.path.join(PymatgenTest.TEST_FILES_DIR, "critic2/MoS2_critic2_stdout.txt") with open(stdout_file) as f: reference_stdout = f.read() self.structure = Structure.from_file(os.path.join(PymatgenTest.TEST_FILES_DIR, "critic2/MoS2.cif")) c2o = Critic2Analysis(self.structure, reference_stdout) self.mos2_sg = c2o.structure_graph(include_critical_points=False) latt = Lattice.cubic(4.17) species = ["Ni", "O"] coords = [[0, 0, 0], [0.5, 0.5, 0.5]] self.NiO = Structure.from_spacegroup(225, latt, species, coords).get_primitive_structure() # BCC example. self.bcc = Structure(Lattice.cubic(5.0), ["He", "He"], [[0, 0, 0], [0.5, 0.5, 0.5]]) warnings.simplefilter("ignore")
def _preprocess_input_to_graph( input: Union[Structure, StructureGraph, Molecule, MoleculeGraph], bonding_strategy: str = "CrystalNN", bonding_strategy_kwargs: Optional[Dict] = None, ) -> Union[StructureGraph, MoleculeGraph]: if isinstance(input, Structure): # ensure fractional co-ordinates are normalized to be in [0,1) # (this is actually not guaranteed by Structure) input = input.as_dict(verbosity=0) for site in input["sites"]: site["abc"] = np.mod(site["abc"], 1) input = Structure.from_dict(input) if not input.is_ordered: # calculating bonds in disordered structures is currently very flaky bonding_strategy = "CutOffDictNN" # we assume most uses of this class will give a structure as an input argument, # meaning we have to calculate the graph for bonding information, however if # the graph is already known and supplied, we will use that if isinstance(input, StructureGraph) or isinstance( input, MoleculeGraph): graph = input else: if (bonding_strategy not in StructureMoleculeComponent. available_bonding_strategies.keys()): raise ValueError( "Bonding strategy not supported. Please supply a name " "of a NearNeighbor subclass, choose from: {}".format( ", ".join(StructureMoleculeComponent. available_bonding_strategies.keys()))) else: bonding_strategy_kwargs = bonding_strategy_kwargs or {} if bonding_strategy == "CutOffDictNN": if "cut_off_dict" in bonding_strategy_kwargs: # TODO: remove this hack by making args properly JSON serializable bonding_strategy_kwargs["cut_off_dict"] = { (x[0], x[1]): x[2] for x in bonding_strategy_kwargs["cut_off_dict"] } bonding_strategy = StructureMoleculeComponent.available_bonding_strategies[ bonding_strategy](**bonding_strategy_kwargs) try: if isinstance(input, Structure): graph = StructureGraph.with_local_env_strategy( input, bonding_strategy) else: graph = MoleculeGraph.with_local_env_strategy( input, bonding_strategy) except: # for some reason computing bonds failed, so let's not have any bonds(!) if isinstance(input, Structure): graph = StructureGraph.with_empty_graph(input) else: graph = MoleculeGraph.with_empty_graph(input) return graph
def structure_graph(self, edge_weight="bond_length", edge_weight_units="Å"): """ A StructureGraph object describing bonding information in the crystal. Lazily constructed. :param edge_weight: a value to store on the Graph edges, by default this is "bond_length" but other supported values are any of the attributes of CriticalPoint :return: """ sg = StructureGraph.with_empty_graph(self.structure, name="bonds", edge_weight_name=edge_weight, edge_weight_units=edge_weight_units) edges = self.edges.copy() idx_to_delete = [] # check for duplicate bonds for idx, edge in edges.items(): unique_idx = self.nodes[idx]['unique_idx'] # only check edges representing bonds, not rings if self.critical_points[unique_idx].type == CriticalPointType.bond: if idx not in idx_to_delete: for idx2, edge2 in edges.items(): if idx != idx2 and edge == edge2: idx_to_delete.append(idx2) warnings.warn("Duplicate edge detected, try re-running " "critic2 with custom parameters to fix this. " "Mostly harmless unless user is also " "interested in rings/cages.") logger.debug("Duplicate edge between points {} (unique point {})" "and {} ({}).".format(idx, self.nodes[idx]['unique_idx'], idx2, self.nodes[idx2]['unique_idx'])) # and remove any duplicate bonds present for idx in idx_to_delete: del edges[idx] for idx, edge in edges.items(): unique_idx = self.nodes[idx]['unique_idx'] # only add edges representing bonds, not rings if self.critical_points[unique_idx].type == CriticalPointType.bond: from_idx = edge['from_idx'] to_idx = edge['to_idx'] from_lvec = edge['from_lvec'] to_lvec = edge['to_lvec'] if edge_weight == "bond_length": weight = self.structure.get_distance(from_idx, to_idx) else: weight = getattr(self.critical_points[unique_idx], edge_weight, None) sg.add_edge(from_idx, to_idx, from_jimage=from_lvec, to_jimage=to_lvec, weight=weight) return sg
def get_interaction_graph(self, filename=None): """ Get a StructureGraph with edges and weights that correspond to exchange interactions and J_ij values, respectively. Args: filename (str): if not None, save interaction graph to filename. Returns: igraph (StructureGraph): Exchange interaction graph. """ structure = self.ordered_structures[0] sgraph = self.sgraphs[0] unique_site_ids = self.unique_site_ids dists = self.dists tol = self.tol igraph = StructureGraph.with_empty_graph( structure, edge_weight_name="exchange_constant", edge_weight_units="meV") if "<J>" in self.ex_params: # Only <J> is available warning_msg = """ Only <J> is available. The interaction graph will not tell you much. """ warnings.warn(warning_msg) # J_ij exchange interaction matrix for i, node in enumerate(sgraph.graph.nodes): connections = sgraph.get_connected_sites(i) for c in connections: jimage = c[1] # relative integer coordinates of atom j dx = jimage[0] dy = jimage[1] dz = jimage[2] j = c[2] # index of neighbor dist = c[-1] # i <-> j distance j_exc = self._get_j_exc(i, j, dist) igraph.add_edge(i, j, to_jimage=jimage, weight=j_exc, warn_duplicates=False) # Save to a json file if desired if filename: if filename.endswith(".json"): dumpfn(igraph, filename) else: filename += ".json" dumpfn(igraph, filename) return igraph
def make_scene_dicts(parchg_list, defect_pos, level=None): band_indices = [c.split(".")[-2] for c in parchg_list] # parchgs = [Chgcar.from_file(c) for c in parchg_list] parchgs = [] for c in parchg_list: try: parchgs.append(Chgcar.from_file(c)) except ValueError: continue if len(parchgs) == 0: return None structure: Structure = parchgs[0].structure if len(parchg_list) > 1: for parchg in parchgs[1:]: assert structure == parchg.structure lattice_matrix = parchgs[0].structure.lattice.matrix # centering to [0.5, 0.5, 0.5] shift_vector = np.array([0.5 - pos for pos in defect_pos]) shape = parchgs[0].data["total"].shape shift_numbers = np.array(np.rint(shape * shift_vector), dtype=np.int) actual_shift_vector = shift_numbers / shape step_size = int(min(parchgs[0].dim) / 30) results = {} for name, parchg in zip(band_indices, parchgs): for spin in [Spin.up, Spin.down]: spin_str = "up" if spin is Spin.up else "down" key = name + "_" + spin_str data = parchg.spin_data[spin] x_shifted = np.roll(data, shift_numbers[0], axis=0) xy_shifted = np.roll(x_shifted, shift_numbers[1], axis=1) xyz_shifted = np.roll(xy_shifted, shift_numbers[2], axis=2) try: vertices, faces = get_vertices_and_faces(xyz_shifted, lattice_matrix, step_size=step_size, level=level) results[key] = {"vertices": vertices, "faces": faces} except RuntimeError: continue structure.translate_sites(indices=list(range(len(structure))), vector=actual_shift_vector) graph = StructureGraph.with_empty_graph(structure=structure) return SceneDicts(results, structure_graph=graph)
def _preprocess_input_to_graph( input: Union[Structure, StructureGraph], bonding_strategy: str = DEFAULTS["bonding_strategy"], bonding_strategy_kwargs: Optional[Dict] = None, ) -> Union[StructureGraph, MoleculeGraph]: # ensure fractional co-ordinates are normalized to be in [0,1) # (this is actually not guaranteed by Structure) try: input = input.as_dict(verbosity=0) except TypeError: # TODO: remove this, necessary for Slab(?), some structure subclasses don't have verbosity input = input.as_dict() for site in input["sites"]: site["abc"] = np.mod(site["abc"], 1) input = Structure.from_dict(input) if not input.is_ordered: # calculating bonds in disordered structures is currently very flaky bonding_strategy = "CutOffDictNN" if (bonding_strategy not in StructureComponent.available_bonding_strategies.keys()): raise ValueError( "Bonding strategy not supported. Please supply a name " "of a NearNeighbor subclass, choose from: {}".format(", ".join( StructureComponent.available_bonding_strategies.keys()))) else: bonding_strategy_kwargs = bonding_strategy_kwargs or {} if bonding_strategy == "CutOffDictNN": if "cut_off_dict" in bonding_strategy_kwargs: # TODO: remove this hack by making args properly JSON serializable bonding_strategy_kwargs["cut_off_dict"] = { (x[0], x[1]): x[2] for x in bonding_strategy_kwargs["cut_off_dict"] } bonding_strategy = StructureComponent.available_bonding_strategies[ bonding_strategy](**bonding_strategy_kwargs) try: with warnings.catch_warnings(): warnings.simplefilter("ignore") graph = StructureGraph.with_local_env_strategy( input, bonding_strategy) except: # for some reason computing bonds failed, so let's not have any bonds(!) graph = StructureGraph.with_empty_graph(input) return graph
def __init__(self, supercell_info: SupercellInfo, *args, **kwargs): s = Structure.from_dict(supercell_info.structure.as_dict()) coords = [[f % 1 for f in x.frac_coords] for x in s] structure = Structure(lattice=s.lattice, species=s.species, coords=coords) self.graph = StructureGraph.with_empty_graph(structure=structure) self.transformation_matrix = supercell_info.transformation_matrix self.interstitials = supercell_info.interstitials super().__init__(*args, **kwargs) self.title = "supercell size: " + supercell_info._transform_matrix_str scene, legend = self.get_scene_and_legend() self.create_store("legend_data", initial_data=legend) self.create_store("graph", initial_data=self.graph) # this is used by a Simple3DScene component, not a dcc.Store self._initial_data["scene"] = scene
def structure_graph(self, edge_weight="bond_length", edge_weight_units="Å"): """ A StructureGraph object describing bonding information in the crystal. Lazily constructed. :param edge_weight: a value to store on the Graph edges, by default this is "bond_length" but other supported values are any of the attributes of CriticalPoint :return: """ sg = StructureGraph.with_empty_graph( self.structure, name="bonds", edge_weight_name=edge_weight, edge_weight_units=edge_weight_units) edges = self.edges.copy() idx_to_delete = [] # check for duplicate bonds for idx, edge in edges.items(): unique_idx = self.nodes[idx]['unique_idx'] # only check edges representing bonds, not rings if self.critical_points[unique_idx].type == CriticalPointType.bond: if idx not in idx_to_delete: for idx2, edge2 in edges.items(): if idx != idx2 and edge == edge2: idx_to_delete.append(idx2) warnings.warn( "Duplicate edge detected, try re-running " "critic2 with custom parameters to fix this. " "Mostly harmless unless user is also " "interested in rings/cages.") logger.debug( "Duplicate edge between points {} (unique point {})" "and {} ({}).".format( idx, self.nodes[idx]['unique_idx'], idx2, self.nodes[idx2]['unique_idx'])) # and remove any duplicate bonds present for idx in idx_to_delete: del edges[idx] for idx, edge in edges.items(): unique_idx = self.nodes[idx]['unique_idx'] # only add edges representing bonds, not rings if self.critical_points[unique_idx].type == CriticalPointType.bond: from_idx = edge['from_idx'] to_idx = edge['to_idx'] from_lvec = edge['from_lvec'] to_lvec = edge['to_lvec'] relative_lvec = np.subtract(to_lvec, from_lvec) if edge_weight == "bond_length": weight = self.structure.get_distance(from_idx, to_idx, jimage=relative_lvec) else: weight = getattr(self.critical_points[unique_idx], edge_weight, None) sg.add_edge(from_idx, to_idx, from_jimage=from_lvec, to_jimage=to_lvec, weight=weight) return sg
def structure_graph(self, include_critical_points=("bond", "ring", "cage")): """ A StructureGraph object describing bonding information in the crystal. Args: include_critical_points: add DummySpecie for the critical points themselves, a list of "nucleus", "bond", "ring", "cage", set to None to disable Returns: a StructureGraph """ structure = self.structure.copy() point_idx_to_struct_idx = {} if include_critical_points: # atoms themselves don't have field information # so set to 0 for prop in ("ellipticity", "laplacian", "field"): structure.add_site_property(prop, [0] * len(structure)) for idx, node in self.nodes.items(): cp = self.critical_points[node["unique_idx"]] if cp.type.value in include_critical_points: specie = DummySpecie("X{}cp".format(cp.type.value[0]), oxidation_state=None) structure.append( specie, node["frac_coords"], properties={ "ellipticity": cp.ellipticity, "laplacian": cp.laplacian, "field": cp.field, }, ) point_idx_to_struct_idx[idx] = len(structure) - 1 edge_weight = "bond_length" edge_weight_units = "Å" sg = StructureGraph.with_empty_graph( structure, name="bonds", edge_weight_name=edge_weight, edge_weight_units=edge_weight_units, ) edges = self.edges.copy() idx_to_delete = [] # check for duplicate bonds for idx, edge in edges.items(): unique_idx = self.nodes[idx]["unique_idx"] # only check edges representing bonds, not rings if self.critical_points[unique_idx].type == CriticalPointType.bond: if idx not in idx_to_delete: for idx2, edge2 in edges.items(): if idx != idx2 and edge == edge2: idx_to_delete.append(idx2) warnings.warn( "Duplicate edge detected, try re-running " "critic2 with custom parameters to fix this. " "Mostly harmless unless user is also " "interested in rings/cages.") logger.debug( "Duplicate edge between points {} (unique point {})" "and {} ({}).".format( idx, self.nodes[idx]["unique_idx"], idx2, self.nodes[idx2]["unique_idx"], )) # and remove any duplicate bonds present for idx in idx_to_delete: del edges[idx] for idx, edge in edges.items(): unique_idx = self.nodes[idx]["unique_idx"] # only add edges representing bonds, not rings if self.critical_points[unique_idx].type == CriticalPointType.bond: from_idx = edge["from_idx"] to_idx = edge["to_idx"] # have to also check bond is between nuclei if non-nuclear # attractors not in structure skip_bond = False if include_critical_points and "nnattr" not in include_critical_points: from_type = self.critical_points[self.nodes[from_idx] ["unique_idx"]].type to_type = self.critical_points[self.nodes[from_idx] ["unique_idx"]].type skip_bond = (from_type != CriticalPointType.nucleus) or ( to_type != CriticalPointType.nucleus) if not skip_bond: from_lvec = edge["from_lvec"] to_lvec = edge["to_lvec"] relative_lvec = np.subtract(to_lvec, from_lvec) # for edge case of including nnattrs in bonding graph when other critical # points also included, indices may get mixed struct_from_idx = point_idx_to_struct_idx.get( from_idx, from_idx) struct_to_idx = point_idx_to_struct_idx.get(to_idx, to_idx) weight = self.structure.get_distance(struct_from_idx, struct_to_idx, jimage=relative_lvec) crit_point = self.critical_points[unique_idx] edge_properties = { "field": crit_point.field, "laplacian": crit_point.laplacian, "ellipticity": crit_point.ellipticity, "frac_coords": self.nodes[idx]["frac_coords"], } sg.add_edge( struct_from_idx, struct_to_idx, from_jimage=from_lvec, to_jimage=to_lvec, weight=weight, edge_properties=edge_properties, ) return sg
def test_auto_image_detection(self): sg = StructureGraph.with_empty_graph(self.structure) sg.add_edge(0, 0) self.assertEqual(len(list(sg.graph.edges(data=True))), 3)
def structure_graph(self, edge_weight=None, edge_weight_units=None, include_critical_points=("bond", "ring", "cage")): """ A StructureGraph object describing bonding information in the crystal. Lazily constructed. Args: edge_weight: a value to store on the Graph edges, by default this is "bond_length" but other supported values are any of the attributes of CriticalPoint edge_weight_units: optional metadata for book-keeping (e.g. Å for "bond_length" edge weight) include_critical_points: add DummySpecie for the critical points themselves, a list of "nucleus", "bond", "ring", "cage", set to None to disable Returns: a StructureGraph """ structure = self.structure.copy() if include_critical_points: # atoms themselves don't have field information # so set to 0 for prop in ("ellipticity", "laplacian", "field"): structure.add_site_property(prop, [0] * len(structure)) for idx, node in self.nodes.items(): cp = self.critical_points[node["unique_idx"]] if cp.type.value in include_critical_points: specie = DummySpecie("{}cp".format(cp.type.value[0]), oxidation_state=None) structure.append(specie, node["frac_coords"], properties={ "ellipticity": cp.ellipticity, "laplacian": cp.laplacian, "field": cp.field }) sg = StructureGraph.with_empty_graph( structure, name="bonds", edge_weight_name=edge_weight, edge_weight_units=edge_weight_units) edges = self.edges.copy() idx_to_delete = [] # check for duplicate bonds for idx, edge in edges.items(): unique_idx = self.nodes[idx]['unique_idx'] # only check edges representing bonds, not rings if self.critical_points[unique_idx].type == CriticalPointType.bond: if idx not in idx_to_delete: for idx2, edge2 in edges.items(): if idx != idx2 and edge == edge2: idx_to_delete.append(idx2) warnings.warn( "Duplicate edge detected, try re-running " "critic2 with custom parameters to fix this. " "Mostly harmless unless user is also " "interested in rings/cages.") logger.debug( "Duplicate edge between points {} (unique point {})" "and {} ({}).".format( idx, self.nodes[idx]['unique_idx'], idx2, self.nodes[idx2]['unique_idx'])) # and remove any duplicate bonds present for idx in idx_to_delete: del edges[idx] for idx, edge in edges.items(): unique_idx = self.nodes[idx]['unique_idx'] # only add edges representing bonds, not rings if self.critical_points[unique_idx].type == CriticalPointType.bond: from_idx = edge['from_idx'] to_idx = edge['to_idx'] from_lvec = edge['from_lvec'] to_lvec = edge['to_lvec'] relative_lvec = np.subtract(to_lvec, from_lvec) if edge_weight == "bond_length": weight = self.structure.get_distance(from_idx, to_idx, jimage=relative_lvec) elif edge_weight: weight = getattr(self.critical_points[unique_idx], edge_weight, None) else: weight = None sg.add_edge(from_idx, to_idx, from_jimage=from_lvec, to_jimage=to_lvec, weight=weight) return sg