<SYSTEM_TASK:>
Solve the following problem using Python, implementing the functions described below, one line at a time
<END_TASK>
<USER_TASK:>
Description:
 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)