Пример #1
0
 def scene(self, name=""):
     faces = self.scene_dict["faces"]
     pos = [
         vert for triangle in self.scene_dict["vertices"][faces].tolist()
         for vert in triangle
     ]
     return Scene(name=name, contents=[Surface(positions=pos)])
Пример #2
0
def get_isosurface_scene(self,
                         data_key="total",
                         isolvl=0.5,
                         step_size=3,
                         origin=(0.0, 0.0, 0.0),
                         **kwargs):
    """Get the isosurface from a VolumetricData object

    Args:
        data_key (str, optional): Use the volumetric data from self.data[data_key]. Defaults to 'total'.
        isolvl (float, optional): The cutoff for the isosurface to using the same units as VESTA so
        e/bhor and kept grid size independent
        step_size (int, optional): step_size parameter for marching_cubes_lewiner. Defaults to 3.

    Returns:
        [type]: [description]
    """
    vol_data = np.copy(self.data[data_key])
    vol = self.structure.volume
    vol_data = vol_data / vol / _ANGS2_TO_BOHR3

    padded_data = np.pad(vol_data, (0, 1), "wrap")
    vertices, faces, normals, values = measure.marching_cubes_lewiner(
        padded_data, level=isolvl, step_size=step_size)
    # transform to fractional coordinates
    vertices = vertices / (vol_data.shape[0], vol_data.shape[1],
                           vol_data.shape[2])
    vertices = np.dot(vertices,
                      self.structure.lattice.matrix)  # transform to cartesian
    pos = [vert for triangle in vertices[faces].tolist() for vert in triangle]
    return Scene("isosurface",
                 origin=origin,
                 contents=[Surface(pos, show_edges=False, **kwargs)])
Пример #3
0
 def scenes(self):
     result = {}
     if len(self.scene_dicts) == 0:
         return {"empty_scene": Scene("empty", contents=[])}
     for k, v in self.scene_dicts.items():
         pos = [
             vert for triangle in v["vertices"][v["faces"]].tolist()
             for vert in triangle
         ]
         result[k] = Scene(k, contents=[Surface(positions=pos)])
     return result
Пример #4
0
def get_volumetric_scene(self,
                         origin=(0, 0, 0),
                         data_key='total',
                         isolvl=2.0,
                         step_size=3,
                         **kwargs):
    o = -np.array(origin)
    vertices, faces, normals, values = measure.marching_cubes_lewiner(
        self.data[data_key], level=isolvl, step_size=step_size)
    vertices = vertices / self.data[
        data_key].shape  # transform to fractional coordinates
    vertices = np.dot(o + vertices,
                      self.structure.lattice.matrix)  # transform to cartesian
    return Scene("volumetric-data",
                 contents=[Surface(vertices, normals, **kwargs)])
    def test_convert_object_to_pythreejs(self):
        # take different crystal toolkit objects and convert them into pythreejs objects
        sphere = Spheres(positions=[[0, 0, 0]], color="#00ab24", radius=1.0)
        assert ("SphereBufferGeometry" in _convert_object_to_pythreejs(
            scene_obj=sphere)[0].__repr__())

        cylinder = Cylinders(positionPairs=[[[0, 0, 0], [0, 1, 1]]],
                             color="#00ab24",
                             radius=1.0)
        assert ("CylinderBufferGeometry" in _convert_object_to_pythreejs(
            scene_obj=cylinder)[0].__repr__())

        surface = Surface([[0, 0, 0], [1, 0, 0], [0, 1, 0]])
        _convert_object_to_pythreejs(scene_obj=surface)[0].__repr__()
        assert ("BufferGeometry" in _convert_object_to_pythreejs(
            scene_obj=surface)[0].__repr__())
