Instruction:
Write a Python 3 script to
insert a node into the MoleculeGraph.

Response:
def insert_node(self, i, species, coords, validate_proximity=False,
                    site_properties=None, edges=None):
        """
        A wrapper around Molecule.insert(), which also incorporates the new
        site into the MoleculeGraph.

        :param i: Index at which to insert the new site
        :param species: Species for the new site
        :param coords: 3x1 array representing coordinates of the new site
        :param validate_proximity: For Molecule.insert(); if True (default
            False), distance will be checked to ensure that site can be safely
            added.
        :param site_properties: Site properties for Molecule
        :param edges: List of dicts representing edges to be added to the
            MoleculeGraph. These edges must include the index of the new site i,
            and all indices used for these edges should reflect the
            MoleculeGraph AFTER the insertion, NOT before. Each dict should at
            least have a "to_index" and "from_index" key, and can also have a
            "weight" and a "properties" key.
        :return:
        """

        self.molecule.insert(i, species, coords,
                             validate_proximity=validate_proximity,
                             properties=site_properties)

        mapping = {}
        for j in range(len(self.molecule) - 1):
            if j < i:
                mapping[j] = j
            else:
                mapping[j] = j + 1
        nx.relabel_nodes(self.graph, mapping, copy=False)

        self.graph.add_node(i)
        self.set_node_attributes()

        if edges is not None:
            for edge in edges:
                try:
                    self.add_edge(edge["from_index"], edge["to_index"],
                                  weight=edge.get("weight", None),
                                  edge_properties=edge.get("properties", None))
                except KeyError:
                    raise RuntimeError("Some edges are invalid.")


Instruction:
Create a Python 3 function to
set the node attributes of the MoleculeGraph to the values of the species coords and properties of the nodes in the MoleculeGraph.

Response:
def set_node_attributes(self):
        """
        Replicates molecule site properties (specie, coords, etc.) in the
        MoleculeGraph.

        :return:
        """

        species = {}
        coords = {}
        properties = {}
        for node in self.graph.nodes():
            species[node] = self.molecule[node].specie.symbol
            coords[node] = self.molecule[node].coords
            properties[node] = self.molecule[node].properties

        nx.set_node_attributes(self.graph, species, "specie")
        nx.set_node_attributes(self.graph, coords, "coords")
        nx.set_node_attributes(self.graph, properties, "properties")


Instruction:
Can you create a Python 3 function that
alters either the weight or the edge_properties of an edge in the MoleculeGraph. :param from_index: int :param to_index: int :param new_weight: alter_edge does not require that weight be altered. As such, by default, this is None. If weight is to be changed, it should be a float. :param new_edge_properties: alter_edge does not require that edge_properties be altered. As such, by default, this is None. If any edge properties are to be changed, it should be a dictionary of edge properties to be changed. :return:

Response:
def alter_edge(self, from_index, to_index,
                   new_weight=None, new_edge_properties=None):
        """
        Alters either the weight or the edge_properties of
        an edge in the MoleculeGraph.

        :param from_index: int
        :param to_index: int
        :param new_weight: alter_edge does not require
        that weight be altered. As such, by default, this
        is None. If weight is to be changed, it should be a
        float.
        :param new_edge_properties: alter_edge does not require
        that edge_properties be altered. As such, by default,
        this is None. If any edge properties are to be changed,
        it should be a dictionary of edge properties to be changed.
        :return:
        """

        existing_edge = self.graph.get_edge_data(from_index, to_index)

        # ensure that edge exists before attempting to change it
        if not existing_edge:
            raise ValueError("Edge between {} and {} cannot be altered;\
                                no edge exists between those sites.".format(
                                from_index, to_index
                                ))

        # Third index should always be 0 because there should only be one edge between any two nodes
        if new_weight is not None:
            self.graph[from_index][to_index][0]['weight'] = new_weight

        if new_edge_properties is not None:
            for prop in list(new_edge_properties.keys()):
                self.graph[from_index][to_index][0][prop] = new_edge_properties[prop]


Instruction:
How would you code a function in Python 3 to
remove an edge from the MoleculeGraph and returns the MoleculeGraphVersion.

Response:
def break_edge(self, from_index, to_index, allow_reverse=False):
        """
        Remove an edge from the MoleculeGraph

        :param from_index: int
        :param to_index: int
        :param allow_reverse: If allow_reverse is True, then break_edge will
        attempt to break both (from_index, to_index) and, failing that,
        will attempt to break (to_index, from_index).
        :return:
        """

        # ensure that edge exists before attempting to remove it
        existing_edge = self.graph.get_edge_data(from_index, to_index)
        existing_reverse = None

        if existing_edge:
            self.graph.remove_edge(from_index, to_index)

        else:
            if allow_reverse:
                existing_reverse = self.graph.get_edge_data(to_index,
                                                            from_index)

            if existing_reverse:
                self.graph.remove_edge(to_index, from_index)
            else:
                raise ValueError("Edge cannot be broken between {} and {};\
                                no edge exists between those sites.".format(
                                from_index, to_index
                                ))


Instruction:
Can you create a Python 3 function that
splits MoleculeGraph into two or more MoleculeGraphs by breaking a set of bonds. This function uses MoleculeGraph.break_edge repeatedly to create disjoint graphs (two or more separate molecules). This function does not only alter the graph information, but also changes the underlying Moledules. If the bonds parameter does not include sufficient bonds to separate two molecule fragments, then this function will fail. Currently, this function naively assigns the charge of the total molecule to a single submolecule. A later effort will be to actually accurately assign charge. NOTE: This function does not modify the original MoleculeGraph. It creates a copy, modifies that, and returns two or more new MoleculeGraph objects. :param bonds: list of tuples (from_index, to_index) representing bonds to be broken to split the MoleculeGraph. :param alterations: a dict {(from_index, to_index): alt}, where alt is a dictionary including weight and/or edge properties to be changed following the split. :param allow_reverse: If allow_reverse is True, then break_edge will attempt to break both (from_index, to_index) and, failing that, will attempt to break (to_index, from_index). :return: list of MoleculeGraphs

Response:
def split_molecule_subgraphs(self, bonds, allow_reverse=False,
                                 alterations=None):
        """
        Split MoleculeGraph into two or more MoleculeGraphs by
        breaking a set of bonds. This function uses
        MoleculeGraph.break_edge repeatedly to create
        disjoint graphs (two or more separate molecules).
        This function does not only alter the graph
        information, but also changes the underlying
        Moledules.
        If the bonds parameter does not include sufficient
        bonds to separate two molecule fragments, then this
        function will fail.
        Currently, this function naively assigns the charge
        of the total molecule to a single submolecule. A
        later effort will be to actually accurately assign
        charge.
        NOTE: This function does not modify the original
        MoleculeGraph. It creates a copy, modifies that, and
        returns two or more new MoleculeGraph objects.

        :param bonds: list of tuples (from_index, to_index)
        representing bonds to be broken to split the MoleculeGraph.
        :param alterations: a dict {(from_index, to_index): alt},
        where alt is a dictionary including weight and/or edge
        properties to be changed following the split.
        :param allow_reverse: If allow_reverse is True, then break_edge will
        attempt to break both (from_index, to_index) and, failing that,
        will attempt to break (to_index, from_index).
        :return: list of MoleculeGraphs
        """

        self.set_node_attributes()

        original = copy.deepcopy(self)

        for bond in bonds:
            original.break_edge(bond[0], bond[1], allow_reverse=allow_reverse)

        if nx.is_weakly_connected(original.graph):
            raise MolGraphSplitError("Cannot split molecule; \
                                MoleculeGraph is still connected.")
        else:

            # alter any bonds before partition, to avoid remapping
            if alterations is not None:
                for (u, v) in alterations.keys():
                    if "weight" in alterations[(u, v)]:
                        weight = alterations[(u, v)]["weight"]
                        del alterations[(u, v)]["weight"]
                        edge_properties = alterations[(u, v)] \
                            if len(alterations[(u, v)]) != 0 else None
                        original.alter_edge(u, v, new_weight=weight,
                                        new_edge_properties=edge_properties)
                    else:
                        original.alter_edge(u, v,
                                        new_edge_properties=alterations[(u, v)])

            sub_mols = []

            # Had to use nx.weakly_connected_components because of deprecation
            # of nx.weakly_connected_component_subgraphs
            components = nx.weakly_connected_components(original.graph)
            subgraphs = [original.graph.subgraph(c) for c in components]

            for subg in subgraphs:

                nodes = sorted(list(subg.nodes))

                # Molecule indices are essentially list-based, so node indices
                # must be remapped, incrementing from 0
                mapping = {}
                for i in range(len(nodes)):
                    mapping[nodes[i]] = i

                # just give charge to whatever subgraph has node with index 0
                # TODO: actually figure out how to distribute charge
                if 0 in nodes:
                    charge = self.molecule.charge
                else:
                    charge = 0

                # relabel nodes in graph to match mapping
                new_graph = nx.relabel_nodes(subg, mapping)

                species = nx.get_node_attributes(new_graph, "specie")
                coords = nx.get_node_attributes(new_graph, "coords")
                raw_props = nx.get_node_attributes(new_graph, "properties")

                properties = {}
                for prop_set in raw_props.values():
                    for prop in prop_set.keys():
                        if prop in properties:
                            properties[prop].append(prop_set[prop])
                        else:
                            properties[prop] = [prop_set[prop]]

                # Site properties must be present for all atoms in the molecule
                # in order to be used for Molecule instantiation
                for k, v in properties.items():
                    if len(v) != len(species):
                        del properties[k]

                new_mol = Molecule(species, coords, charge=charge,
                                   site_properties=properties)
                graph_data = json_graph.adjacency_data(new_graph)

                # create new MoleculeGraph
                sub_mols.append(MoleculeGraph(new_mol, graph_data=graph_data))

            return sub_mols


Instruction:
Can you generate a brief explanation for the following Python 3 code
def build_unique_fragments(self):
        self.set_node_attributes()

        graph = self.graph.to_undirected()

        nm = iso.categorical_node_match("specie", "ERROR")

        # find all possible fragments, aka connected induced subgraphs
        all_fragments = []
        for ii in range(1, len(self.molecule)):
            for combination in combinations(graph.nodes, ii):
                subgraph = nx.subgraph(graph, combination)
                if nx.is_connected(subgraph):
                    all_fragments.append(subgraph)

        # narrow to all unique fragments using graph isomorphism
        unique_fragments = []
        for fragment in all_fragments:
            if not [nx.is_isomorphic(fragment, f, node_match=nm)
                    for f in unique_fragments].count(True) >= 1:
                unique_fragments.append(fragment)

        # convert back to molecule graphs
        unique_mol_graphs = []
        for fragment in unique_fragments:
            mapping = {e: i for i, e in enumerate(sorted(fragment.nodes))}
            remapped = nx.relabel_nodes(fragment, mapping)

            species = nx.get_node_attributes(remapped, "specie")
            coords = nx.get_node_attributes(remapped, "coords")

            edges = {}

            for from_index, to_index, key in remapped.edges:
                edge_props = fragment.get_edge_data(from_index, to_index, key=key)

                edges[(from_index, to_index)] = edge_props

            unique_mol_graphs.append(self.with_edges(Molecule(species=species,
                                                              coords=coords,
                                                              charge=self.molecule.charge),
                                                     edges))
        return unique_mol_graphs

