def test_args(self):
        kwargs = deepcopy(self.default_kwargs)
        kwargs["task"] = Task.dos
        kwargs["num_kpoints"] = 1
        kwargs["is_magnetization"] = True
        kwargs["vbm_cbm"] = None
        kwargs["encut"] = 800.0

        setting = TaskIncarSettings.from_options(**kwargs)
        expected = {
            'IBRION': 2,
            'PREC': 'N',
            'ISIF': 0,
            'ISMEAR': 0,
            'ISPIN': 2,
            'LREAL': False,
            'EDIFF': 1e-05,
            'KPAR': 1,
            'NBANDS': 12,
            'ENCUT': 800.0,
            'EMIN': -20.01,
            'EMAX': 20,
            'NEDOS': 4002
        }
        self.assertEqual(expected, setting.settings)
 def test_structure_opt(self):
     setting = TaskIncarSettings.from_options(**self.default_kwargs)
     expected = {
         'IBRION': 2,
         'PREC': 'N',
         'ISIF': 3,
         'ISMEAR': 0,
         'ISPIN': 1,
         'LREAL': False,
         'EDIFF': 1e-07,
         'NSW': 50,
         'EDIFFG': -0.005,
         'KPAR': 4,
         'ENCUT': 520.0
     }
     self.assertEqual(expected, setting.settings)
 def test_dielectric_dfpt(self):
     kwargs = deepcopy(self.default_kwargs)
     kwargs["task"] = Task.dielectric_dfpt
     setting = TaskIncarSettings.from_options(**kwargs)
     expected = {
         'IBRION': 8,
         'PREC': 'N',
         'ISIF': 0,
         'ISMEAR': 0,
         'ISPIN': 1,
         'LREAL': False,
         'LEPSILON': True,
         'EDIFF': 1e-06,
         'KPAR': 4,
         'ENCUT': 400.0
     }
     self.assertEqual(expected, setting.settings)
 def test_band(self):
     kwargs = deepcopy(self.default_kwargs)
     kwargs["task"] = Task.band
     setting = TaskIncarSettings.from_options(**kwargs)
     expected = {
         'IBRION': 2,
         'PREC': 'N',
         'ISIF': 0,
         'ISMEAR': 0,
         'ISPIN': 1,
         'LREAL': False,
         'EDIFF': 1e-05,
         'KPAR': 4,
         'NBANDS': 12,
         'ENCUT': 400.0
     }
     self.assertEqual(expected, setting.settings)
 def test_phonon_force(self):
     kwargs = deepcopy(self.default_kwargs)
     kwargs["task"] = Task.phonon_force
     setting = TaskIncarSettings.from_options(**kwargs)
     expected = {
         'ADDGRID': True,
         'IBRION': 2,
         'PREC': 'A',
         'ISIF': 2,
         'ISMEAR': 0,
         'ISPIN': 1,
         'LREAL': False,
         'EDIFF': 1e-08,
         'KPAR': 4,
         'ENCUT': 400.0
     }
     self.assertEqual(expected, setting.settings)
 def test_cluster_opt(self):
     kwargs = deepcopy(self.default_kwargs)
     kwargs["task"] = Task.cluster_opt
     setting = TaskIncarSettings.from_options(**kwargs)
     expected = {
         'IBRION': 2,
         'PREC': 'N',
         'ISIF': 2,
         'ISMEAR': 0,
         'ISPIN': 1,
         'LREAL': False,
         'EDIFF': 1e-07,
         'NSW': 50,
         'EDIFFG': -0.005,
         'KPAR': 4,
         'ENCUT': 400.0
     }
     self.assertEqual(expected, setting.settings)
 def test_structure_opt_tight(self):
     kwargs = deepcopy(self.default_kwargs)
     kwargs["task"] = Task.structure_opt_tight
     setting = TaskIncarSettings.from_options(**kwargs)
     expected = {
         'ADDGRID': True,
         'IBRION': 2,
         'PREC': 'A',
         'ISIF': 3,
         'ISMEAR': 0,
         'ISPIN': 1,
         'LREAL': False,
         'EDIFF': 1e-08,
         'NSW': 50,
         'EDIFFG': -0.001,
         'KPAR': 4,
         'ENCUT': 520.0
     }
     self.assertEqual(expected, setting.settings)
 def test_dielectric_function(self):
     kwargs = deepcopy(self.default_kwargs)
     kwargs["task"] = Task.dielectric_function
     setting = TaskIncarSettings.from_options(**kwargs)
     expected = {
         'IBRION': 2,
         'PREC': 'N',
         'ISIF': 0,
         'ISMEAR': -5,
         'ISPIN': 1,
         'LREAL': False,
         'EDIFF': 1e-05,
         'KPAR': 4,
         'NBANDS': 12,
         'CSHIFT': 0.01,
         'LOPTICS': True,
         'ENCUT': 400.0,
         'EMIN': -12.01,
         'EMAX': 23,
         'NEDOS': 3502
     }
     self.assertEqual(expected, setting.settings)
    def test_args_2(self):
        kwargs = deepcopy(self.default_kwargs)
        kwargs["task"] = Task.dos
        kwargs["num_kpoints"] = 18
        kwargs["num_nodes"] = 2
        kwargs["structure_opt_encut_factor"] = 1.5

        setting = TaskIncarSettings.from_options(**kwargs)
        expected = {
            'IBRION': 2,
            'PREC': 'N',
            'ISIF': 0,
            'ISMEAR': -5,
            'ISPIN': 1,
            'LREAL': False,
            'EDIFF': 1e-05,
            'KPAR': 6,
            'NBANDS': 12,
            'ENCUT': 400.0,
            'EMIN': -12.01,
            'EMAX': 23,
            'NEDOS': 3502
        }
        self.assertEqual(expected, setting.settings)
    def make_input(cls,
                   structure: Structure,
                   task: Union[str, Task] = Task.structure_opt,
                   xc: Union[str, Xc] = Xc.pbe,
                   prev_set: "ViseInputSet" = None,
                   abs_files_to_transfer: Optional[dict] = None,
                   user_incar_settings: Optional[dict] = None,
                   **kwargs) -> "ViseInputSet":
        """Construct ViseInputSet from some options including xc and task.

        To make a simple but practical rule for inheriting the previous
        calculation condition and effectively use its results, we adopt
        fail-safe rule.

        When, the prev_set is set, we compare task and xc between current
        input and prev_set, and inherit some options depending on whether
        task and/or xc are common. For instance, when we calculate the band
        structure with the HSE06 hybrid functional, it would be better to
        generate the WAVECAR file with e.g., PBE functional to reduce the
        computational cost. Then, the initial task was done with task=Task.band
        and xc=Xc.pbe with user_incar_setting={"LWAVE": True}, and next task is
        performed with task=Task.band, xc=Xc.hs. Then, task is common, so
        input set related to TaskStructureKpoints and TaskIncarSettings are
        inherited. Note that, CommonIncarSettings options are always inherited.

        Other notes are as follows.

        Note1: Charge set to structure is ignored when determining NELECT. For
               this purpose, charge option needs to be used.
        Note2: When the structure is changed via find_spglib_standard_primitive,
               all the site properties are removed.
        Note3: When different version of ViseInputSet with different defaults,
               the consistency of input set is destroyed, so the same version
               must be used when options are inherited.
        Note4: Other INCAR flags than those defined by TaskIncarSettings,
               XcIncarSettings, XcTaskIncarSettings, and CommonIncarSettings are
               not inherited. When some of them need to be inherited, they
               should be added to COMMON_OPTIONAL_FLAGS.
        Note5: user_incar_settings is not inherited from prev_set. One needs
               to explicitly specify it, again, if needed.

        Args:
            structure (Structure):
                The Structure to create inputs for.
            task (Task):
                Task defined in Task.
            xc (Xc):
                Exchange-correlation (xc) defined in Xc.
            prev_set (ViseInputSet):
                Previous ViseInputSet.
            abs_files_to_transfer (dict):
                Keys are file names with absolute paths to be transferred.
                Values mean the transfer modes, where there are three modes,
                "c": copy file, "m": move file, "l": make symbolic link
                e.g., {"/..../CHGCAR": "c", "/..../WAVECAR": "l", ..}
            user_incar_settings (dict):
                User INCAR settings.
                e.g., {"NSW": 100, "LWAVE": True, "LASPH": "A", ..}
            kwargs (dict):
                OPTION arguments.

        Return:
            ViseInputSet class object.
        """
        abs_files_to_transfer = abs_files_to_transfer or {}
        user_incar_settings = user_incar_settings or {}

        if isinstance(task, str):
            task = Task.from_string(task)
        if isinstance(xc, str):
            xc = Xc.from_string(xc)

        # First, set default.
        opts = deepcopy(cls.ALL_OPTIONS)

        # Second, override with previous condition
        if prev_set:
            key_set = set(cls.COMMON_OPTIONS.keys())
            if prev_set.task == task:
                key_set.update(cls.TASK_OPTIONS.keys())
            if prev_set.xc == xc:
                key_set.update(cls.XC_OPTIONS.keys())
            if prev_set.task == task and prev_set.xc == xc:
                key_set.update(cls.XC_TASK_OPTIONS)

            for k in key_set:
                opts[k] = prev_set.kwargs[k]

        # Third, override with keyword arguments
        for k in kwargs:
            if k not in opts.keys():
                logger.warning(
                    f"Keyword {k} is not adequate for ViseInputSet.")
        opts.update(kwargs)

        task_str_kpt = TaskStructureKpoints.from_options(
            task=task,
            original_structure=structure,
            standardize_structure=opts["standardize_structure"],
            sort_structure=opts["sort_structure"],
            is_magnetization=opts["is_magnetization"],
            kpt_mode=opts["kpt_mode"],
            kpt_density=opts["kpt_density"],
            kpt_shift=opts["kpt_shift"],
            only_even=opts["only_even"],
            band_ref_dist=opts["band_ref_dist"],
            factor=opts["factor"],
            symprec=opts["symprec"],
            angle_tolerance=opts["angle_tolerance"])

        orig_matrix = structure.lattice.matrix
        matrix = task_str_kpt.structure.lattice.matrix
        structure_changed = \
            not np.allclose(orig_matrix, matrix, atol=opts["symprec"])

        if structure_changed and opts["charge"] != 0:
            raise ValueError("Structure is changed but charge is set.")

        if structure_changed:
            # The following files are useless when lattice is changed.
            pattern = \
                re.compile("|".join([r"CHGCAR$", r"WAVECAR$", r"WAVEDER"]))
            for f in abs_files_to_transfer:
                if pattern.match(f):
                    abs_files_to_transfer.pop(f, None)

        # unique_justseen https://docs.python.org/ja/3/library/itertools.html
        # ["H", "H", "O", "O", "H"] -> ['H', 'O', 'H']
        symbol_list = get_symbol_list(task_str_kpt.structure)

        xc_task_potcar = XcTaskPotcar.from_options(
            xc=xc,
            symbol_list=symbol_list,
            potcar_set_name=opts["potcar_set_name"],
            override_potcar_set=opts["override_potcar_set"])

        task_settings = TaskIncarSettings.from_options(
            task=task,
            structure=task_str_kpt.structure,
            potcar=xc_task_potcar.potcar,
            num_kpoints=task_str_kpt.num_kpts,
            max_enmax=xc_task_potcar.max_enmax,
            is_magnetization=opts["is_magnetization"],
            vbm_cbm=opts["vbm_cbm"],
            npar_kpar=opts["npar_kpar"],
            num_nodes=opts["num_nodes"],
            encut=opts["encut"],
            structure_opt_encut_factor=opts["structure_opt_encut_factor"],
            dos_step_size=opts["dos_step_size"])

        xc_settings = XcIncarSettings.from_options(
            xc=xc,
            symbol_list=symbol_list,
            factor=task_str_kpt.factor,
            aexx=opts["aexx"],
            hubbard_u=opts["hubbard_u"],
            ldauu=opts["ldauu"],
            ldaul=opts["ldaul"],
            ldaul_set_name=opts["ldaul_set_name"])

        xc_task_settings = XcTaskIncarSettings.from_options()

        common_settings = CommonIncarSettings.from_options(
            potcar=xc_task_potcar.potcar,
            composition=task_str_kpt.structure.composition,
            charge=opts["charge"])

        incar_settings = \
            {**task_settings.settings, **xc_settings.settings,
             **xc_task_settings.settings, **common_settings.settings}

        # user_incar_settings is the top prioritized.
        incar_settings.update(user_incar_settings)

        # TODO: tweak the unfavorable combination of the input set.
        # e.g., Avoiding ICHARG = 11 is a must for hybrid functional.

        return cls(structure=task_str_kpt.structure,
                   task=task,
                   xc=xc,
                   kpoints=task_str_kpt.kpoints,
                   potcar=xc_task_potcar.potcar,
                   incar_settings=incar_settings,
                   files_to_transfer=abs_files_to_transfer,
                   **opts)