def calc(self, item): struct_or_mol = MontyDecoder().process_decoded( item[self.projected_object_name]) # TODO: will combine these two functions into something more intuitive graph = StructureMoleculeComponent._preprocess_input_to_graph( struct_or_mol, bonding_strategy=self.settings["bonding_strategy"], bonding_strategy_kwargs=self.settings["bonding_strategy_kwargs"], ) scene, legend = StructureMoleculeComponent.get_scene_and_legend( graph, color_scheme=self.settings["color_scheme"], color_scale=self.settings["color_scale"], radius_strategy=self.settings["radius_strategy"], draw_image_atoms=self.settings["draw_image_atoms"], bonded_sites_outside_unit_cell=self. settings["bonded_sites_outside_unit_cell"], hide_incomplete_bonds=self.settings["hide_incomplete_bonds"], ) return { "scene": scene.to_json(), "legend": legend, "settings": self.settings, "source": item[self.projected_object_name], }
def get_graph_data( graph: Union[StructureGraph, MoleculeGraph], color_scheme="Jmol", color_scale=None, ): nodes = [] edges = [] struct_or_mol = StructureMoleculeComponent._get_struct_or_mol(graph) site_prop_types = StructureMoleculeComponent._analyze_site_props( struct_or_mol) colors, _ = StructureMoleculeComponent._get_display_colors_and_legend_for_sites( struct_or_mol, site_prop_types, color_scheme=color_scheme, color_scale=color_scale, ) for idx, node in enumerate(graph.graph.nodes()): nodes.append({ "id": node, "title": f"{struct_or_mol[node].species_string} site " f"({graph.get_coordination_of_site(idx)} neighbors)", "color": colors[node][0], }) for u, v, d in graph.graph.edges(data=True): edge = {"from": u, "to": v, "arrows": ""} to_jimage = d.get("to_jimage", (0, 0, 0)) # TODO: check these edge weights if isinstance(struct_or_mol, Structure): dist = struct_or_mol.get_distance(u, v, jimage=to_jimage) else: dist = struct_or_mol.get_distance(u, v) edge["length"] = 50 * dist label = f"{dist:.2f} Å distance to site" if to_jimage != (0, 0, 0): edge["arrows"] = "to" label += f" at image vector {to_jimage}" if label: edge["title"] = label #if 'weight' in d: # label += f" {d['weight']}" edges.append(edge) return {"nodes": nodes, "edges": edges}
def view(molecule_or_structure, **kwargs): """View a pymatgen Molecule or Structure object interactively in a Jupyter notebook. Args: molecule_or_structure: Molecule or structure to display draw_image_atoms (bool): Show periodic copies of atoms """ with warnings.catch_warnings(): warnings.simplefilter("ignore") # Since the jupyter viewer is meant for quick peaks at the structure the default behaviour should be different # ex. draw_image_atoms should be set to false: if "draw_image_atoms" not in kwargs: kwargs["draw_image_atoms"] = False if "bonded_sites_outside_unit_cell" not in kwargs: kwargs["bonded_sites_outside_unit_cell"] = False if "hide_incomplete_edges" not in kwargs: kwargs["hide_incomplete_edges"] = True obj_or_scene = molecule_or_structure if isinstance(obj_or_scene, CrystalToolkitScene): scene = obj_or_scene elif hasattr(obj_or_scene, "get_scene"): scene = obj_or_scene.get_scene(**kwargs) # TODO: next two elif statements are only here until Molecule and Structure have get_scene() elif isinstance(obj_or_scene, Structure): # TODO Temporary place holder for render structure until structure.get_scene() is implemented struct_or_mol = obj_or_scene.copy() smc = StructureMoleculeComponent( struct_or_mol, static=True, hide_incomplete_bonds=kwargs['hide_incomplete_edges'], draw_image_atoms=kwargs['draw_image_atoms'], bonded_sites_outside_unit_cell=kwargs[ 'bonded_sites_outside_unit_cell'], ) origin = np.sum(obj_or_scene.lattice.matrix, axis=0) / 2. scene = smc.initial_graph.get_scene(origin=origin, **kwargs) elif isinstance(obj_or_scene, Molecule): # TODO Temporary place holder for render molecules kwargs.pop('draw_image_atoms') kwargs.pop('hide_incomplete_edges') kwargs.pop('bonded_sites_outside_unit_cell') origin = obj_or_scene.center_of_mass struct_or_mol = obj_or_scene.copy() smc = StructureMoleculeComponent(struct_or_mol, static=True, **kwargs) scene = smc.initial_graph.get_scene(origin=origin, **kwargs) else: raise ValueError( "Only Scene objects or objects with get_scene() methods " "can be displayed.") display_scene(scene)
def display_struct(structure): """ :param structure: input structure """ smc = StructureMoleculeComponent(structure, bonded_sites_outside_unit_cell=False, hide_incomplete_bonds=True) display_StructureMoleculeComponent(smc)
def get_scene_from_structure(self, bonding_strategy="CrystalNN", bonding_strategy_kwargs=None, **kwargs): sgraph = SMC._preprocess_input_to_graph(self, bonding_strategy=bonding_strategy, bonding_strategy_kwargs=bonding_strategy_kwargs, ) return sgraph.get_scene(origin=None, **kwargs)
def get_graph_data(graph, display_options): color_scheme = display_options.get("color_scheme", "Jmol") nodes = [] edges = [] struct_or_mol = StructureMoleculeComponent._get_struct_or_mol(graph) legend = Legend(struct_or_mol, color_scheme=color_scheme) for idx, node in enumerate(graph.graph.nodes()): # TODO: fix for disordered node_color = legend.get_color( struct_or_mol[node].species.elements[0], site=struct_or_mol[node]) nodes.append({ "id": node, "title": f"{struct_or_mol[node].species_string} site " f"({graph.get_coordination_of_site(idx)} neighbors)", "color": node_color, }) for u, v, d in graph.graph.edges(data=True): edge = {"from": u, "to": v, "arrows": ""} to_jimage = d.get("to_jimage", (0, 0, 0)) # TODO: check these edge weights if isinstance(struct_or_mol, Structure): dist = struct_or_mol.get_distance(u, v, jimage=to_jimage) else: dist = struct_or_mol.get_distance(u, v) edge["length"] = 50 * dist if to_jimage != (0, 0, 0): edge["arrows"] = "to" label = f"{dist:.2f} Å to site at image vector {to_jimage}" else: label = f"{dist:.2f} Å between sites" if label: edge["title"] = label # if 'weight' in d: # label += f" {d['weight']}" edges.append(edge) return {"nodes": nodes, "edges": edges}
def view(struct_or_mol, **kwargs): """ View a Structure or Molecule inside a Jupyter notebook. :param struct_or_mol: Structure or Molecule object :param kwargs: kwargs to pass to StructureMoleculeComponent :return: """ if "crystal_toolkit_app" not in globals(): _init_viewer() with warnings.catch_warnings(): warnings.simplefilter("ignore") component = StructureMoleculeComponent(struct_or_mol, **kwargs) crystal_toolkit_app.title = struct_or_mol.composition.reduced_formula crystal_toolkit_app.layout = html.Div( [component.layout(), component.screenshot_layout()] ) crystal_toolkit_viewer.show(crystal_toolkit_app)
def update_contents(self, new_store_contents): struct = self.from_data(new_store_contents) msa = CollinearMagneticStructureAnalyzer(struct, round_magmoms=1) if not msa.is_magnetic: # TODO: detect magnetic elements (?) return html.Div( "This structure is not magnetic or does not have " "magnetic information associated with it." ) mag_species_and_magmoms = msa.magnetic_species_and_magmoms for k, v in mag_species_and_magmoms.items(): if not isinstance(v, list): mag_species_and_magmoms[k] = [v] magnetic_atoms = "\n".join( [ f"{sp} ({', '.join([f'{magmom} µB' for magmom in magmoms])})" for sp, magmoms in mag_species_and_magmoms.items() ] ) magnetization_per_formula_unit = ( msa.total_magmoms / msa.structure.composition.get_reduced_composition_and_factor()[1] ) rows = [] rows.append( ( html.B("Total magnetization per formula unit"), html.Br(), f"{magnetization_per_formula_unit:.1f} µB", ) ) rows.append((html.B("Atoms with local magnetic moments"), html.Br(), magnetic_atoms)) data_block = html.Div([html.P([html.Span(cell) for cell in row]) for row in rows]) viewer = StructureMoleculeComponent( struct, id=self.id("structure"), color_scheme="magmom", static=True ) return Columns([ Column(html.Div([viewer.struct_layout], style={"height": "60vmin"})), Column(data_block) ])
def update_displayed_structure(clickData): if not clickData: raise PreventUpdate task_id = clickData["points"][0]["text"] with MPRester(endpoint="https://zola.lbl.gov/rest/v2") as mpr: struct = mpr.get_task_data(task_id, prop="structure")[0]["structure"] print(struct) viewer = StructureMoleculeComponent( struct, id=self.id("magnetic_structure"), color_scheme="magmom", static=True, ) return viewer.struct_layout
def get_scene(structure): """ :param structure: """ smc = StructureMoleculeComponent(structure, bonded_sites_outside_unit_cell=False, hide_incomplete_bonds=False) obs = traverse_scene_object(smc.initial_scene_data) scene = Scene( children=[obs, AmbientLight(color='#FFFFFF', intensity=0.75)]) c = PerspectiveCamera(position=[10, 10, 10]) renderer = Renderer(camera=c, background='black', background_opacity=1, scene=scene, controls=[OrbitControls(controlling=c)], width=400, height=400) display(renderer)
def get_chemenv_analysis(struct, distance_cutoff, angle_cutoff): if not struct: raise PreventUpdate struct = self.from_data(struct) kwargs = self.reconstruct_kwargs_from_state( callback_context.inputs) distance_cutoff = kwargs["distance_cutoff"] angle_cutoff = kwargs["angle_cutoff"] # TODO: remove these brittle guard statements, figure out more robust way to handle multiple input types if isinstance(struct, StructureGraph): struct = struct.structure def get_valences(struct): valences = [ getattr(site.specie, "oxi_state", None) for site in struct ] valences = [v for v in valences if v is not None] if len(valences) == len(struct): return valences else: return "undefined" # decide which indices to present to user sga = SpacegroupAnalyzer(struct) symm_struct = sga.get_symmetrized_structure() inequivalent_indices = [ indices[0] for indices in symm_struct.equivalent_indices ] wyckoffs = symm_struct.wyckoff_symbols lgf = LocalGeometryFinder() lgf.setup_structure(structure=struct) se = lgf.compute_structure_environments( maximum_distance_factor=distance_cutoff + 0.01, only_indices=inequivalent_indices, valences=get_valences(struct), ) strategy = SimplestChemenvStrategy(distance_cutoff=distance_cutoff, angle_cutoff=angle_cutoff) lse = LightStructureEnvironments.from_structure_environments( strategy=strategy, structure_environments=se) all_ce = AllCoordinationGeometries() envs = [] unknown_sites = [] for index, wyckoff in zip(inequivalent_indices, wyckoffs): datalist = { "Site": unicodeify_species(struct[index].species_string), "Wyckoff Label": wyckoff, } if not lse.neighbors_sets[index]: unknown_sites.append( f"{struct[index].species_string} ({wyckoff})") continue # represent the local environment as a molecule mol = Molecule.from_sites( [struct[index]] + lse.neighbors_sets[index][0].neighb_sites) mol = mol.get_centered_molecule() mg = MoleculeGraph.with_empty_graph(molecule=mol) for i in range(1, len(mol)): mg.add_edge(0, i) view = html.Div( [ StructureMoleculeComponent( struct_or_mol=mg, disable_callbacks=True, id= f"{struct.composition.reduced_formula}_site_{index}", scene_settings={ "enableZoom": False, "defaultZoom": 0.6 }, )._sub_layouts["struct"] ], style={ "width": "300px", "height": "300px" }, ) env = lse.coordination_environments[index] co = all_ce.get_geometry_from_mp_symbol(env[0]["ce_symbol"]) name = co.name if co.alternative_names: name += f" (also known as {', '.join(co.alternative_names)})" datalist.update({ "Environment": name, "IUPAC Symbol": co.IUPAC_symbol_str, get_tooltip( "CSM", "The continuous symmetry measure (CSM) describes the similarity to an " "ideal coordination environment. It can be understood as a 'distance' to " "a shape and ranges from 0 to 100 in which 0 corresponds to a " "coordination environment that is exactly identical to the ideal one. A " "CSM larger than 5.0 already indicates a relatively strong distortion of " "the investigated coordination environment.", ): f"{env[0]['csm']:.2f}", "Interactive View": view, }) envs.append(get_data_list(datalist)) # TODO: switch to tiles? envs_grouped = [envs[i:i + 2] for i in range(0, len(envs), 2)] analysis_contents = [] for env_group in envs_grouped: analysis_contents.append( Columns([Column(e, size=6) for e in env_group])) if unknown_sites: unknown_sites = html.Strong( f"The following sites were not identified: {', '.join(unknown_sites)}. " f"Please try changing the distance or angle cut-offs to identify these sites, " f"or try an alternative algorithm such as LocalEnv.") else: unknown_sites = html.Span() return html.Div( [html.Div(analysis_contents), html.Br(), unknown_sites])
def retrieve_grain_boundaries(mpid): if not mpid or "mpid" not in mpid: raise PreventUpdate data = None with MPRester() as mpr: data = mpr.get_gb_data(mpid["mpid"]) if not data: return ( "No grain boundary information computed for this crystal structure. " "Grain boundary information has only been computed for elemental ground state " "crystal structures at present.") table_data = [{ "Sigma": d["sigma"], "Rotation Axis": f"{d['rotation_axis']}", "Rotation Angle / º": f"{d['rotation_angle']:.2f}", "Grain Boundary Plane": f"({' '.join(map(str, d['gb_plane']))})", "Grain Boundary Energy / Jm⁻²": f"{d['gb_energy']:.2f}", } for d in data] df = pd.DataFrame(table_data) table = dt.DataTable( id=self.id("table"), columns=[{ "name": i, "id": i } for i in df.columns], data=df.to_dict("records"), style_cell={ "minWidth": "0px", "maxWidth": "200px", "whiteSpace": "normal", }, css=[{ "selector": ".dash-cell div.dash-cell-value", "rule": "display: inline; white-space: inherit; overflow: inherit; text-overflow: inherit;", }], sort_action="native", sort_mode="multi", ) view = html.Div( [ StructureMoleculeComponent( data[2]["initial_structure"], id=self.id("struct"), static=True, color_scheme="grain_label", ).struct_layout ], style={ "width": "400px", "height": "400px" }, ) return Columns([Column(table), Column(view)])
from pymatgen.core.structure import Structure from pymatgen.core.lattice import Lattice from crystal_toolkit.helpers.asymptote import write_asy_file from crystal_toolkit.components.structure import StructureMoleculeComponent import os example_struct = Structure.from_spacegroup( "P6_3mc", Lattice.hexagonal(3.22, 5.24), ["Ga", "N"], [[1 / 3, 2 / 3, 0], [1 / 3, 2 / 3, 3 / 8]], ) smc = StructureMoleculeComponent(example_struct, hide_incomplete_bonds=True) file_name = "./asy_test/single/GaN.asy" write_asy_file(smc, file_name) write_asy_file(smc, "./asy_test/multi/GaN.asy") example_struct = Structure.from_spacegroup( "P6_3mc", Lattice.hexagonal(3.22, 5.24), ["In", "N"], [[1 / 3, 2 / 3, 0], [1 / 3, 2 / 3, 3 / 8]], ) smc = StructureMoleculeComponent(example_struct, hide_incomplete_bonds=True) write_asy_file(smc, "./asy_test/multi/InN.asy") example_struct = Structure.from_spacegroup( "P6_3mc", Lattice.hexagonal(3.22, 5.24), ["Al", "N"], [[1 / 3, 2 / 3, 0], [1 / 3, 2 / 3, 3 / 8]],
def get_combined_scene(bs, extra_scene): smc = StructureMoleculeComponent(bs, scene_additions=extra_scene) return smc
def get_chemenv_analysis(struct, distance_cutoff, angle_cutoff): if not struct: raise PreventUpdate struct = self.from_data(struct) distance_cutoff = float(distance_cutoff) angle_cutoff = float(angle_cutoff) # decide which indices to present to user sga = SpacegroupAnalyzer(struct) symm_struct = sga.get_symmetrized_structure() inequivalent_indices = [ indices[0] for indices in symm_struct.equivalent_indices ] wyckoffs = symm_struct.wyckoff_symbols lgf = LocalGeometryFinder() lgf.setup_structure(structure=struct) se = lgf.compute_structure_environments( maximum_distance_factor=distance_cutoff + 0.01, only_indices=inequivalent_indices, ) strategy = SimplestChemenvStrategy(distance_cutoff=distance_cutoff, angle_cutoff=angle_cutoff) lse = LightStructureEnvironments.from_structure_environments( strategy=strategy, structure_environments=se) all_ce = AllCoordinationGeometries() envs = [] unknown_sites = [] for index, wyckoff in zip(inequivalent_indices, wyckoffs): datalist = { "Site": struct[index].species_string, "Wyckoff Label": wyckoff, } if not lse.neighbors_sets[index]: unknown_sites.append( f"{struct[index].species_string} ({wyckoff})") continue # represent the local environment as a molecule mol = Molecule.from_sites( [struct[index]] + lse.neighbors_sets[index][0].neighb_sites) mol = mol.get_centered_molecule() mg = MoleculeGraph.with_empty_graph(molecule=mol) for i in range(1, len(mol)): mg.add_edge(0, i) view = html.Div( [ StructureMoleculeComponent( struct_or_mol=mg, static=True, id= f"{struct.composition.reduced_formula}_site_{index}", scene_settings={ "enableZoom": False, "defaultZoom": 0.6 }, ).all_layouts["struct"] ], style={ "width": "300px", "height": "300px" }, ) env = lse.coordination_environments[index] co = all_ce.get_geometry_from_mp_symbol(env[0]["ce_symbol"]) name = co.name if co.alternative_names: name += f" (also known as {', '.join(co.alternative_names)})" datalist.update({ "Environment": name, "IUPAC Symbol": co.IUPAC_symbol_str, get_tooltip( "CSM", '"Continuous Symmetry Measure," a measure of how symmetrical a ' "local environment is from most symmetrical at 0% to least " "symmetrical at 100%", ): f"{env[0]['csm']:.2f}%", "Interactive View": view, }) envs.append(get_data_list(datalist)) # TODO: switch to tiles? envs_grouped = [envs[i:i + 2] for i in range(0, len(envs), 2)] analysis_contents = [] for env_group in envs_grouped: analysis_contents.append( Columns([Column(e, size=6) for e in env_group])) if unknown_sites: unknown_sites = html.Strong( f"The following sites were not identified: {', '.join(unknown_sites)}. " f"Please try changing the distance or angle cut-offs to identify these sites." ) else: unknown_sites = html.Span() return html.Div( [html.Div(analysis_contents), html.Br(), unknown_sites])