<SYSTEM_TASK:>
Solve the following problem using Python, implementing the functions described below, one line at a time
<END_TASK>
<USER_TASK:>
Description:
 def full_opt_run(cls, vasp_cmd, vol_change_tol=0.02, max_steps=10, ediffg=-0.05, half_kpts_first_relax=False, **vasp_job_kwargs):
 """ Returns a generator of jobs for a full optimization run. Basically, this runs an infinite series of geometry optimization jobs until the % vol change in a particular optimization is less than vol_change_tol. Args: vasp_cmd (str):
 Command to run vasp as a list of args. For example, if you are using mpirun, it can be something like ["mpirun", "pvasp.5.2.11"] vol_change_tol (float):
 The tolerance at which to stop a run. Defaults to 0.05, i.e., 5%. max_steps (int):
 The maximum number of runs. Defaults to 10 ( highly unlikely that this limit is ever reached). ediffg (float):
 Force convergence criteria for subsequent runs ( ignored for the initial run.) half_kpts_first_relax (bool):
 Whether to halve the kpoint grid for the first relaxation. Speeds up difficult convergence considerably. Defaults to False. \*\*vasp_job_kwargs: Passthrough kwargs to VaspJob. See :class:`custodian.vasp.jobs.VaspJob`. Returns: Generator of jobs. """

        for i in range(max_steps):
            if i == 0:
                settings = None
                backup = True
                if half_kpts_first_relax and os.path.exists("KPOINTS") and \
                        os.path.exists("POSCAR"):
                    kpts = Kpoints.from_file("KPOINTS")
                    orig_kpts_dict = kpts.as_dict()
                    kpts.kpts = np.maximum(np.array(kpts.kpts) / 2, 1).tolist()
                    low_kpts_dict = kpts.as_dict()
                    settings = [
                        {"dict": "KPOINTS",
                         "action": {"_set": low_kpts_dict}}
                    ]
            else:
                backup = False
                initial = Poscar.from_file("POSCAR").structure
                final = Poscar.from_file("CONTCAR").structure
                vol_change = (final.volume - initial.volume) / initial.volume

                logger.info("Vol change = %.1f %%!" % (vol_change * 100))
                if abs(vol_change) < vol_change_tol:
                    logger.info("Stopping optimization!")
                    break
                else:
                    incar_update = {"ISTART": 1}
                    if ediffg:
                        incar_update["EDIFFG"] = ediffg
                    settings = [
                        {"dict": "INCAR",
                         "action": {"_set": incar_update}},
                        {"file": "CONTCAR",
                         "action": {"_file_copy": {"dest": "POSCAR"}}}]
                    if i == 1 and half_kpts_first_relax:
                        settings.append({"dict": "KPOINTS",
                                         "action": {"_set": orig_kpts_dict}})
            logger.info("Generating job = %d!" % (i+1))
            yield VaspJob(vasp_cmd, final=False, backup=backup,
                          suffix=".relax%d" % (i+1), settings_override=settings,
                          **vasp_job_kwargs)