Пример #6
0
def get_site_scene(
    self,
    connected_sites: List[ConnectedSite] = None,
    # connected_site_metadata: None,
    # connected_sites_to_draw,
    connected_sites_not_drawn: List[ConnectedSite] = None,
    hide_incomplete_edges: bool = False,
    incomplete_edge_length_scale: Optional[float] = 1.0,
    connected_sites_colors: Optional[List[str]] = None,
    connected_sites_not_drawn_colors: Optional[List[str]] = None,
    origin: Optional[List[float]] = None,
    draw_polyhedra: bool = True,
    explicitly_calculate_polyhedra_hull: bool = False,
    bond_radius: float = 0.1,
    legend: Optional[Legend] = None,
) -> Scene:
    """

    Args:
        connected_sites:
        connected_sites_not_drawn:
        hide_incomplete_edges:
        incomplete_edge_length_scale:
        connected_sites_colors:
        connected_sites_not_drawn_colors:
        origin:
        explicitly_calculate_polyhedra_hull:
        legend:

    Returns:

    """

    atoms = []
    bonds = []
    polyhedron = []

    legend = legend or Legend(self)

    # for disordered structures
    is_ordered = self.is_ordered
    phiStart, phiEnd = None, None
    occu_start = 0.0

    position = self.coords.tolist()

    for idx, (sp, occu) in enumerate(self.species.items()):

        if isinstance(sp, DummySpecie):

            cube = Cubes(positions=[position],
                         color=legend.get_color(sp, site=self),
                         width=0.4)
            atoms.append(cube)

        else:

            color = legend.get_color(sp, site=self)
            radius = legend.get_radius(sp, site=self)

            # TODO: make optional/default to None
            # in disordered structures, we fractionally color-code spheres,
            # drawing a sphere segment from phi_end to phi_start
            # (think a sphere pie chart)
            if not is_ordered:
                phi_frac_end = occu_start + occu
                phi_frac_start = occu_start
                occu_start = phi_frac_end
                phiStart = phi_frac_start * np.pi * 2
                phiEnd = phi_frac_end * np.pi * 2

            name = str(sp)
            if occu != 1.0:
                name += " ({}% occupancy)".format(occu)
            name += f" ({position[0]:.3f}, {position[1]:.3f}, {position[2]:.3f})"

            sphere = Spheres(
                positions=[position],
                color=color,
                radius=radius,
                phiStart=phiStart,
                phiEnd=phiEnd,
                clickable=True,
                tooltip=name,
            )
            atoms.append(sphere)

    if not is_ordered and not np.isclose(phiEnd, np.pi * 2):
        # if site occupancy doesn't sum to 100%, cap sphere
        sphere = Spheres(
            positions=[position],
            color="#ffffff",
            radius=self.properties["display_radius"][0],
            phiStart=phiEnd,
            phiEnd=np.pi * 2,
        )
        atoms.append(sphere)

    if connected_sites:

        # TODO: more graceful solution here
        # if ambiguous (disordered), re-use last color used
        site_color = color

        # TODO: can cause a bug if all vertices almost co-planar
        # necessary to include center site in case it's outside polyhedra
        all_positions = [self.coords]

        for idx, connected_site in enumerate(connected_sites):

            connected_position = connected_site.site.coords
            bond_midpoint = np.add(position, connected_position) / 2

            if connected_sites_colors:
                color = connected_sites_colors[idx]
            else:
                color = site_color

            cylinder = Cylinders(
                positionPairs=[[position, bond_midpoint.tolist()]],
                color=color,
                radius=bond_radius,
            )
            bonds.append(cylinder)
            all_positions.append(connected_position.tolist())

        if connected_sites_not_drawn and not hide_incomplete_edges:

            for idx, connected_site in enumerate(connected_sites_not_drawn):

                connected_position = connected_site.site.coords
                bond_midpoint = (incomplete_edge_length_scale *
                                 np.add(position, connected_position) / 2)

                if connected_sites_not_drawn_colors:
                    color = connected_sites_not_drawn_colors[idx]
                else:
                    color = site_color

                cylinder = Cylinders(
                    positionPairs=[[position, bond_midpoint.tolist()]],
                    color=color,
                    radius=bond_radius,
                )
                bonds.append(cylinder)
                all_positions.append(connected_position.tolist())

        # ensure intersecting polyhedra are not shown, defaults to choose by electronegativity
        not_most_electro_negative = map(
            lambda x:
            (x.site.specie < self.specie) or (x.site.specie == self.specie),
            connected_sites,
        )

        all_positions = [list(p) for p in all_positions]

        if (draw_polyhedra and len(connected_sites) > 3
                and not connected_sites_not_drawn
                and not any(not_most_electro_negative)):
            if explicitly_calculate_polyhedra_hull:

                try:

                    # all_positions = [[0, 0, 0], [0, 0, 10], [0, 10, 0], [10, 0, 0]]
                    # gives...
                    # .convex_hull = [[2, 3, 0], [1, 3, 0], [1, 2, 0], [1, 2, 3]]
                    # .vertex_neighbor_vertices = [1, 2, 3, 2, 3, 0, 1, 3, 0, 1, 2, 0]

                    vertices_indices = Delaunay(all_positions).convex_hull
                except Exception as e:
                    vertices_indices = []

                vertices = [
                    all_positions[idx]
                    for idx in chain.from_iterable(vertices_indices)
                ]

                polyhedron = [Surface(positions=vertices, color=site_color)]

            else:

                polyhedron = [
                    Convex(positions=all_positions, color=site_color)
                ]

    return Scene(
        self.species_string,
        [
            Scene("atoms", contents=atoms),
            Scene("bonds", contents=bonds),
            Scene("polyhedra", contents=polyhedron),
        ],
        origin=origin,
    )