Response: Build a list of unique fragments for the current Molecule.


Instruction:
How would you implement a function in Python 3 that
substitutes an atom in self. molecule to a functional group.

Response:
def substitute_group(self, index, func_grp, strategy, bond_order=1,
                         graph_dict=None, strategy_params=None, reorder=True,
                         extend_structure=True):
        """
        Builds off of Molecule.substitute to replace an atom in self.molecule
        with a functional group. This method also amends self.graph to
        incorporate the new functional group.

        NOTE: using a MoleculeGraph will generally produce a different graph
        compared with using a Molecule or str (when not using graph_dict).
        This is because of the reordering that occurs when using some of the
        local_env strategies.

        :param index: Index of atom to substitute.
        :param func_grp: Substituent molecule. There are three options:

                1. Providing an actual molecule as the input. The first atom
                   must be a DummySpecie X, indicating the position of
                   nearest neighbor. The second atom must be the next
                   nearest atom. For example, for a methyl group
                   substitution, func_grp should be X-CH3, where X is the
                   first site and C is the second site. What the code will
                   do is to remove the index site, and connect the nearest
                   neighbor to the C atom in CH3. The X-C bond indicates the
                   directionality to connect the atoms.
                2. A string name. The molecule will be obtained from the
                   relevant template in func_groups.json.
                3. A MoleculeGraph object.
        :param strategy: Class from pymatgen.analysis.local_env.
        :param bond_order: A specified bond order to calculate the bond
                length between the attached functional group and the nearest
                neighbor site. Defaults to 1.
        :param graph_dict: Dictionary representing the bonds of the functional
                group (format: {(u, v): props}, where props is a dictionary of
                properties, including weight. If None, then the algorithm
                will attempt to automatically determine bonds using one of
                a list of strategies defined in pymatgen.analysis.local_env.
        :param strategy_params: dictionary of keyword arguments for strategy.
                If None, default parameters will be used.
        :param reorder: bool, representing if graph nodes need to be reordered
                following the application of the local_env strategy
        :param extend_structure: If True (default), then a large artificial box
                will be placed around the Molecule, because some strategies assume
                periodic boundary conditions.
        :return:
        """

        def map_indices(grp):
            grp_map = {}

            # Get indices now occupied by functional group
            # Subtracting 1 because the dummy atom X should not count
            atoms = len(grp) - 1
            offset = len(self.molecule) - atoms

            for i in range(atoms):
                grp_map[i] = i + offset

            return grp_map

        # Work is simplified if a graph is already in place
        if isinstance(func_grp, MoleculeGraph):

            self.molecule.substitute(index, func_grp.molecule,
                                     bond_order=bond_order)

            mapping = map_indices(func_grp.molecule)

            for (u, v) in list(func_grp.graph.edges()):
                edge_props = func_grp.graph.get_edge_data(u, v)[0]
                weight = None
                if "weight" in edge_props.keys():
                    weight = edge_props["weight"]
                    del edge_props["weight"]
                self.add_edge(mapping[u], mapping[v],
                              weight=weight, edge_properties=edge_props)

        else:
            if isinstance(func_grp, Molecule):
                func_grp = copy.deepcopy(func_grp)
            else:
                try:
                    func_grp = copy.deepcopy(FunctionalGroups[func_grp])
                except:
                    raise RuntimeError("Can't find functional group in list. "
                                       "Provide explicit coordinate instead")

            self.molecule.substitute(index, func_grp, bond_order=bond_order)

            mapping = map_indices(func_grp)

            # Remove dummy atom "X"
            func_grp.remove_species("X")

            if graph_dict is not None:
                for (u, v) in graph_dict.keys():
                    edge_props = graph_dict[(u, v)]
                    if "weight" in edge_props.keys():
                        weight = edge_props["weight"]
                        del edge_props["weight"]
                    self.add_edge(mapping[u], mapping[v],
                                  weight=weight, edge_properties=edge_props)

            else:
                if strategy_params is None:
                    strategy_params = {}
                strat = strategy(**strategy_params)
                graph = self.with_local_env_strategy(func_grp, strat, reorder=reorder,
                                                     extend_structure=extend_structure)

                for (u, v) in list(graph.graph.edges()):
                    edge_props = graph.graph.get_edge_data(u, v)[0]
                    weight = None
                    if "weight" in edge_props.keys():
                        weight = edge_props["weight"]
                        del edge_props["weight"]

                    if 0 not in list(graph.graph.nodes()):
                        # If graph indices have different indexing
                        u, v = (u-1), (v-1)

                    self.add_edge(mapping[u], mapping[v],
                                  weight=weight, edge_properties=edge_props)


Instruction:
Can you generate a brief explanation for the following Python 3 code
def replace_group(self, index, func_grp, strategy, bond_order=1,
                      graph_dict=None, strategy_params=None, reorder=True,
                      extend_structure=True):
        """
        Builds off of Molecule.substitute and MoleculeGraph.substitute_group
        to replace a functional group in self.molecule with a functional group.
        This method also amends self.graph to incorporate the new functional
        group.

        TODO: Figure out how to replace into a ring structure.

        :param index: Index of atom to substitute.
        :param func_grp: Substituent molecule. There are three options:

                1. Providing an actual molecule as the input. The first atom
                   must be a DummySpecie X, indicating the position of
                   nearest neighbor. The second atom must be the next
                   nearest atom. For example, for a methyl group
                   substitution, func_grp should be X-CH3, where X is the
                   first site and C is the second site. What the code will
                   do is to remove the index site, and connect the nearest
                   neighbor to the C atom in CH3. The X-C bond indicates the
                   directionality to connect the atoms.
                2. A string name. The molecule will be obtained from the
                   relevant template in func_groups.json.
                3. A MoleculeGraph object.
        :param strategy: Class from pymatgen.analysis.local_env.
        :param bond_order: A specified bond order to calculate the bond
                length between the attached functional group and the nearest
                neighbor site. Defaults to 1.
        :param graph_dict: Dictionary representing the bonds of the functional
                group (format: {(u, v): props}, where props is a dictionary of
                properties, including weight. If None, then the algorithm
                will attempt to automatically determine bonds using one of
                a list of strategies defined in pymatgen.analysis.local_env.
        :param strategy_params: dictionary of keyword arguments for strategy.
                If None, default parameters will be used.
        :param reorder: bool, representing if graph nodes need to be reordered
                following the application of the local_env strategy
        :param extend_structure: If True (default), then a large artificial box
                will be placed around the Molecule, because some strategies assume
                periodic boundary conditions.
        :return:
        """

        self.set_node_attributes()
        neighbors = self.get_connected_sites(index)

        # If the atom at index is terminal
        if len(neighbors) == 1:
            self.substitute_group(index, func_grp, strategy,
                                  bond_order=bond_order, graph_dict=graph_dict,
                                  strategy_params=strategy_params,
                                  reorder=reorder,
                                  extend_structure=extend_structure)

        else:
            rings = self.find_rings(including=[index])
            if len(rings) != 0:
                raise RuntimeError("Currently functional group replacement"
                                   "cannot occur at an atom within a ring"
                                   "structure.")

            to_remove = set()
            sizes = dict()
            disconnected = self.graph.to_undirected()
            disconnected.remove_node(index)
            for neighbor in neighbors:
                sizes[neighbor[2]] = len(nx.descendants(disconnected, neighbor[2]))

            keep = max(sizes, key=lambda x: sizes[x])
            for i in sizes.keys():
                if i != keep:
                    to_remove.add(i)

            self.remove_nodes(list(to_remove))

            self.substitute_group(index, func_grp, strategy,
                                  bond_order=bond_order, graph_dict=graph_dict,
                                  strategy_params=strategy_params,
                                  reorder=reorder,
                                  extend_structure=extend_structure)

Response: Substitute a functional group in self. molecule with a functional group.


Instruction:
Explain what the following Python 3 code does
def find_rings(self, including=None):

        # Copies self.graph such that all edges (u, v) matched by edges (v, u)
        undirected = self.graph.to_undirected()
        directed = undirected.to_directed()

        cycles_nodes = []
        cycles_edges = []

        # Remove all two-edge cycles
        all_cycles = [c for c in nx.simple_cycles(directed) if len(c) > 2]

        # Using to_directed() will mean that each cycle always appears twice
        # So, we must also remove duplicates
        unique_sorted = []
        unique_cycles = []
        for cycle in all_cycles:
            if sorted(cycle) not in unique_sorted:
                unique_sorted.append(sorted(cycle))
                unique_cycles.append(cycle)

        if including is None:
            cycles_nodes = unique_cycles
        else:
            for i in including:
                for cycle in unique_cycles:
                    if i in cycle and cycle not in cycles_nodes:
                        cycles_nodes.append(cycle)

        for cycle in cycles_nodes:
            edges = []
            for i, e in enumerate(cycle):
                edges.append((cycle[i-1], e))
            cycles_edges.append(edges)

        return cycles_edges

Response: Find all rings in the MoleculeGraph.


Instruction:
Implement a function in Python 3 to
return a list of connected sites for a given site in the Molecule.

Response:
def get_connected_sites(self, n):
        """
        Returns a named tuple of neighbors of site n:
        periodic_site, jimage, index, weight.
        Index is the index of the corresponding site
        in the original structure, weight can be
        None if not defined.
        :param n: index of Site in Molecule
        :param jimage: lattice vector of site
        :return: list of ConnectedSite tuples,
        sorted by closest first
        """

        connected_sites = set()

        out_edges = [(u, v, d) for u, v, d in self.graph.out_edges(n, data=True)]
        in_edges = [(u, v, d) for u, v, d in self.graph.in_edges(n, data=True)]

        for u, v, d in out_edges + in_edges:

            weight = d.get('weight', None)

            if v == n:
                site = self.molecule[u]
                dist = self.molecule[v].distance(self.molecule[u])

                connected_site = ConnectedSite(site=site,
                                               jimage=(0, 0, 0),
                                               index=u,
                                               weight=weight,
                                               dist=dist)
            else:
                site = self.molecule[v]
                dist = self.molecule[u].distance(self.molecule[v])

                connected_site = ConnectedSite(site=site,
                                               jimage=(0, 0, 0),
                                               index=v,
                                               weight=weight,
                                               dist=dist)

            connected_sites.add(connected_site)

        # return list sorted by closest sites first
        connected_sites = list(connected_sites)
        connected_sites.sort(key=lambda x: x.dist)

        return connected_sites


