|
37 | 37 | #====================================================================# |
38 | 38 |
|
39 | 39 | import os |
| 40 | +from os import PathLike |
40 | 41 | from pathlib import Path |
| 42 | +import re |
41 | 43 | import numpy as np |
42 | 44 | from .execute import execute |
43 | 45 | from .fileio import TextFile |
@@ -88,6 +90,187 @@ def pp_elem_label(filename,guard=False): |
88 | 90 | #end def pp_elem_label |
89 | 91 |
|
90 | 92 |
|
| 93 | +def read_upf_z_valence(file: PathLike) -> int | float: |
| 94 | + """Read Z-valence from a UPF-compliant pseudopotential file.""" |
| 95 | + # Bind these to the function so we only compile them once. |
| 96 | + if not ( |
| 97 | + hasattr(read_upf_z_valence, "zval_xml_like_pattern") |
| 98 | + and hasattr(read_upf_z_valence, "zval_old_pattern") |
| 99 | + ): |
| 100 | + # Regex: |
| 101 | + # `z[_ ]?valence` -> "z_valence" or "z valence" |
| 102 | + # ` *=? *` -> "=" or " =" or " = " or " " (whitespace optional) |
| 103 | + # `([\d\.eEdD]+)` -> Capturing group gets any numbers in scientific notation. |
| 104 | + # `\"? *() *\"?` -> Anything between quotes or not, with optional whitespace around it too. |
| 105 | + # Note: Both of these are similar, but the first is key then value and the second is value then key. |
| 106 | + zval_xml_like_pattern = re.compile( |
| 107 | + pattern = r'z[_ ]?valence *=? *\"? *([\d\.eEdD]+) *\"?', |
| 108 | + flags = re.IGNORECASE, |
| 109 | + ) |
| 110 | + zval_old_pattern = re.compile( |
| 111 | + pattern = r'\"? *([ \d\.eEdD]+) *\"? *=? *z[_ ]?valence', |
| 112 | + flags = re.IGNORECASE, |
| 113 | + ) |
| 114 | + read_upf_z_valence.zval_xml_like_pattern = zval_xml_like_pattern |
| 115 | + read_upf_z_valence.zval_old_pattern = zval_old_pattern |
| 116 | + #end if |
| 117 | + |
| 118 | + zval = None |
| 119 | + with open(file, "r") as pseudo: |
| 120 | + found_header_start = False |
| 121 | + while not found_header_start: |
| 122 | + line = pseudo.readline() |
| 123 | + if "<PP_HEADER" in line: |
| 124 | + found_header_start = True |
| 125 | + |
| 126 | + if "/>" in line or "</PP_HEADER>" in line: # One-line header |
| 127 | + zval = re.search( |
| 128 | + pattern = read_upf_z_valence.zval_xml_like_pattern, |
| 129 | + string = line, |
| 130 | + ) |
| 131 | + else: |
| 132 | + # We're at the header, but we don't know where the Z-valence is. |
| 133 | + # Search until we hit a line with a proper end token, or until we hit 200 lines. |
| 134 | + i = 0 |
| 135 | + while i < 200: |
| 136 | + i += 1 |
| 137 | + line = pseudo.readline().lower() |
| 138 | + if "valence" in line: |
| 139 | + zval = re.search( |
| 140 | + pattern = read_upf_z_valence.zval_xml_like_pattern, |
| 141 | + string = line, |
| 142 | + ) |
| 143 | + if zval is None: |
| 144 | + zval = re.search( |
| 145 | + pattern = read_upf_z_valence.zval_old_pattern, |
| 146 | + string = line, |
| 147 | + ) |
| 148 | + break |
| 149 | + elif "/>" in line or "</PP_HEADER>" in line: |
| 150 | + break |
| 151 | + #end if |
| 152 | + #end while |
| 153 | + #end if |
| 154 | + |
| 155 | + if zval is None: |
| 156 | + error( |
| 157 | + f"Could not find Z valence in file: {file!s}\n" |
| 158 | + "You may need to provide the Z valence manually!" |
| 159 | + ) |
| 160 | + else: |
| 161 | + zval = float(zval.group(1).lower().replace("d", "e")) |
| 162 | + |
| 163 | + if zval <= 0 or zval > 118: |
| 164 | + error( |
| 165 | + f"Invalid Z-valence found in file, must be in range (0, 118], but is {zval}!" |
| 166 | + ) |
| 167 | + # Round to 8 digits |
| 168 | + if round(zval, 8).is_integer(): |
| 169 | + return int(zval) |
| 170 | + else: |
| 171 | + return zval |
| 172 | +#end def read_upf_z_valence |
| 173 | + |
| 174 | + |
| 175 | +def read_xml_z_valence(file: PathLike) -> int | float: |
| 176 | + """Read the Z-valence from a QMCPACK-compatible XML pseudopotential file.""" |
| 177 | + # Bind these to the function so we only compile them once. |
| 178 | + if not hasattr(read_xml_z_valence, "zval_pattern"): |
| 179 | + # Regex: |
| 180 | + # `zval *= *` -> "zval=" or "zval = " or "zval =" or "zval= " |
| 181 | + # `([\d\.eEdD]+)` -> Capturing group gets any numbers in scientific notation. |
| 182 | + # `\"? *() *\"?` -> Anything between quotes or not, with optional whitespace around it too. |
| 183 | + read_xml_z_valence.zval_pattern = re.compile(r'zval *= *\" *([\d\.eEdD]+) *\"') |
| 184 | + |
| 185 | + header_lines = [] |
| 186 | + with open(file, "r") as xml: |
| 187 | + header_started = False |
| 188 | + for line in xml: |
| 189 | + if "<header" in line: |
| 190 | + header_started = True |
| 191 | + |
| 192 | + if header_started: |
| 193 | + header_lines.append(line) |
| 194 | + |
| 195 | + if "/>" in line or "</header>" in line: |
| 196 | + if line not in header_lines: |
| 197 | + header_lines.append(line) |
| 198 | + break |
| 199 | + |
| 200 | + header = " ".join(header_lines) |
| 201 | + zval = re.search(read_xml_z_valence.zval_pattern, header) |
| 202 | + |
| 203 | + if zval is None: |
| 204 | + error( |
| 205 | + f"Could not find Z valence in file: {file!s}\n" |
| 206 | + "You may need to provide the Z valence manually!" |
| 207 | + ) |
| 208 | + else: |
| 209 | + zval = float(zval.group(1).lower().replace("d", "e")) |
| 210 | + |
| 211 | + if zval <= 0 or zval > 118: |
| 212 | + error( |
| 213 | + f"Invalid Z-valence found in file, must be in range (0, 118], but is {zval}!" |
| 214 | + ) |
| 215 | + # Round to 8 digits |
| 216 | + if round(zval, 8).is_integer(): |
| 217 | + return int(zval) |
| 218 | + else: |
| 219 | + return zval |
| 220 | +#end def read_xml_z_valence |
| 221 | + |
| 222 | + |
| 223 | +def read_potcar_z_valence(file: PathLike) -> int | float: |
| 224 | + """Read the Z-valence from a POTCAR file. |
| 225 | +
|
| 226 | + This function uses the format specifications from the VASP wiki, and |
| 227 | + assumes that the file is a valid POTCAR, so the second line should |
| 228 | + be the Z-valence [1]_. |
| 229 | +
|
| 230 | + References |
| 231 | + ---------- |
| 232 | + .. [1] https://vasp.at/wiki/POTCAR#File_format |
| 233 | + """ |
| 234 | + if not hasattr(read_potcar_z_valence, "zval_pattern"): |
| 235 | + # Regex: |
| 236 | + # `ZVAL ?= ?` -> "ZVAL=" or "ZVAL = " or "ZVAL =" or "ZVAL= " |
| 237 | + # `([\d\.eEdD]+)` -> Capturing group gets any numbers in scientific notation. |
| 238 | + read_potcar_z_valence.zval_pattern = re.compile(r"ZVAL ?= ?([\d\.eEdD]+)") |
| 239 | + file = Path(file).resolve() |
| 240 | + with open(file, "r") as potcar: |
| 241 | + potcar.readline() # Skip first line |
| 242 | + z_valence = potcar.readline().strip() |
| 243 | + try: |
| 244 | + zval = float(z_valence) |
| 245 | + except ValueError: # Improperly formatted POTCAR, but try alternative location |
| 246 | + for line in potcar: |
| 247 | + if "ZVAL" in line: |
| 248 | + zval = re.search( |
| 249 | + pattern = read_potcar_z_valence.zval_pattern, |
| 250 | + string = line |
| 251 | + ) |
| 252 | + break |
| 253 | + |
| 254 | + if zval is None: |
| 255 | + error( |
| 256 | + f"Could not find Z valence in file: {file!s}\n" |
| 257 | + "You may need to provide the Z valence manually!" |
| 258 | + ) |
| 259 | + else: |
| 260 | + zval = float(zval.group(1).lower().replace("d", "e")) |
| 261 | + |
| 262 | + if zval <= 0 or zval > 118: |
| 263 | + error( |
| 264 | + f"Invalid Z-valence found in file, must be in range (0, 118], but is {zval}!" |
| 265 | + ) |
| 266 | + # Round to 8 digits |
| 267 | + if round(zval, 8).is_integer(): |
| 268 | + return int(zval) |
| 269 | + else: |
| 270 | + return zval |
| 271 | +#end def read_potcar_z_valence |
| 272 | + |
| 273 | + |
91 | 274 | # basic interface for nexus, only gamess really needs this for now |
92 | 275 | class PseudoFile(DevBase): |
93 | 276 | def __init__(self,filepath=None): |
|
0 commit comments