Пример #7
0
    def plot_crystal_toolkit(
        self,
        spin: Optional[Spin] = None,
        colors: Optional[Union[str, dict, list]] = None,
        opacity: float = 1.0,
    ) -> "Scene":
        """
        Get a crystal toolkit Scene showing the Fermi surface. The Scene can be
        displayed in an interactive web app using Crystal Toolkit, can be shown
        interactively in Jupyter Lab using the crystal-toolkit lab extension, or
        can be converted to JSON to store for future use.

        Args:
            spin: Which spin channel to plot. By default plot both spin channels if
                available.
            colors: See the docstring for ``get_isosurfaces_and_colors()`` for the
                available options.
            opacity: Opacity of surface. Note that due to limitations of WebGL,
                overlapping semi-transparent surfaces might result in visual artefacts.
        """

        # The implementation here is very similar to the plotly implementation, except
        # the crystal toolkit scene is constructed using the scene primitives from
        # crystal toolkit (Spheres, Surface, Lines, etc.)

        scene_contents = []

        isosurfaces, colors = self.get_isosurfaces_and_colors(spin=spin,
                                                              colors=colors)

        if isinstance(colors, np.ndarray):
            colors = (colors * 255).astype(int)
            colors = ["rgb({},{},{})".format(*c) for c in colors]

        # create a mesh for each electron band which has an isosurfaces at the Fermi
        # energy mesh data is generated by a marching cubes algorithm when the
        # FermiSurface object is created.
        surfaces = []
        for c, (verts, faces) in zip(colors, isosurfaces):
            positions = verts[faces].reshape(-1, 3).tolist()
            surface = Surface(positions=positions, color=c, opacity=opacity)
            surfaces.append(surface)
        fermi_surface = Scene("fermi_surface", contents=surfaces)
        scene_contents.append(fermi_surface)

        # add the cell outline to the plot
        lines = Lines(positions=list(self.reciprocal_space.lines.flatten()))
        # alternatively,
        # cylinders have finite width and are lighted, but no strong reason to choose
        # one over the other
        # cylinders = Cylinders(positionPairs=self.reciprocal_space.lines.tolist(),
        #                       radius=0.01, color="rgb(0,0,0)")
        scene_contents.append(lines)

        spheres = []
        for position, label in zip(self._symmetry_pts[0],
                                   self._symmetry_pts[1]):
            sphere = Spheres(
                positions=[list(position)],
                tooltip=label,
                radius=0.05,
                color="rgb(0, 0, 0)",
            )
            spheres.append(sphere)
        label_scene = Scene("labels", contents=spheres)
        scene_contents.append(label_scene)

        return Scene("ifermi", contents=scene_contents)