Instruction:
How would you explain what the following Python 3 function does
def as_dict(self):

        d = {"@module": self.__class__.__module__,
             "@class": self.__class__.__name__,
             "molecule": self.molecule.as_dict(),
             "graphs": json_graph.adjacency_data(self.graph)}

        return d

Response: As in dict of dicts


Instruction:
How would you implement a function in Python 3 that
creates a new Molecule object from a dictionary.

Response:
def from_dict(cls, d):
        """
        As in :Class: `pymatgen.core.Molecule` except
        restoring graphs using `from_dict_of_dicts`
        from NetworkX to restore graph information.
        """
        m = Molecule.from_dict(d['molecule'])
        return cls(m, d['graphs'])


Instruction:
Can you write a function in Python 3 where it
sorts the Molecule object by the given key.

Response:
def sort(self, key=None, reverse=False):
        """
        Same as Molecule.sort(), also remaps nodes in graph.
        :param key:
        :param reverse:
        :return:
        """

        old_molecule = self.molecule.copy()

        # sort Molecule
        self.molecule._sites = sorted(self.molecule._sites, key=key, reverse=reverse)

        # apply Molecule ordering to graph
        mapping = {idx: self.molecule.index(site) for idx, site in enumerate(old_molecule)}
        self.graph = nx.relabel_nodes(self.graph, mapping, copy=True)

        # normalize directions of edges
        edges_to_remove = []
        edges_to_add = []
        for u, v, k, d in self.graph.edges(keys=True, data=True):
            if v < u:
                new_v, new_u, new_d = u, v, d.copy()
                new_d['to_jimage'] = (0, 0, 0)
                edges_to_remove.append((u, v, k))
                edges_to_add.append((new_u, new_v, new_d))

        # add/delete marked edges
        for edges_to_remove in edges_to_remove:
            self.graph.remove_edge(*edges_to_remove)
        for (u, v, d) in edges_to_add:
            self.graph.add_edge(u, v, **d)


Instruction:
Can you generate the documentation for the following Python 3 function
def isomorphic_to(self, other):
        if self.molecule.composition != other.molecule.composition:
            return False
        else:
            self_undir = self.graph.to_undirected()
            other_undir = other.graph.to_undirected()
            nm = iso.categorical_node_match("specie", "ERROR")
            isomorphic = nx.is_isomorphic(self_undir, other_undir, node_match=nm)
            return isomorphic

Response: Checks if the graphs of two MoleculeGraphs are isomorphic to one MoleculeGraph object.


Instruction:
How would you explain what the following Python 3 function does
def _load_bond_length_data():
    with open(os.path.join(os.path.dirname(__file__),
                           "bond_lengths.json")) as f:
        data = collections.defaultdict(dict)
        for row in json.load(f):
            els = sorted(row['elements'])
            data[tuple(els)][row['bond_order']] = row['length']
        return data

Response: Loads bond length data from json file


Instruction:
Given the following Python 3 function, write the documentation
def obtain_all_bond_lengths(sp1, sp2, default_bl=None):
    if isinstance(sp1, Element):
        sp1 = sp1.symbol
    if isinstance(sp2, Element):
        sp2 = sp2.symbol
    syms = tuple(sorted([sp1, sp2]))
    if syms in bond_lengths:
        return bond_lengths[syms].copy()
    elif default_bl is not None:
        return {1: default_bl}
    else:
        raise ValueError("No bond data for elements {} - {}".format(*syms))

Response: Obtain all bond lengths for all bond orders in the species.


Instruction:
Can you implement a function in Python 3 that
calculates the bond order between two species and returns the value of the bond order.

Response:
def get_bond_order(sp1, sp2, dist, tol=0.2, default_bl=None):
    """
    Calculate the bond order given the distance of 2 species

    Args:
        sp1 (Specie): First specie.
        sp2 (Specie): Second specie.
        dist: Their distance in angstrom
        tol (float): Relative tolerance to test. Basically, the code
            checks if the distance between the sites is larger than
            (1 + tol) * the longest bond distance or smaller than
            (1 - tol) * the shortest bond distance to determine if
            they are bonded or the distance is too short.
            Defaults to 0.2.
        default_bl: If a particular type of bond does not exist, use this
            bond length (bond order = 1). If None, a ValueError will be thrown.

    Returns:
        Float value of bond order. For example, for C-C bond in benzene,
        return 1.7.
    """
    all_lengths = obtain_all_bond_lengths(sp1, sp2, default_bl)
    # Transform bond lengths dict to list assuming bond data is successive
    # and add an imaginary bond 0 length
    lengths_list = [all_lengths[1] * (1 + tol)] + \
                   [all_lengths[idx+1] for idx in range(len(all_lengths))]
    trial_bond_order = 0
    while trial_bond_order < len(lengths_list):
        if lengths_list[trial_bond_order] < dist:
            if trial_bond_order == 0:
                return trial_bond_order
            else:
                low_bl = lengths_list[trial_bond_order]
                high_bl = lengths_list[trial_bond_order - 1]
                return trial_bond_order - (dist - low_bl) / (high_bl - low_bl)
        trial_bond_order += 1
    # Distance shorter than the shortest bond length stored,
    # check if the distance is too short
    if dist < lengths_list[-1] * (1 - tol):  # too short
        warnings.warn('%.2f angstrom distance is too short for %s and %s'
                      % (dist, sp1, sp2))
    # return the highest bond order
    return trial_bond_order - 1


Instruction:
Here you have a function in Python 3, explain what it does
def get_bond_length(sp1, sp2, bond_order=1):
    sp1 = Element(sp1) if isinstance(sp1, str) else sp1
    sp2 = Element(sp2) if isinstance(sp2, str) else sp2
    try:
        all_lengths = obtain_all_bond_lengths(sp1, sp2)
        return all_lengths[bond_order]
    # The ValueError is raised in `obtain_all_bond_lengths` where no bond
    # data for both elements is found. The KeyError is raised in
    # `__getitem__` method of `dict` builtin class where although bond data
    # for both elements is found, the data for specified bond order does
    # not exist. In both cases, sum of atomic radius is returned.
    except (ValueError, KeyError):
        warnings.warn("No order %d bond lengths between %s and %s found in "
                      "database. Returning sum of atomic radius."
                      % (bond_order, sp1, sp2))
        return sp1.atomic_radius + sp2.atomic_radius

Response: Get the bond length between two species.


Instruction:
Can you generate the documentation for the following Python 3 function
def get_bond_order(self, tol=0.2, default_bl=None):
        sp1 = list(self.site1.species.keys())[0]
        sp2 = list(self.site2.species.keys())[0]
        dist = self.site1.distance(self.site2)
        return get_bond_order(sp1, sp2, dist, tol, default_bl)

Response: Returns the bond order of the species in the species list.


Instruction:
Create a Python 3 function to
test if two sites are bonded.

Response:
def is_bonded(site1, site2, tol=0.2, bond_order=None, default_bl=None):
        """
        Test if two sites are bonded, up to a certain limit.
        Args:
            site1 (Site): First site
            site2 (Site): Second site
            tol (float): Relative tolerance to test. Basically, the code
                checks if the distance between the sites is less than (1 +
                tol) * typical bond distances. Defaults to 0.2, i.e.,
                20% longer.
            bond_order: Bond order to test. If None, the code simply checks
                against all possible bond data. Defaults to None.
            default_bl: If a particular type of bond does not exist, use this
                bond length. If None, a ValueError will be thrown.
        Returns:
            Boolean indicating whether two sites are bonded.
        """
        sp1 = list(site1.species.keys())[0]
        sp2 = list(site2.species.keys())[0]
        dist = site1.distance(site2)
        syms = tuple(sorted([sp1.symbol, sp2.symbol]))
        if syms in bond_lengths:
            all_lengths = bond_lengths[syms]
            if bond_order:
                return dist < (1 + tol) * all_lengths[bond_order]
            for v in all_lengths.values():
                if dist < (1 + tol) * v:
                    return True
            return False
        elif default_bl:
            return dist < (1 + tol) * default_bl
        raise ValueError("No bond data for elements {} - {}".format(*syms))


Instruction:
Can you tell what is the following Python 3 function doing
def sendmail(subject, text, mailto, sender=None):
    def user_at_host():
        from socket import gethostname
        return os.getlogin() + "@" + gethostname()

    # Body of the message.
    try:
        sender = user_at_host() if sender is None else sender
    except OSError:
        sender = 'abipyscheduler@youknowwhere'

    if is_string(mailto): mailto = [mailto]

    from email.mime.text import MIMEText
    mail = MIMEText(text)
    mail["Subject"] = subject
    mail["From"] = sender
    mail["To"] = ", ".join(mailto)

    msg = mail.as_string()

    # sendmail works much better than the python interface.
    # Note that sendmail is available only on Unix-like OS.
    from subprocess import Popen, PIPE
    import sys

    sendmail = which("sendmail")
    if sendmail is None: return -1
    if sys.version_info[0] < 3:
        p = Popen([sendmail, "-t"], stdin=PIPE, stderr=PIPE)
    else:
        # msg is string not bytes so must use universal_newlines
        p = Popen([sendmail, "-t"], stdin=PIPE, stderr=PIPE, universal_newlines=True)

    outdata, errdata = p.communicate(msg)
    return len(errdata)

Response: Send an e - mail with unix sendmail.


Instruction:
Write a Python 3 function that can
declare a env variable.

Response:
def declare_var(self, key, val):
        """Declare a env variable. If val is None the variable is unset."""
        if val is not None:
            line = "export " + key + '=' + str(val)
        else:
            line = "unset " + key

        self._add(line)


Instruction:
Can you generate the documentation for the following Python 3 function
def declare_vars(self, d):
        for k, v in d.items():
            self.declare_var(k, v)

Response: Declare the variables defined in the dictionary d.


Instruction:
Can you write a function in Python 3 where it
exports an environment variable.

Response:
def export_envar(self, key, val):
        """Export an environment variable."""
        line = "export " + key + "=" + str(val)
        self._add(line)


Instruction:
Write a Python 3 function for
exporting the environment variables contained in the dict env.

