Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

VaspInput setter and Incar.check_params() are inconsistent #4119

Closed
yantar92 opened this issue Oct 17, 2024 · 8 comments · Fixed by #4122 or #4136
Closed

VaspInput setter and Incar.check_params() are inconsistent #4119

yantar92 opened this issue Oct 17, 2024 · 8 comments · Fixed by #4122 or #4136
Labels

Comments

@yantar92
Copy link
Contributor

yantar92 commented Oct 17, 2024

Python version

Python 3.12.4

Pymatgen version

2024.7.18

Operating system version

No response

Current behavior

When I set INCAR parametrs via vasp_input['INCAR']['PARAM']=value, they do not always pass Incar.check_params() checks.

Using the example below, I am getting:

...: BadIncarWarning: ENCUT: 550 is not a float
  vasp_input.incar.check_params()
...: BadIncarWarning: GGA: Cannot find Ps in the list of values
  vasp_input.incar.check_params()

Expected Behavior

Setting the parameters passes the checks.

Minimal example

from pymatgen.io.vasp.inputs import VaspInput
vasp_input = VaspInput.from_directory('.')
vasp_input['INCAR']['GGA'] = "MK"
vasp_input['INCAR']['GGA']

Relevant files to reproduce this bug

I used the following INCAR

ALGO = Normal
ENCUT = 550
ISMEAR = 0
NCORE = 16
SIGMA = 0.04
SYSTEM = C.hexagonal.191

Other requried inputs may be anything.

@yantar92 yantar92 added the bug label Oct 17, 2024
@DanielYang59
Copy link
Contributor

DanielYang59 commented Oct 17, 2024

Thanks for reporting this, I believe the warning on GGA=PS is owing to:

return val.strip().capitalize()

I.e. any keyword not included inside the following and is not boolean/float/int (str) would be capitalized:

list_keys = (
"LDAUU",
"LDAUL",
"LDAUJ",
"MAGMOM",
"DIPOL",
"LANGEVIN_GAMMA",
"QUAD_EFG",
"EINT",
)
bool_keys = (
"LDAU",
"LWAVE",
"LSCALU",
"LCHARG",
"LPLANE",
"LUSE_VDW",
"LHFCALC",
"ADDGRID",
"LSORBIT",
"LNONCOLLINEAR",
)
float_keys = (
"EDIFF",
"SIGMA",
"TIME",
"ENCUTFOCK",
"HFSCREEN",
"POTIM",
"EDIFFG",
"AGGAC",
"PARAM1",
"PARAM2",
)
int_keys = (
"NSW",
"NBANDS",
"NELMIN",
"ISIF",
"IBRION",
"ISPIN",
"ISTART",
"ICHARG",
"NELM",
"ISMEAR",
"NPAR",
"LDAUPRINT",
"LMAXMIX",
"ENCUT",
"NSIM",
"NKRED",
"NUPDOWN",
"ISPIND",
"LDAUTYPE",
"IVDW",
)
lower_str_keys = ("ML_MODE",)


The warning on ENCUT=550 should be owing to the incorrect classification of it into int_keys instead of float_keys:

int_keys = (
"NSW",
"NBANDS",
"NELMIN",
"ISIF",
"IBRION",
"ISPIN",
"ISTART",
"ICHARG",
"NELM",
"ISMEAR",
"NPAR",
"LDAUPRINT",
"LMAXMIX",
"ENCUT",

I believe it should be a float:

ENCUT = [real]

@yantar92
Copy link
Contributor Author

With Version: 2024.10.22, I am still getting

/net/home/plgrid/plgyantar92/groupdir/functional-input.py:70: BadIncarWarning: Cannot find ZAB_VDW in the list of INCAR tags
  vasp_input.incar.check_params()
/net/home/plgrid/plgyantar92/groupdir/functional-input.py:70: BadIncarWarning: GGA: Cannot find Ml in the list of values
  vasp_input.incar.check_params()

@DanielYang59
Copy link
Contributor

Hi thanks for following up. I just have a look and I believe it's a separate issue related to #4042, i.e. we need to update the incar_parameters.json record.

ZAB_VDW is currently not in the recorded INCAR tags, and GGA doesn't have ML in the recording yet:

"GGA": {
"type": "str",
"values": [
"91",
"PE",
"AM",
"HL",
"CA",
"MK",
"RE",
"VW",
"B3",
"PZ",
"WI",
"RP",
"B5",
"BF",
"CO",
"PS",
"OR",
"BO",
"03",
"05",
"10",
"20",
"RA",
"PL"
]

But currently I don't have a better way other than doing that manually (which would be very inefficiently) given the amount of possible INCAR tags and their values. Updating the tags should be easier with:

@classmethod
def get_incar_tags(cls) -> list[str]:
"""Get a list of all INCAR tags from the VASP wiki."""
tags = []
for url in (
"https://www.vasp.at/wiki/index.php/Category:INCAR_tag",
"https://www.vasp.at/wiki/index.php?title=Category:INCAR_tag&pagefrom=LREAL#mw-pages",
"https://www.vasp.at/wiki/index.php?title=Category:INCAR_tag&pagefrom=Profiling#mw-pages",
):
response = requests.get(url, timeout=60)
soup = BeautifulSoup(response.text, features="html.parser")
for div in soup.findAll("div", {"class": "mw-category-group"}):
children = div.findChildren("li")
for child in children:
tags.append(child.text.strip())
return tags

But updating the values is a bit tricky so I assume we need to helper script to do this? Would you be interested? If not I'm happy to get my hands on this a bit later :)

@yantar92
Copy link
Contributor Author

yantar92 commented Oct 26, 2024

But updating the values is a bit tricky so I assume we need to helper script to do this? Would you be interested? If not I'm happy to get my hands on this a bit later :)

    @classmethod
    def get_incar_tags(cls) -> list[str]:
        """Get a list of all INCAR tags from the VASP wiki."""
        url = ("https://www.vasp.at/wiki/api.php?"
               "action=query&list=categorymembers"
               "&cmtitle=Category:INCAR_tag"
               "&cmlimit=500&format=json")
        response = requests.get(url, timeout=60)
        response_dict = json.loads(response.text)

        def extract_titles(data):
            """Extract keywords from from Wikimedia response data.
            See https://www.vasp.at/wiki/api.php?action=help&modules=query
            Returns: List of keywords as strings.
            """
            return [category_data['title'] for category_data
                    in data['query']['categorymembers']]

        tags = extract_titles(response_dict)
        while 'continue' in response_dict:
            response = requests.get(
                url + f"&cmcontinue={response_dict['continue']['cmcontinue']}",
                timeout=60
            )
            response_dict = json.loads(response.text)
            tags = tags + extract_titles(response_dict)

        return tags

I can make it into pull request if you wish.

@DanielYang59
Copy link
Contributor

DanielYang59 commented Oct 27, 2024

I can make it into pull request if you wish.

That would be hugely appreciated! You might have misread my previous comment #4119 (comment)

Current we already have a method to get the (tags/keywords/parameters):

@classmethod
def get_incar_tags(cls) -> list[str]:
"""Get a list of all INCAR tags from the VASP wiki."""


What we need is something to update the values, which is a bit tricky. Currently VASP wiki doesn't seem to provide an API to get possible values of a tag directly (do they?), and the webpage format seems pretty inconsistent and probably make scraping infeasible (I'm not a web scraping expert and I wish I'm wrong here).

For example the ALGO tag page seems to list all possible values at the start:
image

However the GGA tag only listed a small portion and left the rest inside a table:
image

yantar92 added a commit to yantar92/pymatgen that referenced this issue Oct 27, 2024
* src/pymatgen/io/vasp/help.py (VaspDoc.get_incar_tags):
Use Mediawiki API instead of parsing the HTML source directly.  The
old approach is not stable against changes in the tag list because of
the way URLs are constructed.  pagefrom= parameters start from certain
tag, which is not guaranteed to provide the complete tag list as the
new tags are added before that tag given in pagefrom=.  At the moment
of writing this commit, PRECFOCK tag is already missed using the old
approach.

Following up:  materialsproject#4119 (comment)
@yantar92
Copy link
Contributor Author

What we need is something to update the values, which is a bit tricky. Currently VASP wiki doesn't seem to provide an API to get possible values of a tag directly (do they?),

I do not see anything either.

the webpage format seems pretty inconsistent and probably make scraping infeasible (I'm not a web scraping expert and I wish I'm wrong here).

Scaping is possible, but awkward. One will need to write the scaper specifically for this page and add a number of asserts to catch incompatible changes (those are unlikely in practice though).

I looked into the source of the page at https://www.vasp.at/wiki/index.php?title=GGA&action=edit
and it looks like they define the tag list simply within a table, not using a template (template would make scaping much easier).

It should be still possible to write a dedicated scraper, via pymediawiki + wikitexparser, but non-standard pages like the one for GGA will need to have a specially tailored handler.

I think that the most productive course of action here would be asking VASP maintainers to list all the possible keys in their TAGDEF template (the template defining GGA = PE | ... line) for this page and maybe for other pages with the same problem.

@DanielYang59
Copy link
Contributor

I think that the most productive course of action here would be asking VASP maintainers to list all the possible keys in their TAGDEF template

It sounds like the best option to me, not sure what is the best approach to reach out though, perhaps through the VASP forum?

@yantar92
Copy link
Contributor Author

yantar92 commented Oct 27, 2024

Yup, the forum. I see no better options on https://www.vasp.at/wiki/index.php/The_VASP_Manual#Support

shyuep pushed a commit that referenced this issue Oct 29, 2024
* src/pymatgen/io/vasp/help.py (VaspDoc.get_incar_tags):
Use Mediawiki API instead of parsing the HTML source directly.  The
old approach is not stable against changes in the tag list because of
the way URLs are constructed.  pagefrom= parameters start from certain
tag, which is not guaranteed to provide the complete tag list as the
new tags are added before that tag given in pagefrom=.  At the moment
of writing this commit, PRECFOCK tag is already missed using the old
approach.

Following up:  #4119 (comment)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
2 participants