Пример #8
0
                try:

                    # all_positions = [[0, 0, 0], [0, 0, 10], [0, 10, 0], [10, 0, 0]]
                    # gives...
                    # .convex_hull = [[2, 3, 0], [1, 3, 0], [1, 2, 0], [1, 2, 3]]
                    # .vertex_neighbor_vertices = [1, 2, 3, 2, 3, 0, 1, 3, 0, 1, 2, 0]

                    vertices_indices = Delaunay(all_positions).convex_hull
                except Exception as e:
                    vertices_indices = []

                vertices = [
                    all_positions[idx] for idx in chain.from_iterable(vertices_indices)
                ]

                polyhedron = [Surface(positions=vertices, color=site_color)]

            else:

                polyhedron = [Convex(positions=all_positions, color=site_color)]

    return Scene(
        self.species_string,
        [
            Scene("atoms", contents=atoms),
            Scene("bonds", contents=bonds),
            Scene("polyhedra", contents=polyhedron),
            Scene("magmoms", contents=magmoms),
        ],
        origin=origin,
    )
Пример #9
0
def get_site_scene(
    self,
    connected_sites: List[ConnectedSite] = None,
    connected_sites_not_drawn: List[ConnectedSite] = None,
    hide_incomplete_edges: bool = False,
    incomplete_edge_length_scale: Optional[float] = 1.0,
    connected_sites_colors: Optional[List[str]] = None,
    connected_sites_not_drawn_colors: Optional[List[str]] = None,
    origin: List[float] = (0, 0, 0),
    ellipsoid_site_prop: str = None,
    draw_polyhedra: bool = True,
    explicitly_calculate_polyhedra_hull: bool = False,
) -> Scene:
    """

    Args:
        self:
        connected_sites:
        connected_sites_not_drawn:
        hide_incomplete_edges:
        incomplete_edge_length_scale:
        connected_sites_colors:
        connected_sites_not_drawn_colors:
        origin:
        ellipsoid_site_prop:
        explicitly_calculate_polyhedra_hull:

    Returns:

    """

    atoms = []
    bonds = []
    polyhedron = []

    # for disordered structures
    is_ordered = self.is_ordered
    phiStart, phiEnd = None, None
    occu_start = 0.0

    # for thermal ellipsoids etc.
    def _get_ellipsoids_from_matrix(matrix):
        raise NotImplementedError
        # matrix = np.array(matrix)
        # eigenvalues, eigenvectors = np.linalg.eig(matrix)

    if ellipsoid_site_prop:
        matrix = self.properties[ellipsoid_site_prop]
        ellipsoids = _get_ellipsoids_from_matrix(matrix)
    else:
        ellipsoids = None

    position = np.subtract(self.coords, origin).tolist()

    # site_color is used for bonds and polyhedra, if multiple colors are
    # defined for site (e.g. a disordered site), then we use grey
    all_colors = set(self.properties["display_color"])
    if len(all_colors) > 1:
        site_color = "#555555"
    else:
        site_color = list(all_colors)[0]

    for idx, (sp, occu) in enumerate(self.species.items()):

        if isinstance(sp, DummySpecie):

            cube = Cubes(
                positions=[position],
                color=self.properties["display_color"][idx],
                width=0.4,
            )
            atoms.append(cube)

        else:

            color = self.properties["display_color"][idx]
            radius = self.properties["display_radius"][idx]

            # TODO: make optional/default to None
            # in disordered structures, we fractionally color-code spheres,
            # drawing a sphere segment from phi_end to phi_start
            # (think a sphere pie chart)
            if not is_ordered:
                phi_frac_end = occu_start + occu
                phi_frac_start = occu_start
                occu_start = phi_frac_end
                phiStart = phi_frac_start * np.pi * 2
                phiEnd = phi_frac_end * np.pi * 2

            # TODO: add names for labels
            # name = "{}".format(sp)
            # if occu != 1.0:
            #    name += " ({}% occupancy)".format(occu)

            sphere = Spheres(
                positions=[position],
                color=color,
                radius=radius,
                phiStart=phiStart,
                phiEnd=phiEnd,
            )
            atoms.append(sphere)

    if not is_ordered and not np.isclose(phiEnd, np.pi * 2):
        # if site occupancy doesn't sum to 100%, cap sphere
        sphere = Spheres(
            positions=[position],
            color="#ffffff",
            radius=self.properties["display_radius"][0],
            phiStart=phiEnd,
            phiEnd=np.pi * 2,
        )
        atoms.append(sphere)

    if connected_sites:

        all_positions = []
        for idx, connected_site in enumerate(connected_sites):

            connected_position = np.subtract(connected_site.site.coords,
                                             origin)
            bond_midpoint = np.add(position, connected_position) / 2

            if connected_sites_colors:
                color = connected_sites_colors[idx]
            else:
                color = site_color

            cylinder = Cylinders(
                positionPairs=[[position, bond_midpoint.tolist()]],
                color=color)
            bonds.append(cylinder)
            all_positions.append(connected_position.tolist())

        if connected_sites_not_drawn and not hide_incomplete_edges:

            for idx, connected_site in enumerate(connected_sites_not_drawn):

                connected_position = np.subtract(connected_site.site.coords,
                                                 origin)
                bond_midpoint = (incomplete_edge_length_scale *
                                 np.add(position, connected_position) / 2)

                if connected_sites_not_drawn_colors:
                    color = connected_sites_not_drawn_colors[idx]
                else:
                    color = site_color

                cylinder = Cylinders(
                    positionPairs=[[position, bond_midpoint.tolist()]],
                    color=color)
                bonds.append(cylinder)
                all_positions.append(connected_position.tolist())
        if (draw_polyhedra and len(connected_sites) > 3
                and not connected_sites_not_drawn):
            if explicitly_calculate_polyhedra_hull:

                try:

                    # all_positions = [[0, 0, 0], [0, 0, 10], [0, 10, 0], [10, 0, 0]]
                    # gives...
                    # .convex_hull = [[2, 3, 0], [1, 3, 0], [1, 2, 0], [1, 2, 3]]
                    # .vertex_neighbor_vertices = [1, 2, 3, 2, 3, 0, 1, 3, 0, 1, 2, 0]

                    vertices_indices = Delaunay(all_positions).convex_hull
                except Exception as e:
                    vertices_indices = []

                vertices = [
                    all_positions[idx]
                    for idx in chain.from_iterable(vertices_indices)
                ]

                polyhedron = [
                    Surface(
                        positions=vertices,
                        color=self.properties["display_color"][0],
                    )
                ]

            else:

                polyhedron = [
                    Convex(positions=all_positions, color=site_color)
                ]

    return Scene(
        self.species_string,
        [
            Scene("atoms", contents=atoms),
            Scene("bonds", contents=bonds),
            Scene("polyhedra", contents=polyhedron),
        ],
    )