Response:
def export_envars(self, env):
        """Export the environment variables contained in the dict env."""
        for k, v in env.items():
            self.export_envar(k, v)


Instruction:
Can you write a function in Python 3 where it
returns a string with the script and reset the editor if reset is True

Response:
def get_script_str(self, reset=True):
        """Returns a string with the script and reset the editor if reset is True"""
        s = "\n".join(l for l in self._lines)
        if reset:
            self.reset()
        return s


Instruction:
Implement a function in Python 3 to
run the first task that can be executed in each workflow and updates the database.

Response:
def single_shot(self):
        """
        Run the first :class:`Task` than is ready for execution.

        Returns:
            Number of jobs launched.
        """
        num_launched = 0

        # Get the tasks that can be executed in each workflow.
        tasks = []
        for work in self.flow:
            try:
                task = work.fetch_task_to_run()

                if task is not None:
                    tasks.append(task)
                else:
                    # No task found, this usually happens when we have dependencies.
                    # Beware of possible deadlocks here!
                    logger.debug("No task to run! Possible deadlock")

            except StopIteration:
                logger.info("All tasks completed.")

        # Submit the tasks and update the database.
        if tasks:
            tasks[0].start()
            num_launched += 1

            self.flow.pickle_dump()

        return num_launched


Instruction:
Can you write a function in Python 3 where it
keeps submitting `Tasks` until we are out of jobs or no job is ready to run. Args: max_nlaunch: Maximum number of launches. default: no limit. max_loops: Maximum number of loops sleep_time: seconds to sleep between rapidfire loop iterations Returns: The number of tasks launched.

Response:
def rapidfire(self, max_nlaunch=-1, max_loops=1, sleep_time=5):
        """
        Keeps submitting `Tasks` until we are out of jobs or no job is ready to run.

        Args:
            max_nlaunch: Maximum number of launches. default: no limit.
            max_loops: Maximum number of loops
            sleep_time: seconds to sleep between rapidfire loop iterations

        Returns:
            The number of tasks launched.
        """
        num_launched, do_exit, launched = 0, False, []

        for count in range(max_loops):
            if do_exit:
                break
            if count > 0:
                time.sleep(sleep_time)

            tasks = self.fetch_tasks_to_run()

            # I don't know why but we receive duplicated tasks.
            if any(task in launched for task in tasks):
                logger.critical("numtasks %d already in launched list:\n%s" % (len(tasks), launched))

            # Preventive test.
            tasks = [t for t in tasks if t not in launched]

            if not tasks:
                continue

            for task in tasks:
                fired = task.start()
                if fired:
                    launched.append(task)
                    num_launched += 1

                if num_launched >= max_nlaunch > 0:
                    logger.info('num_launched >= max_nlaunch, going back to sleep')
                    do_exit = True
                    break

        # Update the database.
        self.flow.pickle_dump()

        return num_launched


Instruction:
Implement a Python 3 function for
returning the list of tasks that can be submitted.

Response:
def fetch_tasks_to_run(self):
        """
        Return the list of tasks that can be submitted.
        Empty list if no task has been found.
        """
        tasks_to_run = []

        for work in self.flow:
            tasks_to_run.extend(work.fetch_alltasks_to_run())

        return tasks_to_run


Instruction:
How would you code a function in Python 3 to
read the configuration parameters from a Yaml file.

Response:
def from_file(cls, filepath):
        """Read the configuration parameters from a Yaml file."""
        with open(filepath, "rt") as fh:
            return cls(**yaml.safe_load(fh))


Instruction:
Implement a function in Python 3 to
create an istance from a string containing a YAML dictionary.

Response:
def from_string(cls, s):
        """Create an istance from string s containing a YAML dictionary."""
        stream = cStringIO(s)
        stream.seek(0)
        return cls(**yaml.safe_load(stream))


Instruction:
Explain what the following Python 3 code does
def from_user_config(cls):
        # Try in the current directory.
        path = os.path.join(os.getcwd(), cls.YAML_FILE)

        if os.path.exists(path):
            return cls.from_file(path)

        # Try in the configuration directory.
        path = os.path.join(cls.USER_CONFIG_DIR, cls.YAML_FILE)

        if os.path.exists(path):
            return cls.from_file(path)

        raise cls.Error("Cannot locate %s neither in current directory nor in %s" % (cls.YAML_FILE, path))

Response: Initialize the PyFlowScheduler from the user - supplied YAML file.


Instruction:
Given the following Python 3 function, write the documentation
def add_flow(self, flow):
        if hasattr(self, "_flow"):
            raise self.Error("Only one flow can be added to the scheduler.")

        # Check if we are already using a scheduler to run this flow
        flow.check_pid_file()
        flow.set_spectator_mode(False)

        # Build dirs and files (if not yet done)
        flow.build()

        with open(flow.pid_file, "wt") as fh:
            fh.write(str(self.pid))

        self._pid_file = flow.pid_file
        self._flow = flow

Response: Add an entry to the flow list.


Instruction:
Can you implement a function in Python 3 that
validates input parameters if customer service is on then create directory for tarball files with correct premissions for user and group.

Response:
def _validate_customer_service(self):
        """
        Validate input parameters if customer service is on then
        create directory for tarball files with correct premissions for user and group.
        """
        direc = self.customer_service_dir
        if not direc.exists:
            mode = 0o750
            print("Creating customer_service_dir %s with mode %s" % (direc, mode))
            direc.makedirs()
            os.chmod(direc.path, mode)

        if self.mailto is None:
            raise RuntimeError("customer_service_dir requires mailto option in scheduler.yml")


Instruction:
Can you generate the documentation for the following Python 3 function
def start(self):
        self.history.append("Started on %s" % time.asctime())
        self.start_time = time.time()

        if not has_apscheduler:
            raise RuntimeError("Install apscheduler with pip")

        if has_sched_v3:
            self.sched.add_job(self.callback, "interval", **self.sched_options)
        else:
            self.sched.add_interval_job(self.callback, **self.sched_options)

        errors = self.flow.look_before_you_leap()
        if errors:
            self.exceptions.append(errors)
            return 1

        # Try to run the job immediately. If something goes wrong return without initializing the scheduler.
        self._runem_all()

        if self.exceptions:
            self.cleanup()
            self.send_email(msg="Error while trying to run the flow for the first time!\n %s" % self.exceptions)
            return 1

        try:
            self.sched.start()
            return 0

        except KeyboardInterrupt:
            self.shutdown(msg="KeyboardInterrupt from user")
            if ask_yesno("Do you want to cancel all the jobs in the queue? [Y/n]"):
                print("Number of jobs cancelled:", self.flow.cancel())

            self.flow.pickle_dump()
            return -1

Response: Starts the scheduler in a new thread. Returns 0 if success.


Instruction:
Make a summary of the following Python 3 code
def _runem_all(self):
        excs = []
        flow = self.flow

        # Allow to change the manager at run-time
        if self.use_dynamic_manager:
            from pymatgen.io.abinit.tasks import TaskManager
            new_manager = TaskManager.from_user_config()
            for work in flow:
                work.set_manager(new_manager)

        nqjobs = 0
        if self.contact_resource_manager: # and flow.TaskManager.qadapter.QTYPE == "shell":
            # This call is expensive and therefore it's optional (must be activate in manager.yml)
            nqjobs = flow.get_njobs_in_queue()
            if nqjobs is None:
                nqjobs = 0
                if flow.manager.has_queue:
                    logger.warning('Cannot get njobs_inqueue')
        else:
            # Here we just count the number of tasks in the flow who are running.
            # This logic breaks down if there are multiple schedulers runnig
            # but it's easy to implement without having to contact the resource manager.
            nqjobs = (len(list(flow.iflat_tasks(status=flow.S_RUN))) +
                      len(list(flow.iflat_tasks(status=flow.S_SUB))))

        if nqjobs >= self.max_njobs_inqueue:
            print("Too many jobs in the queue: %s. No job will be submitted." % nqjobs)
            flow.check_status(show=False)
            return

        if self.max_nlaunches == -1:
            max_nlaunch = self.max_njobs_inqueue - nqjobs
        else:
            max_nlaunch = min(self.max_njobs_inqueue - nqjobs, self.max_nlaunches)

        # check status.
        flow.check_status(show=False)

        # This check is not perfect, we should make a list of tasks to sumbit
        # and select only the subset so that we don't exceeed mac_ncores_used
        # Many sections of this code should be rewritten.
        #if self.max_ncores_used is not None and flow.ncores_used > self.max_ncores_used:
        if self.max_ncores_used is not None and flow.ncores_allocated > self.max_ncores_used:
            print("Cannot exceed max_ncores_used %s" % self.max_ncores_used)
            return

        # Try to restart the unconverged tasks
        # TODO: do not fire here but prepare for fireing in rapidfire
        for task in self.flow.unconverged_tasks:
            try:
                logger.info("Flow will try restart task %s" % task)
                fired = task.restart()
                if fired:
                    self.nlaunch += 1
                    max_nlaunch -= 1
                    if max_nlaunch == 0:
                        logger.info("Restart: too many jobs in the queue, returning")
                        flow.pickle_dump()
                        return

            except task.RestartError:
                excs.append(straceback())

        # Temporarily disable by MG because I don't know if fix_critical works after the
        # introduction of the new qadapters
        # reenabled by MsS disable things that do not work at low level
        # fix only prepares for restarting, and sets to ready
        if self.fix_qcritical:
            nfixed = flow.fix_queue_critical()
            if nfixed: print("Fixed %d QCritical error(s)" % nfixed)

        nfixed = flow.fix_abicritical()
        if nfixed: print("Fixed %d AbiCritical error(s)" % nfixed)

        # update database
        flow.pickle_dump()

        # Submit the tasks that are ready.
        try:
            nlaunch = PyLauncher(flow).rapidfire(max_nlaunch=max_nlaunch, sleep_time=10)
            self.nlaunch += nlaunch
            if nlaunch:
                cprint("[%s] Number of launches: %d" % (time.asctime(), nlaunch), "yellow")

        except Exception:
            excs.append(straceback())

        # check status.
        flow.show_status()

        if excs:
            logger.critical("*** Scheduler exceptions:\n *** %s" % "\n".join(excs))
            self.exceptions.extend(excs)

Response: This function checks the status of all tasks and runs all the tasks that can be submitted.


