Пример #1
0
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)
Пример #2
0
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")
Пример #3
0
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))
Пример #4
0
    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
Пример #5
0
        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)]),
            ])
Пример #6
0
 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")
Пример #7
0
    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)]),
        ])
Пример #8
0
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))