def test_scenes(scene_dicts, scene_dict):
    expected = Scene("125_up", contents=[Surface(positions=[[0]*3, [0.1]*3, [0.2]*3])])
    assert scene_dicts.scenes == {"125_up": expected}
    assert scene_dict.scene("125_up") == expected
Пример #11
0
def get_site_scene(
    self,
    connected_sites=None,
    connected_site_color=None,
    origin=(0, 0, 0),
    ellipsoid_site_prop=None,
    all_connected_sites_present=True,
    explicitly_calculate_polyhedra_hull=False,
) -> Scene:
    """
        Sites must have display_radius and display_color, display_vector,
        display_ellipsoid site properties.

        TODO: add bond colours to connected_site_properties
        :param site:
        :param connected_sites:
        :param origin:
        :param all_connected_sites_present: if False, will not calculate
        polyhedra since this would be misleading
        :param explicitly_calculate_polyhedra_hull:
        :return:
        """

    atoms = []
    bonds = []
    polyhedron = []

    # for disordered structures
    is_ordered = self.is_ordered
    phiStart, phiEnd = None, None
    occu_start = 0.0

    # for thermal ellipsoids etc.
    def _get_ellipsoids_from_matrix(matrix):
        raise NotImplementedError
        # matrix = np.array(matrix)
        # eigenvalues, eigenvectors = np.linalg.eig(matrix)

    if ellipsoid_site_prop:
        matrix = self.properties[ellipsoid_site_prop]
        ellipsoids = _get_ellipsoids_from_matrix(matrix)
    else:
        ellipsoids = None

    position = np.subtract(self.coords, origin).tolist()

    # site_color is used for bonds and polyhedra, if multiple colors are
    # defined for site (e.g. a disordered site), then we use grey
    all_colors = set(self.properties["display_color"])
    if len(all_colors) > 1:
        site_color = "#555555"
    else:
        site_color = list(all_colors)[0]

    for idx, (sp, occu) in enumerate(self.species.items()):

        if isinstance(sp, DummySpecie):

            cube = Cubes(
                positions=[position],
                color=self.properties["display_color"][idx],
                width=0.4,
            )
            atoms.append(cube)

        else:

            color = self.properties["display_color"][idx]
            radius = self.properties["display_radius"][idx]

            # TODO: make optional/default to None
            # in disordered structures, we fractionally color-code spheres,
            # drawing a sphere segment from phi_end to phi_start
            # (think a sphere pie chart)
            if not is_ordered:
                phi_frac_end = occu_start + occu
                phi_frac_start = occu_start
                occu_start = phi_frac_end
                phiStart = phi_frac_start * np.pi * 2
                phiEnd = phi_frac_end * np.pi * 2

            # TODO: add names for labels
            # name = "{}".format(sp)
            # if occu != 1.0:
            #    name += " ({}% occupancy)".format(occu)

            sphere = Spheres(
                positions=[position],
                color=color,
                radius=radius,
                phiStart=phiStart,
                phiEnd=phiEnd,
                ellipsoids=ellipsoids,
            )
            atoms.append(sphere)

    if not is_ordered and not np.isclose(phiEnd, np.pi * 2):
        # if site occupancy doesn't sum to 100%, cap sphere
        sphere = Spheres(
            positions=[position],
            color="#ffffff",
            radius=self.properties["display_radius"][0],
            phiStart=phiEnd,
            phiEnd=np.pi * 2,
            ellipsoids=ellipsoids,
        )
        atoms.append(sphere)

    if connected_sites:

        all_positions = []
        for connected_site in connected_sites:

            connected_position = np.subtract(connected_site.site.coords,
                                             origin)
            bond_midpoint = np.add(position, connected_position) / 2

            cylinder = Cylinders(
                positionPairs=[[position, bond_midpoint.tolist()]],
                color=site_color)
            bonds.append(cylinder)
            all_positions.append(connected_position.tolist())

        if len(connected_sites) > 3 and all_connected_sites_present:
            if explicitly_calculate_polyhedra_hull:

                try:

                    # all_positions = [[0, 0, 0], [0, 0, 10], [0, 10, 0], [10, 0, 0]]
                    # gives...
                    # .convex_hull = [[2, 3, 0], [1, 3, 0], [1, 2, 0], [1, 2, 3]]
                    # .vertex_neighbor_vertices = [1, 2, 3, 2, 3, 0, 1, 3, 0, 1, 2, 0]

                    vertices_indices = Delaunay(
                        all_positions).vertex_neighbor_vertices
                    vertices = [all_positions[idx] for idx in vertices_indices]

                    polyhedron = [
                        Surface(
                            positions=vertices,
                            color=self.properties["display_color"][0],
                        )
                    ]

                except Exception as e:

                    polyhedron = []

            else:

                polyhedron = [
                    Convex(positions=all_positions, color=site_color)
                ]

    return Scene(
        self.species_string,
        [
            Scene("atoms", contents=atoms),
            Scene("bonds", contents=bonds),
            Scene("polyhedra", contents=polyhedron),
        ],
    )