Instruction:
Can you tell what is the following Python 3 function doing
def _callback(self):
        if self.debug:
            # Show the number of open file descriptors
            print(">>>>> _callback: Number of open file descriptors: %s" % get_open_fds())

        self._runem_all()

        # Mission accomplished. Shutdown the scheduler.
        all_ok = self.flow.all_ok
        if all_ok:
            return self.shutdown(msg="All tasks have reached S_OK. Will shutdown the scheduler and exit")

        # Handle failures.
        err_lines = []

        # Shall we send a reminder to the user?
        delta_etime = self.get_delta_etime()

        if delta_etime.total_seconds() > self.num_reminders * self.remindme_s:
            self.num_reminders += 1
            msg = ("Just to remind you that the scheduler with pid %s, flow %s\n has been running for %s " %
                  (self.pid, self.flow, delta_etime))
            retcode = self.send_email(msg, tag="[REMINDER]")

            if retcode:
                # Cannot send mail, shutdown now!
                msg += ("\nThe scheduler tried to send an e-mail to remind the user\n" +
                        " but send_email returned %d. Error is not critical though!" % retcode)
                print(msg)
                #err_lines.append(msg)

        #if delta_etime.total_seconds() > self.max_etime_s:
        #    err_lines.append("\nExceeded max_etime_s %s. Will shutdown the scheduler and exit" % self.max_etime_s)

        # Too many exceptions. Shutdown the scheduler.
        if self.num_excs > self.max_num_pyexcs:
            msg = "Number of exceptions %s > %s. Will shutdown the scheduler and exit" % (
                self.num_excs, self.max_num_pyexcs)
            err_lines.append(boxed(msg))

        # Paranoid check: disable the scheduler if we have submitted
        # too many jobs (it might be due to some bug or other external reasons
        # such as race conditions between difference callbacks!)
        if self.nlaunch > self.safety_ratio * self.flow.num_tasks:
            msg = "Too many jobs launched %d. Total number of tasks = %s, Will shutdown the scheduler and exit" % (
                self.nlaunch, self.flow.num_tasks)
            err_lines.append(boxed(msg))

        # Count the number of tasks with status == S_ERROR.
        if self.flow.num_errored_tasks > self.max_num_abierrs:
            msg = "Number of tasks with ERROR status %s > %s. Will shutdown the scheduler and exit" % (
                self.flow.num_errored_tasks, self.max_num_abierrs)
            err_lines.append(boxed(msg))

        # Test on the presence of deadlocks.
        g = self.flow.find_deadlocks()
        if g.deadlocked:
            # Check the flow again so that status are updated.
            self.flow.check_status()

            g = self.flow.find_deadlocks()
            #print("deadlocked:\n", g.deadlocked, "\nrunnables:\n", g.runnables, "\nrunning\n", g.running)
            print("deadlocked:", len(g.deadlocked), ", runnables:", len(g.runnables), ", running:", len(g.running))
            if g.deadlocked and not g.runnables and not g.running:
                err_lines.append("No runnable job with deadlocked tasks:\n%s." % str(g.deadlocked))

        if not g.runnables and not g.running:
            # Check the flow again so that status are updated.
            self.flow.check_status()
            g = self.flow.find_deadlocks()
            if not g.runnables and not g.running:
                err_lines.append("No task is running and cannot find other tasks to submit.")

        # Something wrong. Quit
        if err_lines:
            # Cancel all jobs.
            if self.killjobs_if_errors:
                cprint("killjobs_if_errors set to 'yes' in scheduler file. Will kill jobs before exiting.", "yellow")
                try:
                    num_cancelled = 0
                    for task in self.flow.iflat_tasks():
                        num_cancelled += task.cancel()
                    cprint("Killed %d tasks" % num_cancelled, "yellow")
                except Exception as exc:
                    cprint("Exception while trying to kill jobs:\n%s" % str(exc), "red")

            self.shutdown("\n".join(err_lines))

        return len(self.exceptions)

Response: The main callback function.


Instruction:
Make a summary of the following Python 3 code
def send_email(self, msg, tag=None):
        try:
            return self._send_email(msg, tag)
        except:
            self.exceptions.append(straceback())
            return -2

Response: Sends an e - mail before completing the shutdown. Returns 0 if success.


Instruction:
How would you implement a function in Python 3 that
creates a new instance of the class from a directory.

Response:
def from_dir(cls, top, workdir=None, name=None, manager=None, max_depth=2):
        """
        Find all flows located withing the directory `top` and build the `BatchLauncher`.

        Args:
            top: Top level directory or list of directories.
            workdir: Batch workdir.
            name:
            manager: :class:`TaskManager` object. If None, the manager is read from `manager.yml`
                In this case the YAML file must provide the entry `batch_manager` that defined
                the queue adapter used to submit the batch script.
            max_depth: Search in directory only if it is N or fewer levels below top
        """
        from .flows import Flow
        def find_pickles(dirtop):
            # Walk through each directory inside path and find the pickle database.
            paths = []
            for dirpath, dirnames, filenames in os.walk(dirtop):
                fnames = [f for f in filenames if f == Flow.PICKLE_FNAME]
                paths.extend([os.path.join(dirpath, f) for f in fnames])
            return paths

        if is_string(top):
            pickle_paths = find_pickles(top)
        else:
            # List of directories.
            pickle_paths = []
            for p in top:
                pickle_paths.extend(find_pickles(p))

        #workdir = os.path.join(top, "batch") if workdir is None else workdir
        workdir = "batch" if workdir is None else workdir
        new = cls(workdir, name=name, manager=manager)

        for path in pickle_paths:
            new.add_flow(path)

        return new


Instruction:
Can you tell what is the following Python 3 function doing
def pickle_load(cls, filepath):
        if os.path.isdir(filepath):
            # Walk through each directory inside path and find the pickle database.
            for dirpath, dirnames, filenames in os.walk(filepath):
                fnames = [f for f in filenames if f == cls.PICKLE_FNAME]
                if fnames:
                    if len(fnames) == 1:
                        filepath = os.path.join(dirpath, fnames[0])
                        break  # Exit os.walk
                    else:
                        err_msg = "Found multiple databases:\n %s" % str(fnames)
                        raise RuntimeError(err_msg)
            else:
                err_msg = "Cannot find %s inside directory %s" % (cls.PICKLE_FNAME, filepath)
                raise ValueError(err_msg)

        with open(filepath, "rb") as fh:
            new = pickle.load(fh)

        # new.flows is a list of strings with the workdir of the flows (see __getstate__).
        # Here we read the Flow from the pickle file so that we have
        # and up-to-date version and we set the flow in visitor_mode
        from .flows import Flow
        flow_workdirs, new.flows = new.flows, []
        for flow in map(Flow.pickle_load, flow_workdirs):
            new.add_flow(flow)

        return new

Response: Loads the object from a pickle file.


Instruction:
Here you have a function in Python 3, explain what it does
def pickle_dump(self):
        with open(os.path.join(self.workdir, self.PICKLE_FNAME), mode="wb") as fh:
            pickle.dump(self, fh)

Response: Save the status of the object in pickle format.


Instruction:
Can you create a Python 3 function that
adds a flow to the batchLauncher.

Response:
def add_flow(self, flow):
        """
        Add a flow. Accept filepath or :class:`Flow` object. Return 1 if flow was added else 0.
        """
        from .flows import Flow
        flow = Flow.as_flow(flow)

        if flow in self.flows:
            raise self.Error("Cannot add same flow twice!")

        if not flow.allocated:
            # Set the workdir of the flow here. Create a dir in self.workdir with name flow.name
            flow_workdir = os.path.join(self.workdir, os.path.basename(flow.name))
            if flow_workdir in (flow.workdir for flow in self.flows):
                raise self.Error("Two flows have the same name and hence the same workdir!")
            flow.allocate(workdir=flow_workdir)

        # Check if we are already using a scheduler to run this flow
        flow.check_pid_file()
        flow.set_spectator_mode(False)

        flow.check_status(show=False)

        #if flow.all_ok:
        #    print("flow.all_ok: Ignoring %s" % flow)
        #    return 0

        self.flows.append(flow)
        #print("Flow %s added to the BatchLauncher" % flow)

        return 1


Instruction:
Write a Python 3 function for
submitting a job to the queue.

Response:
def submit(self, **kwargs):
        """
        Submit a job script that will run the schedulers with `abirun.py`.

        Args:
            verbose: Verbosity level
            dry_run: Don't submit the script if dry_run. Default: False

        Returns:
            namedtuple with attributes:
                retcode: Return code as returned by the submission script.
                qjob: :class:`QueueJob` object.
                num_flows_inbatch: Number of flows executed by the batch script

            Return code of the job script submission.
        """
        verbose, dry_run = kwargs.pop("verbose", 0), kwargs.pop("dry_run", False)

        if not self.flows:
            print("Cannot submit an empty list of flows!")
            return 0

        if hasattr(self, "qjob"):
            # This usually happens when we have loaded the object from pickle
            # and we have already submitted to batch script to the queue.
            # At this point we need to understand if the previous batch job
            # is still running before trying to submit it again. There are three cases:
            #
            # 1) The batch script has completed withing timelimit and therefore
            #    the pid_file has been removed by the script. In this case, we
            #    should not try to submit it again.

            # 2) The batch script has been killed due to timelimit (other reasons are possible
            #    but we neglect them). In this case the pid_file exists but there's no job with
            #    this pid runnig and we can resubmit it again.

            # 3) The batch script is still running.
            print("BatchLauncher has qjob %s" % self.qjob)

            if not self.batch_pid_file.exists:
                print("It seems that the batch script reached the end. Wont' try to submit it again")
                return 0

            msg = ("Here I have to understand if qjob is in the queue."
                   " but I need an abstract API that can retrieve info from the queue id")
            raise RuntimeError(msg)

            # TODO: Temptative API
            if self.qjob.in_status("Running|Queued"):
                print("Job is still running. Cannot submit")
            else:
                del self.qjob

        script, num_flows_inbatch = self._get_script_nflows()

        if num_flows_inbatch == 0:
            print("All flows have reached all_ok! Batch script won't be submitted")
            return 0

        if verbose:
            print("*** submission script ***")
            print(script)

        # Write the script.
        self.script_file.write(script)
        self.script_file.chmod(0o740)

        # Builf the flow.
        for flow in self.flows:
            flow.build_and_pickle_dump()

        # Submit the task and save the queue id.
        if dry_run: return -1

        print("Will submit %s flows in batch script" % len(self.flows))
        self.qjob, process = self.qadapter.submit_to_queue(self.script_file.path)

        # Save the queue id in the pid file
        # The file will be removed by the job script if execution is completed.
        self.batch_pidfile.write(str(self.qjob.qid))

        self.pickle_dump()
        process.wait()

        return dict2namedtuple(retcode=process.returncode, qjob=self.qjob,
                               num_flows_inbatch=num_flows_inbatch)


