def _log_structure_information(structure: Structure, symprec): log_banner("STRUCTURE") logger.info("Structure information:") comp = structure.composition lattice = structure.lattice formula = comp.get_reduced_formula_and_factor(iupac_ordering=True)[0] if not symprec: symprec = 1e-32 sga = SpacegroupAnalyzer(structure, symprec=symprec) spg = unicodeify_spacegroup(sga.get_space_group_symbol()) comp_info = [ "formula: {}".format(unicodeify(formula)), "# sites: {}".format(structure.num_sites), "space group: {}".format(spg), ] log_list(comp_info) logger.info("Lattice:") lattice_info = [ "a, b, c [Å]: {:.2f}, {:.2f}, {:.2f}".format(*lattice.abc), "α, β, γ [°]: {:.0f}, {:.0f}, {:.0f}".format(*lattice.angles), ] log_list(lattice_info)
def create(**kwargs): """ Generate deformed structures for calculating deformation potentials. """ from pymatgen.core.structure import Structure from pymatgen.core.tensors import symmetry_reduce from pymatgen.symmetry.analyzer import SpacegroupAnalyzer from pymatgen.util.string import unicodeify_spacegroup from amset.deformation.common import get_formatted_tensors from amset.deformation.generation import get_deformations, get_deformed_structures from amset.deformation.io import write_deformed_poscars symprec = _parse_symprec(kwargs["symprec"]) structure = Structure.from_file(kwargs["filename"]) click.echo("Generating deformations:") click.echo(" - Strain distance: {:g}".format(kwargs["distance"])) deformations = get_deformations(kwargs["distance"]) click.echo(" - # Total deformations: {}".format(len(deformations))) if symprec is not None: sga = SpacegroupAnalyzer(structure, symprec=symprec) spg_symbol = unicodeify_spacegroup(sga.get_space_group_symbol()) spg_number = sga.get_space_group_number() click.echo(" - Spacegroup: {} ({})".format(spg_symbol, spg_number)) deformations = list(symmetry_reduce(deformations, structure, symprec=symprec)) click.echo(" - # Inequivalent deformations: {}".format(len(deformations))) click.echo("\nDeformations:") click.echo(" - " + "\n - ".join(get_formatted_tensors(deformations))) deformed_structures = get_deformed_structures(structure, deformations) write_deformed_poscars(deformed_structures, directory=kwargs["directory"]) click.echo("\nDeformed structures have been created")
def read(bulk_folder, deformation_folders, **kwargs): """ Read deformation calculations and extract deformation potentials. """ from pymatgen.symmetry.analyzer import SpacegroupAnalyzer from pymatgen.util.string import unicodeify_spacegroup from amset.constants import defaults from amset.deformation.common import get_formatted_tensors from amset.deformation.io import parse_calculation, write_deformation_potentials from amset.deformation.potentials import ( calculate_deformation_potentials, extract_bands, get_strain_mapping, get_symmetrized_strain_mapping, strain_coverage_ok, ) from amset.electronic_structure.common import get_ibands from amset.electronic_structure.kpoints import get_kpoints_from_bandstructure from amset.electronic_structure.symmetry import expand_bandstructure energy_cutoff = kwargs.pop("energy_cutoff") if not energy_cutoff: energy_cutoff = defaults["energy_cutoff"] zwk_mode = kwargs.pop("zero_weighted_kpoints") if not zwk_mode: zwk_mode = defaults["zero_weighted_kpoints"] symprec = _parse_symprec(kwargs["symprec"]) symprec_deformation = kwargs["symprec_deformation"] click.echo("Reading bulk (undeformed) calculation") bulk_calculation = parse_calculation(bulk_folder, zero_weighted_kpoints=zwk_mode) bulk_structure = bulk_calculation["bandstructure"].structure deformation_calculations = [] for deformation_folder in deformation_folders: click.echo("Reading deformation calculation in {}".format(deformation_folder)) deformation_calculation = parse_calculation( deformation_folder, zero_weighted_kpoints=zwk_mode ) if check_calculation(bulk_calculation, deformation_calculation): deformation_calculations.append(deformation_calculation) sga = SpacegroupAnalyzer(bulk_structure, symprec=symprec) spg_symbol = unicodeify_spacegroup(sga.get_space_group_symbol()) spg_number = sga.get_space_group_number() click.echo("\nSpacegroup: {} ({})".format(spg_symbol, spg_number)) lattice_match = reciprocal_lattice_match( bulk_calculation["bandstructure"], symprec=symprec ) if not lattice_match: click.echo( "\nWARNING: Reciprocal lattice and k-lattice belong to different\n" " class of lattices. Often results are still useful but\n" " it is recommended to regenerate deformations without\n" " symmetry using: amset deform create --symprec N" ) strain_mapping = get_strain_mapping(bulk_structure, deformation_calculations) click.echo("\nFound {} strains:".format(len(strain_mapping))) fmt_strain = get_formatted_tensors(strain_mapping.keys()) click.echo(" - " + "\n - ".join(fmt_strain)) strain_mapping = get_symmetrized_strain_mapping( bulk_structure, strain_mapping, symprec=symprec, symprec_deformation=symprec_deformation, ) click.echo("\nAfter symmetrization found {} strains:".format(len(strain_mapping))) fmt_strain = get_formatted_tensors(strain_mapping.keys()) click.echo(" - " + "\n - ".join(fmt_strain)) if not strain_coverage_ok(list(strain_mapping.keys())): click.echo("\nERROR: Strains do not cover full tensor, check calculations") sys.exit() click.echo("\nCalculating deformation potentials") bulk_calculation["bandstructure"] = expand_bandstructure( bulk_calculation["bandstructure"], symprec=symprec ) deformation_potentials = calculate_deformation_potentials( bulk_calculation, strain_mapping ) print_deformation_summary(bulk_calculation["bandstructure"], deformation_potentials) if "bands" in kwargs and kwargs["bands"] is not None: ibands = parse_ibands(kwargs["bands"]) else: ibands = get_ibands(energy_cutoff, bulk_calculation["bandstructure"]) echo_ibands(ibands, bulk_calculation["bandstructure"].is_spin_polarized) deformation_potentials = extract_bands(deformation_potentials, ibands) kpoints = get_kpoints_from_bandstructure(bulk_calculation["bandstructure"]) filename = write_deformation_potentials( deformation_potentials, kpoints, bulk_structure, filename=kwargs["output"] ) click.echo("\nDeformation potentials written to {}".format(filename))
def get_plotly( self, color_set="PuBu", off_color="red", alpha=1, custom_colors={}, units_in_JPERM2=True, ): """ Get the Wulff shape as a plotly Figure object. Args: color_set: default is 'PuBu' alpha (float): chosen from 0 to 1 (float), default is 1 off_color: Default color for facets not present on the Wulff shape. custom_colors ({(h,k,l}: [r,g,b,alpha}): Customize color of each facet with a dictionary. The key is the corresponding Miller index and value is the color. Undefined facets will use default color site. Note: If you decide to set your own colors, it probably won't make any sense to have the color bar on. units_in_JPERM2 (bool): Units of surface energy, defaults to Joules per square meter (True) Return: (plotly.graph_objs.Figure) """ units = "Jm⁻²" if units_in_JPERM2 else "eVÅ⁻²" ( color_list, color_proxy, color_proxy_on_wulff, miller_on_wulff, e_surf_on_wulff, ) = self._get_colors(color_set, alpha, off_color, custom_colors=custom_colors) planes_data, color_scale, ticktext, tickvals = [], [], [], [] for plane in self.facets: if len(plane.points) < 1: # empty, plane is not on_wulff. continue plane_color = color_list[plane.index] plane_color = (1, 0, 0, 1) if plane_color == off_color else plane_color # set to red for now pt = self.get_line_in_facet(plane) x_pts, y_pts, z_pts = [], [], [] for p in pt: x_pts.append(p[0]) y_pts.append(p[1]) z_pts.append(p[2]) # remove duplicate x y z pts to save time all_xyz = [] # pylint: disable=E1133,E1136 [all_xyz.append(list(coord)) for coord in np.array([x_pts, y_pts, z_pts]).T if list(coord) not in all_xyz] all_xyz = np.array(all_xyz).T x_pts, y_pts, z_pts = all_xyz[0], all_xyz[1], all_xyz[2] index_list = [int(i) for i in np.linspace(0, len(x_pts) - 1, len(x_pts))] tri_indices = np.array(list(itertools.combinations(index_list, 3))).T hkl = self.miller_list[plane.index] hkl = unicodeify_spacegroup("(" + "%s" * len(hkl) % hkl + ")") color = "rgba(%.5f, %.5f, %.5f, %.5f)" % tuple(np.array(plane_color) * 255) # note hoverinfo is incompatible with latex, need unicode instead planes_data.append( go.Mesh3d( x=x_pts, y=y_pts, z=z_pts, i=tri_indices[0], j=tri_indices[1], k=tri_indices[2], hovertemplate="<br>%{text}<br>" + "{}={:.3f} {}<br>".format("\u03b3", plane.e_surf, units), color=color, text=[r"Miller index: %s" % hkl] * len(x_pts), hoverinfo="name", name="", ) ) # normalize surface energy from a scale of 0 to 1 for colorbar norm_e = (plane.e_surf - min(e_surf_on_wulff)) / (max(e_surf_on_wulff) - min(e_surf_on_wulff)) c = [norm_e, color] if c not in color_scale: color_scale.append(c) ticktext.append("%.3f" % plane.e_surf) tickvals.append(norm_e) # Add colorbar color_scale = sorted(color_scale, key=lambda c: c[0]) colorbar = go.Mesh3d( x=[0], y=[0], z=[0], colorbar=go.ColorBar( title={ "text": r"Surface energy %s" % units, "side": "right", "font": {"size": 25}, }, ticktext=ticktext, tickvals=tickvals, ), colorscale=[[0, "rgb(255,255,255, 255)"]] + color_scale, # fix the scale intensity=[0, 0.33, 0.66, 1], i=[0], j=[0], k=[0], name="y", showscale=True, ) planes_data.append(colorbar) # Format aesthetics: background, axis, etc. axis_dict = dict( title="", autorange=True, showgrid=False, zeroline=False, ticks="", showline=False, showticklabels=False, showbackground=False, ) fig = go.Figure(data=planes_data) fig.update_layout( dict( showlegend=True, scene=dict(xaxis=axis_dict, yaxis=axis_dict, zaxis=axis_dict), ) ) return fig
def update_contents(data, symprec, angle_tolerance): if not data: return html.Div() struct = self.from_data(data) if not isinstance(struct, Structure): return html.Div( "Can only analyze symmetry of crystal structures at present." ) kwargs = self.reconstruct_kwargs_from_state( callback_context.inputs) symprec = kwargs["symprec"] angle_tolerance = kwargs["angle_tolerance"] if symprec <= 0: return html.Span( f"Please use a positive symmetry-finding tolerance (currently {symprec})." ) sga = SpacegroupAnalyzer(struct, symprec=symprec, angle_tolerance=angle_tolerance) try: data = dict() data["Crystal System"] = sga.get_crystal_system().title() data["Lattice System"] = sga.get_lattice_type().title() data["Hall Number"] = sga.get_hall() data["International Number"] = sga.get_space_group_number() data["Symbol"] = unicodeify_spacegroup( sga.get_space_group_symbol()) data["Point Group"] = unicodeify_spacegroup( sga.get_point_group_symbol()) sym_struct = sga.get_symmetrized_structure() except Exception: return html.Span( f"Failed to calculate symmetry with this combination of " f"symmetry-finding ({symprec}) and angle tolerances ({angle_tolerance})." ) datalist = get_data_list(data) wyckoff_contents = [] wyckoff_data = sorted( zip(sym_struct.wyckoff_symbols, sym_struct.equivalent_sites), key=lambda x: "".join(filter(lambda w: w.isalpha(), x[0])), ) for symbol, equiv_sites in wyckoff_data: wyckoff_contents.append( html.Label( f"{symbol}, {unicodeify_species(equiv_sites[0].species_string)}", className="mpc-label", )) site_data = [( self.pretty_frac_format(site.frac_coords[0]), self.pretty_frac_format(site.frac_coords[1]), self.pretty_frac_format(site.frac_coords[2]), ) for site in equiv_sites] wyckoff_contents.append(get_table(site_data)) return Columns([ Column([H5("Overview"), datalist]), Column([H5("Wyckoff Positions"), html.Div(wyckoff_contents)]), ])
def test_unicodeify(self): self.assertEqual(unicodeify("Li3Fe2(PO4)3"), "Li₃Fe₂(PO₄)₃") self.assertRaises(ValueError, unicodeify, "Li0.2Na0.8Cl") self.assertEqual(unicodeify_species("O2+"), "O²⁺") self.assertEqual(unicodeify_spacegroup("F-3m"), "F3̅m")
def update_contents(self, new_store_contents, symprec, angle_tolerance): try: # input sanitation symprec = float(literal_eval(str(symprec))) angle_tolerance = float(literal_eval(str(angle_tolerance))) except: raise PreventUpdate struct_or_mol = self.from_data(new_store_contents) if not isinstance(struct_or_mol, Structure): return html.Div( "Can only analyze symmetry of crystal structures at present.") sga = SpacegroupAnalyzer(struct_or_mol, symprec=symprec, angle_tolerance=angle_tolerance) try: data = {} data["Crystal System"] = sga.get_crystal_system().title() data["Lattice System"] = sga.get_lattice_type().title() data["Hall Number"] = sga.get_hall() data["International Number"] = sga.get_space_group_number() data["Symbol"] = unicodeify_spacegroup( sga.get_space_group_symbol()) data["Point Group"] = unicodeify_spacegroup( sga.get_point_group_symbol()) sym_struct = sga.get_symmetrized_structure() except: return html.Span( f"Failed to calculate symmetry with this combination of " f"symmetry-finding ({symprec}) and angle tolerances ({angle_tolerance})." ) datalist = get_data_list(data) wyckoff_contents = [] wyckoff_data = sorted( zip(sym_struct.wyckoff_symbols, sym_struct.equivalent_sites), key=lambda x: "".join(filter(lambda w: w.isalpha(), x[0])), ) for symbol, equiv_sites in wyckoff_data: wyckoff_contents.append( html.Label( f"{symbol}, {unicodeify_species(equiv_sites[0].species_string)}", className="mpc-label", )) site_data = [( self.pretty_frac_format(site.frac_coords[0]), self.pretty_frac_format(site.frac_coords[1]), self.pretty_frac_format(site.frac_coords[2]), ) for site in equiv_sites] wyckoff_contents.append(get_table(site_data)) return Columns([ Column([H5("Overview"), datalist]), Column([H5("Wyckoff Positions"), html.Div(wyckoff_contents)]), ])
def read(bulk_folder, deformation_folders, **kwargs): """ Read deformation calculations and extract deformation potentials. """ from pymatgen.symmetry.analyzer import SpacegroupAnalyzer from pymatgen.util.string import unicodeify_spacegroup from amset.constants import defaults from amset.deformation.common import get_formatted_tensors from amset.deformation.io import parse_calculation, write_deformation_potentials from amset.electronic_structure.symmetry import expand_bandstructure from amset.deformation.potentials import ( calculate_deformation_potentials, extract_bands, get_strain_mapping, get_symmetrized_strain_mapping, strain_coverage_ok, ) from amset.electronic_structure.common import get_ibands from amset.electronic_structure.kpoints import get_kpoints_from_bandstructure energy_cutoff = kwargs.pop("energy_cutoff") if not energy_cutoff: energy_cutoff = defaults["energy_cutoff"] symprec = _parse_symprec(kwargs["symprec"]) click.echo("Reading bulk (undeformed) calculation") bulk_calculation = parse_calculation(bulk_folder) bulk_structure = bulk_calculation["bandstructure"].structure deformation_calculations = [] for deformation_folder in deformation_folders: click.echo("Reading deformation calculation in {}".format(deformation_folder)) deformation_calculation = parse_calculation(deformation_folder) if check_calculation(bulk_calculation, deformation_calculation): deformation_calculations.append(deformation_calculation) sga = SpacegroupAnalyzer(bulk_structure, symprec=symprec) spg_symbol = unicodeify_spacegroup(sga.get_space_group_symbol()) spg_number = sga.get_space_group_number() click.echo("\nSpacegroup: {} ({})".format(spg_symbol, spg_number)) strain_mapping = get_strain_mapping(bulk_structure, deformation_calculations) click.echo("\nFound {} strains:".format(len(strain_mapping))) fmt_strain = get_formatted_tensors(strain_mapping.keys()) click.echo(" - " + "\n - ".join(fmt_strain)) strain_mapping = get_symmetrized_strain_mapping( bulk_structure, strain_mapping, symprec=symprec ) click.echo("\nAfter symmetrization found {} strains:".format(len(strain_mapping))) fmt_strain = get_formatted_tensors(strain_mapping.keys()) click.echo(" - " + "\n - ".join(fmt_strain)) if not strain_coverage_ok(list(strain_mapping.keys())): click.echo("\nERROR: Strains do not cover full tensor, check calculations") sys.exit() click.echo("\nCalculating deformation potentials") bulk_calculation["bandstructure"] = expand_bandstructure( bulk_calculation["bandstructure"], symprec=symprec ) deformation_potentials = calculate_deformation_potentials( bulk_calculation, strain_mapping ) print_deformation_summary(bulk_calculation["bandstructure"], deformation_potentials) ibands = get_ibands(energy_cutoff, bulk_calculation["bandstructure"]) echo_ibands(ibands, bulk_calculation["bandstructure"].is_spin_polarized) click.echo("") deformation_potentials = extract_bands(deformation_potentials, ibands) kpoints = get_kpoints_from_bandstructure(bulk_calculation["bandstructure"]) filename = write_deformation_potentials( deformation_potentials, kpoints, bulk_structure, filename=kwargs["output"] ) click.echo("\nDeformation potentials written to {}".format(filename))