Instruction:
Can you write a function in Python 3 where it
writes the submission script. Return the script and the number of flows in batch.

Response:
def _get_script_nflows(self):
        """
        Write the submission script. Return (script, num_flows_in_batch)
        """
        flows_torun = [f for f in self.flows if not f.all_ok]
        if not flows_torun:
            return "", 0

        executable = [
            'export _LOG=%s' % self.log_file.path,
            'date1=$(date +"%s")',
            'echo Running abirun.py in batch mode > ${_LOG}',
            " ",
        ]
        app = executable.append

        # Build list of abirun commands and save the name of the log files.
        self.sched_logs, num_flows = [], len(flows_torun)
        for i, flow in enumerate(flows_torun):

            logfile = os.path.join(self.workdir, "log_" + os.path.basename(flow.workdir))

            app("echo Starting flow %d/%d on: `date` >> ${LOG}" % (i+1, num_flows))
            app("\nabirun.py %s scheduler > %s" % (flow.workdir, logfile))
            app("echo Returning from abirun on `date` with retcode $? >> ${_LOG}")

            assert logfile not in self.sched_logs
            self.sched_logs.append(logfile)

        # Remove the batch pid_file and compute elapsed time.
        executable.extend([
            " ",
            "# Remove batch pid file",
            'rm %s' % self.batch_pidfile.path,
            " ",
            "# Compute elapsed time",
            'date2=$(date +"%s")',
            'diff=$(($date2-$date1))',
            'echo $(($diff / 60)) minutes and $(($diff % 60)) seconds elapsed. >> ${_LOG}'
        ])

        return self.qadapter.get_script_str(
            job_name=self.name,
            launch_dir=self.workdir,
            executable=executable,
            qout_path=self.qout_file.path,
            qerr_path=self.qerr_file.path,
        ), num_flows


Instruction:
Write a Python 3 script for
generating the string representation of the CTRL file. This is the first line of the string that is generated by the lmhart. run.

Response:
def get_string(self, sigfigs=8):
        """
        Generates the string representation of the CTRL file. This is
        the mininmal CTRL file necessary to execute lmhart.run.
        """
        ctrl_dict = self.as_dict()
        lines = [] if "HEADER" not in ctrl_dict else \
            ["HEADER".ljust(10) + self.header]
        if "VERS" in ctrl_dict:
            lines.append("VERS".ljust(10) + self.version)

        lines.append("STRUC".ljust(10) +
                     "ALAT="+str(round(ctrl_dict["ALAT"], sigfigs)))
        for l, latt in enumerate(ctrl_dict["PLAT"]):
            if l == 0:
                line = "PLAT=".rjust(15)
            else:
                line = " ".ljust(15)
            line += " ".join([str(round(v, sigfigs)) for v in latt])
            lines.append(line)

        for cat in ["CLASS", "SITE"]:
            for a, atoms in enumerate(ctrl_dict[cat]):
                if a == 0:
                    line = [cat.ljust(9)]
                else:
                    line = [" ".ljust(9)]
                for token, val in sorted(atoms.items()):
                    if token == "POS":
                        line.append("POS=" +
                                    " ".join([str(round(p, sigfigs))
                                              for p in val]))
                    else:
                        line.append(token + "=" + str(val))
                line = " ".join(line)
                lines.append(line)

        return "\n".join(lines)+"\n"


Instruction:
Can you tell what is the following Python 3 function doing
def as_dict(self):
        ctrl_dict = {"@module": self.__class__.__module__,
                     "@class": self.__class__.__name__}
        if self.header is not None:
            ctrl_dict["HEADER"] = self.header
        if self.version is not None:
            ctrl_dict["VERS"] = self.version
        sga = SpacegroupAnalyzer(self.structure)
        alat = sga.get_conventional_standard_structure().lattice.a
        plat = self.structure.lattice.matrix/alat

        """
        The following is to find the classes (atoms that are not symmetry
        equivalent, and create labels. Note that LMTO only attaches
        numbers with the second atom of the same species, e.g. "Bi", "Bi1",
        "Bi2", etc.
        """

        eq_atoms = sga.get_symmetry_dataset()['equivalent_atoms']
        ineq_sites_index = list(set(eq_atoms))
        sites = []
        classes = []
        num_atoms = {}
        for s, site in enumerate(self.structure.sites):
            atom = site.specie
            label_index = ineq_sites_index.index(eq_atoms[s])
            if atom.symbol in num_atoms:
                if label_index + 1 > sum(num_atoms.values()):
                    num_atoms[atom.symbol] += 1
                    atom_label = atom.symbol + str(num_atoms[atom.symbol] - 1)
                    classes.append({"ATOM": atom_label, "Z": atom.Z})
            else:
                num_atoms[atom.symbol] = 1
                classes.append({"ATOM": atom.symbol, "Z": atom.Z})
            sites.append({"ATOM": classes[label_index]["ATOM"],
                          "POS": site.coords/alat})

        ctrl_dict.update({"ALAT": alat/bohr_to_angstrom,
                          "PLAT": plat,
                          "CLASS": classes,
                          "SITE": sites})
        return ctrl_dict

Response: Returns the CTRL as a dictionary.


Instruction:
Create a Python 3 function for
creating a LMTOCtrl object from an existing CTRL file.

Response:
def from_file(cls, filename="CTRL", **kwargs):
        """
        Creates a CTRL file object from an existing file.

        Args:
            filename: The name of the CTRL file. Defaults to 'CTRL'.

        Returns:
            An LMTOCtrl object.
        """
        with zopen(filename, "rt") as f:
            contents = f.read()
        return LMTOCtrl.from_string(contents, **kwargs)


Instruction:
Write a Python 3 script for
creating a new LMTOCtrl object from a string representation of the CTRL file.

Response:
def from_string(cls, data, sigfigs=8):
        """
        Creates a CTRL file object from a string. This will mostly be
        used to read an LMTOCtrl object from a CTRL file. Empty spheres
        are ignored.

        Args:
            data: String representation of the CTRL file.

        Returns:
            An LMTOCtrl object.
        """
        lines = data.split("\n")[:-1]
        struc_lines = {"HEADER": [], "VERS": [], "SYMGRP": [],
                       "STRUC": [], "CLASS": [], "SITE": []}
        for line in lines:
            if line != "" and not line.isspace():
                if not line[0].isspace():
                    cat = line.split()[0]
                if cat in struc_lines:
                    struc_lines[cat].append(line)
                else:
                    pass
        for cat in struc_lines:
            struc_lines[cat] = " ".join(struc_lines[cat]).replace("= ", "=")

        structure_tokens = {"ALAT": None,
                            "PLAT": [],
                            "CLASS": [],
                            "SITE": []}

        for cat in ["STRUC", "CLASS", "SITE"]:
            fields = struc_lines[cat].split("=")
            for f, field in enumerate(fields):
                token = field.split()[-1]
                if token == "ALAT":
                    alat = round(float(fields[f+1].split()[0]), sigfigs)
                    structure_tokens["ALAT"] = alat
                elif token == "ATOM":
                    atom = fields[f+1].split()[0]
                    if not bool(re.match("E[0-9]*$", atom)):
                        if cat == "CLASS":
                            structure_tokens["CLASS"].append(atom)
                        else:
                            structure_tokens["SITE"].append({"ATOM": atom})
                    else:
                        pass
                elif token in ["PLAT", "POS"]:
                    try:
                        arr = np.array([round(float(i), sigfigs)
                                       for i in fields[f+1].split()])
                    except ValueError:
                        arr = np.array([round(float(i), sigfigs)
                                       for i in fields[f+1].split()[:-1]])
                    if token == "PLAT":
                        structure_tokens["PLAT"] = arr.reshape([3, 3])
                    elif not bool(re.match("E[0-9]*$", atom)):
                        structure_tokens["SITE"][-1]["POS"] = arr
                    else:
                        pass
                else:
                    pass
        try:
            spcgrp_index = struc_lines["SYMGRP"].index("SPCGRP")
            spcgrp = struc_lines["SYMGRP"][spcgrp_index:spcgrp_index+12]
            structure_tokens["SPCGRP"] = spcgrp.split("=")[1].split()[0]
        except ValueError:
            pass

        for token in ["HEADER", "VERS"]:
                try:
                    value = re.split(token + r"\s*", struc_lines[token])[1]
                    structure_tokens[token] = value.strip()
                except IndexError:
                    pass
        return LMTOCtrl.from_dict(structure_tokens)


Instruction:
Given the following Python 3 function, write the documentation
def from_dict(cls, d):
        for cat in ["HEADER", "VERS"]:
            if cat not in d:
                d[cat] = None
        alat = d["ALAT"] * bohr_to_angstrom
        plat = d["PLAT"] * alat
        species = []
        positions = []
        for site in d["SITE"]:
            species.append(re.split("[0-9*]", site["ATOM"])[0])
            positions.append(site["POS"] * alat)

        # Only check if the structure is to be generated from the space
        # group if the number of sites is the same as the number of classes.
        # If lattice and the spacegroup don't match, assume it's primitive.
        if "CLASS" in d and "SPCGRP" in d \
           and len(d["SITE"]) == len(d["CLASS"]):
            try:
                structure = Structure.from_spacegroup(d["SPCGRP"], plat,
                                                      species, positions,
                                                      coords_are_cartesian=True)
            except ValueError:
                structure = Structure(plat, species, positions,
                                      coords_are_cartesian=True,
                                      to_unit_cell=True)
        else:
            structure = Structure(plat, species, positions,
                                  coords_are_cartesian=True,
                                  to_unit_cell=True)

        return cls(structure, header=d["HEADER"], version=d["VERS"])

Response: Creates a new LMTOCtrl object from a dictionary.


Instruction:
Given the following Python 3 function, write the documentation
def _get_bond_data(line):

        line = line.split()
        length = float(line[2])
        # Replacing "/" with "-" makes splitting easier
        sites = line[0].replace("/", "-").split("-")
        site_indices = tuple(int(ind) - 1 for ind in sites[1:4:2])
        species = tuple(re.split(r"\d+", spec)[0] for spec in sites[0:3:2])
        label = "%s%d-%s%d" % (species[0], site_indices[0] + 1,
                               species[1], site_indices[1] + 1)
        return label, length, site_indices

Response: Subroutine to extract bond label site indices and length from a COHPCAR header line.


Instruction:
Make a summary of the following Python 3 code
def interpolate(self):
        images = self.__s1.interpolate(self.__s2, nimages=self.__n_images,
                                       interpolate_lattices=False)
        for site_i in self.__relax_sites:
            start_f = images[0].sites[site_i].frac_coords
            end_f = images[-1].sites[site_i].frac_coords

            path = NEBPathfinder.string_relax(
                NEBPathfinder.__f2d(start_f, self.__v),
                NEBPathfinder.__f2d(end_f, self.__v),
                self.__v, n_images=(self.__n_images + 1),
                dr=[self.__s1.lattice.a / self.__v.shape[0],
                    self.__s1.lattice.b / self.__v.shape[1],
                    self.__s1.lattice.c / self.__v.shape[2]])
            for image_i, image in enumerate(images):
                image.translate_sites(site_i,
                                      NEBPathfinder.__d2f(path[image_i],
                                                          self.__v) -
                                      image.sites[site_i].frac_coords,
                                      frac_coords=True, to_unit_cell=True)
        self.__images = images

Response: Interpolates the site indices of the two structures.


Instruction:
How would you implement a function in Python 3 that
generates a POSCAR with the calculated diffusion path with respect to the first endpoint.

Response:
def plot_images(self, outfile):
        """
        Generates a POSCAR with the calculated diffusion path with respect to the first endpoint.
        :param outfile: Output file for the POSCAR
        """
        sum_struct = self.__images[0].sites
        for image in self.__images:
            for site_i in self.__relax_sites:
                sum_struct.append(PeriodicSite(image.sites[site_i].specie,
                                               image.sites[site_i].frac_coords,
                                               self.__images[0].lattice,
                                               to_unit_cell=True,
                                               coords_are_cartesian=False))
        sum_struct = Structure.from_sites(sum_struct, validate_proximity=False)
        p = Poscar(sum_struct)
        p.write_file(outfile)


Instruction:
Make a summary of the following Python 3 code
def string_relax(start, end, V, n_images=25, dr=None, h=3.0, k=0.17,
                     min_iter=100, max_iter=10000, max_tol=5e-6):
        """
        Implements path relaxation via the elastic band method. In general, the
        method is to define a path by a set of points (images) connected with
        bands with some elasticity constant k. The images then relax along the
        forces found in the potential field V, counterbalanced by the elastic
        response of the elastic band. In general the endpoints of the band can
        be allowed to relax also to their local minima, but in this calculation
        they are kept fixed.

        Args:
            start, end: Endpoints of the path calculation given in discrete
                coordinates with respect to the grid in V
            V: potential field through which to calculate the path
            n_images: number of images used to define the path. In general
                anywhere from 20 to 40 seems to be good.
            dr: Conversion ratio from discrete coordinates to real coordinates
                for each of the three coordinate vectors
            h: Step size for the relaxation. h = 0.1 works reliably, but is
                slow. h=10 diverges with large gradients but for the types of
                gradients seen in CHGCARs, works pretty reliably
            k: Elastic constant for the band (in real units, not discrete)
            min_iter, max_iter: Number of optimization steps the string will
                take before exiting (even if unconverged)
            max_tol: Convergence threshold such that if the string moves by
                less than max_tol in a step, and at least min_iter steps have
                passed, the algorithm will terminate. Depends strongly on the
                size of the gradients in V, but 5e-6 works reasonably well for
                CHGCARs.
        """
        #
        # This code is based on the MATLAB example provided by
        # Prof. Eric Vanden-Eijnden of NYU
        # (http://www.cims.nyu.edu/~eve2/main.htm)
        #

        # print("Getting path from {} to {} (coords wrt V grid)".format(start, end))

        # Set parameters
        if not dr:
            dr = np.array(
                [1.0 / V.shape[0], 1.0 / V.shape[1], 1.0 / V.shape[2]])
        else:
            dr = np.array(dr, dtype=float)
        keff = k * dr * n_images
        h0 = h

        # Initialize string
        g1 = np.linspace(0, 1, n_images)
        s0 = start
        s1 = end
        s = np.array([g * (s1 - s0) for g in g1]) + s0
        ds = s - np.roll(s, 1, axis=0)
        ds[0] = (ds[0] - ds[0])
        ls = np.cumsum(la.norm(ds, axis=1))
        ls = ls / ls[-1]
        fi = interp1d(ls, s, axis=0)
        s = fi(g1)

        # Evaluate initial distances (for elastic equilibrium)
        ds0_plus = s - np.roll(s, 1, axis=0)
        ds0_minus = s - np.roll(s, -1, axis=0)
        ds0_plus[0] = (ds0_plus[0] - ds0_plus[0])
        ds0_minus[-1] = (ds0_minus[-1] - ds0_minus[-1])

        # Evaluate potential gradient outside the loop, as potential does not
        # change per step in this approximation.
        dV = np.gradient(V)

        # Evolve string
        for step in range(0, max_iter):
            if step > min_iter:
                # Gradually decay step size to prevent oscillations
                h = h0 * np.exp(-2.0 * (step - min_iter) / max_iter)
            else:
                h = h0
            # Calculate forces acting on string
            d = V.shape
            s0 = s
            edV = np.array([[dV[0][int(pt[0]) % d[0]][int(pt[1]) % d[1]][
                                 int(pt[2]) % d[2]] / dr[0],
                             dV[1][int(pt[0]) % d[0]][int(pt[1]) % d[1]][
                                 int(pt[2]) % d[2]] / dr[0],
                             dV[2][int(pt[0]) % d[0]][int(pt[1]) % d[1]][
                                 int(pt[2]) % d[2]] / dr[0]] for pt in s])
            # if(step % 100 == 0):
            #    print(edV)

            # Update according to force due to potential and string elasticity
            ds_plus = s - np.roll(s, 1, axis=0)
            ds_minus = s - np.roll(s, -1, axis=0)
            ds_plus[0] = (ds_plus[0] - ds_plus[0])
            ds_minus[-1] = (ds_minus[-1] - ds_minus[-1])
            Fpot = edV
            Fel = keff * (la.norm(ds_plus) - la.norm(ds0_plus)) * (
                ds_plus / la.norm(ds_plus))
            Fel += keff * (la.norm(ds_minus) - la.norm(ds0_minus)) * (
                ds_minus / la.norm(ds_minus))
            s -= h * (Fpot + Fel)

            # Fix endpoints
            s[0] = s0[0]
            s[-1] = s0[-1]

            # Reparametrize string
            ds = s - np.roll(s, 1, axis=0)
            ds[0] = (ds[0] - ds[0])
            ls = np.cumsum(la.norm(ds, axis=1))
            ls = ls / ls[-1]
            fi = interp1d(ls, s, axis=0)
            s = fi(g1)

            tol = la.norm((s - s0) * dr) / n_images / h

            if tol > 1e10:
                raise ValueError(
                    "Pathfinding failed, path diverged! Consider reducing h to "
                    "avoid divergence.")

            if step > min_iter and tol < max_tol:
                print("Converged at step {}".format(step))
                break

            if step % 100 == 0:
                print("Step {} - ds = {}".format(step, tol))
        return s

Response: This function calculates the path relaxation of a string in a given potential field V.


Instruction:
Can you tell what is the following Python 3 function doing
def __f2d(frac_coords, v):
        # frac_coords = frac_coords % 1
        return np.array([int(frac_coords[0] * v.shape[0]),
                         int(frac_coords[1] * v.shape[1]),
                         int(frac_coords[2] * v.shape[2])])

Response:
Convert fractional coordinates to discrete coordinates with respect to
        the grid size of v


Instruction:
Can you generate a brief explanation for the following Python 3 code
def __d2f(disc_coords, v):
        return np.array([disc_coords[0] / v.shape[0],
                         disc_coords[1] / v.shape[1],
                         disc_coords[2] / v.shape[2]])

Response:
Convert a point given in discrete coordinates withe respect to the
        grid in v to fractional coordinates.


Instruction:
Can you tell what is the following Python 3 function doing
def normalize(self):
        self.__v = self.__v - np.amin(self.__v)
        self.__v = self.__v / np.amax(self.__v)

Response: Normalizes the potential range 0 to 1.


Instruction:
Can you generate the documentation for the following Python 3 function
def rescale_field(self, new_dim):
        v_dim = self.__v.shape
        padded_v = np.lib.pad(self.__v, ((0, 1), (0, 1), (0, 1)), mode='wrap')
        ogrid_list = np.array([list(c) for c in list(
            np.ndindex(v_dim[0] + 1, v_dim[1] + 1, v_dim[2] + 1))])
        v_ogrid = padded_v.reshape(
            ((v_dim[0] + 1) * (v_dim[1] + 1) * (v_dim[2] + 1), -1))
        ngrid_a, ngrid_b, ngrid_c = np.mgrid[0: v_dim[0]: v_dim[0] / new_dim[0],
                                    0: v_dim[1]: v_dim[1] / new_dim[1],
                                    0: v_dim[2]: v_dim[2] / new_dim[2]]

        v_ngrid = scipy.interpolate.griddata(ogrid_list, v_ogrid,
                                             (ngrid_a, ngrid_b, ngrid_c),
                                             method='linear').reshape(
            (new_dim[0], new_dim[1], new_dim[2]))
        self.__v = v_ngrid

Response: Rescales the potential field by linear interpolation.


Instruction:
How would you explain what the following Python 3 function does
def gaussian_smear(self, r):
        # Since scaling factor in fractional coords is not isotropic, have to
        # have different radii in 3 directions
        a_lat = self.__s.lattice.a
        b_lat = self.__s.lattice.b
        c_lat = self.__s.lattice.c

        # Conversion factors for discretization of v
        v_dim = self.__v.shape
        r_frac = (r / a_lat, r / b_lat, r / c_lat)
        r_disc = (int(math.ceil(r_frac[0] * v_dim[0])),
                  int(math.ceil(r_frac[1] * v_dim[1])),
                  int(math.ceil(r_frac[2] * v_dim[2])))

        # Apply smearing
        # Gaussian filter
        gauss_dist = np.zeros(
            (r_disc[0] * 4 + 1, r_disc[1] * 4 + 1, r_disc[2] * 4 + 1))
        for g_a in np.arange(-2.0 * r_disc[0], 2.0 * r_disc[0] + 1, 1.0):
            for g_b in np.arange(-2.0 * r_disc[1], 2.0 * r_disc[1] + 1, 1.0):
                for g_c in np.arange(-2.0 * r_disc[2], 2.0 * r_disc[2] + 1,
                                     1.0):
                    g = np.array(
                        [g_a / v_dim[0], g_b / v_dim[1], g_c / v_dim[2]]).T
                    gauss_dist[int(g_a + r_disc[0])][int(g_b + r_disc[1])][
                        int(g_c + r_disc[2])] = la.norm(
                        np.dot(self.__s.lattice.matrix, g)) / r
        gauss = scipy.stats.norm.pdf(gauss_dist)
        gauss = gauss / np.sum(gauss, dtype=float)
        padded_v = np.pad(self.__v, (
        (r_disc[0], r_disc[0]), (r_disc[1], r_disc[1]), (r_disc[2], r_disc[2])),
                          mode='wrap')
        smeared_v = scipy.signal.convolve(padded_v, gauss, mode='valid')
        self.__v = smeared_v

Response: Applies an isotropic Gaussian smear of width r to the potential field.


Instruction:
How would you implement a function in Python 3 that
gives a structure returns the predicted volume.

Response:
def predict(self, structure, ref_structure):
        """
        Given a structure, returns the predicted volume.
        Args:
            structure (Structure): structure w/unknown volume
            ref_structure (Structure): A reference structure with a similar
                structure but different species.
        Returns:
            a float value of the predicted volume
        """

        if self.check_isostructural:
            m = StructureMatcher()
            mapping = m.get_best_electronegativity_anonymous_mapping(
                structure, ref_structure)
            if mapping is None:
                raise ValueError("Input structures do not match!")

        if "ionic" in self.radii_type:
            try:
                # Use BV analyzer to determine oxidation states only if the
                # oxidation states are not already specified in the structure
                # and use_bv is true.
                if (not is_ox(structure)) and self.use_bv:
                    a = BVAnalyzer()
                    structure = a.get_oxi_state_decorated_structure(structure)
                if (not is_ox(ref_structure)) and self.use_bv:
                    a = BVAnalyzer()
                    ref_structure = a.get_oxi_state_decorated_structure(
                        ref_structure)

                comp = structure.composition
                ref_comp = ref_structure.composition

                # Check if all the associated ionic radii are available.
                if any([k.ionic_radius is None for k in list(comp.keys())]) or \
                        any([k.ionic_radius is None for k in
                             list(ref_comp.keys())]):
                    raise ValueError("Not all the ionic radii are available!")

                numerator = 0
                denominator = 0
                # Here, the 1/3 factor on the composition accounts for atomic
                # packing. We want the number per unit length.
                for k, v in comp.items():
                    numerator += k.ionic_radius * v ** (1 / 3)
                for k, v in ref_comp.items():
                    denominator += k.ionic_radius * v ** (1 / 3)

                return ref_structure.volume * (numerator / denominator) ** 3
            except Exception as ex:
                warnings.warn("Exception occured. Will attempt atomic radii.")
                # If error occurs during use of ionic radii scheme, pass
                # and see if we can resolve it using atomic radii.
                pass

        if "atomic" in self.radii_type:
            comp = structure.composition
            ref_comp = ref_structure.composition
            # Here, the 1/3 factor on the composition accounts for atomic
            # packing. We want the number per unit length.
            numerator = 0
            denominator = 0
            for k, v in comp.items():
                numerator += k.atomic_radius * v ** (1 / 3)
            for k, v in ref_comp.items():
                denominator += k.atomic_radius * v ** (1 / 3)
            return ref_structure.volume * (numerator / denominator) ** 3

        raise ValueError("Cannot find volume scaling based on radii choices "
                         "specified!")


Instruction:
Given the following Python 3 function, write the documentation
def get_predicted_structure(self, structure, ref_structure):
        new_structure = structure.copy()
        new_structure.scale_lattice(self.predict(structure, ref_structure))

        return new_structure

Response:
Returns back the structure scaled to predicted
        volume.


Instruction:
How would you explain what the following Python 3 function does
def predict(self, structure, icsd_vol=False):

        # Get standard deviation of electronnegativity in the structure.
        std_x = np.std([site.specie.X for site in structure])
        # Sites that have atomic radii
        sub_sites = []
        # Record the "DLS estimated radius" from bond_params.
        bp_dict = {}

        for sp in list(structure.composition.keys()):
            if sp.atomic_radius:
                sub_sites.extend([site for site in structure
                                  if site.specie == sp])
            else:
                warnings.warn("VolumePredictor: no atomic radius data for "
                              "{}".format(sp))

            if sp.symbol not in bond_params:
                warnings.warn("VolumePredictor: bond parameters not found, "
                              "used atomic radii for {}".format(sp))
            else:
                r, k = bond_params[sp.symbol]["r"], bond_params[sp.symbol]["k"]
                bp_dict[sp] = float(r) + float(k) * std_x

        # Structure object that include only sites with known atomic radii.
        reduced_structure = Structure.from_sites(sub_sites)
        smallest_ratio = None

        for site1 in reduced_structure:
            sp1 = site1.specie
            neighbors = reduced_structure.get_neighbors(site1,
                                                        sp1.atomic_radius +
                                                        self.cutoff)

            for site2, dist in neighbors:
                sp2 = site2.specie

                if sp1 in bp_dict and sp2 in bp_dict:
                    expected_dist = bp_dict[sp1] + bp_dict[sp2]
                else:
                    expected_dist = sp1.atomic_radius + sp2.atomic_radius

                if not smallest_ratio or dist / expected_dist < smallest_ratio:
                    smallest_ratio = dist / expected_dist

        if not smallest_ratio:
            raise ValueError("Could not find any bonds within the given cutoff "
                             "in this structure.")

        volume_factor = (1 / smallest_ratio) ** 3

        # icsd volume fudge factor
        if icsd_vol:
            volume_factor *= 1.05

        if self.min_scaling:
            volume_factor = max(self.min_scaling, volume_factor)
        if self.max_scaling:
            volume_factor = min(self.max_scaling, volume_factor)

        return structure.volume * volume_factor

Response: Predicts the predicted volume of a crystal structure.


Instruction:
Write a Python 3 script to
return back the structure scaled to predicted volume.

Response:
def get_predicted_structure(self, structure, icsd_vol=False):
        """
        Given a structure, returns back the structure scaled to predicted
        volume.
        Args:
            structure (Structure): structure w/unknown volume

        Returns:
            a Structure object with predicted volume
        """
        new_structure = structure.copy()
        new_structure.scale_lattice(self.predict(structure, icsd_vol=icsd_vol))
        return new_structure


Instruction:
Can you tell what is the following Python 3 function doing
def uniform_labels(self, mol1, mol2):
        obmol1 = BabelMolAdaptor(mol1).openbabel_mol
        obmol2 = BabelMolAdaptor(mol2).openbabel_mol

        h1 = self.get_molecule_hash(obmol1)
        h2 = self.get_molecule_hash(obmol2)
        if h1 != h2:
            return None, None

        query = ob.CompileMoleculeQuery(obmol1)
        isomapper = ob.OBIsomorphismMapper.GetInstance(query)
        isomorph = ob.vvpairUIntUInt()
        isomapper.MapAll(obmol2, isomorph)

        sorted_isomorph = [sorted(x, key=lambda morp: morp[0])
                           for x in isomorph]
        label2_list = tuple([tuple([p[1] + 1 for p in x])
                             for x in sorted_isomorph])

        vmol1 = obmol1
        aligner = ob.OBAlign(True, False)
        aligner.SetRefMol(vmol1)
        least_rmsd = float("Inf")
        best_label2 = None
        label1 = list(range(1, obmol1.NumAtoms() + 1))
        # noinspection PyProtectedMember
        elements1 = InchiMolAtomMapper._get_elements(vmol1, label1)
        for label2 in label2_list:
            # noinspection PyProtectedMember
            elements2 = InchiMolAtomMapper._get_elements(obmol2, label2)
            if elements1 != elements2:
                continue
            vmol2 = ob.OBMol()
            for i in label2:
                vmol2.AddAtom(obmol2.GetAtom(i))
            aligner.SetTargetMol(vmol2)
            aligner.Align()
            rmsd = aligner.GetRMSD()
            if rmsd < least_rmsd:
                least_rmsd = rmsd
                best_label2 = copy.copy(label2)
        return label1, best_label2

Response: Calculates the RMSD of the two molecules and returns the mapping for the least RMSD atom.


Instruction:
Can you write a function in Python 3 where it
returns inchi as molecular hash

Response:
def get_molecule_hash(self, mol):
        """
        Return inchi as molecular hash
        """
        obconv = ob.OBConversion()
        obconv.SetOutFormat(str("inchi"))
        obconv.AddOption(str("X"), ob.OBConversion.OUTOPTIONS, str("DoNotAddH"))
        inchi_text = obconv.WriteString(mol)
        match = re.search(r"InChI=(?P<inchi>.+)\n", inchi_text)
        return match.group("inchi")


Instruction:
Create a Python 3 function to
return the inchi canonical labels of the heavy atoms in the molecule.

Response:
def _inchi_labels(mol):
        """
        Get the inchi canonical labels of the heavy atoms in the molecule

        Args:
            mol: The molecule. OpenBabel OBMol object

        Returns:
            The label mappings. List of tuple of canonical label,
            original label
            List of equivalent atoms.
        """
        obconv = ob.OBConversion()
        obconv.SetOutFormat(str("inchi"))
        obconv.AddOption(str("a"), ob.OBConversion.OUTOPTIONS)
        obconv.AddOption(str("X"), ob.OBConversion.OUTOPTIONS, str("DoNotAddH"))
        inchi_text = obconv.WriteString(mol)
        match = re.search(r"InChI=(?P<inchi>.+)\nAuxInfo=.+"
                          r"/N:(?P<labels>[0-9,;]+)/(E:(?P<eq_atoms>[0-9,"
                          r";\(\)]*)/)?", inchi_text)
        inchi = match.group("inchi")
        label_text = match.group("labels")
        eq_atom_text = match.group("eq_atoms")
        heavy_atom_labels = tuple([int(i) for i in label_text.replace(
            ';', ',').split(',')])
        eq_atoms = []
        if eq_atom_text is not None:
            eq_tokens = re.findall(r'\(((?:[0-9]+,)+[0-9]+)\)', eq_atom_text
                                   .replace(';', ','))
            eq_atoms = tuple([tuple([int(i) for i in t.split(',')])
                              for t in eq_tokens])
        return heavy_atom_labels, eq_atoms, inchi


Instruction:
Here you have a function in Python 3, explain what it does
def _group_centroid(mol, ilabels, group_atoms):
        c1x, c1y, c1z = 0.0, 0.0, 0.0
        for i in group_atoms:
            orig_idx = ilabels[i-1]
            oa1 = mol.GetAtom(orig_idx)
            c1x += float(oa1.x())
            c1y += float(oa1.y())
            c1z += float(oa1.z())
        num_atoms = len(group_atoms)
        c1x /= num_atoms
        c1y /= num_atoms
        c1z /= num_atoms
        return c1x, c1y, c1z

Response: Calculates the centroids of a group inchi - related object.